diff --git a/benchmark/benchmark_ec.jl b/benchmark/benchmark_ec.jl index 5bd20b4111..5eff114402 100644 --- a/benchmark/benchmark_ec.jl +++ b/benchmark/benchmark_ec.jl @@ -14,10 +14,10 @@ function tabulate_benchmarks(args...; kwargs...) result = run_benchmarks(args...; kwargs...) println("#Elements | Runtime in seconds") for (level, runtime) in zip(result.levels, result.runtimes) - @printf("%9d | %.2e\n", 4^level, 1.0e-9*runtime) + @printf("%9d | %.2e\n", 4^level, 1.0e-9 * runtime) end for (level, runtime) in zip(result.levels, result.runtimes) - @printf("%.16e\n", 1.0e-9*runtime) + @printf("%.16e\n", 1.0e-9 * runtime) end end @@ -31,12 +31,16 @@ function benchmark_euler(; initial_refinement_level = 1, polydeg = 3) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) - mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = initial_refinement_level, - n_cells_max = 100_000) - - semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_weak_blast_wave, - solver) + mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = initial_refinement_level, + n_cells_max = 100_000 + ) + + semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_weak_blast_wave, + solver + ) t0 = 0.0 u0 = compute_coefficients(t0, semi) diff --git a/benchmark/elixir_2d_euler_vortex_p4est.jl b/benchmark/elixir_2d_euler_vortex_p4est.jl index 3ee97cc752..f7140b95e5 100644 --- a/benchmark/elixir_2d_euler_vortex_p4est.jl +++ b/benchmark/elixir_2d_euler_vortex_p4est.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -50,9 +49,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-10.0, -10.0) coordinates_max = (10.0, 10.0) -mesh = P4estMesh((1, 1), polydeg = Trixi.polydeg(solver), - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 4) +mesh = P4estMesh( + (1, 1), polydeg = Trixi.polydeg(solver), + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 4 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -65,12 +66,16 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) @@ -79,6 +84,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) ############################################################################### # run the simulation -sol = solve(ode, BS3(), - save_everystep = false, callback = callbacks); +sol = solve( + ode, BS3(), + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/benchmark/elixir_2d_euler_vortex_structured.jl b/benchmark/elixir_2d_euler_vortex_structured.jl index 5627049c9e..c9fcdd722c 100644 --- a/benchmark/elixir_2d_euler_vortex_structured.jl +++ b/benchmark/elixir_2d_euler_vortex_structured.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -64,12 +63,16 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) @@ -78,6 +81,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) ############################################################################### # run the simulation -sol = solve(ode, BS3(), - save_everystep = false, callback = callbacks); +sol = solve( + ode, BS3(), + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/benchmark/elixir_2d_euler_vortex_tree.jl b/benchmark/elixir_2d_euler_vortex_tree.jl index 68e207c534..2e338d5cf7 100644 --- a/benchmark/elixir_2d_euler_vortex_tree.jl +++ b/benchmark/elixir_2d_euler_vortex_tree.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -50,9 +49,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-10.0, -10.0) coordinates_max = (10.0, 10.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -65,12 +66,16 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) @@ -79,6 +84,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) ############################################################################### # run the simulation -sol = solve(ode, BS3(), - save_everystep = false, callback = callbacks); +sol = solve( + ode, BS3(), + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/benchmark/elixir_2d_euler_vortex_unstructured.jl b/benchmark/elixir_2d_euler_vortex_unstructured.jl index 43e4b6559d..9520f0ce79 100644 --- a/benchmark/elixir_2d_euler_vortex_unstructured.jl +++ b/benchmark/elixir_2d_euler_vortex_unstructured.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -48,8 +47,10 @@ end initial_condition = initial_condition_isentropic_vortex solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) -mesh_file = Trixi.download("https://gist.githubusercontent.com/ranocha/f4ea19ba3b62348968c971db43d7798b/raw/a506abb9479c020920cf6068c142670fc1a9aadc/mesh_uniform_cartesian.mesh", - joinpath(@__DIR__, "mesh_uniform_cartesian.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/ranocha/f4ea19ba3b62348968c971db43d7798b/raw/a506abb9479c020920cf6068c142670fc1a9aadc/mesh_uniform_cartesian.mesh", + joinpath(@__DIR__, "mesh_uniform_cartesian.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) @@ -64,12 +65,16 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) @@ -78,6 +83,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) ############################################################################### # run the simulation -sol = solve(ode, BS3(), - save_everystep = false, callback = callbacks); +sol = solve( + ode, BS3(), + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/benchmark/elixir_3d_euler_source_terms_structured.jl b/benchmark/elixir_3d_euler_source_terms_structured.jl index b44eb0caa7..b8fffde62b 100644 --- a/benchmark/elixir_3d_euler_source_terms_structured.jl +++ b/benchmark/elixir_3d_euler_source_terms_structured.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,8 +15,10 @@ coordinates_max = (2.0, 2.0, 2.0) cells_per_dimension = (4, 4, 4) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -37,6 +38,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) ############################################################################### # run the simulation -sol = solve(ode, BS3(), - save_everystep = false, callback = callbacks); +sol = solve( + ode, BS3(), + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/benchmark/elixir_3d_euler_source_terms_tree.jl b/benchmark/elixir_3d_euler_source_terms_tree.jl index 369b935958..77a41f3d74 100644 --- a/benchmark/elixir_3d_euler_source_terms_tree.jl +++ b/benchmark/elixir_3d_euler_source_terms_tree.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,12 +12,16 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -38,6 +41,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) ############################################################################### # run the simulation -sol = solve(ode, BS3(), - save_everystep = false, callback = callbacks); +sol = solve( + ode, BS3(), + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/benchmark/multiply_dimensionwise/benchmark_multiply_dimensionwise.jl b/benchmark/multiply_dimensionwise/benchmark_multiply_dimensionwise.jl index e6dd0d4744..4bab9d928e 100644 --- a/benchmark/multiply_dimensionwise/benchmark_multiply_dimensionwise.jl +++ b/benchmark/multiply_dimensionwise/benchmark_multiply_dimensionwise.jl @@ -14,306 +14,313 @@ using Tullio ################################################################################################### # 2D versions function multiply_dimensionwise_sequential!( - data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde, - tmp1=zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2))) - n_vars = size(data_out, 1) - n_nodes_out = size(vandermonde, 1) - n_nodes_in = size(vandermonde, 2) - data_out .= zero(eltype(data_out)) - - @boundscheck begin - inbounds = (size(data_out, 2) == size(data_out, 3) == n_nodes_out) && - (size(data_in, 1) == n_vars) && - (size(data_in, 2) == size(data_in, 3) == n_nodes_in) - inbounds || throw(BoundsError()) - end - - # Interpolate in x-direction - @inbounds for j in 1:n_nodes_in, i in 1:n_nodes_out - for ii in 1:n_nodes_in - for v in 1:n_vars - tmp1[v, i, j] += vandermonde[i, ii] * data_in[v, ii, j] - end + data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde, + tmp1 = zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2)) + ) + n_vars = size(data_out, 1) + n_nodes_out = size(vandermonde, 1) + n_nodes_in = size(vandermonde, 2) + data_out .= zero(eltype(data_out)) + + @boundscheck begin + inbounds = (size(data_out, 2) == size(data_out, 3) == n_nodes_out) && + (size(data_in, 1) == n_vars) && + (size(data_in, 2) == size(data_in, 3) == n_nodes_in) + inbounds || throw(BoundsError()) end - end - - # Interpolate in y-direction - @inbounds for j in 1:n_nodes_out, i in 1:n_nodes_out - for jj in 1:n_nodes_in - for v in 1:n_vars - data_out[v, i, j] += vandermonde[j, jj] * tmp1[v, i, jj] - end + + # Interpolate in x-direction + @inbounds for j in 1:n_nodes_in, i in 1:n_nodes_out + for ii in 1:n_nodes_in + for v in 1:n_vars + tmp1[v, i, j] += vandermonde[i, ii] * data_in[v, ii, j] + end + end end - end - return data_out + # Interpolate in y-direction + @inbounds for j in 1:n_nodes_out, i in 1:n_nodes_out + for jj in 1:n_nodes_in + for v in 1:n_vars + data_out[v, i, j] += vandermonde[j, jj] * tmp1[v, i, jj] + end + end + end + + return data_out end function multiply_dimensionwise_sequential_avx!( - data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde, - tmp1=zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2))) - n_vars = size(data_out, 1) - n_nodes_out = size(vandermonde, 1) - n_nodes_in = size(vandermonde, 2) - data_out .= zero(eltype(data_out)) - - @boundscheck begin - inbounds = (size(data_out, 2) == size(data_out, 3) == n_nodes_out) && - (size(data_in, 1) == n_vars) && - (size(data_in, 2) == size(data_in, 3) == n_nodes_in) - inbounds || throw(BoundsError()) - end - - # Interpolate in x-direction - @avx for j in 1:n_nodes_in, i in 1:n_nodes_out - for ii in 1:n_nodes_in - for v in 1:n_vars - tmp1[v, i, j] += vandermonde[i, ii] * data_in[v, ii, j] - end + data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde, + tmp1 = zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2)) + ) + n_vars = size(data_out, 1) + n_nodes_out = size(vandermonde, 1) + n_nodes_in = size(vandermonde, 2) + data_out .= zero(eltype(data_out)) + + @boundscheck begin + inbounds = (size(data_out, 2) == size(data_out, 3) == n_nodes_out) && + (size(data_in, 1) == n_vars) && + (size(data_in, 2) == size(data_in, 3) == n_nodes_in) + inbounds || throw(BoundsError()) + end + + # Interpolate in x-direction + @avx for j in 1:n_nodes_in, i in 1:n_nodes_out + for ii in 1:n_nodes_in + for v in 1:n_vars + tmp1[v, i, j] += vandermonde[i, ii] * data_in[v, ii, j] + end + end end - end - - # Interpolate in y-direction - @avx for j in 1:n_nodes_out, i in 1:n_nodes_out - for jj in 1:n_nodes_in - for v in 1:n_vars - data_out[v, i, j] += vandermonde[j, jj] * tmp1[v, i, jj] - end + + # Interpolate in y-direction + @avx for j in 1:n_nodes_out, i in 1:n_nodes_out + for jj in 1:n_nodes_in + for v in 1:n_vars + data_out[v, i, j] += vandermonde[j, jj] * tmp1[v, i, jj] + end + end end - end - return data_out + return data_out end function multiply_dimensionwise_sequential_tullio!( - data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde, - tmp1=zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2))) + data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde, + tmp1 = zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2)) + ) - # Interpolate in x-direction - @tullio threads=false tmp1[v, i, j] = vandermonde[i, ii] * data_in[v, ii, j] + # Interpolate in x-direction + @tullio threads = false tmp1[v, i, j] = vandermonde[i, ii] * data_in[v, ii, j] - # Interpolate in y-direction - @tullio threads=false data_out[v, i, j] = vandermonde[j, jj] * tmp1[v, i, jj] + # Interpolate in y-direction + @tullio threads = false data_out[v, i, j] = vandermonde[j, jj] * tmp1[v, i, jj] - return data_out + return data_out end @generated function multiply_dimensionwise_sequential_nexpr!( - data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde::SMatrix{n_nodes_out,n_nodes_in}, ::Val{n_vars}) where {n_nodes_out, n_nodes_in, n_vars} - quote - @boundscheck begin - inbounds = (size(data_out, 1) == $n_vars) && - (size(data_out, 2) == size(data_out, 3) == $n_nodes_out) && - (size(data_in, 1) == $n_vars) && - (size(data_in, 2) == size(data_in, 3) == $n_nodes_in) - inbounds || throw(BoundsError()) - end + data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde::SMatrix{n_nodes_out, n_nodes_in}, ::Val{n_vars} + ) where {n_nodes_out, n_nodes_in, n_vars} + quote + @boundscheck begin + inbounds = (size(data_out, 1) == $n_vars) && + (size(data_out, 2) == size(data_out, 3) == $n_nodes_out) && + (size(data_in, 1) == $n_vars) && + (size(data_in, 2) == size(data_in, 3) == $n_nodes_in) + inbounds || throw(BoundsError()) + end - # Interpolate in x-direction - @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_in j -> begin - Base.Cartesian.@nexprs $n_nodes_out i -> begin - Base.Cartesian.@nexprs $n_vars v -> begin - tmp1_v_i_j = zero(eltype(data_out)) - Base.Cartesian.@nexprs $n_nodes_in ii -> begin - tmp1_v_i_j += vandermonde[i, ii] * data_in[v, ii, j] - end + # Interpolate in x-direction + @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_in j -> begin + Base.Cartesian.@nexprs $n_nodes_out i -> begin + Base.Cartesian.@nexprs $n_vars v -> begin + tmp1_v_i_j = zero(eltype(data_out)) + Base.Cartesian.@nexprs $n_nodes_in ii -> begin + tmp1_v_i_j += vandermonde[i, ii] * data_in[v, ii, j] + end + end + end end - end - end - # Interpolate in y-direction - @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_out j -> begin - Base.Cartesian.@nexprs $n_nodes_out i -> begin - Base.Cartesian.@nexprs $n_vars v -> begin - tmp2_v_i_j = zero(eltype(data_out)) - Base.Cartesian.@nexprs $n_nodes_in jj -> begin - tmp2_v_i_j += vandermonde[j, jj] * tmp1_v_i_jj - end - data_out[v, i, j] = tmp2_v_i_j + # Interpolate in y-direction + @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_out j -> begin + Base.Cartesian.@nexprs $n_nodes_out i -> begin + Base.Cartesian.@nexprs $n_vars v -> begin + tmp2_v_i_j = zero(eltype(data_out)) + Base.Cartesian.@nexprs $n_nodes_in jj -> begin + tmp2_v_i_j += vandermonde[j, jj] * tmp1_v_i_jj + end + data_out[v, i, j] = tmp2_v_i_j + end + end end - end - end - return data_out - end + return data_out + end end function multiply_dimensionwise_squeezed!( - data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde) - n_vars = size(data_out, 1) - n_nodes_out = size(vandermonde, 1) - n_nodes_in = size(vandermonde, 2) - data_out .= zero(eltype(data_out)) - - @boundscheck begin - inbounds = (size(data_out, 2) == size(data_out, 3) == n_nodes_out) && - (size(data_in, 1) == n_vars) && - (size(data_in, 2) == size(data_in, 3) == n_nodes_in) - inbounds || throw(BoundsError()) - end - - @inbounds for j in 1:n_nodes_out, i in 1:n_nodes_out - for v in 1:n_vars - acc = zero(eltype(data_out)) - for jj in 1:n_nodes_in, ii in 1:n_nodes_in - acc += vandermonde[i, ii]* vandermonde[j, jj] * data_in[v, ii, jj] - end - data_out[v, i, j] = acc + data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde + ) + n_vars = size(data_out, 1) + n_nodes_out = size(vandermonde, 1) + n_nodes_in = size(vandermonde, 2) + data_out .= zero(eltype(data_out)) + + @boundscheck begin + inbounds = (size(data_out, 2) == size(data_out, 3) == n_nodes_out) && + (size(data_in, 1) == n_vars) && + (size(data_in, 2) == size(data_in, 3) == n_nodes_in) + inbounds || throw(BoundsError()) end - end - return data_out + @inbounds for j in 1:n_nodes_out, i in 1:n_nodes_out + for v in 1:n_vars + acc = zero(eltype(data_out)) + for jj in 1:n_nodes_in, ii in 1:n_nodes_in + acc += vandermonde[i, ii] * vandermonde[j, jj] * data_in[v, ii, jj] + end + data_out[v, i, j] = acc + end + end + + return data_out end function multiply_dimensionwise_squeezed_avx!( - data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde) - n_vars = size(data_out, 1) - n_nodes_out = size(vandermonde, 1) - n_nodes_in = size(vandermonde, 2) - data_out .= zero(eltype(data_out)) - - @boundscheck begin - inbounds = (size(data_out, 2) == size(data_out, 3) == n_nodes_out) && - (size(data_in, 1) == n_vars) && - (size(data_in, 2) == size(data_in, 3) == n_nodes_in) - inbounds || throw(BoundsError()) - end - - @avx for j in 1:n_nodes_out, i in 1:n_nodes_out - for v in 1:n_vars - acc = zero(eltype(data_out)) - for jj in 1:n_nodes_in, ii in 1:n_nodes_in - acc += vandermonde[i, ii] * vandermonde[j, jj] * data_in[v, ii, jj] - end - data_out[v, i, j] = acc + data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde + ) + n_vars = size(data_out, 1) + n_nodes_out = size(vandermonde, 1) + n_nodes_in = size(vandermonde, 2) + data_out .= zero(eltype(data_out)) + + @boundscheck begin + inbounds = (size(data_out, 2) == size(data_out, 3) == n_nodes_out) && + (size(data_in, 1) == n_vars) && + (size(data_in, 2) == size(data_in, 3) == n_nodes_in) + inbounds || throw(BoundsError()) end - end - return data_out + @avx for j in 1:n_nodes_out, i in 1:n_nodes_out + for v in 1:n_vars + acc = zero(eltype(data_out)) + for jj in 1:n_nodes_in, ii in 1:n_nodes_in + acc += vandermonde[i, ii] * vandermonde[j, jj] * data_in[v, ii, jj] + end + data_out[v, i, j] = acc + end + end + + return data_out end function multiply_dimensionwise_squeezed_tullio!( - data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde) + data_out::AbstractArray{<:Any, 3}, data_in::AbstractArray{<:Any, 3}, vandermonde + ) - @tullio threads=false data_out[v, i, j] = vandermonde[i, ii] * vandermonde[j, jj] * data_in[v, ii, jj] + @tullio threads = false data_out[v, i, j] = vandermonde[i, ii] * vandermonde[j, jj] * data_in[v, ii, jj] - return data_out + return data_out end -function run_benchmarks_2d(n_vars=4, n_nodes_in=4, n_nodes_out=2*n_nodes_in) - data_in = randn(n_vars, n_nodes_in, n_nodes_in) - data_out = randn(n_vars, n_nodes_out, n_nodes_out) - vandermonde_dynamic = randn(n_nodes_out, n_nodes_in) - vandermonde_static = SMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) - vandermonde_mmatrix = MMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) - - println("\n\n# 2D ", "#"^70) - println("n_vars = ", n_vars) - println("n_nodes_in = ", n_nodes_in) - println("n_nodes_out = ", n_nodes_out) - println() - - println("\n","multiply_dimensionwise_sequential!") - println("vandermonde_dynamic") - multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_dynamic) - data_out_copy = copy(data_out) - display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_static))) - println("\n", "vandermonde_mmatrix") - multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_mmatrix) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_mmatrix))) - println() - - println("\n","multiply_dimensionwise_sequential_avx!") - println("vandermonde_dynamic") - multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_static))) - println("\n", "vandermonde_mmatrix") - multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_mmatrix) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_mmatrix))) - println() - - println("\n","multiply_dimensionwise_sequential_tullio!") - println("vandermonde_dynamic") - multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static))) - println("\n", "vandermonde_mmatrix") - multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_mmatrix) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_mmatrix))) - println() - - println("\n","multiply_dimensionwise_sequential_nexpr!") - println("vandermonde_static") - multiply_dimensionwise_sequential_nexpr!(data_out, data_in, vandermonde_static, Val(n_vars)) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_nexpr!($(data_out), $(data_in), $(vandermonde_static), $(Val(n_vars)))) - println() - - - println("\n","multiply_dimensionwise_squeezed!") - println("vandermonde_dynamic") - multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_static))) - println("\n", "vandermonde_mmatrix") - multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_mmatrix) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_mmatrix))) - println() - - println("\n","multiply_dimensionwise_squeezed_avx!") - println("vandermonde_dynamic") - multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_static))) - println("\n", "vandermonde_mmatrix") - multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_mmatrix) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_mmatrix))) - println() - - println("\n","multiply_dimensionwise_squeezed_tullio!") - println("vandermonde_dynamic") - multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_static))) - println("\n", "vandermonde_mmatrix") - multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_mmatrix) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_mmatrix))) - println() - - nothing +function run_benchmarks_2d(n_vars = 4, n_nodes_in = 4, n_nodes_out = 2 * n_nodes_in) + data_in = randn(n_vars, n_nodes_in, n_nodes_in) + data_out = randn(n_vars, n_nodes_out, n_nodes_out) + vandermonde_dynamic = randn(n_nodes_out, n_nodes_in) + vandermonde_static = SMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) + vandermonde_mmatrix = MMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) + + println("\n\n# 2D ", "#"^70) + println("n_vars = ", n_vars) + println("n_nodes_in = ", n_nodes_in) + println("n_nodes_out = ", n_nodes_out) + println() + + println("\n", "multiply_dimensionwise_sequential!") + println("vandermonde_dynamic") + multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_dynamic) + data_out_copy = copy(data_out) + display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_static))) + println("\n", "vandermonde_mmatrix") + multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_mmatrix) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_mmatrix))) + println() + + println("\n", "multiply_dimensionwise_sequential_avx!") + println("vandermonde_dynamic") + multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_static))) + println("\n", "vandermonde_mmatrix") + multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_mmatrix) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_mmatrix))) + println() + + println("\n", "multiply_dimensionwise_sequential_tullio!") + println("vandermonde_dynamic") + multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static))) + println("\n", "vandermonde_mmatrix") + multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_mmatrix) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_mmatrix))) + println() + + println("\n", "multiply_dimensionwise_sequential_nexpr!") + println("vandermonde_static") + multiply_dimensionwise_sequential_nexpr!(data_out, data_in, vandermonde_static, Val(n_vars)) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_nexpr!($(data_out), $(data_in), $(vandermonde_static), $(Val(n_vars)))) + println() + + + println("\n", "multiply_dimensionwise_squeezed!") + println("vandermonde_dynamic") + multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_static))) + println("\n", "vandermonde_mmatrix") + multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_mmatrix) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_mmatrix))) + println() + + println("\n", "multiply_dimensionwise_squeezed_avx!") + println("vandermonde_dynamic") + multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_static))) + println("\n", "vandermonde_mmatrix") + multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_mmatrix) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_mmatrix))) + println() + + println("\n", "multiply_dimensionwise_squeezed_tullio!") + println("vandermonde_dynamic") + multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_static))) + println("\n", "vandermonde_mmatrix") + multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_mmatrix) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_mmatrix))) + println() + + nothing end # TODO @@ -321,75 +328,75 @@ run_benchmarks_2d(4, 4, 4) # n_vars, n_nodes_in, n_nodes_out function compute_benchmarks_2d(n_vars, n_nodes_in, n_nodes_out) - data_in = randn(n_vars, n_nodes_in, n_nodes_in) - data_out = randn(n_vars, n_nodes_out, n_nodes_out) - vandermonde_dynamic = randn(n_nodes_out, n_nodes_in) - vandermonde_static = SMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) - tmp1 = zeros(eltype(data_out), n_vars, n_nodes_out, n_nodes_in) - - println("n_vars = ", n_vars, "; n_nodes_in = ", n_nodes_in, "; n_nodes_out = ", n_nodes_out) - sequential_dynamic = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic)) - sequential_static = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static)) - #FIXME sequential_nexpr = @benchmark multiply_dimensionwise_sequential_nexpr!($(data_out), $(data_in), $(vandermonde_static), $(Val(n_vars))) - sequential_dynamic_prealloc = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic), $(tmp1)) - sequential_static_prealloc = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static), $(tmp1)) - squeezed_dynamic = @benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_dynamic)) - squeezed_static = @benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_static)) - - return time(median(sequential_dynamic)), - time(median(sequential_static)), - NaN, #FIXME time(median(sequential_nexpr)), - time(median(sequential_dynamic_prealloc)), - time(median(sequential_static_prealloc)), - time(median(squeezed_dynamic)), - time(median(squeezed_static)) + data_in = randn(n_vars, n_nodes_in, n_nodes_in) + data_out = randn(n_vars, n_nodes_out, n_nodes_out) + vandermonde_dynamic = randn(n_nodes_out, n_nodes_in) + vandermonde_static = SMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) + tmp1 = zeros(eltype(data_out), n_vars, n_nodes_out, n_nodes_in) + + println("n_vars = ", n_vars, "; n_nodes_in = ", n_nodes_in, "; n_nodes_out = ", n_nodes_out) + sequential_dynamic = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic)) + sequential_static = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static)) + #FIXME sequential_nexpr = @benchmark multiply_dimensionwise_sequential_nexpr!($(data_out), $(data_in), $(vandermonde_static), $(Val(n_vars))) + sequential_dynamic_prealloc = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic), $(tmp1)) + sequential_static_prealloc = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static), $(tmp1)) + squeezed_dynamic = @benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_dynamic)) + squeezed_static = @benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_static)) + + return time(median(sequential_dynamic)), + time(median(sequential_static)), + NaN, #FIXME time(median(sequential_nexpr)), + time(median(sequential_dynamic_prealloc)), + time(median(sequential_static_prealloc)), + time(median(squeezed_dynamic)), + time(median(squeezed_static)) end function compute_benchmarks_2d(n_vars_list, n_nodes_in_list) - sequential_dynamic = zeros(length(n_vars_list), length(n_nodes_in_list)) - sequential_static = zeros(length(n_vars_list), length(n_nodes_in_list)) - sequential_nexpr = zeros(length(n_vars_list), length(n_nodes_in_list)) - sequential_dynamic_prealloc = zeros(length(n_vars_list), length(n_nodes_in_list)) - sequential_static_prealloc = zeros(length(n_vars_list), length(n_nodes_in_list)) - squeezed_dynamic = zeros(length(n_vars_list), length(n_nodes_in_list)) - squeezed_static = zeros(length(n_vars_list), length(n_nodes_in_list)) - - # n_nodes_out = n_nodes_in - # mortar - # superset of n_vars = 1, n_nodes_out = n_nodes_in, used for blending - for (idx_nodes, n_nodes_in) in enumerate(n_nodes_in_list) - for (idx_variables, n_vars) in enumerate(n_vars_list) - n_nodes_out = n_nodes_in - sequential_dynamic[idx_variables, idx_nodes], - sequential_static[idx_variables, idx_nodes], - sequential_nexpr[idx_variables, idx_nodes], - sequential_dynamic_prealloc[idx_variables, idx_nodes], - sequential_static_prealloc[idx_variables, idx_nodes], - squeezed_dynamic[idx_variables, idx_nodes], - squeezed_static[idx_variables, idx_nodes] = - compute_benchmarks_2d(n_vars, n_nodes_in, n_nodes_out) + sequential_dynamic = zeros(length(n_vars_list), length(n_nodes_in_list)) + sequential_static = zeros(length(n_vars_list), length(n_nodes_in_list)) + sequential_nexpr = zeros(length(n_vars_list), length(n_nodes_in_list)) + sequential_dynamic_prealloc = zeros(length(n_vars_list), length(n_nodes_in_list)) + sequential_static_prealloc = zeros(length(n_vars_list), length(n_nodes_in_list)) + squeezed_dynamic = zeros(length(n_vars_list), length(n_nodes_in_list)) + squeezed_static = zeros(length(n_vars_list), length(n_nodes_in_list)) + + # n_nodes_out = n_nodes_in + # mortar + # superset of n_vars = 1, n_nodes_out = n_nodes_in, used for blending + for (idx_nodes, n_nodes_in) in enumerate(n_nodes_in_list) + for (idx_variables, n_vars) in enumerate(n_vars_list) + n_nodes_out = n_nodes_in + sequential_dynamic[idx_variables, idx_nodes], + sequential_static[idx_variables, idx_nodes], + sequential_nexpr[idx_variables, idx_nodes], + sequential_dynamic_prealloc[idx_variables, idx_nodes], + sequential_static_prealloc[idx_variables, idx_nodes], + squeezed_dynamic[idx_variables, idx_nodes], + squeezed_static[idx_variables, idx_nodes] = + compute_benchmarks_2d(n_vars, n_nodes_in, n_nodes_out) + end end - end - BSON.@save "2D_nVarTotal_nNodesIn.bson" n_vars_list n_nodes_in_list sequential_dynamic sequential_static sequential_nexpr sequential_dynamic_prealloc sequential_static_prealloc squeezed_dynamic squeezed_static - - # n_nodes_out = 2*n_nodes_in - # visualization - for (idx_nodes, n_nodes_in) in enumerate(n_nodes_in_list) - for (idx_variables, n_vars) in enumerate(n_vars_list) - n_nodes_out = 2 * n_nodes_in - sequential_dynamic[idx_variables, idx_nodes], - sequential_static[idx_variables, idx_nodes], - sequential_nexpr[idx_variables, idx_nodes], - sequential_dynamic_prealloc[idx_variables, idx_nodes], - sequential_static_prealloc[idx_variables, idx_nodes], - squeezed_dynamic[idx_variables, idx_nodes], - squeezed_static[idx_variables, idx_nodes] = - compute_benchmarks_2d(n_vars, n_nodes_in, n_nodes_out) + BSON.@save "2D_nVarTotal_nNodesIn.bson" n_vars_list n_nodes_in_list sequential_dynamic sequential_static sequential_nexpr sequential_dynamic_prealloc sequential_static_prealloc squeezed_dynamic squeezed_static + + # n_nodes_out = 2*n_nodes_in + # visualization + for (idx_nodes, n_nodes_in) in enumerate(n_nodes_in_list) + for (idx_variables, n_vars) in enumerate(n_vars_list) + n_nodes_out = 2 * n_nodes_in + sequential_dynamic[idx_variables, idx_nodes], + sequential_static[idx_variables, idx_nodes], + sequential_nexpr[idx_variables, idx_nodes], + sequential_dynamic_prealloc[idx_variables, idx_nodes], + sequential_static_prealloc[idx_variables, idx_nodes], + squeezed_dynamic[idx_variables, idx_nodes], + squeezed_static[idx_variables, idx_nodes] = + compute_benchmarks_2d(n_vars, n_nodes_in, n_nodes_out) + end end - end - BSON.@save "2D_nVarTotal_2nNodesIn.bson" n_vars_list n_nodes_in_list sequential_dynamic sequential_static sequential_nexpr sequential_dynamic_prealloc sequential_static_prealloc squeezed_dynamic squeezed_static + BSON.@save "2D_nVarTotal_2nNodesIn.bson" n_vars_list n_nodes_in_list sequential_dynamic sequential_static sequential_nexpr sequential_dynamic_prealloc sequential_static_prealloc squeezed_dynamic squeezed_static - return nothing + return nothing end # TODO @@ -400,322 +407,329 @@ compute_benchmarks_2d(1:10, 2:10) # 3D versions function multiply_dimensionwise_sequential!( - data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde, - tmp1=zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2), size(vandermonde, 2)), - tmp2=zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 1), size(vandermonde, 2))) - n_vars = size(data_out, 1) - n_nodes_out = size(vandermonde, 1) - n_nodes_in = size(vandermonde, 2) - data_out .= zero(eltype(data_out)) - - @boundscheck begin - inbounds = (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == n_nodes_out) && - (size(data_in, 1) == n_vars) && - (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == n_nodes_in) - inbounds || throw(BoundsError()) - end - - # Interpolate in x-direction - @inbounds for k in 1:n_nodes_in, j in 1:n_nodes_in, i in 1:n_nodes_out - for ii in 1:n_nodes_in - for v in 1:n_vars - tmp1[v, i, j, k] += vandermonde[i, ii] * data_in[v, ii, j, k] - end + data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde, + tmp1 = zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2), size(vandermonde, 2)), + tmp2 = zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 1), size(vandermonde, 2)) + ) + n_vars = size(data_out, 1) + n_nodes_out = size(vandermonde, 1) + n_nodes_in = size(vandermonde, 2) + data_out .= zero(eltype(data_out)) + + @boundscheck begin + inbounds = (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == n_nodes_out) && + (size(data_in, 1) == n_vars) && + (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == n_nodes_in) + inbounds || throw(BoundsError()) + end + + # Interpolate in x-direction + @inbounds for k in 1:n_nodes_in, j in 1:n_nodes_in, i in 1:n_nodes_out + for ii in 1:n_nodes_in + for v in 1:n_vars + tmp1[v, i, j, k] += vandermonde[i, ii] * data_in[v, ii, j, k] + end + end end - end - - # Interpolate in y-direction - @inbounds for k in 1:n_nodes_in, j in 1:n_nodes_out, i in 1:n_nodes_out - for jj in 1:n_nodes_in - for v in 1:n_vars - tmp2[v, i, j, k] += vandermonde[j, jj] * tmp1[v, i, jj, k] - end + + # Interpolate in y-direction + @inbounds for k in 1:n_nodes_in, j in 1:n_nodes_out, i in 1:n_nodes_out + for jj in 1:n_nodes_in + for v in 1:n_vars + tmp2[v, i, j, k] += vandermonde[j, jj] * tmp1[v, i, jj, k] + end + end end - end - - # Interpolate in z-direction - @inbounds for k in 1:n_nodes_out, j in 1:n_nodes_out, i in 1:n_nodes_out - for kk in 1:n_nodes_in - for v in 1:n_vars - data_out[v, i, j, k] += vandermonde[k, kk] * tmp2[v, i, j, kk] - end + + # Interpolate in z-direction + @inbounds for k in 1:n_nodes_out, j in 1:n_nodes_out, i in 1:n_nodes_out + for kk in 1:n_nodes_in + for v in 1:n_vars + data_out[v, i, j, k] += vandermonde[k, kk] * tmp2[v, i, j, kk] + end + end end - end - return data_out + return data_out end function multiply_dimensionwise_sequential_avx!( - data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde, - tmp1=zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2), size(vandermonde, 2)), - tmp2=zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 1), size(vandermonde, 2))) - n_vars = size(data_out, 1) - n_nodes_out = size(vandermonde, 1) - n_nodes_in = size(vandermonde, 2) - data_out .= zero(eltype(data_out)) - - @boundscheck begin - inbounds = (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == n_nodes_out) && - (size(data_in, 1) == n_vars) && - (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == n_nodes_in) - inbounds || throw(BoundsError()) - end - - # Interpolate in x-direction - @avx for k in 1:n_nodes_in, j in 1:n_nodes_in, i in 1:n_nodes_out - for ii in 1:n_nodes_in - for v in 1:n_vars - tmp1[v, i, j, k] += vandermonde[i, ii] * data_in[v, ii, j, k] - end + data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde, + tmp1 = zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2), size(vandermonde, 2)), + tmp2 = zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 1), size(vandermonde, 2)) + ) + n_vars = size(data_out, 1) + n_nodes_out = size(vandermonde, 1) + n_nodes_in = size(vandermonde, 2) + data_out .= zero(eltype(data_out)) + + @boundscheck begin + inbounds = (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == n_nodes_out) && + (size(data_in, 1) == n_vars) && + (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == n_nodes_in) + inbounds || throw(BoundsError()) + end + + # Interpolate in x-direction + @avx for k in 1:n_nodes_in, j in 1:n_nodes_in, i in 1:n_nodes_out + for ii in 1:n_nodes_in + for v in 1:n_vars + tmp1[v, i, j, k] += vandermonde[i, ii] * data_in[v, ii, j, k] + end + end end - end - - # Interpolate in y-direction - @avx for k in 1:n_nodes_in, j in 1:n_nodes_out, i in 1:n_nodes_out - for jj in 1:n_nodes_in - for v in 1:n_vars - tmp2[v, i, j, k] += vandermonde[j, jj] * tmp1[v, i, jj, k] - end + + # Interpolate in y-direction + @avx for k in 1:n_nodes_in, j in 1:n_nodes_out, i in 1:n_nodes_out + for jj in 1:n_nodes_in + for v in 1:n_vars + tmp2[v, i, j, k] += vandermonde[j, jj] * tmp1[v, i, jj, k] + end + end end - end - - # Interpolate in z-direction - @avx for k in 1:n_nodes_out, j in 1:n_nodes_out, i in 1:n_nodes_out - for kk in 1:n_nodes_in - for v in 1:n_vars - data_out[v, i, j, k] += vandermonde[k, kk] * tmp2[v, i, j, kk] - end + + # Interpolate in z-direction + @avx for k in 1:n_nodes_out, j in 1:n_nodes_out, i in 1:n_nodes_out + for kk in 1:n_nodes_in + for v in 1:n_vars + data_out[v, i, j, k] += vandermonde[k, kk] * tmp2[v, i, j, kk] + end + end end - end - return data_out + return data_out end function multiply_dimensionwise_sequential_tullio!( - data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde, - tmp1=zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2), size(vandermonde, 2)), - tmp2=zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 1), size(vandermonde, 2))) + data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde, + tmp1 = zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 2), size(vandermonde, 2)), + tmp2 = zeros(eltype(data_out), size(data_out, 1), size(vandermonde, 1), size(vandermonde, 1), size(vandermonde, 2)) + ) - # Interpolate in x-direction - @tullio threads=false tmp1[v, i, j, k] = vandermonde[i, ii] * data_in[v, ii, j, k] + # Interpolate in x-direction + @tullio threads = false tmp1[v, i, j, k] = vandermonde[i, ii] * data_in[v, ii, j, k] - # Interpolate in y-direction - @tullio threads=false tmp2[v, i, j, k] = vandermonde[j, jj] * tmp1[v, i, jj, k] + # Interpolate in y-direction + @tullio threads = false tmp2[v, i, j, k] = vandermonde[j, jj] * tmp1[v, i, jj, k] - # Interpolate in z-direction - @tullio threads=false data_out[v, i, j, k] = vandermonde[k, kk] * tmp2[v, i, j, kk] + # Interpolate in z-direction + @tullio threads = false data_out[v, i, j, k] = vandermonde[k, kk] * tmp2[v, i, j, kk] - return data_out + return data_out end @generated function multiply_dimensionwise_sequential_nexpr!( - data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde::SMatrix{n_nodes_out,n_nodes_in}, ::Val{n_vars}) where {n_nodes_out, n_nodes_in, n_vars} - quote - @boundscheck begin - inbounds = (size(data_out, 1) == $n_vars) && - (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == $n_nodes_out) && - (size(data_in, 1) == $n_vars) && - (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == $n_nodes_in) - inbounds || throw(BoundsError()) - end + data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde::SMatrix{n_nodes_out, n_nodes_in}, ::Val{n_vars} + ) where {n_nodes_out, n_nodes_in, n_vars} + quote + @boundscheck begin + inbounds = (size(data_out, 1) == $n_vars) && + (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == $n_nodes_out) && + (size(data_in, 1) == $n_vars) && + (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == $n_nodes_in) + inbounds || throw(BoundsError()) + end - # Interpolate in x-direction - @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_in k -> begin - Base.Cartesian.@nexprs $n_nodes_in j -> begin - Base.Cartesian.@nexprs $n_nodes_out i -> begin - Base.Cartesian.@nexprs $n_vars v -> begin - tmp1_v_i_j_k = zero(eltype(data_out)) - Base.Cartesian.@nexprs $n_nodes_in ii -> begin - tmp1_v_i_j_k += vandermonde[i, ii] * data_in[v, ii, j, k] + # Interpolate in x-direction + @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_in k -> begin + Base.Cartesian.@nexprs $n_nodes_in j -> begin + Base.Cartesian.@nexprs $n_nodes_out i -> begin + Base.Cartesian.@nexprs $n_vars v -> begin + tmp1_v_i_j_k = zero(eltype(data_out)) + Base.Cartesian.@nexprs $n_nodes_in ii -> begin + tmp1_v_i_j_k += vandermonde[i, ii] * data_in[v, ii, j, k] + end + end + end end - end end - end - end - # Interpolate in y-direction - @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_in k -> begin - Base.Cartesian.@nexprs $n_nodes_out j -> begin - Base.Cartesian.@nexprs $n_nodes_out i -> begin - Base.Cartesian.@nexprs $n_vars v -> begin - tmp2_v_i_j_k = zero(eltype(data_out)) - Base.Cartesian.@nexprs $n_nodes_in jj -> begin - tmp2_v_i_j_k += vandermonde[j, jj] * tmp1_v_i_jj_k + # Interpolate in y-direction + @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_in k -> begin + Base.Cartesian.@nexprs $n_nodes_out j -> begin + Base.Cartesian.@nexprs $n_nodes_out i -> begin + Base.Cartesian.@nexprs $n_vars v -> begin + tmp2_v_i_j_k = zero(eltype(data_out)) + Base.Cartesian.@nexprs $n_nodes_in jj -> begin + tmp2_v_i_j_k += vandermonde[j, jj] * tmp1_v_i_jj_k + end + end + end end - end end - end - end - # Interpolate in z-direction - @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_out k -> begin - Base.Cartesian.@nexprs $n_nodes_out j -> begin - Base.Cartesian.@nexprs $n_nodes_out i -> begin - Base.Cartesian.@nexprs $n_vars v -> begin - tmp3_v_i_j_k = zero(eltype(data_out)) - Base.Cartesian.@nexprs $n_nodes_in kk -> begin - tmp3_v_i_j_k += vandermonde[k, kk] * tmp2_v_i_j_kk + # Interpolate in z-direction + @inbounds @muladd Base.Cartesian.@nexprs $n_nodes_out k -> begin + Base.Cartesian.@nexprs $n_nodes_out j -> begin + Base.Cartesian.@nexprs $n_nodes_out i -> begin + Base.Cartesian.@nexprs $n_vars v -> begin + tmp3_v_i_j_k = zero(eltype(data_out)) + Base.Cartesian.@nexprs $n_nodes_in kk -> begin + tmp3_v_i_j_k += vandermonde[k, kk] * tmp2_v_i_j_kk + end + data_out[v, i, j, k] = tmp3_v_i_j_k + end + end end - data_out[v, i, j, k] = tmp3_v_i_j_k - end end - end - end - return data_out - end + return data_out + end end function multiply_dimensionwise_squeezed!( - data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde) - n_vars = size(data_out, 1) - n_nodes_out = size(vandermonde, 1) - n_nodes_in = size(vandermonde, 2) - data_out .= zero(eltype(data_out)) - - @boundscheck begin - inbounds = (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == n_nodes_out) && - (size(data_in, 1) == n_vars) && - (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == n_nodes_in) - inbounds || throw(BoundsError()) - end - - @inbounds for k in 1:n_nodes_out, j in 1:n_nodes_out, i in 1:n_nodes_out - for v in 1:n_vars - acc = zero(eltype(data_out)) - for kk in 1:n_nodes_in, jj in 1:n_nodes_in, ii in 1:n_nodes_in - acc += vandermonde[i, ii] * vandermonde[j, jj] * vandermonde[k, kk] * data_in[v, ii, jj, kk] - end - data_out[v, i, j, k] = acc + data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde + ) + n_vars = size(data_out, 1) + n_nodes_out = size(vandermonde, 1) + n_nodes_in = size(vandermonde, 2) + data_out .= zero(eltype(data_out)) + + @boundscheck begin + inbounds = (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == n_nodes_out) && + (size(data_in, 1) == n_vars) && + (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == n_nodes_in) + inbounds || throw(BoundsError()) end - end - return data_out + @inbounds for k in 1:n_nodes_out, j in 1:n_nodes_out, i in 1:n_nodes_out + for v in 1:n_vars + acc = zero(eltype(data_out)) + for kk in 1:n_nodes_in, jj in 1:n_nodes_in, ii in 1:n_nodes_in + acc += vandermonde[i, ii] * vandermonde[j, jj] * vandermonde[k, kk] * data_in[v, ii, jj, kk] + end + data_out[v, i, j, k] = acc + end + end + + return data_out end function multiply_dimensionwise_squeezed_avx!( - data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde) - n_vars = size(data_out, 1) - n_nodes_out = size(vandermonde, 1) - n_nodes_in = size(vandermonde, 2) - data_out .= zero(eltype(data_out)) - - @boundscheck begin - inbounds = (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == n_nodes_out) && - (size(data_in, 1) == n_vars) && - (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == n_nodes_in) - inbounds || throw(BoundsError()) - end - - @avx for k in 1:n_nodes_out, j in 1:n_nodes_out, i in 1:n_nodes_out - for v in 1:n_vars - acc = zero(eltype(data_out)) - for kk in 1:n_nodes_in, jj in 1:n_nodes_in, ii in 1:n_nodes_in - acc += vandermonde[i, ii] * vandermonde[j, jj] * vandermonde[k, kk] * data_in[v, ii, jj, kk] - end - data_out[v, i, j, k] = acc + data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde + ) + n_vars = size(data_out, 1) + n_nodes_out = size(vandermonde, 1) + n_nodes_in = size(vandermonde, 2) + data_out .= zero(eltype(data_out)) + + @boundscheck begin + inbounds = (size(data_out, 2) == size(data_out, 3) == size(data_out, 4) == n_nodes_out) && + (size(data_in, 1) == n_vars) && + (size(data_in, 2) == size(data_in, 3) == size(data_in, 4) == n_nodes_in) + inbounds || throw(BoundsError()) end - end - return data_out + @avx for k in 1:n_nodes_out, j in 1:n_nodes_out, i in 1:n_nodes_out + for v in 1:n_vars + acc = zero(eltype(data_out)) + for kk in 1:n_nodes_in, jj in 1:n_nodes_in, ii in 1:n_nodes_in + acc += vandermonde[i, ii] * vandermonde[j, jj] * vandermonde[k, kk] * data_in[v, ii, jj, kk] + end + data_out[v, i, j, k] = acc + end + end + + return data_out end function multiply_dimensionwise_squeezed_tullio!( - data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde) + data_out::AbstractArray{<:Any, 4}, data_in::AbstractArray{<:Any, 4}, vandermonde + ) - @tullio threads=false data_out[v, i, j, k] = vandermonde[i, ii] * vandermonde[j, jj] * vandermonde[k, kk] * data_in[v, ii, jj, kk] + @tullio threads = false data_out[v, i, j, k] = vandermonde[i, ii] * vandermonde[j, jj] * vandermonde[k, kk] * data_in[v, ii, jj, kk] - return data_out + return data_out end -function run_benchmarks_3d(n_vars=4, n_nodes_in=4, n_nodes_out=2*n_nodes_in) - data_in = randn(n_vars, n_nodes_in, n_nodes_in, n_nodes_in) - data_out = randn(n_vars, n_nodes_out, n_nodes_out, n_nodes_out) - vandermonde_dynamic = randn(n_nodes_out, n_nodes_in) - vandermonde_static = SMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) - - println("\n\n# 3D ", "#"^70) - println("n_vars = ", n_vars) - println("n_nodes_in = ", n_nodes_in) - println("n_nodes_out = ", n_nodes_out) - println() - - println("\n","multiply_dimensionwise_sequential!") - println("vandermonde_dynamic") - multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_dynamic) - data_out_copy = copy(data_out) - display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_static))) - println() - - println("\n","multiply_dimensionwise_sequential_avx!") - println("vandermonde_dynamic") - multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_static))) - println() - - println("\n","multiply_dimensionwise_sequential_tullio!") - println("vandermonde_dynamic") - multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static))) - println() - - println("\n","multiply_dimensionwise_sequential_nexpr!") - println("vandermonde_static") - multiply_dimensionwise_sequential_nexpr!(data_out, data_in, vandermonde_static, Val(n_vars)) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_sequential_nexpr!($(data_out), $(data_in), $(vandermonde_static), $(Val(n_vars)))) - println() - - println("\n","multiply_dimensionwise_squeezed!") - println("vandermonde_dynamic") - multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_static))) - println() - - println("\n","multiply_dimensionwise_squeezed_avx!") - println("vandermonde_dynamic") - multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_static))) - println() - - println("\n","multiply_dimensionwise_squeezed_tullio!") - println("vandermonde_dynamic") - multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_dynamic) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_dynamic))) - println("\n", "vandermonde_static") - multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_static) - @assert data_out ≈ data_out_copy - display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_static))) - println() - - nothing +function run_benchmarks_3d(n_vars = 4, n_nodes_in = 4, n_nodes_out = 2 * n_nodes_in) + data_in = randn(n_vars, n_nodes_in, n_nodes_in, n_nodes_in) + data_out = randn(n_vars, n_nodes_out, n_nodes_out, n_nodes_out) + vandermonde_dynamic = randn(n_nodes_out, n_nodes_in) + vandermonde_static = SMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) + + println("\n\n# 3D ", "#"^70) + println("n_vars = ", n_vars) + println("n_nodes_in = ", n_nodes_in) + println("n_nodes_out = ", n_nodes_out) + println() + + println("\n", "multiply_dimensionwise_sequential!") + println("vandermonde_dynamic") + multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_dynamic) + data_out_copy = copy(data_out) + display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_sequential!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential!($(data_out), $(data_in), $(vandermonde_static))) + println() + + println("\n", "multiply_dimensionwise_sequential_avx!") + println("vandermonde_dynamic") + multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_sequential_avx!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_avx!($(data_out), $(data_in), $(vandermonde_static))) + println() + + println("\n", "multiply_dimensionwise_sequential_tullio!") + println("vandermonde_dynamic") + multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_sequential_tullio!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static))) + println() + + println("\n", "multiply_dimensionwise_sequential_nexpr!") + println("vandermonde_static") + multiply_dimensionwise_sequential_nexpr!(data_out, data_in, vandermonde_static, Val(n_vars)) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_sequential_nexpr!($(data_out), $(data_in), $(vandermonde_static), $(Val(n_vars)))) + println() + + println("\n", "multiply_dimensionwise_squeezed!") + println("vandermonde_dynamic") + multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_squeezed!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed!($(data_out), $(data_in), $(vandermonde_static))) + println() + + println("\n", "multiply_dimensionwise_squeezed_avx!") + println("vandermonde_dynamic") + multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_squeezed_avx!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_avx!($(data_out), $(data_in), $(vandermonde_static))) + println() + + println("\n", "multiply_dimensionwise_squeezed_tullio!") + println("vandermonde_dynamic") + multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_dynamic) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_dynamic))) + println("\n", "vandermonde_static") + multiply_dimensionwise_squeezed_tullio!(data_out, data_in, vandermonde_static) + @assert data_out ≈ data_out_copy + display(@benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_static))) + println() + + nothing end # TODO @@ -723,78 +737,78 @@ run_benchmarks_3d(5, 4, 4) # n_vars, n_nodes_in, n_nodes_out function compute_benchmarks_3d(n_vars, n_nodes_in, n_nodes_out) - data_in = randn(n_vars, n_nodes_in, n_nodes_in, n_nodes_in) - data_out = randn(n_vars, n_nodes_out, n_nodes_out, n_nodes_out) - vandermonde_dynamic = randn(n_nodes_out, n_nodes_in) - vandermonde_static = SMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) - tmp1 = zeros(eltype(data_out), n_vars, n_nodes_out, n_nodes_in, n_nodes_in) - tmp2 = zeros(eltype(data_out), n_vars, n_nodes_out, n_nodes_out, n_nodes_in) - - println("n_vars = ", n_vars, "; n_nodes_in = ", n_nodes_in, "; n_nodes_out = ", n_nodes_out) - sequential_dynamic = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic)) - sequential_static = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static)) - #FIXME sequential_nexpr = @benchmark multiply_dimensionwise_sequential_nexpr!($(data_out), $(data_in), $(vandermonde_static), $(Val(n_vars))) - sequential_dynamic_prealloc = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic), $(tmp1), $(tmp2)) - sequential_static_prealloc = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static), $(tmp1), $(tmp2)) - squeezed_dynamic = @benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_dynamic)) - squeezed_static = @benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_static)) - - return time(median(sequential_dynamic)), - time(median(sequential_static)), - NaN, #FIXME time(median(sequential_nexpr)), - time(median(sequential_dynamic_prealloc)), - time(median(sequential_static_prealloc)), - time(median(squeezed_dynamic)), - time(median(squeezed_static)) + data_in = randn(n_vars, n_nodes_in, n_nodes_in, n_nodes_in) + data_out = randn(n_vars, n_nodes_out, n_nodes_out, n_nodes_out) + vandermonde_dynamic = randn(n_nodes_out, n_nodes_in) + vandermonde_static = SMatrix{n_nodes_out, n_nodes_in}(vandermonde_dynamic) + tmp1 = zeros(eltype(data_out), n_vars, n_nodes_out, n_nodes_in, n_nodes_in) + tmp2 = zeros(eltype(data_out), n_vars, n_nodes_out, n_nodes_out, n_nodes_in) + + println("n_vars = ", n_vars, "; n_nodes_in = ", n_nodes_in, "; n_nodes_out = ", n_nodes_out) + sequential_dynamic = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic)) + sequential_static = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static)) + #FIXME sequential_nexpr = @benchmark multiply_dimensionwise_sequential_nexpr!($(data_out), $(data_in), $(vandermonde_static), $(Val(n_vars))) + sequential_dynamic_prealloc = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_dynamic), $(tmp1), $(tmp2)) + sequential_static_prealloc = @benchmark multiply_dimensionwise_sequential_tullio!($(data_out), $(data_in), $(vandermonde_static), $(tmp1), $(tmp2)) + squeezed_dynamic = @benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_dynamic)) + squeezed_static = @benchmark multiply_dimensionwise_squeezed_tullio!($(data_out), $(data_in), $(vandermonde_static)) + + return time(median(sequential_dynamic)), + time(median(sequential_static)), + NaN, #FIXME time(median(sequential_nexpr)), + time(median(sequential_dynamic_prealloc)), + time(median(sequential_static_prealloc)), + time(median(squeezed_dynamic)), + time(median(squeezed_static)) end function compute_benchmarks_3d(n_vars_list, n_nodes_in_list) - sequential_dynamic = zeros(length(n_vars_list), length(n_nodes_in_list)) - sequential_static = zeros(length(n_vars_list), length(n_nodes_in_list)) - sequential_nexpr = zeros(length(n_vars_list), length(n_nodes_in_list)) - sequential_dynamic_prealloc = zeros(length(n_vars_list), length(n_nodes_in_list)) - sequential_static_prealloc = zeros(length(n_vars_list), length(n_nodes_in_list)) - squeezed_dynamic = zeros(length(n_vars_list), length(n_nodes_in_list)) - squeezed_static = zeros(length(n_vars_list), length(n_nodes_in_list)) - - # n_nodes_out = n_nodes_in - # mortar - # superset of n_vars = 1, n_nodes_out = n_nodes_in, used for blending - for (idx_nodes, n_nodes_in) in enumerate(n_nodes_in_list) - for (idx_variables, n_vars) in enumerate(n_vars_list) - n_nodes_out = n_nodes_in - sequential_dynamic[idx_variables, idx_nodes], - sequential_static[idx_variables, idx_nodes], - sequential_nexpr[idx_variables, idx_nodes], - sequential_dynamic_prealloc[idx_variables, idx_nodes], - sequential_static_prealloc[idx_variables, idx_nodes], - squeezed_dynamic[idx_variables, idx_nodes], - squeezed_static[idx_variables, idx_nodes] = - compute_benchmarks_3d(n_vars, n_nodes_in, n_nodes_out) + sequential_dynamic = zeros(length(n_vars_list), length(n_nodes_in_list)) + sequential_static = zeros(length(n_vars_list), length(n_nodes_in_list)) + sequential_nexpr = zeros(length(n_vars_list), length(n_nodes_in_list)) + sequential_dynamic_prealloc = zeros(length(n_vars_list), length(n_nodes_in_list)) + sequential_static_prealloc = zeros(length(n_vars_list), length(n_nodes_in_list)) + squeezed_dynamic = zeros(length(n_vars_list), length(n_nodes_in_list)) + squeezed_static = zeros(length(n_vars_list), length(n_nodes_in_list)) + + # n_nodes_out = n_nodes_in + # mortar + # superset of n_vars = 1, n_nodes_out = n_nodes_in, used for blending + for (idx_nodes, n_nodes_in) in enumerate(n_nodes_in_list) + for (idx_variables, n_vars) in enumerate(n_vars_list) + n_nodes_out = n_nodes_in + sequential_dynamic[idx_variables, idx_nodes], + sequential_static[idx_variables, idx_nodes], + sequential_nexpr[idx_variables, idx_nodes], + sequential_dynamic_prealloc[idx_variables, idx_nodes], + sequential_static_prealloc[idx_variables, idx_nodes], + squeezed_dynamic[idx_variables, idx_nodes], + squeezed_static[idx_variables, idx_nodes] = + compute_benchmarks_3d(n_vars, n_nodes_in, n_nodes_out) + end end - end - BSON.@save "3D_nVarTotal_nNodesIn.bson" n_vars_list n_nodes_in_list sequential_dynamic sequential_static sequential_nexpr sequential_dynamic_prealloc sequential_static_prealloc squeezed_dynamic squeezed_static - - # TODO deactivated to save some time - # # n_nodes_out = 2*n_nodes_in - # # visualization - # title = "n_vars = 2*n_vars, n_nodes_out = n_nodes_in" - # for (idx_nodes, n_nodes_in) in enumerate(n_nodes_in_list) - # for (idx_variables, n_vars) in enumerate(n_vars_list) - # n_nodes_out = 2 * n_nodes_in - # sequential_dynamic[idx_variables, idx_nodes], - # sequential_static[idx_variables, idx_nodes], - # sequential_nexpr[idx_variables, idx_nodes], - # sequential_dynamic_prealloc[idx_variables, idx_nodes], - # sequential_static_prealloc[idx_variables, idx_nodes], - # squeezed_dynamic[idx_variables, idx_nodes], - # squeezed_static[idx_variables, idx_nodes] = - # compute_benchmarks_3d(n_vars, n_nodes_in, n_nodes_out) - # end - # end - # BSON.@save "3D_nVarTotal_2nNodesIn.bson" n_vars_list n_nodes_in_list sequential_dynamic sequential_static sequential_nexpr sequential_dynamic_prealloc sequential_static_prealloc squeezed_dynamic squeezed_static - - return nothing + BSON.@save "3D_nVarTotal_nNodesIn.bson" n_vars_list n_nodes_in_list sequential_dynamic sequential_static sequential_nexpr sequential_dynamic_prealloc sequential_static_prealloc squeezed_dynamic squeezed_static + + # TODO deactivated to save some time + # # n_nodes_out = 2*n_nodes_in + # # visualization + # title = "n_vars = 2*n_vars, n_nodes_out = n_nodes_in" + # for (idx_nodes, n_nodes_in) in enumerate(n_nodes_in_list) + # for (idx_variables, n_vars) in enumerate(n_vars_list) + # n_nodes_out = 2 * n_nodes_in + # sequential_dynamic[idx_variables, idx_nodes], + # sequential_static[idx_variables, idx_nodes], + # sequential_nexpr[idx_variables, idx_nodes], + # sequential_dynamic_prealloc[idx_variables, idx_nodes], + # sequential_static_prealloc[idx_variables, idx_nodes], + # squeezed_dynamic[idx_variables, idx_nodes], + # squeezed_static[idx_variables, idx_nodes] = + # compute_benchmarks_3d(n_vars, n_nodes_in, n_nodes_out) + # end + # end + # BSON.@save "3D_nVarTotal_2nNodesIn.bson" n_vars_list n_nodes_in_list sequential_dynamic sequential_static sequential_nexpr sequential_dynamic_prealloc sequential_static_prealloc squeezed_dynamic squeezed_static + + return nothing end diff --git a/benchmark/run_benchmarks.jl b/benchmark/run_benchmarks.jl index 7b8c25752f..e93cd5e0f4 100644 --- a/benchmark/run_benchmarks.jl +++ b/benchmark/run_benchmarks.jl @@ -6,18 +6,30 @@ Pkg.instantiate() using PkgBenchmark using Trixi -let results = judge(Trixi, - BenchmarkConfig(juliacmd = `$(Base.julia_cmd()) --check-bounds=no --threads=1`), # target - BenchmarkConfig(juliacmd = `$(Base.julia_cmd()) --check-bounds=no --threads=1`, - id = "main")) - export_markdown(pkgdir(Trixi, "benchmark", "results_$(gethostname())_threads1.md"), - results) +let results = judge( + Trixi, + BenchmarkConfig(juliacmd = `$(Base.julia_cmd()) --check-bounds=no --threads=1`), # target + BenchmarkConfig( + juliacmd = `$(Base.julia_cmd()) --check-bounds=no --threads=1`, + id = "main" + ) + ) + export_markdown( + pkgdir(Trixi, "benchmark", "results_$(gethostname())_threads1.md"), + results + ) end -let results = judge(Trixi, - BenchmarkConfig(juliacmd = `$(Base.julia_cmd()) --check-bounds=no --threads=2`), # target - BenchmarkConfig(juliacmd = `$(Base.julia_cmd()) --check-bounds=no --threads=2`, - id = "main")) - export_markdown(pkgdir(Trixi, "benchmark", "results_$(gethostname())_threads2.md"), - results) +let results = judge( + Trixi, + BenchmarkConfig(juliacmd = `$(Base.julia_cmd()) --check-bounds=no --threads=2`), # target + BenchmarkConfig( + juliacmd = `$(Base.julia_cmd()) --check-bounds=no --threads=2`, + id = "main" + ) + ) + export_markdown( + pkgdir(Trixi, "benchmark", "results_$(gethostname())_threads2.md"), + results + ) end diff --git a/docs/literate/make.jl b/docs/literate/make.jl index 262a236971..a48df9ad63 100644 --- a/docs/literate/make.jl +++ b/docs/literate/make.jl @@ -3,22 +3,22 @@ using Test: @testset import Pkg # Create markdown and notebook files for `file` -function create_files(title, file, repo_src, pages_dir, notebooks_dir; folder="") +function create_files(title, file, repo_src, pages_dir, notebooks_dir; folder = "") notebook_filename = first(splitext(file)) * ".ipynb" if !isempty(folder) notebook_filename = joinpath(folder, notebook_filename) end - binder_logo = "https://mybinder.org/badge_logo.svg" + binder_logo = "https://mybinder.org/badge_logo.svg" nbviewer_logo = "https://img.shields.io/badge/render-nbviewer-f37726" raw_notebook_logo = "https://img.shields.io/badge/raw-notebook-4cc61e" notebook_path = "tutorials/notebooks/$notebook_filename" - binder_url = "https://mybinder.org/v2/gh/trixi-framework/Trixi.jl/tutorial_notebooks?filepath=$notebook_path" + binder_url = "https://mybinder.org/v2/gh/trixi-framework/Trixi.jl/tutorial_notebooks?filepath=$notebook_path" nbviewer_url = "https://nbviewer.jupyter.org/github/trixi-framework/Trixi.jl/blob/tutorial_notebooks/$notebook_path" raw_notebook_url = "https://raw.githubusercontent.com/trixi-framework/Trixi.jl/tutorial_notebooks/$notebook_path" - binder_badge = "# [![]($binder_logo)]($binder_url)" + binder_badge = "# [![]($binder_logo)]($binder_url)" nbviewer_badge = "# [![]($nbviewer_logo)]($nbviewer_url)" raw_notebook_badge = "# [![]($raw_notebook_logo)]($raw_notebook_url)" @@ -28,25 +28,25 @@ function create_files(title, file, repo_src, pages_dir, notebooks_dir; folder="" # available for the latest stable release of Trixi.jl at the time of caching.\n\n" return string("# # $title\n\n", warning, content) end - Literate.notebook(joinpath(repo_src, folder, file), joinpath(notebooks_dir, folder); execute=false, preprocess=preprocess_notebook, credit=false) + Literate.notebook(joinpath(repo_src, folder, file), joinpath(notebooks_dir, folder); execute = false, preprocess = preprocess_notebook, credit = false) # Generate markdown file function preprocess_docs(content) return string("# # [$title](@id $(splitext(file)[1]))\n $binder_badge\n $nbviewer_badge\n $raw_notebook_badge\n\n", content) end - Literate.markdown(joinpath(repo_src, folder, file), joinpath(pages_dir, folder); preprocess=preprocess_docs,) + Literate.markdown(joinpath(repo_src, folder, file), joinpath(pages_dir, folder); preprocess = preprocess_docs) end # Create tutorials with Literate.jl function create_tutorials(files) - repo_src = joinpath(@__DIR__, "src", "files") + repo_src = joinpath(@__DIR__, "src", "files") - pages_dir = joinpath(@__DIR__, "..", "src", "tutorials") - notebooks_dir = joinpath(pages_dir, "notebooks") + pages_dir = joinpath(@__DIR__, "..", "src", "tutorials") + notebooks_dir = joinpath(pages_dir, "notebooks") - Sys.rm(pages_dir; recursive=true, force=true) + Sys.rm(pages_dir; recursive = true, force = true) - Sys.rm("out"; recursive=true, force=true) + Sys.rm("out"; recursive = true, force = true) # Run tests on all tutorial files @testset "TrixiTutorials" begin @@ -59,7 +59,7 @@ function create_tutorials(files) mod = gensym(filename[j][2][2]) @testset "$(filename[j][2][2])" begin @eval module $mod - include(joinpath($repo_src, $(filename[j][2][1]), $(filename[j][2][2]))) + include(joinpath($repo_src, $(filename[j][2][1]), $(filename[j][2][2]))) end end end @@ -67,7 +67,7 @@ function create_tutorials(files) mod = gensym(title) @testset "$title" begin @eval module $mod - include(joinpath($repo_src, $filename)) + include(joinpath($repo_src, $filename)) end end end @@ -85,9 +85,9 @@ function create_tutorials(files) end return content end - Literate.markdown(joinpath(repo_src, "index.jl"), pages_dir; name="introduction", preprocess=preprocess_introduction) + Literate.markdown(joinpath(repo_src, "index.jl"), pages_dir; name = "introduction", preprocess = preprocess_introduction) # Navigation system for makedocs - pages = Any["Introduction" => "tutorials/introduction.md",] + pages = Any["Introduction" => "tutorials/introduction.md"] # Create markdown and notebook files for tutorials for (i, (title, filename)) in enumerate(files) @@ -95,8 +95,10 @@ function create_tutorials(files) if filename isa Vector vector = [] for j in eachindex(filename) - create_files("$i.$j: $title: $(filename[j][1])", filename[j][2][2], repo_src, - pages_dir, notebooks_dir; folder=filename[j][2][1]) + create_files( + "$i.$j: $title: $(filename[j][1])", filename[j][2][2], repo_src, + pages_dir, notebooks_dir; folder = filename[j][2][1] + ) path = "$(filename[j][2][1])/$(splitext(filename[j][2][2])[1]).md" push!(vector, "$i.$j $(filename[j][1])" => "tutorials/$path") diff --git a/docs/literate/src/files/DGMulti_1.jl b/docs/literate/src/files/DGMulti_1.jl index 6c9a5aea93..66f949c3f3 100644 --- a/docs/literate/src/files/DGMulti_1.jl +++ b/docs/literate/src/files/DGMulti_1.jl @@ -21,31 +21,39 @@ initial_condition = initial_condition_weak_blast_wave # To use the Gauss-Lobatto nodes again, we choose `approximation_type=SBP()` in the solver. # Since we want to start with a Cartesian domain discretized with squares, we use the element # type `Quad()`. -dg = DGMulti(polydeg = 3, - element_type = Quad(), - approximation_type = SBP(), - surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, + element_type = Quad(), + approximation_type = SBP(), + surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) cells_per_dimension = (32, 32) -mesh = DGMultiMesh(dg, - cells_per_dimension, # initial_refinement_level = 5 - coordinates_min=(-2.0, -2.0), - coordinates_max=( 2.0, 2.0), - periodicity=true) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - boundary_conditions=boundary_condition_periodic) +mesh = DGMultiMesh( + dg, + cells_per_dimension, # initial_refinement_level = 5 + coordinates_min = (-2.0, -2.0), + coordinates_max = (2.0, 2.0), + periodicity = true +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + boundary_conditions = boundary_condition_periodic +) tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan) -alive_callback = AliveCallback(alive_interval=10) -analysis_callback = AnalysisCallback(semi, interval=100, uEltype=real(dg)) +alive_callback = AliveCallback(alive_interval = 10) +analysis_callback = AnalysisCallback(semi, interval = 100, uEltype = real(dg)) callbacks = CallbackSet(analysis_callback, alive_callback); # Run the simulation with the same time integration algorithm as before. -sol = solve(ode, RDPK3SpFSAL49(), abstol=1.0e-6, reltol=1.0e-6, - callback=callbacks, save_everystep=false); +sol = solve( + ode, RDPK3SpFSAL49(), abstol = 1.0e-6, reltol = 1.0e-6, + callback = callbacks, save_everystep = false +); #- using Plots pd = PlotData2D(sol) @@ -70,30 +78,38 @@ initial_condition = initial_condition_weak_blast_wave # We now use Gauss nodes instead of Gauss-Lobatto nodes which can be done for the element types # `Quad()` and `Hex()`. Therefore, we set `approximation_type=GaussSBP()`. Alternatively, we # can use a modal approach using the approximation type `Polynomial()`. -dg = DGMulti(polydeg = 3, - element_type = Quad(), - approximation_type = GaussSBP(), - surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, + element_type = Quad(), + approximation_type = GaussSBP(), + surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) cells_per_dimension = (32, 32) -mesh = DGMultiMesh(dg, - cells_per_dimension, # initial_refinement_level = 5 - coordinates_min=(-2.0, -2.0), - coordinates_max=( 2.0, 2.0), - periodicity=true) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - boundary_conditions=boundary_condition_periodic) +mesh = DGMultiMesh( + dg, + cells_per_dimension, # initial_refinement_level = 5 + coordinates_min = (-2.0, -2.0), + coordinates_max = (2.0, 2.0), + periodicity = true +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + boundary_conditions = boundary_condition_periodic +) tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan) -alive_callback = AliveCallback(alive_interval=10) -analysis_callback = AnalysisCallback(semi, interval=100, uEltype=real(dg)) +alive_callback = AliveCallback(alive_interval = 10) +analysis_callback = AnalysisCallback(semi, interval = 100, uEltype = real(dg)) callbacks = CallbackSet(analysis_callback, alive_callback); -sol = solve(ode, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, - ode_default_options()..., callback=callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); #- using Plots pd = PlotData2D(sol) @@ -110,30 +126,38 @@ initial_condition = initial_condition_weak_blast_wave # `SBP()` now uses Gauss-Lobatto nodes on faces and special nodes in the interior of the triangular. # More details can be found in the documentation of # [StartUpDG.jl](https://jlchan.github.io/StartUpDG.jl/dev/RefElemData/#RefElemData-based-on-SBP-finite-differences). -dg = DGMulti(polydeg = 3, - element_type = Tri(), - approximation_type = SBP(), - surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, + element_type = Tri(), + approximation_type = SBP(), + surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) cells_per_dimension = (32, 32) -mesh = DGMultiMesh(dg, - cells_per_dimension, # initial_refinement_level = 5 - coordinates_min=(-2.0, -2.0), - coordinates_max=( 2.0, 2.0), - periodicity=true) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - boundary_conditions=boundary_condition_periodic) +mesh = DGMultiMesh( + dg, + cells_per_dimension, # initial_refinement_level = 5 + coordinates_min = (-2.0, -2.0), + coordinates_max = (2.0, 2.0), + periodicity = true +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + boundary_conditions = boundary_condition_periodic +) tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan) -alive_callback = AliveCallback(alive_interval=10) -analysis_callback = AnalysisCallback(semi, interval=100, uEltype=real(dg)) +alive_callback = AliveCallback(alive_interval = 10) +analysis_callback = AnalysisCallback(semi, interval = 100, uEltype = real(dg)) callbacks = CallbackSet(analysis_callback, alive_callback); -sol = solve(ode, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, - ode_default_options()..., callback=callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); #- using Plots pd = PlotData2D(sol) @@ -156,10 +180,12 @@ initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test # We create the solver `DGMulti` with triangular elements (`Tri()`) as before. -dg = DGMulti(polydeg = 3, element_type = Tri(), - approximation_type=Polynomial(), - surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, element_type = Tri(), + approximation_type = Polynomial(), + surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) # StartUpDG.jl provides for instance a pre-defined Triangulate geometry for a rectangular domain with # hole `RectangularDomainWithHole`. Other pre-defined Triangulate geometries are e.g., `SquareDomain`, @@ -168,25 +194,31 @@ meshIO = StartUpDG.triangulate_domain(StartUpDG.RectangularDomainWithHole()); # The pre-defined Triangulate geometry in StartUpDG has integer boundary tags. With [`DGMultiMesh`](@ref) # we assign boundary faces based on these integer boundary tags and create a mesh compatible with Trixi.jl. -mesh = DGMultiMesh(dg, meshIO, Dict(:outer_boundary=>1, :inner_boundary=>2)) +mesh = DGMultiMesh(dg, meshIO, Dict(:outer_boundary => 1, :inner_boundary => 2)) #- boundary_condition_convergence_test = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (; :outer_boundary => boundary_condition_convergence_test, - :inner_boundary => boundary_condition_convergence_test) +boundary_conditions = (; + :outer_boundary => boundary_condition_convergence_test, + :inner_boundary => boundary_condition_convergence_test, +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) tspan = (0.0, 0.2) ode = semidiscretize(semi, tspan) -alive_callback = AliveCallback(alive_interval=20) -analysis_callback = AnalysisCallback(semi, interval=200, uEltype=real(dg)) +alive_callback = AliveCallback(alive_interval = 20) +analysis_callback = AnalysisCallback(semi, interval = 200, uEltype = real(dg)) callbacks = CallbackSet(alive_callback, analysis_callback); -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep=false, callback=callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); #- using Plots pd = PlotData2D(sol) @@ -204,5 +236,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "StartUpDG", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "StartUpDG", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/DGMulti_2.jl b/docs/literate/src/files/DGMulti_2.jl index 0624856234..d598f9eb46 100644 --- a/docs/literate/src/files/DGMulti_2.jl +++ b/docs/literate/src/files/DGMulti_2.jl @@ -8,8 +8,10 @@ # to the `DGMulti` constructor. For example, the classical second-order FD SBP operator # can be created as using Trixi.SummationByPartsOperators # or add SummationByPartsOperators to your project and use it directly -D = derivative_operator(MattssonNordström2004(), derivative_order=1, accuracy_order=2, - xmin=0.0, xmax=1.0, N=11) +D = derivative_operator( + MattssonNordström2004(), derivative_order = 1, accuracy_order = 2, + xmin = 0.0, xmax = 1.0, N = 11 +) # Here, the arguments `xmin` and `xmax` do not matter beyond setting the real type # used for the operator - they just set a reference element and are rescaled on the # physical elements. The parameter `N` determines the number of finite difference nodes. @@ -20,8 +22,10 @@ D = derivative_operator(MattssonNordström2004(), derivative_order=1, accuracy_o # # You can also use fully periodic single-block FD methods by creating a periodic SBP # operator. For example, a fully periodic FD operator can be constructed as -D = periodic_derivative_operator(derivative_order=1, accuracy_order=2, - xmin=0.0, xmax=1.0, N=11) +D = periodic_derivative_operator( + derivative_order = 1, accuracy_order = 2, + xmin = 0.0, xmax = 1.0, N = 11 +) # An example using such an FD method is implemented in # [`elixir_euler_fdsbp_periodic.jl`](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/dgmulti_2d/elixir_euler_fdsbp_periodic.jl). # For all parameters and other calling options, please have a look in the @@ -31,8 +35,10 @@ D = periodic_derivative_operator(derivative_order=1, accuracy_order=2, # method with polynomial degree of `3` (`N=4` Legendre Lobatto nodes on `[0, 1]`) coupled continuously # on a uniform mesh with `Nx=10` elements by setting `approximation_type` to using Trixi.SummationByPartsOperators # or add SummationByPartsOperators to your project and use it directly -D = couple_continuously(legendre_derivative_operator(xmin=0.0, xmax=1.0, N=4), - UniformPeriodicMesh1D(xmin=-1.0, xmax=1.0, Nx=10)) +D = couple_continuously( + legendre_derivative_operator(xmin = 0.0, xmax = 1.0, N = 4), + UniformPeriodicMesh1D(xmin = -1.0, xmax = 1.0, Nx = 10) +) # To choose a discontinuous coupling (DGSEM), use `couple_discontinuously()` instead of `couple_continuously()`. @@ -48,5 +54,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "StartUpDG", "SummationByPartsOperators"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "StartUpDG", "SummationByPartsOperators"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/DGSEM_FluxDiff.jl b/docs/literate/src/files/DGSEM_FluxDiff.jl index a576990026..f118836190 100644 --- a/docs/literate/src/files/DGSEM_FluxDiff.jl +++ b/docs/literate/src/files/DGSEM_FluxDiff.jl @@ -114,7 +114,6 @@ # guarantee decreasing entropy, i.e. entropy stability. - # ## [Implementation in Trixi.jl](@id fluxDiffExample) # Now, we have a look at the implementation of DGSEM with flux differencing with [Trixi.jl](https://github.com/trixi-framework/Trixi.jl). using OrdinaryDiffEq, Trixi @@ -158,21 +157,27 @@ initial_condition = initial_condition_weak_blast_wave # We will confirm the entropy conservation property numerically. volume_flux = flux_ranocha # = f_vol -solver = DGSEM(polydeg=3, surface_flux=volume_flux, - volume_integral=VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = volume_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Now, we implement Trixi.jl's `mesh`, `semi` and `ode` in a simple framework. For more information please # have a look at the documentation, the basic tutorial [introduction to DG methods](@ref scalar_linear_advection_1d) # or some basic elixirs. coordinates_min = (-2.0, -2.0) -coordinates_max = ( 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level=5, - n_cells_max=10_000, - periodicity=true) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions=boundary_condition_periodic) +coordinates_max = (2.0, 2.0) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000, + periodicity = true +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition_periodic +) ## ODE solvers tspan = (0.0, 0.4) @@ -180,11 +185,13 @@ ode = semidiscretize(semi, tspan); # To analyse the entropy conservation of the approximation, we will use the analysis calllback # implemented in Trixi. It provides some information about the approximation including the entropy change. -analysis_callback = AnalysisCallback(semi, interval=100); +analysis_callback = AnalysisCallback(semi, interval = 100); # We now run the simulation using `flux_ranocha` for both surface and volume flux. -sol = solve(ode, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, - ode_default_options()..., callback=analysis_callback); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = analysis_callback +); # A look at the change in entropy $\sum \partial S/\partial U \cdot U_t$ in the analysis callback # confirms that the flux is entropy conserving since the change is about machine precision. @@ -202,28 +209,36 @@ equations = CompressibleEulerEquations2D(gamma) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_ranocha # = f_vol -solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs, - volume_integral=VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0) -coordinates_max = ( 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level=5, - n_cells_max=10_000, - periodicity=true) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions=boundary_condition_periodic) +coordinates_max = (2.0, 2.0) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000, + periodicity = true +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition_periodic +) ## ODE solvers tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan); -analysis_callback = AnalysisCallback(semi, interval=100); +analysis_callback = AnalysisCallback(semi, interval = 100); # We now run the simulation using the volume flux `flux_ranocha` and surface flux `flux_lax_friedrichs`. -sol = solve(ode, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, - ode_default_options()..., callback=analysis_callback); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = analysis_callback +); # The change in entropy confirms the expected entropy stability. using Plots @@ -246,5 +261,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/adaptive_mesh_refinement.jl b/docs/literate/src/files/adaptive_mesh_refinement.jl index 46af8f7952..1c47683d58 100644 --- a/docs/literate/src/files/adaptive_mesh_refinement.jl +++ b/docs/literate/src/files/adaptive_mesh_refinement.jl @@ -114,13 +114,15 @@ advection_velocity = (0.2, -0.7) equations = LinearScalarAdvectionEquation2D(advection_velocity) initial_condition = initial_condition_gauss -solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs) +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0) -coordinates_max = ( 5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level=4, - n_cells_max=30_000) +coordinates_max = (5.0, 5.0) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -132,29 +134,35 @@ ode = semidiscretize(semi, tspan); # `IndicatorMax`. As described before, it returns the maximal value of the specified variable # (here the only conserved variable). Therefore, regions with a high maximum are refined. # This is not really useful numerical application, but a nice demonstration example. -amr_indicator = IndicatorMax(semi, variable=first) +amr_indicator = IndicatorMax(semi, variable = first) # These values are transferred to a refinement level with the `ControllerThreeLevel`, such that # every element with maximal value greater than `0.1` is refined once and elements with maximum # above `0.6` are refined twice. -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level=4, - med_level=5, med_threshold=0.1, - max_level=6, max_threshold=0.6) - -amr_callback = AMRCallback(semi, amr_controller, - interval=5, - adapt_initial_condition=true, - adapt_initial_condition_only_refine=true) - -stepsize_callback = StepsizeCallback(cfl=0.9) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +stepsize_callback = StepsizeCallback(cfl = 0.9) callbacks = CallbackSet(amr_callback, stepsize_callback); # Running the simulation. -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # We plot the solution and add the refined mesh at the end of the simulation. using Plots @@ -212,5 +220,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/adding_new_parabolic_terms.jl b/docs/literate/src/files/adding_new_parabolic_terms.jl index 209ef62c98..490da33e95 100644 --- a/docs/literate/src/files/adding_new_parabolic_terms.jl +++ b/docs/literate/src/files/adding_new_parabolic_terms.jl @@ -27,12 +27,12 @@ equations_hyperbolic = LinearScalarAdvectionEquation2D(advection_velocity); # [`CompressibleNavierStokesDiffusion2D`](@ref), which can utilize either "primitive" or "entropy" variables. struct ConstantAnisotropicDiffusion2D{E, T} <: Trixi.AbstractEquationsParabolic{2, 1, GradientVariablesConservative} - diffusivity::T - equations_hyperbolic::E + diffusivity::T + equations_hyperbolic::E end varnames(variable_mapping, equations_parabolic::ConstantAnisotropicDiffusion2D) = - varnames(variable_mapping, equations_parabolic.equations_hyperbolic) + varnames(variable_mapping, equations_parabolic.equations_hyperbolic) # Next, we define the viscous flux function. We assume that the mixed hyperbolic-parabolic system # is of the form @@ -47,13 +47,13 @@ varnames(variable_mapping, equations_parabolic::ConstantAnisotropicDiffusion2D) # Here, we specialize the flux to our new parabolic equation type `ConstantAnisotropicDiffusion2D`. function Trixi.flux(u, gradients, orientation::Integer, equations_parabolic::ConstantAnisotropicDiffusion2D) - @unpack diffusivity = equations_parabolic - dudx, dudy = gradients - if orientation == 1 - return SVector(diffusivity[1, 1] * dudx + diffusivity[1, 2] * dudy) - else # if orientation == 2 - return SVector(diffusivity[2, 1] * dudx + diffusivity[2, 2] * dudy) - end + @unpack diffusivity = equations_parabolic + dudx, dudy = gradients + if orientation == 1 + return SVector(diffusivity[1, 1] * dudx + diffusivity[1, 2] * dudy) + else # if orientation == 2 + return SVector(diffusivity[2, 1] * dudx + diffusivity[2, 2] * dudy) + end end # ## Defining boundary conditions @@ -76,7 +76,7 @@ end # As an example, let us introduce a Dirichlet boundary condition with constant boundary data. struct BoundaryConditionConstantDirichlet{T <: Real} - boundary_value::T + boundary_value::T end # This boundary condition contains only the field `boundary_value`, which we assume to be some @@ -87,10 +87,12 @@ end # the `Gradient` and `Divergence`. Since the gradient is operating on the solution `u`, the boundary # data should be the value of `u`, and we can directly impose Dirichlet data. -@inline function (boundary_condition::BoundaryConditionConstantDirichlet)(flux_inner, u_inner, normal::AbstractVector, - x, t, operator_type::Trixi.Gradient, - equations_parabolic::ConstantAnisotropicDiffusion2D) - return boundary_condition.boundary_value +@inline function (boundary_condition::BoundaryConditionConstantDirichlet)( + flux_inner, u_inner, normal::AbstractVector, + x, t, operator_type::Trixi.Gradient, + equations_parabolic::ConstantAnisotropicDiffusion2D + ) + return boundary_condition.boundary_value end # While the gradient acts on the solution `u`, the divergence acts on the viscous flux ``\bm{\sigma}``. @@ -102,10 +104,12 @@ end # `flux_inner`, which is boundary data for ``\bm{\sigma}`` computed using the "inner" or interior solution. # This way, we supply boundary data for the divergence operation without imposing any additional conditions. -@inline function (boundary_condition::BoundaryConditionConstantDirichlet)(flux_inner, u_inner, normal::AbstractVector, - x, t, operator_type::Trixi.Divergence, - equations_parabolic::ConstantAnisotropicDiffusion2D) - return flux_inner +@inline function (boundary_condition::BoundaryConditionConstantDirichlet)( + flux_inner, u_inner, normal::AbstractVector, + x, t, operator_type::Trixi.Divergence, + equations_parabolic::ConstantAnisotropicDiffusion2D + ) + return flux_inner end # ### A note on the choice of gradient variables @@ -130,37 +134,49 @@ using Trixi: SMatrix diffusivity = 5.0e-2 * SMatrix{2, 2}([2 -1; -1 2]) equations_parabolic = ConstantAnisotropicDiffusion2D(diffusivity, equations_hyperbolic); -boundary_conditions_hyperbolic = (; x_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(1.0)), - y_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(2.0)), - y_pos = boundary_condition_do_nothing, - x_pos = boundary_condition_do_nothing) - -boundary_conditions_parabolic = (; x_neg = BoundaryConditionConstantDirichlet(1.0), - y_neg = BoundaryConditionConstantDirichlet(2.0), - y_pos = BoundaryConditionConstantDirichlet(0.0), - x_pos = BoundaryConditionConstantDirichlet(0.0)); - -solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs) +boundary_conditions_hyperbolic = (; + x_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(1.0)), + y_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(2.0)), + y_pos = boundary_condition_do_nothing, + x_pos = boundary_condition_do_nothing, +) + +boundary_conditions_parabolic = (; + x_neg = BoundaryConditionConstantDirichlet(1.0), + y_neg = BoundaryConditionConstantDirichlet(2.0), + y_pos = BoundaryConditionConstantDirichlet(0.0), + x_pos = BoundaryConditionConstantDirichlet(0.0), +); + +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) -coordinates_max = ( 1.0, 1.0) # maximum coordinates (max(x), max(y)) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level=4, - periodicity=false, n_cells_max=30_000) # set maximum capacity of tree data structure +coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + periodicity = false, n_cells_max = 30_000 +) # set maximum capacity of tree data structure initial_condition = (x, t, equations) -> SVector(0.0) -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations_hyperbolic, equations_parabolic), - initial_condition, solver; - boundary_conditions=(boundary_conditions_hyperbolic, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations_hyperbolic, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions_hyperbolic, + boundary_conditions_parabolic, + ) +) tspan = (0.0, 2.0) ode = semidiscretize(semi, tspan) callbacks = CallbackSet(SummaryCallback()) time_int_tol = 1.0e-6 -sol = solve(ode, RDPK3SpFSAL49(); abstol=time_int_tol, reltol=time_int_tol, - ode_default_options()..., callback=callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +); using Plots plot(sol) @@ -174,6 +190,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) - +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/adding_new_scalar_equations.jl b/docs/literate/src/files/adding_new_scalar_equations.jl index a65b4de7f1..79f0fff829 100644 --- a/docs/literate/src/files/adding_new_scalar_equations.jl +++ b/docs/literate/src/files/adding_new_scalar_equations.jl @@ -12,8 +12,10 @@ # ## Basic setup using Trixi -struct CubicEquation <: Trixi.AbstractEquations{1 #= number of spatial dimensions =#, - 1 #= number of primary variables, i.e. scalar =#}; +struct CubicEquation <: Trixi.AbstractEquations{ + 1 #= number of spatial dimensions =#, + 1, #= number of primary variables, i.e. scalar =# + } end # We create `CubicEquation` as an empty `struct` since we do not use any parameters @@ -23,7 +25,7 @@ end # Next, we define the physical flux `f(u) = u^3` using the calling structure # used in Trixi.jl. -Trixi.flux(u, orientation, equation::CubicEquation) = u.^3 +Trixi.flux(u, orientation, equation::CubicEquation) = u .^ 3 Trixi.varnames(_, ::CubicEquation) = ("scalar",) # In Trixi.jl, the conserved variables `u` are usually passed as `SVector`s of variables @@ -40,9 +42,11 @@ equation = CubicEquation() initial_condition_sine(x, t, equation::CubicEquation) = SVector(sinpi(x[1])) -mesh = TreeMesh(-1.0, 1.0, # min/max coordinates - initial_refinement_level=4, - n_cells_max=10^4) +mesh = TreeMesh( + -1.0, 1.0, # min/max coordinates + initial_refinement_level = 4, + n_cells_max = 10^4 +) solver = DGSEM(3 #= polynomial degree =#, flux_central) @@ -66,8 +70,10 @@ summary_callback = SummaryCallback() callbacks = CallbackSet(summary_callback) ## OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, SSPRK43(); - ode_default_options()..., callback=callbacks); +sol = solve( + ode, SSPRK43(); + ode_default_options()..., callback = callbacks +); # That's it, you ran your first simulation using your new equation with Trixi.jl! Now, we can plot # the solution at the final time using Plots.jl. @@ -92,7 +98,7 @@ plot(sol) # selected parameters, in this case the `solver`. ## A new setup with dissipation -semi = remake(semi, solver=DGSEM(3, flux_godunov)) +semi = remake(semi, solver = DGSEM(3, flux_godunov)) ode = semidiscretize(semi, tspan) sol = solve(ode, SSPRK43(); ode_default_options()...) plot!(sol) @@ -101,7 +107,7 @@ plot!(sol) # Now let's increase the final time (and also the spatial resolution). ## A larger final time: Nonclassical shocks develop (you can even increase the refinement to 12) -semi = remake(semi, mesh=TreeMesh(-1.0, 1.0, initial_refinement_level=8, n_cells_max=10^5)) +semi = remake(semi, mesh = TreeMesh(-1.0, 1.0, initial_refinement_level = 8, n_cells_max = 10^5)) ode = semidiscretize(semi, (0.0, 0.5) #= tspan =#) sol = solve(ode, SSPRK43(); ode_default_options()...) plot(sol) @@ -112,14 +118,14 @@ plot(sol) # to define an entropy-conservative numerical flux @inline function Trixi.flux_ec(u_ll, u_rr, orientation, equation::CubicEquation) - return SVector(0.25 * (u_ll[1]^3 + u_ll[1]^2 * u_rr[1] + u_ll[1] * u_rr[1]^2 + u_rr[1]^3)) + return SVector(0.25 * (u_ll[1]^3 + u_ll[1]^2 * u_rr[1] + u_ll[1] * u_rr[1]^2 + u_rr[1]^3)) end # and use a [`VolumeIntegralFluxDifferencing`](@ref) instead of the standard # [`VolumeIntegralWeakForm`](@ref) in the DGSEM. ## Let's use a provably entropy-dissipative semidiscretization -semi = remake(semi, solver=DGSEM(3, flux_godunov, VolumeIntegralFluxDifferencing(flux_ec))) +semi = remake(semi, solver = DGSEM(3, flux_godunov, VolumeIntegralFluxDifferencing(flux_ec))) ode = semidiscretize(semi, (0.0, 0.5)) sol = solve(ode, SSPRK43(); ode_default_options()...); plot(sol) @@ -148,19 +154,21 @@ plot(sol) ## Define new physics module CubicConservationLaw -using Trixi + using Trixi -struct CubicEquation <: Trixi.AbstractEquations{1 #= number of spatial dimensions =#, - 1 #= number of primary variables, i.e. scalar =#} -end + struct CubicEquation <: Trixi.AbstractEquations{ + 1 #= number of spatial dimensions =#, + 1, #= number of primary variables, i.e. scalar =# + } + end -@inline Trixi.flux(u, orientation, equation::CubicEquation) = u.^3 -Trixi.varnames(_, ::CubicEquation) = ("scalar",) + @inline Trixi.flux(u, orientation, equation::CubicEquation) = u .^ 3 + Trixi.varnames(_, ::CubicEquation) = ("scalar",) -@inline Trixi.flux_godunov(u_ll, u_rr, orientation, equation::CubicEquation) = flux(u_ll, orientation, equation) -@inline function Trixi.flux_ec(u_ll, u_rr, orientation, equation::CubicEquation) - return SVector(0.25 * (u_ll[1]^3 + u_ll[1]^2 * u_rr[1] + u_ll[1] * u_rr[1]^2 + u_rr[1]^3)) -end + @inline Trixi.flux_godunov(u_ll, u_rr, orientation, equation::CubicEquation) = flux(u_ll, orientation, equation) + @inline function Trixi.flux_ec(u_ll, u_rr, orientation, equation::CubicEquation) + return SVector(0.25 * (u_ll[1]^3 + u_ll[1]^2 * u_rr[1] + u_ll[1] * u_rr[1]^2 + u_rr[1]^3)) + end end # module @@ -175,9 +183,11 @@ equation = CubicConservationLaw.CubicEquation() initial_condition_sine(x, t, equation::CubicConservationLaw.CubicEquation) = SVector(sinpi(x[1])) -mesh = TreeMesh(-1.0, 1.0, # min/max coordinates - initial_refinement_level=4, - n_cells_max=10^4) +mesh = TreeMesh( + -1.0, 1.0, # min/max coordinates + initial_refinement_level = 4, + n_cells_max = 10^4 +) solver = DGSEM(3 #= polynomial degree =#, flux_central) @@ -193,21 +203,21 @@ plot(sol) ## A new setup with dissipation -semi = remake(semi, solver=DGSEM(3, flux_godunov)) +semi = remake(semi, solver = DGSEM(3, flux_godunov)) ode = semidiscretize(semi, tspan) sol = solve(ode, SSPRK43(); ode_default_options()...) plot!(sol) ## A larger final time: Nonclassical shocks develop (you can even increase the refinement to 12) -semi = remake(semi, mesh=TreeMesh(-1.0, 1.0, initial_refinement_level=8, n_cells_max=10^5)) +semi = remake(semi, mesh = TreeMesh(-1.0, 1.0, initial_refinement_level = 8, n_cells_max = 10^5)) ode = semidiscretize(semi, (0.0, 0.5)) sol = solve(ode, SSPRK43(); ode_default_options()...) plot(sol) ## Let's use a provably entropy-dissipative semidiscretization -semi = remake(semi, solver=DGSEM(3, flux_godunov, VolumeIntegralFluxDifferencing(flux_ec))) +semi = remake(semi, solver = DGSEM(3, flux_godunov, VolumeIntegralFluxDifferencing(flux_ec))) ode = semidiscretize(semi, (0.0, 0.5)) sol = solve(ode, SSPRK43(); ode_default_options()...) plot(sol) @@ -221,5 +231,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/adding_nonconservative_equation.jl b/docs/literate/src/files/adding_nonconservative_equation.jl index b40e21fb11..ab627a92fc 100644 --- a/docs/literate/src/files/adding_nonconservative_equation.jl +++ b/docs/literate/src/files/adding_nonconservative_equation.jl @@ -36,12 +36,14 @@ using Test: @test #src using Trixi using Trixi: AbstractEquations, get_node_vars import Trixi: varnames, default_analysis_integrals, flux, max_abs_speed_naive, - have_nonconservative_terms + have_nonconservative_terms ## Since there is no native support for variable coefficients, we use two ## variables: one for the basic unknown `u` and another one for the coefficient `a` -struct NonconservativeLinearAdvectionEquation <: AbstractEquations{1 #= spatial dimension =#, - 2 #= two variables (u,a) =#} +struct NonconservativeLinearAdvectionEquation <: AbstractEquations{ + 1 #= spatial dimension =#, + 2, #= two variables (u,a) =# + } end varnames(::typeof(cons2cons), ::NonconservativeLinearAdvectionEquation) = ("scalar", "advection_velocity") @@ -70,10 +72,12 @@ have_nonconservative_terms(::NonconservativeLinearAdvectionEquation) = Trixi.Tru ## Thus, a discrete difference approximation of this nonconservative term needs ## - `u mine`: the value of `u` at the current position (for g(u)) ## - `u_other`: the values of `u` in a neighborhood of the current position (for ∂ₓ h(u)) -function flux_nonconservative(u_mine, u_other, orientation, - equations::NonconservativeLinearAdvectionEquation) +function flux_nonconservative( + u_mine, u_other, orientation, + equations::NonconservativeLinearAdvectionEquation + ) _, advection_velocity = u_mine - scalar, _ = u_other + scalar, _ = u_other return SVector(advection_velocity * scalar, zero(scalar)) end @@ -104,16 +108,20 @@ function initial_condition_sine(x, t, equation::NonconservativeLinearAdvectionEq end ## Create a uniform mesh in 1D in the interval [-π, π] with periodic boundaries -mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates - initial_refinement_level=4, n_cells_max=10^4) +mesh = TreeMesh( + -Float64(π), Float64(π), # min/max coordinates + initial_refinement_level = 4, n_cells_max = 10^4 +) ## Create a DGSEM solver with polynomials of degree `polydeg` ## Remember to pass a tuple of the form `(conservative_flux, nonconservative_flux)` ## as `surface_flux` and `volume_flux` when working with nonconservative terms -volume_flux = (flux_central, flux_nonconservative) +volume_flux = (flux_central, flux_nonconservative) surface_flux = (flux_lax_friedrichs, flux_nonconservative) -solver = DGSEM(polydeg=3, surface_flux=surface_flux, - volume_integral=VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ## Setup the spatial semidiscretization containing all ingredients semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver) @@ -125,13 +133,15 @@ ode = semidiscretize(semi, tspan) ## Set up some standard callbacks summarizing the simulation setup and computing ## errors of the numerical solution summary_callback = SummaryCallback() -analysis_callback = AnalysisCallback(semi, interval=50) +analysis_callback = AnalysisCallback(semi, interval = 50) callbacks = CallbackSet(summary_callback, analysis_callback) ## OrdinaryDiffEq's `solve` method evolves the solution in time and executes ## the passed callbacks -sol = solve(ode, Tsit5(), abstol=1.0e-6, reltol=1.0e-6, - save_everystep=false, callback=callbacks) +sol = solve( + ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6, + save_everystep = false, callback = callbacks +) ## Print the timer summary summary_callback() @@ -151,8 +161,10 @@ error_1 = analysis_callback(sol).l2 |> first # Next, we increase the grid resolution by one refinement level and run the # simulation again. -mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates - initial_refinement_level=5, n_cells_max=10^4) +mesh = TreeMesh( + -Float64(π), Float64(π), # min/max coordinates + initial_refinement_level = 5, n_cells_max = 10^4 +) semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver) @@ -160,24 +172,25 @@ tspan = (0.0, 1.0) ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() -analysis_callback = AnalysisCallback(semi, interval=50) +analysis_callback = AnalysisCallback(semi, interval = 50) callbacks = CallbackSet(summary_callback, analysis_callback); -sol = solve(ode, Tsit5(), abstol=1.0e-6, reltol=1.0e-6, - save_everystep=false, callback=callbacks); +sol = solve( + ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6, + save_everystep = false, callback = callbacks +); summary_callback() #nb #- error_2 = analysis_callback(sol).l2 |> first -@test isapprox(error_2, 1.860295931682964e-5, rtol=0.05) #src +@test isapprox(error_2, 1.860295931682964e-5, rtol = 0.05) #src #- error_1 / error_2 -@test isapprox(error_1 / error_2, 15.916970234784808, rtol=0.05) #src +@test isapprox(error_1 / error_2, 15.916970234784808, rtol = 0.05) #src # As expected, the new error is roughly reduced by a factor of 16, corresponding # to an experimental order of convergence of 4 (for polynomials of degree 3). - # ## Summary of the code # Here is the complete code that we used (without the callbacks since these @@ -190,55 +203,58 @@ error_1 / error_2 # Define new physics module NonconservativeLinearAdvection -using Trixi -using Trixi: AbstractEquations, get_node_vars -import Trixi: varnames, default_analysis_integrals, flux, max_abs_speed_naive, - have_nonconservative_terms + using Trixi + using Trixi: AbstractEquations, get_node_vars + import Trixi: varnames, default_analysis_integrals, flux, max_abs_speed_naive, + have_nonconservative_terms -## Since there is not yet native support for variable coefficients, we use two -## variables: one for the basic unknown `u` and another one for the coefficient `a` -struct NonconservativeLinearAdvectionEquation <: AbstractEquations{1 #= spatial dimension =#, - 2 #= two variables (u,a) =#} -end + ## Since there is not yet native support for variable coefficients, we use two + ## variables: one for the basic unknown `u` and another one for the coefficient `a` + struct NonconservativeLinearAdvectionEquation <: AbstractEquations{ + 1 #= spatial dimension =#, + 2, #= two variables (u,a) =# + } + end -varnames(::typeof(cons2cons), ::NonconservativeLinearAdvectionEquation) = ("scalar", "advection_velocity") + varnames(::typeof(cons2cons), ::NonconservativeLinearAdvectionEquation) = ("scalar", "advection_velocity") -default_analysis_integrals(::NonconservativeLinearAdvectionEquation) = () + default_analysis_integrals(::NonconservativeLinearAdvectionEquation) = () -## The conservative part of the flux is zero -flux(u, orientation, equation::NonconservativeLinearAdvectionEquation) = zero(u) + ## The conservative part of the flux is zero + flux(u, orientation, equation::NonconservativeLinearAdvectionEquation) = zero(u) -## Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, ::NonconservativeLinearAdvectionEquation) - _, advection_velocity_ll = u_ll - _, advection_velocity_rr = u_rr + ## Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, ::NonconservativeLinearAdvectionEquation) + _, advection_velocity_ll = u_ll + _, advection_velocity_rr = u_rr - return max(abs(advection_velocity_ll), abs(advection_velocity_rr)) -end + return max(abs(advection_velocity_ll), abs(advection_velocity_rr)) + end -## We use nonconservative terms -have_nonconservative_terms(::NonconservativeLinearAdvectionEquation) = Trixi.True() + ## We use nonconservative terms + have_nonconservative_terms(::NonconservativeLinearAdvectionEquation) = Trixi.True() -## This "nonconservative numerical flux" implements the nonconservative terms. -## In general, nonconservative terms can be written in the form -## g(u) ∂ₓ h(u) -## Thus, a discrete difference approximation of this nonconservative term needs -## - `u mine`: the value of `u` at the current position (for g(u)) -## - `u_other`: the values of `u` in a neighborhood of the current position (for ∂ₓ h(u)) -function flux_nonconservative(u_mine, u_other, orientation, - equations::NonconservativeLinearAdvectionEquation) - _, advection_velocity = u_mine - scalar, _ = u_other + ## This "nonconservative numerical flux" implements the nonconservative terms. + ## In general, nonconservative terms can be written in the form + ## g(u) ∂ₓ h(u) + ## Thus, a discrete difference approximation of this nonconservative term needs + ## - `u mine`: the value of `u` at the current position (for g(u)) + ## - `u_other`: the values of `u` in a neighborhood of the current position (for ∂ₓ h(u)) + function flux_nonconservative( + u_mine, u_other, orientation, + equations::NonconservativeLinearAdvectionEquation + ) + _, advection_velocity = u_mine + scalar, _ = u_other - return SVector(advection_velocity * scalar, zero(scalar)) -end + return SVector(advection_velocity * scalar, zero(scalar)) + end end # module - ## Create a simulation setup import .NonconservativeLinearAdvection using Trixi @@ -256,16 +272,20 @@ function initial_condition_sine(x, t, equation::NonconservativeLinearAdvection.N end ## Create a uniform mesh in 1D in the interval [-π, π] with periodic boundaries -mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates - initial_refinement_level=4, n_cells_max=10^4) +mesh = TreeMesh( + -Float64(π), Float64(π), # min/max coordinates + initial_refinement_level = 4, n_cells_max = 10^4 +) ## Create a DGSEM solver with polynomials of degree `polydeg` ## Remember to pass a tuple of the form `(conservative_flux, nonconservative_flux)` ## as `surface_flux` and `volume_flux` when working with nonconservative terms -volume_flux = (flux_central, NonconservativeLinearAdvection.flux_nonconservative) +volume_flux = (flux_central, NonconservativeLinearAdvection.flux_nonconservative) surface_flux = (flux_lax_friedrichs, NonconservativeLinearAdvection.flux_nonconservative) -solver = DGSEM(polydeg=3, surface_flux=surface_flux, - volume_integral=VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ## Setup the spatial semidiscretization containing all ingredients semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver) @@ -277,13 +297,15 @@ ode = semidiscretize(semi, tspan); ## Set up some standard callbacks summarizing the simulation setup and computing ## errors of the numerical solution summary_callback = SummaryCallback() -analysis_callback = AnalysisCallback(semi, interval=50) +analysis_callback = AnalysisCallback(semi, interval = 50) callbacks = CallbackSet(summary_callback, analysis_callback); ## OrdinaryDiffEq's `solve` method evolves the solution in time and executes ## the passed callbacks -sol = solve(ode, Tsit5(), abstol=1.0e-6, reltol=1.0e-6, - save_everystep=false); +sol = solve( + ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6, + save_everystep = false +); ## Plot the numerical solution at the final time using Plots: plot @@ -298,5 +320,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/behind_the_scenes_simulation_setup.jl b/docs/literate/src/files/behind_the_scenes_simulation_setup.jl index c93660e9bc..bfe80899a6 100644 --- a/docs/literate/src/files/behind_the_scenes_simulation_setup.jl +++ b/docs/literate/src/files/behind_the_scenes_simulation_setup.jl @@ -26,12 +26,18 @@ equations = LinearScalarAdvectionEquation2D((-0.2, 0.7)) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -coarsening_patches = ((type = "box", coordinates_min = [0.0, -2.0], - coordinates_max = [2.0, 0.0]),) - -mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level = 2, - n_cells_max = 30_000, - coarsening_patches = coarsening_patches) +coarsening_patches = ( + ( + type = "box", coordinates_min = [0.0, -2.0], + coordinates_max = [2.0, 0.0], + ), +) + +mesh = TreeMesh( + coordinates_min, coordinates_max, initial_refinement_level = 2, + n_cells_max = 30_000, + coarsening_patches = coarsening_patches +) # The created `TreeMesh` looks like the following: @@ -55,8 +61,10 @@ solver = DGSEM(polydeg = 3) # throughout the entire simulation process. This is where [`SemidiscretizationHyperbolic`](@ref) # comes into play. -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) # The constructor for the `SemidiscretizationHyperbolic` object calls numerous sub-functions to # perform the necessary initialization steps. A brief description of the key sub-functions is @@ -67,7 +75,7 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergen # The fundamental elements for approximating the solution are the leaf # cells. The solution is constructed as a polynomial of the degree specified in the `DGSEM` -# solver in each spatial direction on each leaf cell. This polynomial approximation is evaluated +# solver in each spatial direction on each leaf cell. This polynomial approximation is evaluated # at the Gauss-Lobatto nodes mentioned earlier. The `init_elements` function extracts # these leaf cells from the `TreeMesh`, assigns them the label "elements", records their # coordinates, and maps the Gauss-Lobatto nodes from the 1D interval ``[-1, 1]`` onto each coordinate axis @@ -95,7 +103,7 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergen # As demonstrated earlier, the elements can have varying sizes. Let us initially consider # neighbors with equal size. For these elements, the `init_interfaces` function generates # interfaces that store information about adjacent elements, their relative positions, and -# allocate containers for sharing solution data between neighbors during the solution process. +# allocate containers for sharing solution data between neighbors during the solution process. # In our visualization, these interfaces would conceptually resemble tubes connecting the # corresponding elements. @@ -159,7 +167,7 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergen # The purpose of the [`semidiscretize`](@ref) function is to wrap the semidiscretization as an # `ODEProblem` within the specified time interval. During this procedure the approximate solution -# is created at the given initial time via the specified `initial_condition` function from the +# is created at the given initial time via the specified `initial_condition` function from the # `SemidiscretizationHyperbolic` object. This `ODEProblem` can be subsequently passed to the # `solve` function from the [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) package # or to [`Trixi.solve`](@ref). @@ -189,7 +197,7 @@ ode = semidiscretize(semi, (0.0, 1.0)); # This is why the `u_ode` vector is wrapped by the `wrap_array` function using `unsafe_wrap` # to form a multidimensional array `u`. In this array, the first dimension corresponds to # variables, followed by N dimensions corresponding to nodes for each of N space dimensions. -# The last dimension corresponds to the elements. +# The last dimension corresponds to the elements. # Consequently, navigation within this multidimensional array becomes noticeably easier. # "Wrapping" in this context involves the creation of a reference to the same storage location @@ -221,8 +229,10 @@ ode = semidiscretize(semi, (0.0, 1.0)); # the OrdinaryDiffEq.jl package can be utilized to compute an approximated solution using the # instructions contained in the `ODEProblem` object. -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), dt = 0.01, - save_everystep = false); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), dt = 0.01, + save_everystep = false +); # Since the `solve` function and the ODE solver have no knowledge # of a particular spatial discretization, it is necessary to define a diff --git a/docs/literate/src/files/behind_the_scenes_simulation_setup_plots/src/SemidiscretizationHyperbolic_structure_figure.jl b/docs/literate/src/files/behind_the_scenes_simulation_setup_plots/src/SemidiscretizationHyperbolic_structure_figure.jl index cae7b19d47..5671944f22 100644 --- a/docs/literate/src/files/behind_the_scenes_simulation_setup_plots/src/SemidiscretizationHyperbolic_structure_figure.jl +++ b/docs/literate/src/files/behind_the_scenes_simulation_setup_plots/src/SemidiscretizationHyperbolic_structure_figure.jl @@ -1,64 +1,64 @@ using Plots -plot(Shape([(-2.3,4.5), (2.35,4.5), (2.35,2.5), (-2.3,2.5)]), linecolor="black", fillcolor="white", label=false,linewidth=2, size=(800,600), showaxis=false, grid=false, xlim=(-2.4,2.8), ylim=(-25,5.5)) +plot(Shape([(-2.3, 4.5), (2.35, 4.5), (2.35, 2.5), (-2.3, 2.5)]), linecolor = "black", fillcolor = "white", label = false, linewidth = 2, size = (800, 600), showaxis = false, grid = false, xlim = (-2.4, 2.8), ylim = (-25, 5.5)) annotate!(2.3, 3.5, ("SemidiscretizationHyperbolic(mesh, equations, initial_conditions, solver; source_terms, boundary_conditions, RealT, uEltype, initial_cache) ", 10, :black, :right)) annotate!(-2.3, 1.5, ("creates and returns SemidiscretizationHyperbolic object, initialized using a mesh, equations, initial_conditions, boundary_conditions, source_terms, solver and cache", 9, :black, :left)) -plot!([-1.2,-1.2],[0.6,-2],arrow=true,color=:black,linewidth=2,label="") -plot!([-1.2,-1.4],[0.6,-2],arrow=true,color=:black,linewidth=2,label="") -plot!([-1.2,-1.],[0.6,-2],arrow=true,color=:black,linewidth=2,label="") +plot!([-1.2, -1.2], [0.6, -2], arrow = true, color = :black, linewidth = 2, label = "") +plot!([-1.2, -1.4], [0.6, -2], arrow = true, color = :black, linewidth = 2, label = "") +plot!([-1.2, -1.0], [0.6, -2], arrow = true, color = :black, linewidth = 2, label = "") annotate!(-1, -0.7, ("specialized for mesh and solver types", 9, :black, :left)) -plot!([1.25,1.25],[0.6,-2],arrow=true,color=:black,linewidth=2,label="") -plot!([1.25,1.05],[0.6,-2],arrow=true,color=:black,linewidth=2,label="") -plot!([1.25,1.45],[0.6,-2],arrow=true,color=:black,linewidth=2,label="") +plot!([1.25, 1.25], [0.6, -2], arrow = true, color = :black, linewidth = 2, label = "") +plot!([1.25, 1.05], [0.6, -2], arrow = true, color = :black, linewidth = 2, label = "") +plot!([1.25, 1.45], [0.6, -2], arrow = true, color = :black, linewidth = 2, label = "") annotate!(1.48, -0.7, ("specialized for mesh and boundary_conditions types", 9, :black, :left)) -plot!(Shape([(-2.3,-2), (-0.1,-2), (-0.1,-4), (-2.3,-4)]), linecolor="black", fillcolor="white", label=false,linewidth=2) +plot!(Shape([(-2.3, -2), (-0.1, -2), (-0.1, -4), (-2.3, -4)]), linecolor = "black", fillcolor = "white", label = false, linewidth = 2) annotate!(-1.2, -3, ("create_cache(mesh::TreeMesh, equations, solver::Dg, RealT, uEltype)", 10, :black, :center)) -plot!([-2.22,-2.22],[-4,-22],arrow=false,color=:black,linewidth=2,label="") +plot!([-2.22, -2.22], [-4, -22], arrow = false, color = :black, linewidth = 2, label = "") -plot!(Shape([(-0.05,-2), (2.6,-2), (2.6,-4), (-0.05,-4)]), linecolor="black", fillcolor="white", label=false,linewidth=2) +plot!(Shape([(-0.05, -2), (2.6, -2), (2.6, -4), (-0.05, -4)]), linecolor = "black", fillcolor = "white", label = false, linewidth = 2) annotate!(1.27, -3, ("digest_boundary_conditions(boundary_conditions, mesh, solver, cache)", 10, :black, :center)) annotate!(2.6, -5, ("if necessary, converts passed boundary_conditions into a suitable form for processing by Trixi.jl", 9, :black, :right)) -plot!(Shape([(-2,-6), (-0.55,-6), (-0.55,-7.1), (-2,-7.1)]), linecolor="black", fillcolor="white", label=false,linewidth=2) +plot!(Shape([(-2, -6), (-0.55, -6), (-0.55, -7.1), (-2, -7.1)]), linecolor = "black", fillcolor = "white", label = false, linewidth = 2) annotate!(-1.95, -6.5, ("local_leaf_cells(mesh.tree)", 10, :black, :left)) annotate!(-2, -7.5, ("returns cells for which an element needs to be created (i.e. all leaf cells)", 9, :black, :left)) -plot!([-2.22,-2],[-6.5,-6.5],arrow=true,color=:black,linewidth=2,label="") +plot!([-2.22, -2], [-6.5, -6.5], arrow = true, color = :black, linewidth = 2, label = "") -plot!(Shape([(-2,-9), (1.73,-9), (1.73,-10.1), (-2,-10.1)]), linecolor="black", fillcolor="white", label=false,linewidth=2) +plot!(Shape([(-2, -9), (1.73, -9), (1.73, -10.1), (-2, -10.1)]), linecolor = "black", fillcolor = "white", label = false, linewidth = 2) annotate!(-1.95, -9.5, ("init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype)", 10, :black, :left)) annotate!(-2, -10.5, ("creates and initializes elements, projects Gauss-Lobatto basis onto each of them", 9, :black, :left)) -plot!([-2.22,-2],[-9.5,-9.5],arrow=true,color=:black,linewidth=2,label="") +plot!([-2.22, -2], [-9.5, -9.5], arrow = true, color = :black, linewidth = 2, label = "") -plot!(Shape([(-2,-12), (0.4,-12), (0.4,-13.1), (-2,-13.1)]), linecolor="black", fillcolor="white", label=false,linewidth=2) +plot!(Shape([(-2, -12), (0.4, -12), (0.4, -13.1), (-2, -13.1)]), linecolor = "black", fillcolor = "white", label = false, linewidth = 2) annotate!(-1.95, -12.5, ("init_interfaces(leaf_cell_ids, mesh, elements)", 10, :black, :left)) annotate!(-2, -13.5, ("creates and initializes interfaces between each pair of adjacent elements of the same size", 9, :black, :left)) -plot!([-2.22,-2],[-12.5,-12.5],arrow=true,color=:black,linewidth=2,label="") +plot!([-2.22, -2], [-12.5, -12.5], arrow = true, color = :black, linewidth = 2, label = "") -plot!(Shape([(-2,-15), (0.5,-15), (0.5,-16.1), (-2,-16.1)]), linecolor="black", fillcolor="white", label=false,linewidth=2) +plot!(Shape([(-2, -15), (0.5, -15), (0.5, -16.1), (-2, -16.1)]), linecolor = "black", fillcolor = "white", label = false, linewidth = 2) annotate!(-1.95, -15.5, ("init_boundaries(leaf_cell_ids, mesh, elements)", 10, :black, :left)) annotate!(-2, -17, ("creates and initializes boundaries, remembers each boundary element, as well as the coordinates of each boundary node", 9, :black, :left)) -plot!([-2.22,-2],[-15.5,-15.5],arrow=true,color=:black,linewidth=2,label="") +plot!([-2.22, -2], [-15.5, -15.5], arrow = true, color = :black, linewidth = 2, label = "") -plot!(Shape([(-1.6,-18), (1.3,-18), (1.3,-19.1), (-1.6,-19.1)]), linecolor="black", fillcolor="white", label=false,linewidth=2) +plot!(Shape([(-1.6, -18), (1.3, -18), (1.3, -19.1), (-1.6, -19.1)]), linecolor = "black", fillcolor = "white", label = false, linewidth = 2) annotate!(-1.55, -18.5, ("init_mortars(leaf_cell_ids, mesh, elements, dg.mortar)", 10, :black, :left)) annotate!(-1.6, -20, ("creates and initializes mortars (type of interfaces) between each triple of adjacent coarsened and corresponding small elements", 9, :black, :left)) -plot!([-2.22,-1.6],[-18.5,-18.5],arrow=true,color=:black,linewidth=2,label="") +plot!([-2.22, -1.6], [-18.5, -18.5], arrow = true, color = :black, linewidth = 2, label = "") annotate!(-2.15, -19, ("2D and 3D", 8, :black, :left)) -plot!(Shape([(-2,-21), (1.5,-21), (1.5,-23.1), (-2,-23.1)]), linecolor="black", fillcolor="white", label=false,linewidth=2) +plot!(Shape([(-2, -21), (1.5, -21), (1.5, -23.1), (-2, -23.1)]), linecolor = "black", fillcolor = "white", label = false, linewidth = 2) annotate!(-1.95, -22, ("create_cache(mesh, equations, dg.volume_integral, dg, uEltype) for 2D and 3D create_cache(mesh, equations, dg.mortar, uEltype)", 10, :black, :left)) annotate!(-2, -23.5, ("add specialized parts of the cache required to compute the volume integral, etc.", 9, :black, :left)) -plot!([-2.22,-2],[-22,-22],arrow=true,color=:black,linewidth=2,label="") +plot!([-2.22, -2], [-22, -22], arrow = true, color = :black, linewidth = 2, label = "") -savefig("./SemidiscretizationHyperbolic") \ No newline at end of file +savefig("./SemidiscretizationHyperbolic") diff --git a/docs/literate/src/files/behind_the_scenes_simulation_setup_plots/src/generate_boundary_figure.jl b/docs/literate/src/files/behind_the_scenes_simulation_setup_plots/src/generate_boundary_figure.jl index 14475d2133..a450e9b1f3 100644 --- a/docs/literate/src/files/behind_the_scenes_simulation_setup_plots/src/generate_boundary_figure.jl +++ b/docs/literate/src/files/behind_the_scenes_simulation_setup_plots/src/generate_boundary_figure.jl @@ -1,190 +1,190 @@ using Plots function min(coordinates::Vector{Tuple{Float64, Float64}}, i) - min=coordinates[1][i] - for j in coordinates - if min>j[i] - min=j[i] + min = coordinates[1][i] + for j in coordinates + if min > j[i] + min = j[i] + end end - end - return min + return min end function max(coordinates::Vector{Tuple{Float64, Float64}}, i) - max=coordinates[1][i] - for j in coordinates - if maxj[i] - min=j[i] + min = coordinates[1][i] + for j in coordinates + if min > j[i] + min = j[i] + end end - end - return min + return min end function max(coordinates::Vector{Tuple{Float64, Float64}}, i) - max=coordinates[1][i] - for j in coordinates - if maxj[i] - min=j[i] + min = coordinates[1][i] + for j in coordinates + if min > j[i] + min = j[i] + end end - end - return min + return min end function max(coordinates::Vector{Tuple{Float64, Float64}}, i) - max=coordinates[1][i] - for j in coordinates - if maxj[i] - min=j[i] + min = coordinates[1][i] + for j in coordinates + if min > j[i] + min = j[i] + end end - end - return min + return min end function max(coordinates::Vector{Tuple{Float64, Float64}}, i) - max=coordinates[1][i] - for j in coordinates - if max begin - equations_inner = CompressibleEulerEquations2D(first(γ)) - semi_inner = Trixi.remake(semi, equations=equations_inner, uEltype=eltype(γ)) - Trixi.rhs!(du_ode, u0_ode, semi_inner, 0.0) -end, similar(u0_ode), [1.4]); # γ needs to be an `AbstractArray` +J = ForwardDiff.jacobian( + (du_ode, γ) -> begin + equations_inner = CompressibleEulerEquations2D(first(γ)) + semi_inner = Trixi.remake(semi, equations = equations_inner, uEltype = eltype(γ)) + Trixi.rhs!(du_ode, u0_ode, semi_inner, 0.0) + end, similar(u0_ode), [1.4] +); # γ needs to be an `AbstractArray` -round.(extrema(J), sigdigits=2) -@test round.(extrema(J), sigdigits=2) == (-220.0, 220.0) #src +round.(extrema(J), sigdigits = 2) +@test round.(extrema(J), sigdigits = 2) == (-220.0, 220.0) #src # Note that we create a semidiscretization `semi` at first to determine the state `u0_ode` around # which we want to perform the linearization. Next, we wrap the RHS evaluation inside a closure @@ -222,22 +224,24 @@ using Trixi, OrdinaryDiffEq, ForwardDiff, Plots function energy_at_final_time(k) # k is the wave number of the initial condition equations = LinearScalarAdvectionEquation2D(1.0, -0.3) - mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=3, n_cells_max=10^4) + mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level = 3, n_cells_max = 10^4) solver = DGSEM(3, flux_lax_friedrichs) initial_condition = (x, t, equation) -> begin - x_trans = Trixi.x_trans_periodic_2d(x - equation.advection_velocity * t) - return SVector(sinpi(k * sum(x_trans))) + x_trans = Trixi.x_trans_periodic_2d(x - equation.advection_velocity * t) + return SVector(sinpi(k * sum(x_trans))) end - semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - uEltype=typeof(k)) + semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + uEltype = typeof(k) + ) ode = semidiscretize(semi, (0.0, 1.0)) - sol = solve(ode, BS3(), save_everystep=false) + sol = solve(ode, BS3(), save_everystep = false) Trixi.integrate(energy_total, sol.u[end], semi) end -k_values = range(0.9, 1.1, length=101) +k_values = range(0.9, 1.1, length = 101) -plot(k_values, energy_at_final_time.(k_values), label="Energy") +plot(k_values, energy_at_final_time.(k_values), label = "Energy") # You see a plot of a curve that resembles a parabola with local maximum around `k = 1.0`. # Why's that? Well, the domain is fixed but the wave number changes. Thus, if the wave number is @@ -249,44 +253,49 @@ plot(k_values, energy_at_final_time.(k_values), label="Energy") # We can compute the discrete derivative of the energy at the final time with respect to the wave # number `k` as follows. -round(ForwardDiff.derivative(energy_at_final_time, 1.0), sigdigits=2) -@test round(ForwardDiff.derivative(energy_at_final_time, 1.0), sigdigits=2) == 1.4e-5 #src +round(ForwardDiff.derivative(energy_at_final_time, 1.0), sigdigits = 2) +@test round(ForwardDiff.derivative(energy_at_final_time, 1.0), sigdigits = 2) == 1.4e-5 #src # This is rather small and we can treat it as zero in comparison to the value of this derivative at # other wave numbers `k`. dk_values = ForwardDiff.derivative.((energy_at_final_time,), k_values); -plot(k_values, dk_values, label="Derivative") +plot(k_values, dk_values, label = "Derivative") # If you remember basic calculus, a sufficient condition for a local maximum is that the first derivative # vanishes and the second derivative is negative. We can also check this discretely. -second_derivative = round(ForwardDiff.derivative( - k -> Trixi.ForwardDiff.derivative(energy_at_final_time, k), 1.0), - sigdigits=2) +second_derivative = round( + ForwardDiff.derivative( + k -> Trixi.ForwardDiff.derivative(energy_at_final_time, k), 1.0 + ), + sigdigits = 2 +) @test second_derivative ≈ -0.9 #src # Having seen this application, let's break down what happens step by step. function energy_at_final_time(k) # k is the wave number of the initial condition equations = LinearScalarAdvectionEquation2D(1.0, -0.3) - mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=3, n_cells_max=10^4) + mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level = 3, n_cells_max = 10^4) solver = DGSEM(3, flux_lax_friedrichs) initial_condition = (x, t, equation) -> begin x_trans = Trixi.x_trans_periodic_2d(x - equation.advection_velocity * t) return SVector(sinpi(k * sum(x_trans))) end - semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - uEltype=typeof(k)) + semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + uEltype = typeof(k) + ) ode = semidiscretize(semi, (0.0, 1.0)) - sol = solve(ode, BS3(), save_everystep=false) + sol = solve(ode, BS3(), save_everystep = false) Trixi.integrate(energy_total, sol.u[end], semi) end k = 1.0 -round(ForwardDiff.derivative(energy_at_final_time, k), sigdigits=2) -@test round(ForwardDiff.derivative(energy_at_final_time, 1.0), sigdigits=2) == 1.4e-5 #src +round(ForwardDiff.derivative(energy_at_final_time, k), sigdigits = 2) +@test round(ForwardDiff.derivative(energy_at_final_time, 1.0), sigdigits = 2) == 1.4e-5 #src # When calling `ForwardDiff.derivative(energy_at_final_time, k)` with `k=1.0`, ForwardDiff.jl # will basically use the chain rule and known derivatives of existing basic functions @@ -300,7 +309,7 @@ round(ForwardDiff.derivative(energy_at_final_time, k), sigdigits=2) # The first step in this example creates some basic ingredients of our simulation. equations = LinearScalarAdvectionEquation2D(1.0, -0.3) -mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=3, n_cells_max=10^4) +mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level = 3, n_cells_max = 10^4) solver = DGSEM(3, flux_lax_friedrichs); # These do not have internal caches storing intermediate values of the numerical @@ -324,15 +333,17 @@ end; # and speed up the computations, e.g. for numerical fluxes at interfaces. Thus, we # need to tell Trixi.jl to allow `ForwardDiff.Dual` numbers in these caches. That's what # the keyword argument `uEltype=typeof(k)` in -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - uEltype=typeof(k)); +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + uEltype = typeof(k) +); # does. This is basically the only part where you need to modify your standard Trixi.jl # code to enable automatic differentiation. From there on, the remaining steps ode = semidiscretize(semi, (0.0, 1.0)) -sol = solve(ode, BS3(), save_everystep=false) -round(Trixi.integrate(energy_total, sol.u[end], semi), sigdigits=5) -@test round(Trixi.integrate(energy_total, sol.u[end], semi), sigdigits=5) == 0.24986 #src +sol = solve(ode, BS3(), save_everystep = false) +round(Trixi.integrate(energy_total, sol.u[end], semi), sigdigits = 5) +@test round(Trixi.integrate(energy_total, sol.u[end], semi), sigdigits = 5) == 0.24986 #src # do not need any modifications since they are sufficiently generic (and enough effort # has been spend to allow general types inside these calls). @@ -354,16 +365,18 @@ using Trixi, OrdinaryDiffEq, Measurements, Plots, LaTeXStrings equations = LinearScalarAdvectionEquation1D(1.0 ± 0.1) -mesh = TreeMesh((-1.0,), (1.0,), n_cells_max=10^5, initial_refinement_level=5) +mesh = TreeMesh((-1.0,), (1.0,), n_cells_max = 10^5, initial_refinement_level = 5) solver = DGSEM(3) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver, uEltype=Measurement{Float64}) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver, uEltype = Measurement{Float64} +) ode = semidiscretize(semi, (0.0, 1.5)) -sol = solve(ode, BS3(), save_everystep=false); +sol = solve(ode, BS3(), save_everystep = false); plot(sol) @@ -390,7 +403,7 @@ equations = CompressibleEulerEquations2D(1.4) solver = DGSEM(3, flux_central) -mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=2, n_cells_max=10^5) +mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level = 2, n_cells_max = 10^5) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_density_wave, solver) @@ -426,7 +439,7 @@ equations = LinearScalarAdvectionEquation2D(1.0, -0.3) solver = DGSEM(3, flux_lax_friedrichs) -mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level=2, n_cells_max=10^5) +mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level = 2, n_cells_max = 10^5) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, solver) @@ -456,5 +469,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots", "ForwardDiff"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots", "ForwardDiff"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/first_steps/changing_trixi.jl b/docs/literate/src/files/first_steps/changing_trixi.jl index b8f1fff6de..9e4f05b3e4 100644 --- a/docs/literate/src/files/first_steps/changing_trixi.jl +++ b/docs/literate/src/files/first_steps/changing_trixi.jl @@ -26,13 +26,13 @@ # - In the opened window, navigate to the `URL` tab and paste `trixi-framework/Trixi.jl` or # `YourGitHubUserName/Trixi.jl` to clone your own fork of Trixi.jl, and choose the # path to the folder where you want to save Trixi.jl. Then click `Clone` and Trixi.jl will be -# cloned to your computer. +# cloned to your computer. # Now you cloned Trixi.jl and only need to tell Julia to use the local clone as the package sources: # - Open a terminal using `Win+r` and `cmd`. Navigate to the folder with the cloned Trixi.jl using `cd`. # - Create a new directory `run`, enter it, and start Julia with the `--project=.` flag: # ```shell -# mkdir run +# mkdir run # cd run # julia --project=. # ``` @@ -55,11 +55,11 @@ # You can clone Trixi.jl to your computer by executing the following commands: # ```shell -# git clone git@github.com:trixi-framework/Trixi.jl.git +# git clone git@github.com:trixi-framework/Trixi.jl.git # # If an error occurs, try the following: # # git clone https://github.com/trixi-framework/Trixi.jl # cd Trixi.jl -# mkdir run +# mkdir run # cd run # julia --project=. -e 'using Pkg; Pkg.develop(PackageSpec(path=".."))' # Tell Julia to use the local Trixi.jl clone # julia --project=. -e 'using Pkg; Pkg.add(["OrdinaryDiffEq", "Plots"])' # Install additional packages @@ -103,7 +103,7 @@ # configurations. # - [Introduction to DG methods](@ref scalar_linear_advection_1d) will teach you how to set up a # simple way to approximate the solution of a hyperbolic partial differential equation. It will -# be especially useful to learn about the +# be especially useful to learn about the # [Discontinuous Galerkin method](https://en.wikipedia.org/wiki/Discontinuous_Galerkin_method) # and the way it is implemented in Trixi.jl. # - [Adding a new scalar conservation law](@ref adding_new_scalar_equations) and diff --git a/docs/literate/src/files/first_steps/create_first_setup.jl b/docs/literate/src/files/first_steps/create_first_setup.jl index 227e4cd2cc..fafb8ad996 100644 --- a/docs/literate/src/files/first_steps/create_first_setup.jl +++ b/docs/literate/src/files/first_steps/create_first_setup.jl @@ -20,7 +20,7 @@ # The first step is to create and open a file with the .jl extension. You can do this with your # favorite text editor (if you do not have one, we recommend [VS Code](https://code.visualstudio.com/)). # In this file, you will create your setup. The file can then be executed in Julia using, for example, `trixi_include()`. -# Alternatively, you can execute each line of the following code one by one in the +# Alternatively, you can execute each line of the following code one by one in the # Julia REPL. This will generate useful output for nearly every # command and improve your comprehension of the process. @@ -60,10 +60,12 @@ equations = LinearScalarAdvectionEquation2D(advection_velocity) # All minimum and all maximum coordinates must be combined into `Tuples`. coordinates_min = (-1.0, -1.0) -coordinates_max = ( 1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +coordinates_max = (1.0, 1.0) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # To approximate the solution of the defined model, we create a [`DGSEM`](@ref) solver. # The solution in each of the recently defined mesh elements will be approximated by a polynomial @@ -72,7 +74,7 @@ mesh = TreeMesh(coordinates_min, coordinates_max, # in the weak formulation `DGSEM` initializes the surface flux as `flux_central` and uses the physical flux for # the volume integral. -solver = DGSEM(polydeg=3) +solver = DGSEM(polydeg = 3) # Now we need to define an initial condition for our problem. All the already implemented # initial conditions for [`LinearScalarAdvectionEquation2D`](@ref) can be found in @@ -108,17 +110,19 @@ initial_condition = initial_condition_sinpi # equation itself as arguments and returns the source term as a static vector `SVector`. function source_term_exp_sinpi(u, x, t, equations::LinearScalarAdvectionEquation2D) - u = - 2 * exp(-t) * sinpi(2*(x[1] - t)) * sinpi(2*(x[2] - t)) + u = - 2 * exp(-t) * sinpi(2 * (x[1] - t)) * sinpi(2 * (x[2] - t)) return SVector(u) end # Now we collect all the information that is necessary to define a spatial discretization, -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; - source_terms = source_term_exp_sinpi) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver; + source_terms = source_term_exp_sinpi +) # which leaves us with an ODE problem in time with a span from `0.0` to `1.0`. -# This approach is commonly referred to as the method of lines. +# This approach is commonly referred to as the method of lines. tspan = (0.0, 1.0) ode = semidiscretize(semi, tspan) @@ -161,11 +165,13 @@ stepsize_callback = StepsizeCallback(cfl = 0.9) # a solution from saved files using Trixi2Vtk.jl and ParaView, which is described below in the # section [Visualize the solution](@ref Visualize-the-solution). -save_solution = SaveSolutionCallback(interval = 20, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 20, + save_initial_solution = true, + save_final_solution = true +) -# Alternatively, we have the option to print solution files at fixed time intervals. +# Alternatively, we have the option to print solution files at fixed time intervals. # ```julua # save_solution = SaveSolutionCallback(dt = 0.1, # save_initial_solution = true, @@ -182,8 +188,10 @@ save_restart = SaveRestartCallback(interval = 100, save_final_restart = true) # Create a `CallbackSet` to collect all callbacks so that they can be passed to the `solve` # function. -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, stepsize_callback, - save_solution, save_restart); +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, stepsize_callback, + save_solution, save_restart +); # The last step is to choose the time integration method. OrdinaryDiffEq.jl defines a wide range of # [ODE solvers](https://docs.sciml.ai/DiffEqDocs/latest/solvers/ode_solve/), including the @@ -191,7 +199,7 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, ste # the ODE problem, the ODE solver and the callbacks to the `solve` function. Also, to use # `StepsizeCallback`, we must explicitly specify the initial trial time step `dt`, the selected # value is not important, because it will be overwritten by the `StepsizeCallback`. And there is no -# need to save every step of the solution, as we are only interested the output provided by +# need to save every step of the solution, as we are only interested the output provided by # our callback [`SaveSolutionCallback`](@ref). sol = solve(ode, SSPRK33(); dt = 1.0, save_everystep = false, callback = callbacks); @@ -265,7 +273,7 @@ plot!(getmesh(pd)) # `out` folder. using Trixi2Vtk -trixi2vtk(joinpath("out", "solution_000000032.h5"), output_directory="out") +trixi2vtk(joinpath("out", "solution_000000032.h5"), output_directory = "out") # Now two files `solution_000000032.vtu` and `solution_000000032_celldata.vtu` have been generated in the # `out` folder. The first one contains all the information for visualizing the solution, the @@ -293,4 +301,4 @@ trixi2vtk(joinpath("out", "solution_000000032.h5"), output_directory="out") # Trixi.jl. If you have an interest in contributing to Trixi.jl as a developer, refer to the third # part of the introduction titled [Changing Trixi.jl itself](@ref changing_trixi). -Sys.rm("out"; recursive=true, force=true) #hide #md \ No newline at end of file +Sys.rm("out"; recursive = true, force = true) #hide #md diff --git a/docs/literate/src/files/first_steps/getting_started.jl b/docs/literate/src/files/first_steps/getting_started.jl index 80ea78b51f..abf8210d43 100644 --- a/docs/literate/src/files/first_steps/getting_started.jl +++ b/docs/literate/src/files/first_steps/getting_started.jl @@ -64,9 +64,9 @@ # Trixi.jl and its related tools are registered Julia packages, thus their installation # happens inside Julia. -# For a smooth workflow experience with Trixi.jl, you need to install +# For a smooth workflow experience with Trixi.jl, you need to install # [Trixi.jl](https://github.com/trixi-framework/Trixi.jl), -# [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl), and +# [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl), and # [Plots.jl](https://github.com/JuliaPlots/Plots.jl). # - Open a terminal and start Julia. @@ -79,7 +79,7 @@ # ``` # - On Windows, the firewall may request permission to install packages. -# Besides Trixi.jl you have now installed two additional +# Besides Trixi.jl you have now installed two additional # packages: [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl) provides time # integration schemes used by Trixi.jl and [Plots.jl](https://github.com/JuliaPlots/Plots.jl) # can be used to directly visualize Trixi.jl results from the Julia REPL. @@ -102,11 +102,11 @@ # For convenience, the [`examples_dir`](@ref) function returns a path to the # [`examples`](https://github.com/trixi-framework/Trixi.jl/tree/main/examples) # folder, which has been locally downloaded while installing Trixi.jl. -# `joinpath(...)` can be used to join path components into a full path. +# `joinpath(...)` can be used to join path components into a full path. # Let's execute a short two-dimensional problem setup. It approximates the solution of # the compressible Euler equations in 2D for an ideal gas ([`CompressibleEulerEquations2D`](@ref)) -# with a weak blast wave as the initial condition and periodic boundary conditions. +# with a weak blast wave as the initial condition and periodic boundary conditions. # The compressible Euler equations in two spatial dimensions are given by # ```math @@ -138,7 +138,7 @@ # is the pressure. # The [`initial_condition_weak_blast_wave`](@ref) is specified in -# [`compressible_euler_2d.jl`](https://github.com/trixi-framework/Trixi.jl/blob/main/src/equations/compressible_euler_2d.jl) +# [`compressible_euler_2d.jl`](https://github.com/trixi-framework/Trixi.jl/blob/main/src/equations/compressible_euler_2d.jl) # Start Julia in a terminal and execute the following code: @@ -147,7 +147,7 @@ # trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl")) # ``` using Trixi, OrdinaryDiffEq #hide #md -trixi_include(@__MODULE__,joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl")) #hide #md +trixi_include(@__MODULE__, joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl")) #hide #md # The output contains a recap of the setup and various information about the course of the simulation. # For instance, the solution was approximated over the [`TreeMesh`](@ref) with 1024 effective cells using @@ -155,9 +155,9 @@ trixi_include(@__MODULE__,joinpath(examples_dir(), "tree_2d_dgsem", "elixir_eule # solver. Further details about the ODE solver can be found in the # [documentation of OrdinaryDiffEq.jl](https://docs.sciml.ai/DiffEqDocs/stable/solvers/ode_solve/#Low-Storage-Methods) -# To analyze the result of the computation, we can use the Plots.jl package and the function +# To analyze the result of the computation, we can use the Plots.jl package and the function # `plot(...)`, which creates a graphical representation of the solution. `sol` is a variable -# defined in the executed example and it contains the solution after the simulation +# defined in the executed example and it contains the solution after the simulation # finishes. `sol.u` holds the vector of values at each saved timestep, while `sol.t` holds the # corresponding times for each saved timestep. In this instance, only two timesteps were saved: the # initial and final ones. The plot depicts the distribution of the weak blast wave at the final moment @@ -214,8 +214,8 @@ function initial_condition_density_waves(x, t, equations::CompressibleEulerEquat v2 = 0.2 # velocity along y-axis rho = 1.0 + 0.98 * sinpi(sum(x) - t * (v1 + v2)) # density wave profile p = 20 # pressure - rho_e = p / (equations.gamma - 1) + 1/2 * rho * (v1^2 + v2^2) - return SVector(rho, rho*v1, rho*v2, rho_e) + rho_e = p / (equations.gamma - 1) + 1 / 2 * rho * (v1^2 + v2^2) + return SVector(rho, rho * v1, rho * v2, rho_e) end initial_condition = initial_condition_density_waves nothing; #hide #md @@ -231,13 +231,15 @@ nothing; #hide #md # Then you will obtain a new solution from running the simulation with a different initial # condition. -trixi_include(@__MODULE__,joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl"), #hide #md - initial_condition=initial_condition) #hide #md +trixi_include( + @__MODULE__, joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl"), #hide #md + initial_condition = initial_condition +) #hide #md pd = PlotData2D(sol) #hide #md p1 = plot(pd["rho"]) #hide #md -p2 = plot(pd["v1"], clim=(0.05, 0.15)) #hide #md -p3 = plot(pd["v2"], clim=(0.15, 0.25)) #hide #md -p4 = plot(pd["p"], clim=(10, 30)) #hide #md +p2 = plot(pd["v1"], clim = (0.05, 0.15)) #hide #md +p3 = plot(pd["v2"], clim = (0.15, 0.25)) #hide #md +p4 = plot(pd["p"], clim = (10, 30)) #hide #md plot(p1, p2, p3, p4) #hide #md # To get exactly the same picture execute the following. @@ -256,4 +258,4 @@ plot(p1, p2, p3, p4) #hide #md # further details on setting up a new simulation with Trixi.jl, refer to the second part of # the introduction titled [Create your first setup](@ref create_first_setup). -Sys.rm("out"; recursive=true, force=true) #hide #md \ No newline at end of file +Sys.rm("out"; recursive = true, force = true) #hide #md diff --git a/docs/literate/src/files/hohqmesh_tutorial.jl b/docs/literate/src/files/hohqmesh_tutorial.jl index f5d93428f1..d2793082cd 100644 --- a/docs/literate/src/files/hohqmesh_tutorial.jl +++ b/docs/literate/src/files/hohqmesh_tutorial.jl @@ -36,8 +36,8 @@ using Trixi rm("out", force = true, recursive = true) #hide #md -redirect_stdio(stdout=devnull, stderr=devnull) do # code that prints annoying stuff we don't want to see here #hide #md -trixi_include(default_example_unstructured()) +redirect_stdio(stdout = devnull, stderr = devnull) do # code that prints annoying stuff we don't want to see here #hide #md + trixi_include(default_example_unstructured()) end #hide #md # This will compute a smooth, manufactured solution test case for the 2D compressible Euler equations @@ -53,8 +53,8 @@ end #hide #md # To convert the HDF5-formatted `.h5` output file(s) from Trixi.jl into VTK format execute the following using Trixi2Vtk -redirect_stdio(stdout=devnull, stderr=devnull) do # code that prints annoying stuff we don't want to see here #hide #md -trixi2vtk("out/solution_000000180.h5", output_directory="out") +redirect_stdio(stdout = devnull, stderr = devnull) do # code that prints annoying stuff we don't want to see here #hide #md + trixi2vtk("out/solution_000000180.h5", output_directory = "out") end #hide #md # Note this step takes about 15-30 seconds as the package `Trixi2Vtk` must be precompiled and executed for the first time @@ -63,8 +63,8 @@ end #hide #md # where the new files will be saved; it defaults to the current directory. (2) Specifying a higher number of # visualization nodes. For instance, if we want to use 12 uniformly spaced nodes for visualization we can execute -redirect_stdio(stdout=devnull, stderr=devnull) do # code that prints annoying stuff we don't want to see here #hide #md -trixi2vtk("out/solution_000000180.h5", output_directory="out", nvisnodes=12) +redirect_stdio(stdout = devnull, stderr = devnull) do # code that prints annoying stuff we don't want to see here #hide #md + trixi2vtk("out/solution_000000180.h5", output_directory = "out", nvisnodes = 12) end #hide #md # By default `trixi2vtk` sets `nvisnodes` to be the same as the number of nodes specified in @@ -72,8 +72,8 @@ end #hide #md # Finally, if you want to convert all the solution files to VTK execute -redirect_stdio(stdout=devnull, stderr=devnull) do # code that prints annoying stuff we don't want to see here #hide #md -trixi2vtk("out/solution_000*.h5", output_directory="out", nvisnodes=12) +redirect_stdio(stdout = devnull, stderr = devnull) do # code that prints annoying stuff we don't want to see here #hide #md + trixi2vtk("out/solution_000*.h5", output_directory = "out", nvisnodes = 12) end #hide #md # then it is possible to open the `.pvd` file with ParaView and create a video of the simulation. @@ -95,64 +95,66 @@ end #hide #md # The associated `ice_cream_straight_sides.control` file is created below. open("out/ice_cream_straight_sides.control", "w") do io - println(io, raw""" -\begin{CONTROL_INPUT} - \begin{RUN_PARAMETERS} - mesh file name = ice_cream_straight_sides.mesh - plot file name = ice_cream_straight_sides.tec - stats file name = none - mesh file format = ISM-v2 - polynomial order = 4 - plot file format = skeleton - \end{RUN_PARAMETERS} - - \begin{BACKGROUND_GRID} - x0 = [-8.0, -8.0, 0.0] - dx = [1.0, 1.0, 0.0] - N = [16,16,1] - \end{BACKGROUND_GRID} - - \begin{SPRING_SMOOTHER} - smoothing = ON - smoothing type = LinearAndCrossBarSpring - number of iterations = 25 - \end{SPRING_SMOOTHER} - -\end{CONTROL_INPUT} - -\begin{MODEL} - - \begin{INNER_BOUNDARIES} - - \begin{CHAIN} - name = IceCreamCone - \begin{END_POINTS_LINE} - name = LeftSlant - xStart = [-2.0, 1.0, 0.0] - xEnd = [ 0.0, -3.0, 0.0] - \end{END_POINTS_LINE} - - \begin{END_POINTS_LINE} - name = RightSlant - xStart = [ 0.0, -3.0, 0.0] - xEnd = [ 2.0, 1.0, 0.0] - \end{END_POINTS_LINE} - - \begin{CIRCULAR_ARC} - name = IceCream - units = degrees - center = [ 0.0, 1.0, 0.0] - radius = 2.0 - start angle = 0.0 - end angle = 180.0 - \end{CIRCULAR_ARC} - \end{CHAIN} - - \end{INNER_BOUNDARIES} - -\end{MODEL} -\end{FILE} -""") + println( + io, raw""" + \begin{CONTROL_INPUT} + \begin{RUN_PARAMETERS} + mesh file name = ice_cream_straight_sides.mesh + plot file name = ice_cream_straight_sides.tec + stats file name = none + mesh file format = ISM-v2 + polynomial order = 4 + plot file format = skeleton + \end{RUN_PARAMETERS} + + \begin{BACKGROUND_GRID} + x0 = [-8.0, -8.0, 0.0] + dx = [1.0, 1.0, 0.0] + N = [16,16,1] + \end{BACKGROUND_GRID} + + \begin{SPRING_SMOOTHER} + smoothing = ON + smoothing type = LinearAndCrossBarSpring + number of iterations = 25 + \end{SPRING_SMOOTHER} + + \end{CONTROL_INPUT} + + \begin{MODEL} + + \begin{INNER_BOUNDARIES} + + \begin{CHAIN} + name = IceCreamCone + \begin{END_POINTS_LINE} + name = LeftSlant + xStart = [-2.0, 1.0, 0.0] + xEnd = [ 0.0, -3.0, 0.0] + \end{END_POINTS_LINE} + + \begin{END_POINTS_LINE} + name = RightSlant + xStart = [ 0.0, -3.0, 0.0] + xEnd = [ 2.0, 1.0, 0.0] + \end{END_POINTS_LINE} + + \begin{CIRCULAR_ARC} + name = IceCream + units = degrees + center = [ 0.0, 1.0, 0.0] + radius = 2.0 + start angle = 0.0 + end angle = 180.0 + \end{CIRCULAR_ARC} + \end{CHAIN} + + \end{INNER_BOUNDARIES} + + \end{MODEL} + \end{FILE} + """ + ) end # The first three blocks of information are wrapped within a `CONTROL_INPUT` environment block as they define the @@ -305,18 +307,18 @@ equations = CompressibleEulerEquations2D(1.4) # set gas gamma = 1.4 ## freestream flow state with Ma_inf = 0.3 @inline function uniform_flow_state(x, t, equations::CompressibleEulerEquations2D) - ## set the freestream flow parameters - rho_freestream = 1.0 - u_freestream = 0.3 - p_freestream = inv(equations.gamma) + ## set the freestream flow parameters + rho_freestream = 1.0 + u_freestream = 0.3 + p_freestream = inv(equations.gamma) - theta = 0.0 # zero angle of attack - si, co = sincos(theta) - v1 = u_freestream * co - v2 = u_freestream * si + theta = 0.0 # zero angle of attack + si, co = sincos(theta) + v1 = u_freestream * co + v2 = u_freestream * si - prim = SVector(rho_freestream, v1, v2, p_freestream) - return prim2cons(prim, equations) + prim = SVector(rho_freestream, v1, v2, p_freestream) + return prim2cons(prim, equations) end ## initial condition @@ -326,13 +328,15 @@ initial_condition = uniform_flow_state boundary_condition_uniform_flow = BoundaryConditionDirichlet(uniform_flow_state) ## boundary condition dictionary -boundary_conditions = Dict( :Bottom => boundary_condition_uniform_flow, - :Top => boundary_condition_uniform_flow, - :Right => boundary_condition_uniform_flow, - :Left => boundary_condition_uniform_flow, - :LeftSlant => boundary_condition_slip_wall, - :RightSlant => boundary_condition_slip_wall, - :IceCream => boundary_condition_slip_wall ); +boundary_conditions = Dict( + :Bottom => boundary_condition_uniform_flow, + :Top => boundary_condition_uniform_flow, + :Right => boundary_condition_uniform_flow, + :Left => boundary_condition_uniform_flow, + :LeftSlant => boundary_condition_slip_wall, + :RightSlant => boundary_condition_slip_wall, + :IceCream => boundary_condition_slip_wall +); ## DGSEM solver. ## 1) polydeg must be >= the polynomial order set in the HOHQMesh control file to guarantee @@ -340,16 +344,20 @@ boundary_conditions = Dict( :Bottom => boundary_condition_uniform_flow, ## 2) VolumeIntegralFluxDifferencing with central volume flux is activated ## for dealiasing volume_flux = flux_ranocha -solver = DGSEM(polydeg=4, surface_flux=flux_hll, - volume_integral=VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = flux_hll, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ## create the unstructured mesh from your mesh file mesh_file = joinpath("out", "ice_cream_straight_sides.mesh") mesh = UnstructuredMesh2D(mesh_file) ## Create semidiscretization with all spatial discretization-related components -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions=boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ## Create ODE problem from semidiscretization with time span from 0.0 to 2.0 tspan = (0.0, 2.0) @@ -358,20 +366,24 @@ ode = semidiscretize(semi, tspan) ## Create the callbacks to output solution files and adapt the time step summary_callback = SummaryCallback() -save_solution = SaveSolutionCallback(interval=10, - save_initial_solution=true, - save_final_solution=true) -stepsize_callback = StepsizeCallback(cfl=1.0) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true +) +stepsize_callback = StepsizeCallback(cfl = 1.0) callbacks = CallbackSet(summary_callback, save_solution, stepsize_callback) -redirect_stdio(stdout=devnull, stderr=devnull) do # code that prints annoying stuff we don't want to see here #hide #md -## Evolve ODE problem in time using `solve` from OrdinaryDiffEq -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); -## print the timer summary -summary_callback() +redirect_stdio(stdout = devnull, stderr = devnull) do # code that prints annoying stuff we don't want to see here #hide #md + ## Evolve ODE problem in time using `solve` from OrdinaryDiffEq + sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks + ) + ## print the timer summary + summary_callback() end #hide #md # Visualization of the solution is carried out in a similar way as above. That is, one converts the `.h5` @@ -389,72 +401,74 @@ end #hide #md # We create the new control file `ice_cream_curved_sides.control` file below and will then highlight the # major differences compared to `ice_cream_straight_sides.control`. open("out/ice_cream_curved_sides.control", "w") do io - println(io, raw""" -\begin{CONTROL_INPUT} - \begin{RUN_PARAMETERS} - mesh file name = ice_cream_curved_sides.mesh - plot file name = ice_cream_curved_sides.tec - stats file name = none - mesh file format = ISM-v2 - polynomial order = 4 - plot file format = skeleton - \end{RUN_PARAMETERS} - - \begin{BACKGROUND_GRID} - background grid size = [1.0, 1.0, 0.0] - \end{BACKGROUND_GRID} - - \begin{SPRING_SMOOTHER} - smoothing = ON - smoothing type = LinearAndCrossBarSpring - number of iterations = 25 - \end{SPRING_SMOOTHER} - -\end{CONTROL_INPUT} - -\begin{MODEL} - - \begin{OUTER_BOUNDARY} - \begin{PARAMETRIC_EQUATION_CURVE} - name = OuterCircle - xEqn = x(t) = 8.0*sin(2.0*pi*t) - yEqn = y(t) = 8.0*cos(2.0*pi*t) - zEqn = z(t) = 0.0 - \end{PARAMETRIC_EQUATION_CURVE} - - \end{OUTER_BOUNDARY} - - \begin{INNER_BOUNDARIES} - - \begin{CHAIN} - name = IceCreamCone - \begin{END_POINTS_LINE} - name = LeftSlant - xStart = [-2.0, 1.0, 0.0] - xEnd = [ 0.0, -3.0, 0.0] - \end{END_POINTS_LINE} - - \begin{END_POINTS_LINE} - name = RightSlant - xStart = [ 0.0, -3.0, 0.0] - xEnd = [ 2.0, 1.0, 0.0] - \end{END_POINTS_LINE} - - \begin{CIRCULAR_ARC} - name = IceCream - units = degrees - center = [ 0.0, 1.0, 0.0] - radius = 2.0 - start angle = 0.0 - end angle = 180.0 - \end{CIRCULAR_ARC} - \end{CHAIN} - - \end{INNER_BOUNDARIES} - -\end{MODEL} -\end{FILE} -""") + println( + io, raw""" + \begin{CONTROL_INPUT} + \begin{RUN_PARAMETERS} + mesh file name = ice_cream_curved_sides.mesh + plot file name = ice_cream_curved_sides.tec + stats file name = none + mesh file format = ISM-v2 + polynomial order = 4 + plot file format = skeleton + \end{RUN_PARAMETERS} + + \begin{BACKGROUND_GRID} + background grid size = [1.0, 1.0, 0.0] + \end{BACKGROUND_GRID} + + \begin{SPRING_SMOOTHER} + smoothing = ON + smoothing type = LinearAndCrossBarSpring + number of iterations = 25 + \end{SPRING_SMOOTHER} + + \end{CONTROL_INPUT} + + \begin{MODEL} + + \begin{OUTER_BOUNDARY} + \begin{PARAMETRIC_EQUATION_CURVE} + name = OuterCircle + xEqn = x(t) = 8.0*sin(2.0*pi*t) + yEqn = y(t) = 8.0*cos(2.0*pi*t) + zEqn = z(t) = 0.0 + \end{PARAMETRIC_EQUATION_CURVE} + + \end{OUTER_BOUNDARY} + + \begin{INNER_BOUNDARIES} + + \begin{CHAIN} + name = IceCreamCone + \begin{END_POINTS_LINE} + name = LeftSlant + xStart = [-2.0, 1.0, 0.0] + xEnd = [ 0.0, -3.0, 0.0] + \end{END_POINTS_LINE} + + \begin{END_POINTS_LINE} + name = RightSlant + xStart = [ 0.0, -3.0, 0.0] + xEnd = [ 2.0, 1.0, 0.0] + \end{END_POINTS_LINE} + + \begin{CIRCULAR_ARC} + name = IceCream + units = degrees + center = [ 0.0, 1.0, 0.0] + radius = 2.0 + start angle = 0.0 + end angle = 180.0 + \end{CIRCULAR_ARC} + \end{CHAIN} + + \end{INNER_BOUNDARIES} + + \end{MODEL} + \end{FILE} + """ + ) end # The first alteration is that we have altered the second block of information @@ -501,10 +515,12 @@ output = generate_mesh(control_file); # dictionary because we now have a boundary named `OuterCircle` instead of four edges of a bounding box. ## boundary condition dictionary -boundary_conditions = Dict( :OuterCircle => boundary_condition_uniform_flow, - :LeftSlant => boundary_condition_slip_wall, - :RightSlant => boundary_condition_slip_wall, - :IceCream => boundary_condition_slip_wall ); +boundary_conditions = Dict( + :OuterCircle => boundary_condition_uniform_flow, + :LeftSlant => boundary_condition_slip_wall, + :RightSlant => boundary_condition_slip_wall, + :IceCream => boundary_condition_slip_wall +); # Also, we must update the construction of the mesh from our new mesh file `ice_cream_curved_sides.mesh` that # is located in the `out` folder. @@ -577,5 +593,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots", "Trixi2Vtk", "HOHQMesh"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots", "Trixi2Vtk", "HOHQMesh"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/index.jl b/docs/literate/src/files/index.jl index fd1d884d47..03f2a6efb6 100644 --- a/docs/literate/src/files/index.jl +++ b/docs/literate/src/files/index.jl @@ -181,7 +181,6 @@ # and explains how to extend them for custom tasks. - # ## Examples in Trixi.jl # Trixi.jl already contains several more coding examples, the so-called `elixirs`. You can find them # in the folder [`examples/`](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/). diff --git a/docs/literate/src/files/non_periodic_boundaries.jl b/docs/literate/src/files/non_periodic_boundaries.jl index 8f0e320dfd..dab7ee0667 100644 --- a/docs/literate/src/files/non_periodic_boundaries.jl +++ b/docs/literate/src/files/non_periodic_boundaries.jl @@ -16,7 +16,7 @@ # state as arguments, and solves an approximate Riemann problem to introduce dissipation (and # hence stabilization) at the boundary. Hence, the performance of the Dirichlet BC depends on the # fidelity of the numerical surface flux. -# An easy-to read introductory reference on this topic is the paper by +# An easy-to read introductory reference on this topic is the paper by # [Mengaldo et al.](https://doi.org/10.2514/6.2014-2923). # The passed boundary value function is called with the same arguments as an initial condition @@ -41,7 +41,7 @@ initial_condition_zero(x, t, equation::LinearScalarAdvectionEquation1D) = SVecto initial_condition = initial_condition_zero using Plots -plot(x -> sum(initial_condition(x, 0.0, equations)), label="initial condition", ylim=(-1.5, 1.5)) +plot(x -> sum(initial_condition(x, 0.0, equations)), label = "initial condition", ylim = (-1.5, 1.5)) # Using an advection velocity of `1.0` and the (local) Lax-Friedrichs/Rusanov flux # [`FluxLaxFriedrichs`](@ref) as a numerical surface flux, we are able to create an inflow boundary @@ -59,49 +59,59 @@ end boundary_condition = boundary_condition_sine_sector # We set the BC in negative and positive x-direction. -boundary_conditions = (x_neg=BoundaryConditionDirichlet(boundary_condition), - x_pos=BoundaryConditionDirichlet(boundary_condition)) +boundary_conditions = ( + x_neg = BoundaryConditionDirichlet(boundary_condition), + x_pos = BoundaryConditionDirichlet(boundary_condition), +) #- -solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs) +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0,) coordinates_max = (2.0,) # For the mesh type `TreeMesh` the parameter `periodicity` must be set to `false` in the # corresponding direction. -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level=4, - n_cells_max=10_000, - periodicity=false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = false +) -semi = SemidiscretizationHyperbolic(mesh, equations, - initial_condition, - solver, - boundary_conditions=boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, + initial_condition, + solver, + boundary_conditions = boundary_conditions +) tspan = (0.0, 6.0) ode = semidiscretize(semi, tspan) -analysis_callback = AnalysisCallback(semi, interval=100,) +analysis_callback = AnalysisCallback(semi, interval = 100) -stepsize_callback = StepsizeCallback(cfl=0.9) +stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(analysis_callback, - stepsize_callback); +callbacks = CallbackSet( + analysis_callback, + stepsize_callback +); # We define some equidistant nodes for the visualization -visnodes = range(tspan[1], tspan[2], length=300) +visnodes = range(tspan[1], tspan[2], length = 300) # and run the simulation. -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, saveat=visnodes, callback=callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, saveat = visnodes, callback = callbacks +); using Plots @gif for step in eachindex(sol.u) - plot(sol.u[step], semi, ylim=(-1.5, 1.5), legend=true, label="approximation", title="time t=$(round(sol.t[step], digits=5))") - scatter!([0.0], [sum(boundary_condition(SVector(0.0), sol.t[step], equations))], label="boundary condition") + plot(sol.u[step], semi, ylim = (-1.5, 1.5), legend = true, label = "approximation", title = "time t=$(round(sol.t[step], digits = 5))") + scatter!([0.0], [sum(boundary_condition(SVector(0.0), sol.t[step], equations))], label = "boundary condition") end @@ -167,5 +177,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/p4est_from_gmsh.jl b/docs/literate/src/files/p4est_from_gmsh.jl index abfe70eebc..bc213b8a1b 100644 --- a/docs/literate/src/files/p4est_from_gmsh.jl +++ b/docs/literate/src/files/p4est_from_gmsh.jl @@ -13,11 +13,11 @@ # Trixi.jl supports solving hyperbolic-parabolic problems on several mesh types. # A somewhat complex example that employs the `P4estMesh` is the near-field simulation of a -# Mach 2 flow around the NACA6412 airfoil. +# Mach 2 flow around the NACA6412 airfoil. using Trixi -redirect_stdio(stdout=devnull, stderr=devnull) do # code that prints annoying stuff we don't want to see here #hide #md -trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", "elixir_euler_NACA6412airfoil_mach2.jl"), tspan=(0.0, 0.5)) +redirect_stdio(stdout = devnull, stderr = devnull) do # code that prints annoying stuff we don't want to see here #hide #md + trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", "elixir_euler_NACA6412airfoil_mach2.jl"), tspan = (0.0, 0.5)) end #hide #md # Conveniently, we use the Plots package to have a first look at the results: @@ -30,7 +30,7 @@ end #hide #md # ## Creating a mesh using `gmsh` -# The creation of an unstructured quadrilateral mesh using `gmsh` is driven by a **geometry file**. +# The creation of an unstructured quadrilateral mesh using `gmsh` is driven by a **geometry file**. # There are plenty of possibilities for the user, see the [documentation](https://gmsh.info/doc/texinfo/gmsh.html) and [tutorials](https://gitlab.onelab.info/gmsh/gmsh/tree/master/tutorials). # To begin, we provide a complete geometry file for the NACA6412 airfoil bounded by a rectangular box. After this we give a breakdown @@ -40,11 +40,11 @@ end #hide #md # The associated `NACA6412.geo` file is given below: # ```c++ -# // GMSH geometry script for a NACA 6412 airfoil with 11 degree angle of attack +# // GMSH geometry script for a NACA 6412 airfoil with 11 degree angle of attack # // in a box (near-field mesh). # // see https://github.com/cfsengineering/GMSH-Airfoil-2D # // for software to generate gmsh `.geo` geometry files for NACA airfoils. -# +# # // outer bounding box # Point(1) = {-1.25, -0.5, 0, 1.0}; # Point(2) = {1.25, -0.5, 0, 1.0}; @@ -66,7 +66,7 @@ end #hide #md # Mesh.RecombineAll = 1; # // Violet instead of green base color for better visibility # Mesh.ColorCarousel = 0; -# +# # // points of the airfoil contour # // Format: {x, y, z, DesiredCellSize}. See the documentation: https://gmsh.info/doc/texinfo/gmsh.html#Points # // These concrete points are generated using the tool from https://github.com/cfsengineering/GMSH-Airfoil-2D @@ -268,16 +268,16 @@ end #hide #md # Point(200) = {-0.4869343135575803, 0.09127818988394577, 0, 0.125}; # Point(201) = {-0.4884813930704814, 0.09387753278635144, 0, 0.125}; # Point(202) = {-0.4895156730580155, 0.09656301401871749, 0, 0.125}; -# +# # // splines of the airfoil # Spline(5) = {5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104}; # Spline(6) = {104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,5}; -# +# # // airfoil # Line Loop(9) = {5, 6}; # // complete domain # Plane Surface(1) = {8, 9}; -# +# # // labeling of the boundary parts # Physical Line(1) = {4}; // inflow # Physical Line(2) = {2}; // outflow @@ -316,7 +316,7 @@ end #hide #md # This is strictly required to be able to use the mesh later with `p4est`, which supports only straight-sided quads, # i.e., `C2D4, CPS4, S4` in 2D and `C3D` in 3D. # See for more details the (short) [documentation](https://p4est.github.io/p4est-howto.pdf) on the interaction of `p4est` with `.inp` files. -# In principle, it should also be possible to use the `recombine` function of `gmsh` to convert the triangles to quads, +# In principle, it should also be possible to use the `recombine` function of `gmsh` to convert the triangles to quads, # but this is observed to be less robust than enforcing quads from the beginning. # # Then the airfoil is defined by a set of points: @@ -353,7 +353,7 @@ end #hide #md # which are crucial for the correct assignment of boundary conditions in `Trixi.jl`. # In particular, it is the responsibility of a user to keep track on the physical boundary names between the mesh generation and assignment of boundary condition functions in an elixir. # -# After opening this file in `gmsh`, meshing the geometry and exporting to Abaqus `.inp` format, +# After opening this file in `gmsh`, meshing the geometry and exporting to Abaqus `.inp` format, # we can have a look at the input file: # ``` # *Heading @@ -372,29 +372,29 @@ end #hide #md # 191, 272, 46, 263, 807 # ... # *NSET,NSET=PhysicalLine1 -# 1, 4, 52, 53, 54, 55, 56, 57, 58, +# 1, 4, 52, 53, 54, 55, 56, 57, 58, # *NSET,NSET=PhysicalLine2 -# 2, 3, 26, 27, 28, 29, 30, 31, 32, +# 2, 3, 26, 27, 28, 29, 30, 31, 32, # *NSET,NSET=PhysicalLine3 -# 1, 2, 3, 4, 7, 8, 9, 10, 11, 12, -# 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, -# 23, 24, 25, 33, 34, 35, 36, 37, 38, 39, -# 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, -# 50, 51, +# 1, 2, 3, 4, 7, 8, 9, 10, 11, 12, +# 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, +# 23, 24, 25, 33, 34, 35, 36, 37, 38, 39, +# 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, +# 50, 51, # *NSET,NSET=PhysicalLine4 -# 5, 6, 59, 60, 61, 62, 63, 64, 65, 66, -# 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, -# 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, -# 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, -# 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, -# 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, -# 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, -# 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, -# 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, -# 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, -# 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, -# 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, -# 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, +# 5, 6, 59, 60, 61, 62, 63, 64, 65, 66, +# 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, +# 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, +# 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, +# 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, +# 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, +# 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, +# 127, 128, 129, 130, 131, 132, 133, 134, 135, 136, +# 137, 138, 139, 140, 141, 142, 143, 144, 145, 146, +# 147, 148, 149, 150, 151, 152, 153, 154, 155, 156, +# 157, 158, 159, 160, 161, 162, 163, 164, 165, 166, +# 167, 168, 169, 170, 171, 172, 173, 174, 175, 176, +# 177, 178, 179, 180, 181, 182, 183, 184, 185, 186, # 187, 188, 189, 190, # ``` # @@ -402,7 +402,7 @@ end #hide #md # Note that `gmsh` exports also line elements of type `T3D2` which are ignored by `p4est`. # The relevant elements in 2D which form the gridcells are of type `CPS4` which are defined by their four corner nodes. # This is followed by the nodesets encoded via `*NSET` which are used to assign boundary conditions in Trixi.jl. -# Trixi.jl parses the `.inp` file and assigns the edges (in 2D, surfaces in 3D) of elements to the corresponding boundary condition based on +# Trixi.jl parses the `.inp` file and assigns the edges (in 2D, surfaces in 3D) of elements to the corresponding boundary condition based on # the supplied `boundary_symbols` that have to be supplied to the `P4estMesh` constructor: # ```julia # # boundary symbols @@ -437,13 +437,13 @@ end #hide #md # # boundary_conditions = Dict(:PhysicalLine1 => boundary_condition_supersonic_inflow, # Left boundary # :PhysicalLine2 => boundary_condition_supersonic_outflow, # Right boundary -# :PhysicalLine3 => boundary_condition_supersonic_outflow, # Top and bottom boundary +# :PhysicalLine3 => boundary_condition_supersonic_outflow, # Top and bottom boundary # :PhysicalLine4 => boundary_condition_slip_wall) # Airfoil -# +# # semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, # boundary_conditions = boundary_conditions) # ``` -# Note that you **have to** supply the `boundary_symbols` keyword to the `P4estMesh` constructor +# Note that you **have to** supply the `boundary_symbols` keyword to the `P4estMesh` constructor # to select the boundaries from the available nodesets in the `.inp` file. # If the `boundary_symbols` keyword is not supplied, all boundaries will be assigned to the default set `:all`. @@ -455,5 +455,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots", "Download"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots", "Download"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/parabolic_terms.jl b/docs/literate/src/files/parabolic_terms.jl index d0a355bbc1..5824143fde 100644 --- a/docs/literate/src/files/parabolic_terms.jl +++ b/docs/literate/src/files/parabolic_terms.jl @@ -34,27 +34,33 @@ equations_parabolic = LaplaceDiffusion2D(diffusivity, equations_hyperbolic); boundary_condition_zero_dirichlet = BoundaryConditionDirichlet((x, t, equations) -> SVector(0.0)) -boundary_conditions_hyperbolic = (; x_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(1 + 0.5 * x[2])), - y_neg = boundary_condition_zero_dirichlet, - y_pos = boundary_condition_do_nothing, - x_pos = boundary_condition_do_nothing) - -boundary_conditions_parabolic = (; x_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(1 + 0.5 * x[2])), - y_neg = boundary_condition_zero_dirichlet, - y_pos = boundary_condition_zero_dirichlet, - x_pos = boundary_condition_zero_dirichlet); +boundary_conditions_hyperbolic = (; + x_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(1 + 0.5 * x[2])), + y_neg = boundary_condition_zero_dirichlet, + y_pos = boundary_condition_do_nothing, + x_pos = boundary_condition_do_nothing, +) + +boundary_conditions_parabolic = (; + x_neg = BoundaryConditionDirichlet((x, t, equations) -> SVector(1 + 0.5 * x[2])), + y_neg = boundary_condition_zero_dirichlet, + y_pos = boundary_condition_zero_dirichlet, + x_pos = boundary_condition_zero_dirichlet, +); # ## Defining the solver and mesh # The process of creating the DG solver and mesh is the same as for a purely # hyperbolic system of equations. -solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs) +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) -coordinates_max = ( 1.0, 1.0) # maximum coordinates (max(x), max(y)) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level=4, - periodicity=false, n_cells_max=30_000) # set maximum capacity of tree data structure +coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + periodicity = false, n_cells_max = 30_000 +) # set maximum capacity of tree data structure initial_condition = (x, t, equations) -> SVector(0.0); @@ -65,11 +71,15 @@ initial_condition = (x, t, equations) -> SVector(0.0); # hyperbolic and parabolic equation, as well as a `Tuple` containing the hyperbolic and parabolic # boundary conditions. -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations_hyperbolic, equations_parabolic), - initial_condition, solver; - boundary_conditions=(boundary_conditions_hyperbolic, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations_hyperbolic, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions_hyperbolic, + boundary_conditions_parabolic, + ) +) # The rest of the code is identical to the hyperbolic case. We create a system of ODEs through # `semidiscretize`, defining callbacks, and then passing the system to OrdinaryDiffEq.jl. @@ -78,8 +88,10 @@ tspan = (0.0, 1.5) ode = semidiscretize(semi, tspan) callbacks = CallbackSet(SummaryCallback()) time_int_tol = 1.0e-6 -sol = solve(ode, RDPK3SpFSAL49(); abstol=time_int_tol, reltol=time_int_tol, - ode_default_options()..., callback=callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +); # We can now visualize the solution, which develops a boundary layer at the outflow boundaries. @@ -95,5 +107,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/scalar_linear_advection_1d.jl b/docs/literate/src/files/scalar_linear_advection_1d.jl index 3e2c7e6d0d..fcfbe0cbbe 100644 --- a/docs/literate/src/files/scalar_linear_advection_1d.jl +++ b/docs/literate/src/files/scalar_linear_advection_1d.jl @@ -104,7 +104,7 @@ weights = basis.weights # \end{align*} # ``` # Let's use our nodes and weights for $N=3$ and plug in -integral = sum(nodes.^3 .* weights) +integral = sum(nodes .^ 3 .* weights) # Using this polynomial approach leads to the equation @@ -119,10 +119,10 @@ integral = sum(nodes.^3 .* weights) # for every node. x = Matrix{Float64}(undef, length(nodes), n_elements) for element in 1:n_elements - x_l = coordinates_min + (element - 1) * dx + dx/2 + x_l = coordinates_min + (element - 1) * dx + dx / 2 for i in eachindex(nodes) ξ = nodes[i] # nodes in [-1, 1] - x[i, element] = x_l + dx/2 * ξ + x[i, element] = x_l + dx / 2 * ξ end end @@ -130,7 +130,7 @@ u0 = initial_condition_sine_wave.(x) # To have a look at the initial sinus curve, we plot it. using Plots -plot(vec(x), vec(u0), label="initial condition", legend=:topleft) +plot(vec(x), vec(u0), label = "initial condition", legend = :topleft) # ### iii. Variational formulation # After defining the equation and initial condition, we want to implement an algorithm to @@ -243,8 +243,8 @@ D = basis.derivative_matrix # ``` # for $k=0,...,N$ and therefore, $\underline{f}' = D \underline{f}$. basis_N8 = LobattoLegendreBasis(8) -plot(vec(x), x -> 3 * x^2, label="f'", lw=2) -scatter!(basis_N8.nodes, basis_N8.derivative_matrix * basis_N8.nodes.^3, label="Df", lw=3) +plot(vec(x), x -> 3 * x^2, label = "f'", lw = 2) +scatter!(basis_N8.nodes, basis_N8.derivative_matrix * basis_N8.nodes .^ 3, label = "Df", lw = 3) # Combining the volume term for every $i=0,...,N$ results in # ```math @@ -302,13 +302,13 @@ function rhs!(du, u, x, t) ## Trixi.jl needs the equation we are dealing with and an additional `1`, that indicates the ## first coordinate direction. equations = LinearScalarAdvectionEquation1D(1.0) - for element in 2:n_elements-1 + for element in 2:(n_elements - 1) ## left interface - flux_numerical[1, element] = surface_flux(u[end, element-1], u[1, element], 1, equations) - flux_numerical[end, element-1] = flux_numerical[1, element] + flux_numerical[1, element] = surface_flux(u[end, element - 1], u[1, element], 1, equations) + flux_numerical[end, element - 1] = flux_numerical[1, element] ## right interface - flux_numerical[end, element] = surface_flux(u[end, element], u[1, element+1], 1, equations) - flux_numerical[1, element+1] = flux_numerical[end, element] + flux_numerical[end, element] = surface_flux(u[end, element], u[1, element + 1], 1, equations) + flux_numerical[1, element + 1] = flux_numerical[end, element] end ## boundary flux flux_numerical[1, 1] = surface_flux(u[end, end], u[1, 1], 1, equations) @@ -342,12 +342,10 @@ using OrdinaryDiffEq tspan = (0.0, 2.0) ode = ODEProblem(rhs!, u0, tspan, x) -sol = solve(ode, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, ode_default_options()...) -@test maximum(abs.(u0 - sol.u[end])) < 5e-5 #src - -plot(vec(x), vec(sol.u[end]), label="solution at t=$(tspan[2])", legend=:topleft, lw=3) - +sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, ode_default_options()...) +@test maximum(abs.(u0 - sol.u[end])) < 5.0e-5 #src +plot(vec(x), vec(sol.u[end]), label = "solution at t=$(tspan[2])", legend = :topleft, lw = 3) # ## Alternative Implementation based on Trixi.jl @@ -362,7 +360,7 @@ equations = LinearScalarAdvectionEquation1D(advection_velocity) # Then, create a DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux. # The implementation of the basis and the numerical flux is now already done. -solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs) +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) # We will now create a mesh with 16 elements for the physical domain `[-1, 1]` with periodic boundaries. # We use Trixi.jl's standard mesh [`TreeMesh`](@ref). Since it's limited to hypercube domains, we @@ -370,9 +368,11 @@ solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs) # if we don't need AMR here. coordinates_min = -1.0 # minimum coordinate coordinates_max = 1.0 # maximum coordinate -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level=4, # number of elements = 2^4 - n_cells_max=30_000) # set maximum capacity of tree data structure (only needed for AMR) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, # number of elements = 2^4 + n_cells_max = 30_000 +) # set maximum capacity of tree data structure (only needed for AMR) # A semidiscretization collects data structures and functions for the spatial discretization. # In Trixi.jl, an initial condition has the following parameter structure and is of the type `SVector`. @@ -383,17 +383,15 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sine_wave # Again, combining all definitions and the function that calculates the right-hand side, we define the ODE and # solve it until `t=2` with OrdinaryDiffEq's `solve` function and the Runge-Kutta method `RDPK3SpFSAL49()`. tspan = (0.0, 2.0) -ode_trixi = semidiscretize(semi, tspan) +ode_trixi = semidiscretize(semi, tspan) -sol_trixi = solve(ode_trixi, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, ode_default_options()...); +sol_trixi = solve(ode_trixi, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, ode_default_options()...); # We add a plot of the new approximated solution to the one calculated before. -plot!(sol_trixi, label="solution at t=$(tspan[2]) with Trixi.jl", legend=:topleft, linestyle=:dash, lw=2) +plot!(sol_trixi, label = "solution at t=$(tspan[2]) with Trixi.jl", legend = :topleft, linestyle = :dash, lw = 2) @test maximum(abs.(vec(u0) - sol_trixi.u[end])) ≈ maximum(abs.(u0 - sol.u[end])) #src - - # ## Summary of the code # To sum up, here is the complete code that we used. @@ -410,16 +408,16 @@ B = diagm([-1; zeros(polydeg - 1); 1]) ## mesh coordinates_min = -1.0 # minimum coordinate coordinates_max = 1.0 # maximum coordinate -n_elements = 16 # number of elements +n_elements = 16 # number of elements dx = (coordinates_max - coordinates_min) / n_elements # length of one element x = Matrix{Float64}(undef, length(nodes), n_elements) for element in 1:n_elements - x_l = -1 + (element - 1) * dx + dx/2 + x_l = -1 + (element - 1) * dx + dx / 2 for i in eachindex(nodes) # basis points in [-1, 1] ξ = nodes[i] - x[i, element] = x_l + dx/2 * ξ + x[i, element] = x_l + dx / 2 * ξ end end @@ -427,7 +425,7 @@ end initial_condition_sine_wave(x) = 1.0 + 0.5 * sin(pi * x) u0 = initial_condition_sine_wave.(x) -plot(vec(x), vec(u0), label="initial condition", legend=:topleft) +plot(vec(x), vec(u0), label = "initial condition", legend = :topleft) ## flux Lax-Friedrichs surface_flux = flux_lax_friedrichs @@ -440,13 +438,13 @@ function rhs!(du, u, x, t) ## calculate interface and boundary fluxes equations = LinearScalarAdvectionEquation1D(1.0) - for element in 2:n_elements-1 + for element in 2:(n_elements - 1) ## left interface - flux_numerical[1, element] = surface_flux(u[end, element-1], u[1, element], 1, equations) - flux_numerical[end, element-1] = flux_numerical[1, element] + flux_numerical[1, element] = surface_flux(u[end, element - 1], u[1, element], 1, equations) + flux_numerical[end, element - 1] = flux_numerical[1, element] ## right interface - flux_numerical[end, element] = surface_flux(u[end, element], u[1, element+1], 1, equations) - flux_numerical[1, element+1] = flux_numerical[end, element] + flux_numerical[end, element] = surface_flux(u[end, element], u[1, element + 1], 1, equations) + flux_numerical[1, element + 1] = flux_numerical[end, element] end ## boundary flux flux_numerical[1, 1] = surface_flux(u[end, end], u[1, 1], 1, equations) @@ -476,10 +474,10 @@ tspan = (0.0, 2.0) ode = ODEProblem(rhs!, u0, tspan, x) ## solve -sol = solve(ode, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, ode_default_options()...) +sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, ode_default_options()...) @test maximum(abs.(vec(u0) - sol_trixi.u[end])) ≈ maximum(abs.(u0 - sol.u[end])) #src -plot(vec(x), vec(sol.u[end]), label="solution at t=$(tspan[2])", legend=:topleft, lw=3) +plot(vec(x), vec(sol.u[end]), label = "solution at t=$(tspan[2])", legend = :topleft, lw = 3) # ### Alternative Implementation based on Trixi.jl @@ -490,14 +488,16 @@ advection_velocity = 1.0 equations = LinearScalarAdvectionEquation1D(advection_velocity) ## create DG solver with flux lax friedrichs and LGL basis -solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs) +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) ## distretize domain with `TreeMesh` coordinates_min = -1.0 # minimum coordinate coordinates_max = 1.0 # maximum coordinate -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level=4, # number of elements = 2^4 - n_cells_max=30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, # number of elements = 2^4 + n_cells_max = 30_000 +) ## create initial condition and semidiscretization initial_condition_sine_wave(x, t, equations) = SVector(1.0 + 0.5 * sin(pi * sum(x - equations.advection_velocity * t))) @@ -506,10 +506,10 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sine_wave ## solve tspan = (0.0, 2.0) -ode_trixi = semidiscretize(semi, tspan) -sol_trixi = solve(ode_trixi, RDPK3SpFSAL49(); abstol=1.0e-6, reltol=1.0e-6, ode_default_options()...); +ode_trixi = semidiscretize(semi, tspan) +sol_trixi = solve(ode_trixi, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, ode_default_options()...); -plot!(sol_trixi, label="solution at t=$(tspan[2]) with Trixi.jl", legend=:topleft, linestyle=:dash, lw=2) +plot!(sol_trixi, label = "solution at t=$(tspan[2]) with Trixi.jl", legend = :topleft, linestyle = :dash, lw = 2) @test maximum(abs.(vec(u0) - sol_trixi.u[end])) ≈ maximum(abs.(u0 - sol.u[end])) #src @@ -521,5 +521,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/shock_capturing.jl b/docs/literate/src/files/shock_capturing.jl index dd6698c2a8..e6e878905f 100644 --- a/docs/literate/src/files/shock_capturing.jl +++ b/docs/literate/src/files/shock_capturing.jl @@ -85,7 +85,6 @@ # ```` - # # Positivity preserving limiter # Some numerical solutions are physically meaningless, for instance negative values of pressure @@ -143,26 +142,26 @@ equations = CompressibleEulerEquations2D(1.4) # As our initial condition we use the Sedov blast wave setup. function initial_condition_sedov_blast_wave(x, t, equations::CompressibleEulerEquations2D) - ## Set up polar coordinates - inicenter = SVector(0.0, 0.0) - x_norm = x[1] - inicenter[1] - y_norm = x[2] - inicenter[2] - r = sqrt(x_norm^2 + y_norm^2) - - r0 = 0.21875 # = 3.5 * smallest dx (for domain length=4 and max-ref=6) - ## r0 = 0.5 # = more reasonable setup - E = 1.0 - p0_inner = 3 * (equations.gamma - 1) * E / (3 * pi * r0^2) - p0_outer = 1.0e-5 # = true Sedov setup - ## p0_outer = 1.0e-3 # = more reasonable setup - - ## Calculate primitive variables - rho = 1.0 - v1 = 0.0 - v2 = 0.0 - p = r > r0 ? p0_outer : p0_inner - - return prim2cons(SVector(rho, v1, v2, p), equations) + ## Set up polar coordinates + inicenter = SVector(0.0, 0.0) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + + r0 = 0.21875 # = 3.5 * smallest dx (for domain length=4 and max-ref=6) + ## r0 = 0.5 # = more reasonable setup + E = 1.0 + p0_inner = 3 * (equations.gamma - 1) * E / (3 * pi * r0^2) + p0_outer = 1.0e-5 # = true Sedov setup + ## p0_outer = 1.0e-3 # = more reasonable setup + + ## Calculate primitive variables + rho = 1.0 + v1 = 0.0 + v2 = 0.0 + p = r > r0 ? p0_outer : p0_inner + + return prim2cons(SVector(rho, v1, v2, p), equations) end initial_condition = initial_condition_sedov_blast_wave #- @@ -171,7 +170,7 @@ basis = LobattoLegendreBasis(3) # We set the numerical fluxes and divide between the surface flux and the two volume fluxes for the DG # and FV method. Here, we are using [`flux_lax_friedrichs`](@ref) and [`flux_ranocha`](@ref). surface_flux = flux_lax_friedrichs -volume_flux = flux_ranocha +volume_flux = flux_ranocha # Now, we specify the shock capturing indicator $\alpha$. @@ -179,27 +178,33 @@ volume_flux = flux_ranocha # `equations`, `basis`, `alpha_max`, `alpha_min`, `alpha_smooth` and `variable`. # Since density and pressure are the critical variables in this example, we use # `density_pressure = density * pressure = rho * p` as indicator variable. -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max=0.5, - alpha_min=0.001, - alpha_smooth=true, - variable=density_pressure) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) # Now, we can use the defined fluxes and the indicator to implement the volume integral using shock # capturing. -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg=volume_flux, - volume_flux_fv=surface_flux) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) # We finalize the discretization by implementing Trixi.jl's `solver`, `mesh`, `semi` and `ode`, # while `solver` now has the extra parameter `volume_integral`. solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) -coordinates_max = ( 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level=6, - n_cells_max=10_000) +coordinates_max = (2.0, 2.0) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -207,20 +212,24 @@ tspan = (0.0, 1.0) ode = semidiscretize(semi, tspan); # We add some callbacks to get an solution analysis and use a CFL-based time step size calculation. -analysis_callback = AnalysisCallback(semi, interval=100) +analysis_callback = AnalysisCallback(semi, interval = 100) -stepsize_callback = StepsizeCallback(cfl=0.8) +stepsize_callback = StepsizeCallback(cfl = 0.8) callbacks = CallbackSet(analysis_callback, stepsize_callback); # We now run the simulation using the positivity preserving limiter of Zhang and Shu for the variables # density and pressure. -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds=(5.0e-6, 5.0e-6), - variables=(Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (5.0e-6, 5.0e-6), + variables = (Trixi.density, pressure) +) -sol = solve(ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve( + ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); using Plots plot(sol) @@ -234,5 +243,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/structured_mesh_mapping.jl b/docs/literate/src/files/structured_mesh_mapping.jl index c8da30bc2b..af693261db 100644 --- a/docs/literate/src/files/structured_mesh_mapping.jl +++ b/docs/literate/src/files/structured_mesh_mapping.jl @@ -19,14 +19,14 @@ equations = CompressibleEulerEquations2D(1.4) # We start with a pressure perturbation at `(xs, 0.0)` as initial condition. function initial_condition_pressure_perturbation(x, t, equations::CompressibleEulerEquations2D) - xs = 1.5 # location of the initial disturbance on the x axis - w = 1/8 # half width - p = exp(-log(2) * ((x[1]-xs)^2 + x[2]^2)/w^2) + 1.0 - v1 = 0.0 - v2 = 0.0 - rho = 1.0 - - return prim2cons(SVector(rho, v1, v2, p), equations) + xs = 1.5 # location of the initial disturbance on the x axis + w = 1 / 8 # half width + p = exp(-log(2) * ((x[1] - xs)^2 + x[2]^2) / w^2) + 1.0 + v1 = 0.0 + v2 = 0.0 + rho = 1.0 + + return prim2cons(SVector(rho, v1, v2, p), equations) end initial_condition = initial_condition_pressure_perturbation @@ -35,8 +35,10 @@ boundary_conditions = boundary_condition_slip_wall # The approximation setup is an entropy-stable split-form DG method with `polydeg=4`. We are using # the two fluxes [`flux_ranocha`](@ref) and [`flux_lax_friedrichs`](@ref). -solver = DGSEM(polydeg=4, surface_flux=flux_lax_friedrichs, - volume_integral=VolumeIntegralFluxDifferencing(flux_ranocha)) +solver = DGSEM( + polydeg = 4, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) # We want to define a circular cylinder as physical domain. It contains an inner semicircle with # radius `r0` and an outer semicircle of radius `r1`. @@ -76,37 +78,43 @@ solver = DGSEM(polydeg=4, surface_flux=flux_lax_friedrichs, # In our case we can define the domain boundary curves as follows: r0 = 0.5 # inner radius r1 = 5.0 # outer radius -f1(xi) = SVector( r0 + 0.5 * (r1 - r0) * (xi + 1), 0.0) # right line -f2(xi) = SVector(-r0 - 0.5 * (r1 - r0) * (xi + 1), 0.0) # left line +f1(xi) = SVector(r0 + 0.5 * (r1 - r0) * (xi + 1), 0.0) # right line +f2(xi) = SVector(-r0 - 0.5 * (r1 - r0) * (xi + 1), 0.0) # left line f3(eta) = SVector(r0 * cos(0.5 * pi * (eta + 1)), r0 * sin(0.5 * pi * (eta + 1))) # inner circle f4(eta) = SVector(r1 * cos(0.5 * pi * (eta + 1)), r1 * sin(0.5 * pi * (eta + 1))) # outer circle # We create a curved mesh with 16 x 16 elements. The defined domain boundary curves are passed as a tuple. cells_per_dimension = (16, 16) -mesh = StructuredMesh(cells_per_dimension, (f1, f2, f3, f4), periodicity=false) +mesh = StructuredMesh(cells_per_dimension, (f1, f2, f3, f4), periodicity = false) # Then, we define the simulation with endtime `T=3` with `semi`, `ode` and `callbacks`. -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions=boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) tspan = (0.0, 3.0) ode = semidiscretize(semi, tspan) analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval=analysis_interval) +analysis_callback = AnalysisCallback(semi, interval = analysis_interval) -alive_callback = AliveCallback(analysis_interval=analysis_interval) +alive_callback = AliveCallback(analysis_interval = analysis_interval) -stepsize_callback = StepsizeCallback(cfl=0.9) +stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(analysis_callback, - alive_callback, - stepsize_callback); +callbacks = CallbackSet( + analysis_callback, + alive_callback, + stepsize_callback +); # Running the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); using Plots plot(sol) @@ -132,22 +140,26 @@ equations = CompressibleEulerEquations2D(1.4) # initial condition. initial_condition = initial_condition_constant -solver = DGSEM(polydeg=3, surface_flux=flux_lax_friedrichs) +solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) # We define the transformation mapping with variables in $[-1, 1]$ as described in # Rueda-Ramírez et al. (2021), p.18 (reduced to 2D): function mapping(xi_, eta_) - ## Transform input variables between -1 and 1 onto [0,3] - xi = 1.5 * xi_ + 1.5 - eta = 1.5 * eta_ + 1.5 + ## Transform input variables between -1 and 1 onto [0,3] + xi = 1.5 * xi_ + 1.5 + eta = 1.5 * eta_ + 1.5 - y = eta + 3/8 * (cos(1.5 * pi * (2 * xi - 3)/3) * - cos(0.5 * pi * (2 * eta - 3)/3)) + y = eta + 3 / 8 * ( + cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3) + ) - x = xi + 3/8 * (cos(0.5 * pi * (2 * xi - 3)/3) * - cos(2 * pi * (2 * y - 3)/3)) + x = xi + 3 / 8 * ( + cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3) + ) - return SVector(x, y) + return SVector(x, y) end # Instead of a tuple of boundary functions, the `mesh` now has the mapping as its parameter. @@ -159,28 +171,32 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) tspan = (0.0, 1.0) ode = semidiscretize(semi, tspan) -analysis_callback = AnalysisCallback(semi, interval=250) +analysis_callback = AnalysisCallback(semi, interval = 250) -stepsize_callback = StepsizeCallback(cfl=0.8) +stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(analysis_callback, - stepsize_callback) +callbacks = CallbackSet( + analysis_callback, + stepsize_callback +) -sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), - dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep=false, callback=callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Now, we want to verify the free-stream preservation property and plot the mesh. For the verification, # we calculate the absolute difference of the first conservation variable density `u[1]` and `1.0`. # To plot this error and the mesh, we are using the visualization feature `ScalarPlotData2D`, # explained in [visualization](@ref visualization). error_density = let u = Trixi.wrap_array(sol.u[end], semi) - abs.(u[1, :, :, :] .- 1.0) # density, x, y, elements + abs.(u[1, :, :, :] .- 1.0) # density, x, y, elements end pd = ScalarPlotData2D(error_density, semi) using Plots -plot(pd, title="Error in density") +plot(pd, title = "Error in density") plot!(getmesh(pd)) # We observe that the errors in the variable `density` are at the level of machine accuracy. @@ -211,5 +227,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq", "Plots"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq", "Plots"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/subcell_shock_capturing.jl b/docs/literate/src/files/subcell_shock_capturing.jl index 8b5399c23a..31eac06dd3 100644 --- a/docs/literate/src/files/subcell_shock_capturing.jl +++ b/docs/literate/src/files/subcell_shock_capturing.jl @@ -78,8 +78,10 @@ positivity_variables_cons = ["rho"] # The quantity names are passed as a vector to allow several quantities. # This is used, for instance, if you want to limit the density of two different components using # the multicomponent compressible Euler equations. -equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.648), - gas_constants = (0.287, 1.578)) +equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.648), + gas_constants = (0.287, 1.578) +) # Then, we just pass both quantity names. positivity_variables_cons = ["rho1", "rho2"] @@ -139,7 +141,7 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation rho = r > 0.5 ? 1.0 : 1.1691 v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi - p = r > 0.5 ? 1.0E-3 : 1.245 + p = r > 0.5 ? 1.0e-3 : 1.245 return prim2cons(SVector(rho, v1, v2, p), equations) end @@ -159,14 +161,18 @@ volume_flux = flux_ranocha # or listed in the docstring) you can specify and enable additional limiting options. # Here, the simulation should contain local limiting for the density using lower and upper bounds. basis = LobattoLegendreBasis(3) -limiter_idp = SubcellLimiterIDP(equations, basis; - local_twosided_variables_cons = ["rho"]) +limiter_idp = SubcellLimiterIDP( + equations, basis; + local_twosided_variables_cons = ["rho"] +) # The initialized limiter is passed to `VolumeIntegralSubcellLimiting` in addition to the volume # fluxes of the low-order and high-order scheme. -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) # Then, the volume integral is passed to `solver` as it is done for the standard flux-differencing # DG scheme or the element-wise limiting. @@ -174,9 +180,11 @@ solver = DGSEM(basis, surface_flux, volume_integral) #- coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -190,17 +198,21 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.3) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +); # As explained above, the IDP limiter works a-posteriori and requires the additional use of a # correction stage implemented with the stage callback [`SubcellLimiterIDPCorrection`](@ref). @@ -210,9 +222,11 @@ stage_callbacks = (SubcellLimiterIDPCorrection(),) # Moreover, as mentioned before as well, simulations with subcell limiting require a Trixi-intern # SSPRK time integration methods with passed stage callbacks and a Trixi-intern `Trixi.solve(...)` # routine. -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); +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 +); summary_callback() # print the timer summary diff --git a/docs/literate/src/files/time_stepping.jl b/docs/literate/src/files/time_stepping.jl index de7a2a83a4..6c42c07010 100644 --- a/docs/literate/src/files/time_stepping.jl +++ b/docs/literate/src/files/time_stepping.jl @@ -83,5 +83,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "OrdinaryDiffEq"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "OrdinaryDiffEq"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/literate/src/files/upwind_fdsbp.jl b/docs/literate/src/files/upwind_fdsbp.jl index 6d3379fa30..84af860600 100644 --- a/docs/literate/src/files/upwind_fdsbp.jl +++ b/docs/literate/src/files/upwind_fdsbp.jl @@ -8,9 +8,11 @@ # The first step is to set up an SBP operator. A classical (central) SBP # operator can be created as follows. using Trixi -D_SBP = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order=1, accuracy_order=2, - xmin=0.0, xmax=1.0, N=11) +D_SBP = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, accuracy_order = 2, + xmin = 0.0, xmax = 1.0, N = 11 +) # Instead of prefixing the source of coefficients `MattssonNordström2004()`, # you can also load the package SummationByPartsOperators.jl. Either way, # this yields an object representing the operator efficiently. If you want to @@ -20,9 +22,11 @@ Matrix(D_SBP) # Upwind SBP operators are a concept introduced in 2017 by Ken Mattsson. You can # create them as follows. -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order=1, accuracy_order=2, - xmin=0.0, xmax=1.0, N=11) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, accuracy_order = 2, + xmin = 0.0, xmax = 1.0, N = 11 +) # Upwind operators are derivative operators biased towards one direction. # The "minus" variants has a bias towards the left side, i.e., it uses values # from more nodes to the left than from the right to compute the discrete @@ -72,5 +76,7 @@ using InteractiveUtils versioninfo() using Pkg -Pkg.status(["Trixi", "SummationByPartsOperators"], - mode=PKGMODE_MANIFEST) +Pkg.status( + ["Trixi", "SummationByPartsOperators"], + mode = PKGMODE_MANIFEST +) diff --git a/docs/make.jl b/docs/make.jl index e0243a8bca..07daf9c696 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -22,20 +22,22 @@ authors_text = replace(authors_text, "in the [LICENSE.md](LICENSE.md) file" => " write(joinpath(@__DIR__, "src", "authors.md"), authors_text) # Define module-wide setups such that the respective modules are available in doctests -DocMeta.setdocmeta!(Trixi, :DocTestSetup, :(using Trixi); recursive=true) -DocMeta.setdocmeta!(Trixi2Vtk, :DocTestSetup, :(using Trixi2Vtk); recursive=true) +DocMeta.setdocmeta!(Trixi, :DocTestSetup, :(using Trixi); recursive = true) +DocMeta.setdocmeta!(Trixi2Vtk, :DocTestSetup, :(using Trixi2Vtk); recursive = true) # Copy some files from the repository root directory to the docs and modify them # as necessary # Based on: https://github.com/ranocha/SummationByPartsOperators.jl/blob/0206a74140d5c6eb9921ca5021cb7bf2da1a306d/docs/make.jl#L27-L41 open(joinpath(@__DIR__, "src", "code_of_conduct.md"), "w") do io # Point to source license file - println(io, - """ - ```@meta - EditURL = "https://github.com/trixi-framework/Trixi.jl/blob/main/CODE_OF_CONDUCT.md" - ``` - """) + println( + io, + """ + ```@meta + EditURL = "https://github.com/trixi-framework/Trixi.jl/blob/main/CODE_OF_CONDUCT.md" + ``` + """ + ) # Write the modified contents println(io, "# [Code of Conduct](@id code-of-conduct)") println(io, "") @@ -47,17 +49,19 @@ end open(joinpath(@__DIR__, "src", "contributing.md"), "w") do io # Point to source license file - println(io, - """ - ```@meta - EditURL = "https://github.com/trixi-framework/Trixi.jl/blob/main/CONTRIBUTING.md" - ``` - """) + println( + io, + """ + ```@meta + EditURL = "https://github.com/trixi-framework/Trixi.jl/blob/main/CONTRIBUTING.md" + ``` + """ + ) # Write the modified contents for line in eachline(joinpath(dirname(@__DIR__), "CONTRIBUTING.md")) - line = replace(line, "[LICENSE.md](LICENSE.md)" => "[License](@ref)") - line = replace(line, "[AUTHORS.md](AUTHORS.md)" => "[Authors](@ref)") - println(io, line) + line = replace(line, "[LICENSE.md](LICENSE.md)" => "[License](@ref)") + line = replace(line, "[AUTHORS.md](AUTHORS.md)" => "[Authors](@ref)") + println(io, line) end end @@ -97,7 +101,7 @@ files = [ "Explicit time stepping" => "time_stepping.jl", "Differentiable programming" => "differentiable_programming.jl", "Custom semidiscretizations" => "custom_semidiscretization.jl", - ] +] tutorials = create_tutorials(files) # Create changelog @@ -153,10 +157,10 @@ makedocs( ], "Time integration" => "time_integration.md", "Callbacks" => "callbacks.md", - "Coupling" => "multi-physics_coupling.md" + "Coupling" => "multi-physics_coupling.md", ], "Advanced topics & developers" => [ - "Conventions" =>"conventions.md", + "Conventions" => "conventions.md", "Development" => "development.md", "GitHub & Git" => "github-git.md", "Style guide" => "styleguide.md", @@ -166,10 +170,10 @@ makedocs( ], "Troubleshooting and FAQ" => "troubleshooting.md", "Reference" => [ - "Trixi.jl" => "reference-trixi.md", - "TrixiBase.jl" => "reference-trixibase.md", - "Trixi2Vtk.jl" => "reference-trixi2vtk.md" - ], + "Trixi.jl" => "reference-trixi.md", + "TrixiBase.jl" => "reference-trixibase.md", + "Trixi2Vtk.jl" => "reference-trixi2vtk.md", + ], "Changelog" => "changelog.md", "Authors" => "authors.md", "Contributing" => "contributing.md", diff --git a/examples/dgmulti_1d/elixir_advection_gauss_sbp.jl b/examples/dgmulti_1d/elixir_advection_gauss_sbp.jl index 09d66fe8ae..eaa463e70f 100644 --- a/examples/dgmulti_1d/elixir_advection_gauss_sbp.jl +++ b/examples/dgmulti_1d/elixir_advection_gauss_sbp.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,19 +11,23 @@ equations = LinearScalarAdvectionEquation1D(advection_velocity) # setup the GSBP DG discretization that uses the Gauss operators from Chan et al. surface_flux = FluxLaxFriedrichs() -dg = DGMulti(polydeg = 3, - element_type = Line(), - approximation_type = GaussSBP(), - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, + element_type = Line(), + approximation_type = GaussSBP(), + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralWeakForm() +) ############################################################################### # setup the 1D mesh cells_per_dimension = (8,) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (-1.0,), coordinates_max = (1.0,), - periodicity = true) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (-1.0,), coordinates_max = (1.0,), + periodicity = true +) ############################################################################### # setup the test problem (no source term needed for linear advection) @@ -34,10 +37,12 @@ initial_condition = initial_condition_convergence_test ############################################################################### # setup the semidiscretization and ODE problem -semi = SemidiscretizationHyperbolic(mesh, - equations, - initial_condition, - dg) +semi = SemidiscretizationHyperbolic( + mesh, + equations, + initial_condition, + dg +) tspan = (0.0, 1.5) ode = semidiscretize(semi, tspan) @@ -60,8 +65,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/dgmulti_1d/elixir_burgers_gauss_shock_capturing.jl b/examples/dgmulti_1d/elixir_burgers_gauss_shock_capturing.jl index b0632b1f97..80e4280307 100644 --- a/examples/dgmulti_1d/elixir_burgers_gauss_shock_capturing.jl +++ b/examples/dgmulti_1d/elixir_burgers_gauss_shock_capturing.jl @@ -4,8 +4,8 @@ using OrdinaryDiffEq equations = InviscidBurgersEquation1D() ############################################################################### -# setup the GSBP DG discretization that uses the Gauss operators from -# Chan, Del Rey Fernandez, Carpenter (2019). +# setup the GSBP DG discretization that uses the Gauss operators from +# Chan, Del Rey Fernandez, Carpenter (2019). # [https://doi.org/10.1137/18M1209234](https://doi.org/10.1137/18M1209234) surface_flux = flux_lax_friedrichs @@ -14,34 +14,44 @@ volume_flux = flux_ec polydeg = 3 basis = DGMultiBasis(Line(), polydeg, approximation_type = GaussSBP()) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = first) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -dg = DGMulti(basis, - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = first +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +dg = DGMulti( + basis, + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = volume_integral +) ############################################################################### # setup the 1D mesh cells_per_dimension = (32,) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (-1.0,), coordinates_max = (1.0,), - periodicity = true) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (-1.0,), coordinates_max = (1.0,), + periodicity = true +) ############################################################################### # setup the semidiscretization and ODE problem -semi = SemidiscretizationHyperbolic(mesh, - equations, - initial_condition_convergence_test, - dg) +semi = SemidiscretizationHyperbolic( + mesh, + equations, + initial_condition_convergence_test, + dg +) tspan = (0.0, 2.0) ode = semidiscretize(semi, tspan) @@ -64,5 +74,7 @@ callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) # ############################################################################### # # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, save_everystep = false, callback = callbacks +); diff --git a/examples/dgmulti_1d/elixir_euler_fdsbp_periodic.jl b/examples/dgmulti_1d/elixir_euler_fdsbp_periodic.jl index 6abd7118fe..556205ae2d 100644 --- a/examples/dgmulti_1d/elixir_euler_fdsbp_periodic.jl +++ b/examples/dgmulti_1d/elixir_euler_fdsbp_periodic.jl @@ -1,23 +1,30 @@ - using Trixi, OrdinaryDiffEq -dg = DGMulti(element_type = Line(), - approximation_type = periodic_derivative_operator(derivative_order = 1, - accuracy_order = 4, - xmin = 0.0, xmax = 1.0, - N = 50), - surface_flux = flux_hll, - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + element_type = Line(), + approximation_type = periodic_derivative_operator( + derivative_order = 1, + accuracy_order = 4, + xmin = 0.0, xmax = 1.0, + N = 50 + ), + surface_flux = flux_hll, + volume_integral = VolumeIntegralWeakForm() +) equations = CompressibleEulerEquations1D(1.4) initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test -mesh = DGMultiMesh(dg, coordinates_min = (-1.0,), - coordinates_max = (1.0,)) +mesh = DGMultiMesh( + dg, coordinates_min = (-1.0,), + coordinates_max = (1.0,) +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms +) tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan) @@ -27,12 +34,16 @@ alive_callback = AliveCallback(alive_interval = 10) analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype = real(dg)) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, alive_callback, stepsize_callback, - analysis_callback) +callbacks = CallbackSet( + summary_callback, alive_callback, stepsize_callback, + analysis_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_1d/elixir_euler_flux_diff.jl b/examples/dgmulti_1d/elixir_euler_flux_diff.jl index 56a24d25d0..6144c27aaf 100644 --- a/examples/dgmulti_1d/elixir_euler_flux_diff.jl +++ b/examples/dgmulti_1d/elixir_euler_flux_diff.jl @@ -2,9 +2,11 @@ using Trixi, OrdinaryDiffEq surface_flux = FluxLaxFriedrichs() volume_flux = flux_ranocha -dg = DGMulti(polydeg = 3, element_type = Line(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +dg = DGMulti( + polydeg = 3, element_type = Line(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) equations = CompressibleEulerEquations1D(1.4) @@ -12,10 +14,14 @@ initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test cells_per_dimension = (8,) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (-1.0,), coordinates_max = (1.0,), periodicity = true) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg; - source_terms = source_terms) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (-1.0,), coordinates_max = (1.0,), periodicity = true +) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg; + source_terms = source_terms +) tspan = (0.0, 1.1) ode = semidiscretize(semi, tspan) @@ -24,14 +30,18 @@ summary_callback = SummaryCallback() alive_callback = AliveCallback(alive_interval = 10) analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype = real(dg)) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_1d/elixir_euler_quasi_1d.jl b/examples/dgmulti_1d/elixir_euler_quasi_1d.jl index 19269fa925..9493182b8c 100644 --- a/examples/dgmulti_1d/elixir_euler_quasi_1d.jl +++ b/examples/dgmulti_1d/elixir_euler_quasi_1d.jl @@ -11,15 +11,21 @@ initial_condition = initial_condition_convergence_test surface_flux = (flux_chan_etal, flux_nonconservative_chan_etal) volume_flux = surface_flux -dg = DGMulti(polydeg = 4, element_type = Line(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +dg = DGMulti( + polydeg = 4, element_type = Line(), approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) cells_per_dimension = (8,) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (-1.0,), coordinates_max = (1.0,), periodicity = true) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg; - source_terms = source_terms_convergence_test) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (-1.0,), coordinates_max = (1.0,), periodicity = true +) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg; + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -35,15 +41,19 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_1d/elixir_euler_shu_osher_gauss_shock_capturing.jl b/examples/dgmulti_1d/elixir_euler_shu_osher_gauss_shock_capturing.jl index 79c9265617..c3ce1231d6 100644 --- a/examples/dgmulti_1d/elixir_euler_shu_osher_gauss_shock_capturing.jl +++ b/examples/dgmulti_1d/elixir_euler_shu_osher_gauss_shock_capturing.jl @@ -5,8 +5,8 @@ gamma_gas = 1.4 equations = CompressibleEulerEquations1D(gamma_gas) ############################################################################### -# setup the GSBP DG discretization that uses the Gauss operators from -# Chan, Del Rey Fernandez, Carpenter (2019). +# setup the GSBP DG discretization that uses the Gauss operators from +# Chan, Del Rey Fernandez, Carpenter (2019). # [https://doi.org/10.1137/18M1209234](https://doi.org/10.1137/18M1209234) # Shu-Osher initial condition for 1D compressible Euler equations @@ -27,8 +27,10 @@ function initial_condition_shu_osher(x, t, equations::CompressibleEulerEquations v = ifelse(x[1] > x0, v_right, v_left) p = ifelse(x[1] > x0, p_right, p_left) - return prim2cons(SVector(rho, v, p), - equations) + return prim2cons( + SVector(rho, v, p), + equations + ) end initial_condition = initial_condition_shu_osher @@ -39,18 +41,24 @@ volume_flux = flux_ranocha polydeg = 3 basis = DGMultiBasis(Line(), polydeg, approximation_type = GaussSBP()) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -dg = DGMulti(basis, - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +dg = DGMulti( + basis, + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = volume_integral +) boundary_condition = BoundaryConditionDirichlet(initial_condition) boundary_conditions = (; :entire_boundary => boundary_condition) @@ -59,15 +67,19 @@ boundary_conditions = (; :entire_boundary => boundary_condition) # setup the 1D mesh cells_per_dimension = (64,) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (-5.0,), coordinates_max = (5.0,), - periodicity = false) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (-5.0,), coordinates_max = (5.0,), + periodicity = false +) ############################################################################### # setup the semidiscretization and ODE problem -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, - dg, boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, + dg, boundary_conditions = boundary_conditions +) tspan = (0.0, 1.0) ode = semidiscretize(semi, tspan) diff --git a/examples/dgmulti_1d/elixir_shallow_water_quasi_1d.jl b/examples/dgmulti_1d/elixir_shallow_water_quasi_1d.jl index 85741f9dbd..872f5a09ef 100644 --- a/examples/dgmulti_1d/elixir_shallow_water_quasi_1d.jl +++ b/examples/dgmulti_1d/elixir_shallow_water_quasi_1d.jl @@ -10,19 +10,27 @@ equations = ShallowWaterEquationsQuasi1D(gravity_constant = 9.81) initial_condition = initial_condition_convergence_test volume_flux = (flux_chan_etal, flux_nonconservative_chan_etal) -surface_flux = (FluxPlusDissipation(flux_chan_etal, DissipationLocalLaxFriedrichs()), - flux_nonconservative_chan_etal) +surface_flux = ( + FluxPlusDissipation(flux_chan_etal, DissipationLocalLaxFriedrichs()), + flux_nonconservative_chan_etal, +) -dg = DGMulti(polydeg = 4, element_type = Line(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +dg = DGMulti( + polydeg = 4, element_type = Line(), approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) cells_per_dimension = (8,) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (0.0,), coordinates_max = (sqrt(2),), - periodicity = true) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg; - source_terms = source_terms_convergence_test) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (0.0,), coordinates_max = (sqrt(2),), + periodicity = true +) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg; + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -37,13 +45,17 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_advection_diffusion.jl b/examples/dgmulti_2d/elixir_advection_diffusion.jl index ce7b0e745a..1ee8ad0773 100644 --- a/examples/dgmulti_2d/elixir_advection_diffusion.jl +++ b/examples/dgmulti_2d/elixir_advection_diffusion.jl @@ -1,8 +1,10 @@ using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralWeakForm() +) equations = LinearScalarAdvectionEquation2D(1.5, 1.0) equations_parabolic = LaplaceDiffusion2D(5.0e-2, equations) @@ -21,28 +23,40 @@ cells_per_dimension = (16, 16) mesh = DGMultiMesh(dg, cells_per_dimension; is_on_boundary) # BC types -boundary_condition_left = BoundaryConditionDirichlet((x, t, equations) -> SVector(1 + - 0.1 * - x[2])) +boundary_condition_left = BoundaryConditionDirichlet( + (x, t, equations) -> SVector( + 1 + + 0.1 * + x[2] + ) +) boundary_condition_zero = BoundaryConditionDirichlet((x, t, equations) -> SVector(0.0)) boundary_condition_neumann_zero = BoundaryConditionNeumann((x, t, equations) -> SVector(0.0)) # define inviscid boundary conditions -boundary_conditions = (; :left => boundary_condition_left, - :bottom => boundary_condition_zero, - :top => boundary_condition_do_nothing, - :right => boundary_condition_do_nothing) +boundary_conditions = (; + :left => boundary_condition_left, + :bottom => boundary_condition_zero, + :top => boundary_condition_do_nothing, + :right => boundary_condition_do_nothing, +) # define viscous boundary conditions -boundary_conditions_parabolic = (; :left => boundary_condition_left, - :bottom => boundary_condition_zero, - :top => boundary_condition_zero, - :right => boundary_condition_neumann_zero) +boundary_conditions_parabolic = (; + :left => boundary_condition_left, + :bottom => boundary_condition_zero, + :top => boundary_condition_zero, + :right => boundary_condition_neumann_zero, +) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, dg; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) tspan = (0.0, 1.5) ode = semidiscretize(semi, tspan) @@ -56,7 +70,9 @@ callbacks = CallbackSet(summary_callback, alive_callback) ############################################################################### # run the simulation -time_int_tol = 1e-6 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-6 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_advection_diffusion_nonperiodic.jl b/examples/dgmulti_2d/elixir_advection_diffusion_nonperiodic.jl index d2f11f1850..402500ef10 100644 --- a/examples/dgmulti_2d/elixir_advection_diffusion_nonperiodic.jl +++ b/examples/dgmulti_2d/elixir_advection_diffusion_nonperiodic.jl @@ -1,8 +1,10 @@ using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralWeakForm() +) diffusivity() = 5.0e-2 @@ -34,31 +36,41 @@ right(x, tol = 50 * eps()) = abs(x[1]) < tol bottom(x, tol = 50 * eps()) = abs(x[2] + 0.5) < tol top(x, tol = 50 * eps()) = abs(x[2] - 0.5) < tol entire_boundary(x, tol = 50 * eps()) = true -is_on_boundary = Dict(:left => left, :right => right, :top => top, :bottom => bottom, - :entire_boundary => entire_boundary) +is_on_boundary = Dict( + :left => left, :right => right, :top => top, :bottom => bottom, + :entire_boundary => entire_boundary +) cells_per_dimension = (16, 16) -mesh = DGMultiMesh(dg, cells_per_dimension; - coordinates_min = (-1.0, -0.5), - coordinates_max = (0.0, 0.5), - is_on_boundary) +mesh = DGMultiMesh( + dg, cells_per_dimension; + coordinates_min = (-1.0, -0.5), + coordinates_max = (0.0, 0.5), + is_on_boundary +) # BC types boundary_condition = BoundaryConditionDirichlet(initial_condition) # define inviscid boundary conditions, enforce "do nothing" boundary condition at the outflow -boundary_conditions = (; :left => boundary_condition, - :top => boundary_condition, - :bottom => boundary_condition, - :right => boundary_condition_do_nothing) +boundary_conditions = (; + :left => boundary_condition, + :top => boundary_condition, + :bottom => boundary_condition, + :right => boundary_condition_do_nothing, +) # define viscous boundary conditions boundary_conditions_parabolic = (; :entire_boundary => boundary_condition) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, dg; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) tspan = (0.0, 1.5) ode = semidiscretize(semi, tspan) @@ -72,7 +84,9 @@ callbacks = CallbackSet(summary_callback, alive_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_advection_diffusion_periodic.jl b/examples/dgmulti_2d/elixir_advection_diffusion_periodic.jl index c498e5468d..fc90d7c070 100644 --- a/examples/dgmulti_2d/elixir_advection_diffusion_periodic.jl +++ b/examples/dgmulti_2d/elixir_advection_diffusion_periodic.jl @@ -1,8 +1,10 @@ using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 1, element_type = Tri(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 1, element_type = Tri(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralWeakForm() +) equations = LinearScalarAdvectionEquation2D(0.0, 0.0) equations_parabolic = LaplaceDiffusion2D(5.0e-1, equations) @@ -14,8 +16,10 @@ initial_condition = initial_condition_sharp_gaussian cells_per_dimension = (16, 16) mesh = DGMultiMesh(dg, cells_per_dimension, periodicity = true) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, dg +) tspan = (0.0, 0.1) ode = semidiscretize(semi, tspan) @@ -29,8 +33,10 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-6 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - dt = time_int_tol, ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-6 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + dt = time_int_tol, ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_bilinear.jl b/examples/dgmulti_2d/elixir_euler_bilinear.jl index cd498bf39b..ce7d25d146 100644 --- a/examples/dgmulti_2d/elixir_euler_bilinear.jl +++ b/examples/dgmulti_2d/elixir_euler_bilinear.jl @@ -1,9 +1,10 @@ - using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(flux_hll), - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(flux_hll), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test @@ -20,8 +21,10 @@ function mapping(xi, eta) return SVector(x, y) end cells_per_dimension = (16, 16) -vertex_coordinates, EToV = StartUpDG.uniform_mesh(dg.basis.element_type, - cells_per_dimension...) +vertex_coordinates, EToV = StartUpDG.uniform_mesh( + dg.basis.element_type, + cells_per_dimension... +) for i in eachindex(vertex_coordinates[1]) vx, vy = getindex.(vertex_coordinates, i) setindex!.(vertex_coordinates, mapping(vx, vy), i) @@ -30,12 +33,16 @@ end mesh = DGMultiMesh(dg, vertex_coordinates, EToV, is_on_boundary = is_on_boundary) boundary_condition_convergence_test = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (; :top => boundary_condition_convergence_test, - :rest => boundary_condition_convergence_test) +boundary_conditions = (; + :top => boundary_condition_convergence_test, + :rest => boundary_condition_convergence_test, +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan) @@ -49,7 +56,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_brown_minion_vortex.jl b/examples/dgmulti_2d/elixir_euler_brown_minion_vortex.jl index e7830c4736..ce35ce5530 100644 --- a/examples/dgmulti_2d/elixir_euler_brown_minion_vortex.jl +++ b/examples/dgmulti_2d/elixir_euler_brown_minion_vortex.jl @@ -1,8 +1,10 @@ using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 4, element_type = Quad(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(FluxLaxFriedrichs()), - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 4, element_type = Quad(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(FluxLaxFriedrichs()), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) equations = CompressibleEulerEquations2D(1.4) @@ -29,9 +31,11 @@ end initial_condition = initial_condition_BM_vortex cells_per_dimension = (16, 16) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (-0.5, -0.5), coordinates_max = (0.5, 0.5), - periodicity = true) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (-0.5, -0.5), coordinates_max = (0.5, 0.5), + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg) tspan = (0.0, 1.0) @@ -47,7 +51,9 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # run the simulation tol = 1.0e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = tol, reltol = tol, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = tol, reltol = tol, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_curved.jl b/examples/dgmulti_2d/elixir_euler_curved.jl index 48662f4b12..8abf0ed5f6 100644 --- a/examples/dgmulti_2d/elixir_euler_curved.jl +++ b/examples/dgmulti_2d/elixir_euler_curved.jl @@ -1,9 +1,10 @@ - using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(flux_hll), - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(flux_hll), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test @@ -23,12 +24,16 @@ cells_per_dimension = (16, 16) mesh = DGMultiMesh(dg, cells_per_dimension, mapping, is_on_boundary = is_on_boundary) boundary_condition_convergence_test = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (; :top => boundary_condition_convergence_test, - :rest => boundary_condition_convergence_test) +boundary_conditions = (; + :top => boundary_condition_convergence_test, + :rest => boundary_condition_convergence_test, +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan) @@ -43,7 +48,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) # run the simulation alg = RDPK3SpFSAL49() -sol = solve(ode, alg; abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, alg; abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_fdsbp_periodic.jl b/examples/dgmulti_2d/elixir_euler_fdsbp_periodic.jl index 3a73089b56..770f91c422 100644 --- a/examples/dgmulti_2d/elixir_euler_fdsbp_periodic.jl +++ b/examples/dgmulti_2d/elixir_euler_fdsbp_periodic.jl @@ -1,23 +1,30 @@ - using Trixi, OrdinaryDiffEq -dg = DGMulti(element_type = Quad(), - approximation_type = periodic_derivative_operator(derivative_order = 1, - accuracy_order = 4, - xmin = 0.0, xmax = 1.0, - N = 50), - surface_flux = flux_hll, - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + element_type = Quad(), + approximation_type = periodic_derivative_operator( + derivative_order = 1, + accuracy_order = 4, + xmin = 0.0, xmax = 1.0, + N = 50 + ), + surface_flux = flux_hll, + volume_integral = VolumeIntegralWeakForm() +) equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test -mesh = DGMultiMesh(dg, coordinates_min = (-1.0, -1.0), - coordinates_max = (1.0, 1.0)) +mesh = DGMultiMesh( + dg, coordinates_min = (-1.0, -1.0), + coordinates_max = (1.0, 1.0) +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms +) tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan) @@ -27,12 +34,16 @@ alive_callback = AliveCallback(alive_interval = 10) analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype = real(dg)) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, alive_callback, stepsize_callback, - analysis_callback) +callbacks = CallbackSet( + summary_callback, alive_callback, stepsize_callback, + analysis_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_hohqmesh.jl b/examples/dgmulti_2d/elixir_euler_hohqmesh.jl index 9b14a5c682..7a4e4d1247 100644 --- a/examples/dgmulti_2d/elixir_euler_hohqmesh.jl +++ b/examples/dgmulti_2d/elixir_euler_hohqmesh.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,32 +13,40 @@ initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test boundary_condition_convergence_test = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (; :Slant => boundary_condition_convergence_test, - :Bezier => boundary_condition_convergence_test, - :Right => boundary_condition_convergence_test, - :Bottom => boundary_condition_convergence_test, - :Top => boundary_condition_convergence_test) +boundary_conditions = (; + :Slant => boundary_condition_convergence_test, + :Bezier => boundary_condition_convergence_test, + :Right => boundary_condition_convergence_test, + :Bottom => boundary_condition_convergence_test, + :Top => boundary_condition_convergence_test, +) ############################################################################### # Get the DG approximation space -dg = DGMulti(polydeg = 8, element_type = Quad(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 8, element_type = Quad(), approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/52056f1487853fab63b7f4ed7f171c80/raw/9d573387dfdbb8bce2a55db7246f4207663ac07f/mesh_trixi_unstructured_mesh_docs.mesh", - joinpath(@__DIR__, "mesh_trixi_unstructured_mesh_docs.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/52056f1487853fab63b7f4ed7f171c80/raw/9d573387dfdbb8bce2a55db7246f4207663ac07f/mesh_trixi_unstructured_mesh_docs.mesh", + joinpath(@__DIR__, "mesh_trixi_unstructured_mesh_docs.mesh") +) mesh = DGMultiMesh(dg, mesh_file) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -54,15 +61,19 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - dt = time_int_tol, ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + dt = time_int_tol, ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_kelvin_helmholtz_instability.jl b/examples/dgmulti_2d/elixir_euler_kelvin_helmholtz_instability.jl index 14de0bf0e8..07652be245 100644 --- a/examples/dgmulti_2d/elixir_euler_kelvin_helmholtz_instability.jl +++ b/examples/dgmulti_2d/elixir_euler_kelvin_helmholtz_instability.jl @@ -1,8 +1,10 @@ using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(FluxLaxFriedrichs()), - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(FluxLaxFriedrichs()), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) equations = CompressibleEulerEquations2D(1.4) @@ -15,8 +17,10 @@ A version of the classical Kelvin-Helmholtz instability based on of the Euler Equations [arXiv: 2102.06017](https://arxiv.org/abs/2102.06017) """ -function initial_condition_kelvin_helmholtz_instability(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_kelvin_helmholtz_instability( + x, t, + equations::CompressibleEulerEquations2D + ) # change discontinuity to tanh # typical resolution 128^2, 256^2 # domain size is [-1,+1]^2 @@ -43,14 +47,18 @@ summary_callback = SummaryCallback() alive_callback = AliveCallback(alive_interval = 10) analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype = real(dg)) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_rayleigh_taylor_instability.jl b/examples/dgmulti_2d/elixir_euler_rayleigh_taylor_instability.jl index 49c8b28eaf..4c02e2896a 100644 --- a/examples/dgmulti_2d/elixir_euler_rayleigh_taylor_instability.jl +++ b/examples/dgmulti_2d/elixir_euler_rayleigh_taylor_instability.jl @@ -1,4 +1,3 @@ - using Trixi, OrdinaryDiffEq ############################################################################### @@ -27,10 +26,12 @@ all boundaries or This should be used together with `source_terms_rayleigh_taylor_instability`, which is defined below. """ -@inline function initial_condition_rayleigh_taylor_instability(x, t, - equations::CompressibleEulerEquations2D, - slope = 1000) - tol = 1e2 * eps() +@inline function initial_condition_rayleigh_taylor_instability( + x, t, + equations::CompressibleEulerEquations2D, + slope = 1000 + ) + tol = 1.0e2 * eps() if x[2] < 0.5 p = 2 * x[2] + 1 @@ -51,8 +52,10 @@ defined below. return prim2cons(SVector(rho, u, v, p), equations) end -@inline function source_terms_rayleigh_taylor_instability(u, x, t, - equations::CompressibleEulerEquations2D) +@inline function source_terms_rayleigh_taylor_instability( + u, x, t, + equations::CompressibleEulerEquations2D + ) g = 1.0 rho, rho_v1, rho_v2, rho_e = u @@ -60,22 +63,28 @@ end end # numerical parameters -dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_hll), - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_hll), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) num_elements = 16 cells_per_dimension = (num_elements, 4 * num_elements) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (0.0, 0.0), coordinates_max = (0.25, 1.0), - periodicity = (true, false)) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (0.0, 0.0), coordinates_max = (0.25, 1.0), + periodicity = (true, false) +) initial_condition = initial_condition_rayleigh_taylor_instability boundary_conditions = (; :entire_boundary => boundary_condition_slip_wall) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg; - source_terms = source_terms_rayleigh_taylor_instability, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg; + source_terms = source_terms_rayleigh_taylor_instability, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -90,14 +99,18 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_shockcapturing.jl b/examples/dgmulti_2d/elixir_euler_shockcapturing.jl index 36494b268d..1d1a416467 100644 --- a/examples/dgmulti_2d/elixir_euler_shockcapturing.jl +++ b/examples/dgmulti_2d/elixir_euler_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,17 +14,23 @@ volume_flux = flux_ranocha polydeg = 3 basis = DGMultiBasis(Quad(), polydeg, approximation_type = GaussSBP()) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -dg = DGMulti(basis, - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) +dg = DGMulti( + basis, + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = volume_integral +) cells_per_dimension = (8, 8) mesh = DGMultiMesh(dg, cells_per_dimension, periodicity = true) @@ -44,7 +49,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_shockcapturing_curved.jl b/examples/dgmulti_2d/elixir_euler_shockcapturing_curved.jl index 5e8d9e6c8e..a6a2430b69 100644 --- a/examples/dgmulti_2d/elixir_euler_shockcapturing_curved.jl +++ b/examples/dgmulti_2d/elixir_euler_shockcapturing_curved.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,17 +14,23 @@ volume_flux = flux_ranocha polydeg = 3 basis = DGMultiBasis(Quad(), polydeg, approximation_type = GaussSBP()) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -dg = DGMulti(basis, - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) +dg = DGMulti( + basis, + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = volume_integral +) function mapping(xi, eta) x = xi + 0.1 * sin(pi * xi) * sin(pi * eta) @@ -49,7 +54,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_triangulate_pkg_mesh.jl b/examples/dgmulti_2d/elixir_euler_triangulate_pkg_mesh.jl index 53661af259..a727525aed 100644 --- a/examples/dgmulti_2d/elixir_euler_triangulate_pkg_mesh.jl +++ b/examples/dgmulti_2d/elixir_euler_triangulate_pkg_mesh.jl @@ -1,8 +1,10 @@ using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Tri(), - surface_integral = SurfaceIntegralWeakForm(flux_hll), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Tri(), + surface_integral = SurfaceIntegralWeakForm(flux_hll), + volume_integral = VolumeIntegralWeakForm() +) equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test @@ -16,12 +18,16 @@ meshIO = StartUpDG.triangulate_domain(StartUpDG.RectangularDomainWithHole()) mesh = DGMultiMesh(dg, meshIO, Dict(:outer_boundary => 1, :inner_boundary => 2)) boundary_condition_convergence_test = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (; :outer_boundary => boundary_condition_convergence_test, - :inner_boundary => boundary_condition_convergence_test) +boundary_conditions = (; + :outer_boundary => boundary_condition_convergence_test, + :inner_boundary => boundary_condition_convergence_test, +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) tspan = (0.0, 0.2) ode = semidiscretize(semi, tspan) @@ -35,6 +41,8 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_weakform.jl b/examples/dgmulti_2d/elixir_euler_weakform.jl index ecf5e94a86..be1d57e367 100644 --- a/examples/dgmulti_2d/elixir_euler_weakform.jl +++ b/examples/dgmulti_2d/elixir_euler_weakform.jl @@ -1,9 +1,10 @@ - using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_hll), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_hll), + volume_integral = VolumeIntegralWeakForm() +) equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test @@ -18,12 +19,16 @@ cells_per_dimension = (8, 8) mesh = DGMultiMesh(dg, cells_per_dimension, is_on_boundary = is_on_boundary) boundary_condition_convergence_test = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (; :top => boundary_condition_convergence_test, - :rest => boundary_condition_convergence_test) +boundary_conditions = (; + :top => boundary_condition_convergence_test, + :rest => boundary_condition_convergence_test, +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan) @@ -33,12 +38,16 @@ alive_callback = AliveCallback(alive_interval = 10) analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype = real(dg)) stepsize_callback = StepsizeCallback(cfl = 1.5) -callbacks = CallbackSet(summary_callback, alive_callback, stepsize_callback, - analysis_callback) +callbacks = CallbackSet( + summary_callback, alive_callback, stepsize_callback, + analysis_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_euler_weakform_periodic.jl b/examples/dgmulti_2d/elixir_euler_weakform_periodic.jl index 307bf5cbf4..a0fabe3102 100644 --- a/examples/dgmulti_2d/elixir_euler_weakform_periodic.jl +++ b/examples/dgmulti_2d/elixir_euler_weakform_periodic.jl @@ -1,9 +1,10 @@ - using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_hll), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_hll), + volume_integral = VolumeIntegralWeakForm() +) equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test @@ -11,8 +12,10 @@ source_terms = source_terms_convergence_test cells_per_dimension = (4, 4) mesh = DGMultiMesh(dg, cells_per_dimension, periodicity = true) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms +) tspan = (0.0, 0.4) ode = semidiscretize(semi, tspan) @@ -26,6 +29,8 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_mhd_reflective_wall.jl b/examples/dgmulti_2d/elixir_mhd_reflective_wall.jl index 1167028852..5507d07b0f 100644 --- a/examples/dgmulti_2d/elixir_mhd_reflective_wall.jl +++ b/examples/dgmulti_2d/elixir_mhd_reflective_wall.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi using LinearAlgebra: norm, dot # for use in the MHD boundary condition @@ -32,9 +31,11 @@ initial_condition = initial_condition_perturbation surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = GaussSBP(), - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = GaussSBP(), + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) x_neg(x, tol = 50 * eps()) = abs(x[1] + 1) < tol x_pos(x, tol = 50 * eps()) = abs(x[1] - 1) < tol @@ -43,14 +44,18 @@ y_pos(x, tol = 50 * eps()) = abs(x[2] - 1) < tol is_on_boundary = Dict(:x_neg => x_neg, :x_pos => x_pos, :y_neg => y_neg, :y_pos => y_pos) cells_per_dimension = (16, 16) -mesh = DGMultiMesh(solver, cells_per_dimension; periodicity = (false, false), - is_on_boundary) +mesh = DGMultiMesh( + solver, cells_per_dimension; periodicity = (false, false), + is_on_boundary +) # Create a "reflective-like" boundary condition by mirroring the velocity but leaving the magnetic field alone. # Note that this boundary condition is probably not entropy stable. -function boundary_condition_velocity_slip_wall(u_inner, normal_direction::AbstractVector, - x, t, surface_flux_function, - equations::IdealGlmMhdEquations2D) +function boundary_condition_velocity_slip_wall( + u_inner, normal_direction::AbstractVector, + x, t, surface_flux_function, + equations::IdealGlmMhdEquations2D + ) # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later norm_ = norm(normal_direction) @@ -60,20 +65,28 @@ function boundary_condition_velocity_slip_wall(u_inner, normal_direction::Abstra rho, v1, v2, v3, p, B1, B2, B3, psi = cons2prim(u_inner, equations) v_normal = dot(normal, SVector(v1, v2)) - u_mirror = prim2cons(SVector(rho, v1 - 2 * v_normal * normal[1], - v2 - 2 * v_normal * normal[2], - v3, p, B1, B2, B3, psi), equations) + u_mirror = prim2cons( + SVector( + rho, v1 - 2 * v_normal * normal[1], + v2 - 2 * v_normal * normal[2], + v3, p, B1, B2, B3, psi + ), equations + ) return surface_flux_function(u_inner, u_mirror, normal, equations) * norm_ end -boundary_conditions = (; x_neg = boundary_condition_velocity_slip_wall, - x_pos = boundary_condition_velocity_slip_wall, - y_neg = boundary_condition_do_nothing, - y_pos = BoundaryConditionDirichlet(initial_condition)) +boundary_conditions = (; + x_neg = boundary_condition_velocity_slip_wall, + x_pos = boundary_condition_velocity_slip_wall, + y_neg = boundary_condition_do_nothing, + y_pos = BoundaryConditionDirichlet(initial_condition), +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver; + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -84,25 +97,31 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - uEltype = real(solver)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + uEltype = real(solver) +) alive_callback = AliveCallback(alive_interval = 10) cfl = 0.5 stepsize_callback = StepsizeCallback(cfl = cfl) glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1e-5, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0e-5, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_mhd_weak_blast_wave.jl b/examples/dgmulti_2d/elixir_mhd_weak_blast_wave.jl index 663301e189..c11f0ae842 100644 --- a/examples/dgmulti_2d/elixir_mhd_weak_blast_wave.jl +++ b/examples/dgmulti_2d/elixir_mhd_weak_blast_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -11,14 +10,18 @@ initial_condition = initial_condition_weak_blast_wave surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +dg = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) cells_per_dimension = (16, 16) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (-2.0, -2.0), coordinates_max = (2.0, 2.0), - periodicity = true) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (-2.0, -2.0), coordinates_max = (2.0, 2.0), + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg) ############################################################################### @@ -38,17 +41,21 @@ stepsize_callback = StepsizeCallback(cfl = cfl) glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, - stepsize_callback, - alive_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + stepsize_callback, + alive_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_mhd_weak_blast_wave_SBP.jl b/examples/dgmulti_2d/elixir_mhd_weak_blast_wave_SBP.jl index 3dc070a729..d64a36482d 100644 --- a/examples/dgmulti_2d/elixir_mhd_weak_blast_wave_SBP.jl +++ b/examples/dgmulti_2d/elixir_mhd_weak_blast_wave_SBP.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,13 +14,17 @@ initial_condition = initial_condition_weak_blast_wave surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +dg = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (-2.0, -2.0), coordinates_max = (2.0, 2.0), - periodicity = true) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (-2.0, -2.0), coordinates_max = (2.0, 2.0), + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg) ############################################################################### @@ -43,10 +46,12 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) # # glm_speed_callback = GlmSpeedCallback(glm_scale=0.5, cfl=cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, - #stepsize_callback, - alive_callback) #=glm_speed_callback=# +callbacks = CallbackSet( + summary_callback, + analysis_callback, + #stepsize_callback, + alive_callback +) #=glm_speed_callback=# ############################################################################### # run the simulation @@ -55,7 +60,9 @@ callbacks = CallbackSet(summary_callback, # sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), # dt=1.0, # solve needs some value here but it will be overwritten by the stepsize_callback # save_everystep=false, callback=callbacks); -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_navierstokes_convergence.jl b/examples/dgmulti_2d/elixir_navierstokes_convergence.jl index 38cf3d7984..ca52897863 100644 --- a/examples/dgmulti_2d/elixir_navierstokes_convergence.jl +++ b/examples/dgmulti_2d/elixir_navierstokes_convergence.jl @@ -10,14 +10,18 @@ mu() = 0.01 equations = CompressibleEulerEquations2D(1.4) # Note: If you change the Navier-Stokes parameters here, also change them in the initial condition # I really do not like this structure but it should work for now -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -dg = DGMulti(polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralWeakForm() +) top_bottom(x, tol = 50 * eps()) = abs(abs(x[2]) - 1) < tol is_on_boundary = Dict(:top_bottom => top_bottom) @@ -79,16 +83,24 @@ end v1_t = -pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * sin(pi_t) v1_x = pi * cos(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_y = sin(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) v1_xx = -pi * pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_xy = pi * cos(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) - v1_yy = (sin(pi_x) * - (2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - - A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - - (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0))) * cos(pi_t)) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) + v1_yy = ( + sin(pi_x) * + ( + 2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - + A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - + (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0)) + ) * cos(pi_t) + ) v2 = v1 v2_t = v1_t v2_x = v1_x @@ -119,58 +131,68 @@ end du1 = rho_t + rho_x * v1 + rho * v1_x + rho_y * v2 + rho * v2_y # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 - + 2.0 * rho * v1 * v1_x - + rho_y * v1 * v2 - + rho * v1_y * v2 - + rho * v1 * v2_y - - # stress tensor from x-direction - 4.0 / 3.0 * v1_xx * mu_ + - 2.0 / 3.0 * v2_xy * mu_ - - v1_yy * mu_ - - v2_xy * mu_) + du2 = ( + rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y - + # stress tensor from x-direction + 4.0 / 3.0 * v1_xx * mu_ + + 2.0 / 3.0 * v2_xy * mu_ - + v1_yy * mu_ - + v2_xy * mu_ + ) # y-momentum equation - du3 = (rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 - + rho * v1_x * v2 - + rho * v1 * v2_x - + rho_y * v2^2 - + 2.0 * rho * v2 * v2_y - - # stress tensor from y-direction - v1_xy * mu_ - - v2_xx * mu_ - - 4.0 / 3.0 * v2_yy * mu_ + - 2.0 / 3.0 * v1_xy * mu_) + du3 = ( + rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y - + # stress tensor from y-direction + v1_xy * mu_ - + v2_xx * mu_ - + 4.0 / 3.0 * v2_yy * mu_ + + 2.0 / 3.0 * v1_xy * mu_ + ) # total energy equation - du4 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + v2_y * (E + p) + v2 * (E_y + p_y) - - # stress tensor and temperature gradient terms from x-direction - 4.0 / 3.0 * v1_xx * v1 * mu_ + - 2.0 / 3.0 * v2_xy * v1 * mu_ - - 4.0 / 3.0 * v1_x * v1_x * mu_ + - 2.0 / 3.0 * v2_y * v1_x * mu_ - - v1_xy * v2 * mu_ - - v2_xx * v2 * mu_ - - v1_y * v2_x * mu_ - - v2_x * v2_x * mu_ - - T_const * inv_rho_cubed * - (p_xx * rho * rho - - 2.0 * p_x * rho * rho_x + - 2.0 * p * rho_x * rho_x - - p * rho * rho_xx) * mu_ - - # stress tensor and temperature gradient terms from y-direction - v1_yy * v1 * mu_ - - v2_xy * v1 * mu_ - - v1_y * v1_y * mu_ - - v2_x * v1_y * mu_ - - 4.0 / 3.0 * v2_yy * v2 * mu_ + - 2.0 / 3.0 * v1_xy * v2 * mu_ - - 4.0 / 3.0 * v2_y * v2_y * mu_ + - 2.0 / 3.0 * v1_x * v2_y * mu_ - - T_const * inv_rho_cubed * - (p_yy * rho * rho - - 2.0 * p_y * rho * rho_y + - 2.0 * p * rho_y * rho_y - - p * rho * rho_yy) * mu_) + du4 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) - + # stress tensor and temperature gradient terms from x-direction + 4.0 / 3.0 * v1_xx * v1 * mu_ + + 2.0 / 3.0 * v2_xy * v1 * mu_ - + 4.0 / 3.0 * v1_x * v1_x * mu_ + + 2.0 / 3.0 * v2_y * v1_x * mu_ - + v1_xy * v2 * mu_ - + v2_xx * v2 * mu_ - + v1_y * v2_x * mu_ - + v2_x * v2_x * mu_ - + T_const * inv_rho_cubed * + ( + p_xx * rho * rho - + 2.0 * p_x * rho * rho_x + + 2.0 * p * rho_x * rho_x - + p * rho * rho_xx + ) * mu_ - + # stress tensor and temperature gradient terms from y-direction + v1_yy * v1 * mu_ - + v2_xy * v1 * mu_ - + v1_y * v1_y * mu_ - + v2_x * v1_y * mu_ - + 4.0 / 3.0 * v2_yy * v2 * mu_ + + 2.0 / 3.0 * v1_xy * v2 * mu_ - + 4.0 / 3.0 * v2_y * v2_y * mu_ + + 2.0 / 3.0 * v1_x * v2_y * mu_ - + T_const * inv_rho_cubed * + ( + p_yy * rho * rho - + 2.0 * p_y * rho * rho_y + + 2.0 * p * rho_y * rho_y - + p * rho * rho_yy + ) * mu_ + ) return SVector(du1, du2, du3, du4) end @@ -178,12 +200,18 @@ end initial_condition = initial_condition_navier_stokes_convergence_test # BC types -velocity_bc_top_bottom = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2:3]) +velocity_bc_top_bottom = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2:3] +) heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, - heat_bc_top_bottom) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall( + velocity_bc_top_bottom, + heat_bc_top_bottom +) # define inviscid boundary conditions boundary_conditions = (; :top_bottom => boundary_condition_slip_wall) @@ -191,11 +219,15 @@ boundary_conditions = (; :top_bottom => boundary_condition_slip_wall) # define viscous boundary conditions boundary_conditions_parabolic = (; :top_bottom => boundary_condition_top_bottom) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, dg; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -213,7 +245,9 @@ callbacks = CallbackSet(summary_callback, alive_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_navierstokes_convergence_curved.jl b/examples/dgmulti_2d/elixir_navierstokes_convergence_curved.jl index 87ffd0e099..032f740a66 100644 --- a/examples/dgmulti_2d/elixir_navierstokes_convergence_curved.jl +++ b/examples/dgmulti_2d/elixir_navierstokes_convergence_curved.jl @@ -10,14 +10,18 @@ mu() = 0.01 equations = CompressibleEulerEquations2D(1.4) # Note: If you change the Navier-Stokes parameters here, also change them in the initial condition # I really do not like this structure but it should work for now -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -dg = DGMulti(polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Tri(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralWeakForm() +) top_bottom(x, tol = 50 * eps()) = abs(abs(x[2]) - 1) < tol is_on_boundary = Dict(:top_bottom => top_bottom) @@ -28,8 +32,10 @@ function mapping(xi, eta) return SVector(x, y) end cells_per_dimension = (16, 16) -mesh = DGMultiMesh(dg, cells_per_dimension, mapping; periodicity = (true, false), - is_on_boundary) +mesh = DGMultiMesh( + dg, cells_per_dimension, mapping; periodicity = (true, false), + is_on_boundary +) # This initial condition is taken from `examples/dgmulti_2d/elixir_navierstokes_convergence.jl` @@ -87,16 +93,24 @@ end v1_t = -pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * sin(pi_t) v1_x = pi * cos(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_y = sin(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) v1_xx = -pi * pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_xy = pi * cos(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) - v1_yy = (sin(pi_x) * - (2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - - A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - - (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0))) * cos(pi_t)) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) + v1_yy = ( + sin(pi_x) * + ( + 2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - + A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - + (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0)) + ) * cos(pi_t) + ) v2 = v1 v2_t = v1_t v2_x = v1_x @@ -127,58 +141,68 @@ end du1 = rho_t + rho_x * v1 + rho * v1_x + rho_y * v2 + rho * v2_y # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 - + 2.0 * rho * v1 * v1_x - + rho_y * v1 * v2 - + rho * v1_y * v2 - + rho * v1 * v2_y - - # stress tensor from x-direction - 4.0 / 3.0 * v1_xx * mu_ + - 2.0 / 3.0 * v2_xy * mu_ - - v1_yy * mu_ - - v2_xy * mu_) + du2 = ( + rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y - + # stress tensor from x-direction + 4.0 / 3.0 * v1_xx * mu_ + + 2.0 / 3.0 * v2_xy * mu_ - + v1_yy * mu_ - + v2_xy * mu_ + ) # y-momentum equation - du3 = (rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 - + rho * v1_x * v2 - + rho * v1 * v2_x - + rho_y * v2^2 - + 2.0 * rho * v2 * v2_y - - # stress tensor from y-direction - v1_xy * mu_ - - v2_xx * mu_ - - 4.0 / 3.0 * v2_yy * mu_ + - 2.0 / 3.0 * v1_xy * mu_) + du3 = ( + rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y - + # stress tensor from y-direction + v1_xy * mu_ - + v2_xx * mu_ - + 4.0 / 3.0 * v2_yy * mu_ + + 2.0 / 3.0 * v1_xy * mu_ + ) # total energy equation - du4 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + v2_y * (E + p) + v2 * (E_y + p_y) - - # stress tensor and temperature gradient terms from x-direction - 4.0 / 3.0 * v1_xx * v1 * mu_ + - 2.0 / 3.0 * v2_xy * v1 * mu_ - - 4.0 / 3.0 * v1_x * v1_x * mu_ + - 2.0 / 3.0 * v2_y * v1_x * mu_ - - v1_xy * v2 * mu_ - - v2_xx * v2 * mu_ - - v1_y * v2_x * mu_ - - v2_x * v2_x * mu_ - - T_const * inv_rho_cubed * - (p_xx * rho * rho - - 2.0 * p_x * rho * rho_x + - 2.0 * p * rho_x * rho_x - - p * rho * rho_xx) * mu_ - - # stress tensor and temperature gradient terms from y-direction - v1_yy * v1 * mu_ - - v2_xy * v1 * mu_ - - v1_y * v1_y * mu_ - - v2_x * v1_y * mu_ - - 4.0 / 3.0 * v2_yy * v2 * mu_ + - 2.0 / 3.0 * v1_xy * v2 * mu_ - - 4.0 / 3.0 * v2_y * v2_y * mu_ + - 2.0 / 3.0 * v1_x * v2_y * mu_ - - T_const * inv_rho_cubed * - (p_yy * rho * rho - - 2.0 * p_y * rho * rho_y + - 2.0 * p * rho_y * rho_y - - p * rho * rho_yy) * mu_) + du4 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) - + # stress tensor and temperature gradient terms from x-direction + 4.0 / 3.0 * v1_xx * v1 * mu_ + + 2.0 / 3.0 * v2_xy * v1 * mu_ - + 4.0 / 3.0 * v1_x * v1_x * mu_ + + 2.0 / 3.0 * v2_y * v1_x * mu_ - + v1_xy * v2 * mu_ - + v2_xx * v2 * mu_ - + v1_y * v2_x * mu_ - + v2_x * v2_x * mu_ - + T_const * inv_rho_cubed * + ( + p_xx * rho * rho - + 2.0 * p_x * rho * rho_x + + 2.0 * p * rho_x * rho_x - + p * rho * rho_xx + ) * mu_ - + # stress tensor and temperature gradient terms from y-direction + v1_yy * v1 * mu_ - + v2_xy * v1 * mu_ - + v1_y * v1_y * mu_ - + v2_x * v1_y * mu_ - + 4.0 / 3.0 * v2_yy * v2 * mu_ + + 2.0 / 3.0 * v1_xy * v2 * mu_ - + 4.0 / 3.0 * v2_y * v2_y * mu_ + + 2.0 / 3.0 * v1_x * v2_y * mu_ - + T_const * inv_rho_cubed * + ( + p_yy * rho * rho - + 2.0 * p_y * rho * rho_y + + 2.0 * p * rho_y * rho_y - + p * rho * rho_yy + ) * mu_ + ) return SVector(du1, du2, du3, du4) end @@ -186,12 +210,18 @@ end initial_condition = initial_condition_navier_stokes_convergence_test # BC types -velocity_bc_top_bottom = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2:3]) +velocity_bc_top_bottom = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2:3] +) heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, - heat_bc_top_bottom) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall( + velocity_bc_top_bottom, + heat_bc_top_bottom +) # define inviscid boundary conditions boundary_conditions = (; :top_bottom => boundary_condition_slip_wall) @@ -199,11 +229,15 @@ boundary_conditions = (; :top_bottom => boundary_condition_slip_wall) # define viscous boundary conditions boundary_conditions_parabolic = (; :top_bottom => boundary_condition_top_bottom) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, dg; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -221,7 +255,9 @@ callbacks = CallbackSet(summary_callback, alive_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_navierstokes_lid_driven_cavity.jl b/examples/dgmulti_2d/elixir_navierstokes_lid_driven_cavity.jl index a612dd0e0d..caf8720ce4 100644 --- a/examples/dgmulti_2d/elixir_navierstokes_lid_driven_cavity.jl +++ b/examples/dgmulti_2d/elixir_navierstokes_lid_driven_cavity.jl @@ -8,13 +8,17 @@ prandtl_number() = 0.72 mu = 0.001 equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu, + Prandtl = prandtl_number() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = GaussSBP(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = GaussSBP(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) top(x, tol = 50 * eps()) = abs(x[2] - 1) < tol rest_of_boundary(x, tol = 50 * eps()) = !top(x, tol) @@ -40,17 +44,25 @@ boundary_condition_lid = BoundaryConditionNavierStokesWall(velocity_bc_lid, heat boundary_condition_cavity = BoundaryConditionNavierStokesWall(velocity_bc_cavity, heat_bc) # define inviscid boundary conditions -boundary_conditions = (; :top => boundary_condition_slip_wall, - :rest_of_boundary => boundary_condition_slip_wall) +boundary_conditions = (; + :top => boundary_condition_slip_wall, + :rest_of_boundary => boundary_condition_slip_wall, +) # define viscous boundary conditions -boundary_conditions_parabolic = (; :top => boundary_condition_lid, - :rest_of_boundary => boundary_condition_cavity) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +boundary_conditions_parabolic = (; + :top => boundary_condition_lid, + :rest_of_boundary => boundary_condition_cavity, +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, dg; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -68,7 +80,9 @@ callbacks = CallbackSet(summary_callback, alive_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_2d/elixir_shallowwater_source_terms.jl b/examples/dgmulti_2d/elixir_shallowwater_source_terms.jl index f7120d8091..9029f8f41a 100644 --- a/examples/dgmulti_2d/elixir_shallowwater_source_terms.jl +++ b/examples/dgmulti_2d/elixir_shallowwater_source_terms.jl @@ -10,17 +10,23 @@ initial_condition = initial_condition_convergence_test volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal) -dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +dg = DGMulti( + polydeg = 3, element_type = Quad(), approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) cells_per_dimension = (8, 8) -mesh = DGMultiMesh(dg, cells_per_dimension, - coordinates_min = (0.0, 0.0), coordinates_max = (sqrt(2), sqrt(2)), - periodicity = true) +mesh = DGMultiMesh( + dg, cells_per_dimension, + coordinates_min = (0.0, 0.0), coordinates_max = (sqrt(2), sqrt(2)), + periodicity = true +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg; - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg; + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -34,14 +40,18 @@ analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype = real(dg)) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_3d/elixir_advection_tensor_wedge.jl b/examples/dgmulti_3d/elixir_advection_tensor_wedge.jl index e877e60254..757006b524 100644 --- a/examples/dgmulti_3d/elixir_advection_tensor_wedge.jl +++ b/examples/dgmulti_3d/elixir_advection_tensor_wedge.jl @@ -11,20 +11,26 @@ initial_condition = initial_condition_convergence_test # of the tensor-prism tensor_polydeg = (3, 4) -dg = DGMulti(element_type = Wedge(), - approximation_type = Polynomial(), - surface_flux = flux_lax_friedrichs, - polydeg = tensor_polydeg) +dg = DGMulti( + element_type = Wedge(), + approximation_type = Polynomial(), + surface_flux = flux_lax_friedrichs, + polydeg = tensor_polydeg +) cells_per_dimension = (8, 8, 8) -mesh = DGMultiMesh(dg, - cells_per_dimension, - coordinates_min = (-1.0, -1.0, -1.0), - coordinates_max = (1.0, 1.0, 1.0), - periodicity = true) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - boundary_conditions = boundary_condition_periodic) +mesh = DGMultiMesh( + dg, + cells_per_dimension, + coordinates_min = (-1.0, -1.0, -1.0), + coordinates_max = (1.0, 1.0, 1.0), + periodicity = true +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + boundary_conditions = boundary_condition_periodic +) ############################################################################### # ODE solvers, callbacks etc. @@ -42,13 +48,17 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), dt = 1.0, - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), dt = 1.0, + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_3d/elixir_euler_curved.jl b/examples/dgmulti_3d/elixir_euler_curved.jl index 67c84b5097..16ec926726 100644 --- a/examples/dgmulti_3d/elixir_euler_curved.jl +++ b/examples/dgmulti_3d/elixir_euler_curved.jl @@ -1,9 +1,10 @@ - using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Hex(), approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(flux_hll), - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, element_type = Hex(), approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(flux_hll), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test @@ -24,12 +25,16 @@ cells_per_dimension = (4, 4, 4) mesh = DGMultiMesh(dg, cells_per_dimension, mapping, is_on_boundary = is_on_boundary) boundary_condition_convergence_test = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (; :top => boundary_condition_convergence_test, - :rest => boundary_condition_convergence_test) +boundary_conditions = (; + :top => boundary_condition_convergence_test, + :rest => boundary_condition_convergence_test, +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) tspan = (0.0, 0.1) ode = semidiscretize(semi, tspan) @@ -43,6 +48,8 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_3d/elixir_euler_fdsbp_periodic.jl b/examples/dgmulti_3d/elixir_euler_fdsbp_periodic.jl index 0eb3867468..0ba0f27ee2 100644 --- a/examples/dgmulti_3d/elixir_euler_fdsbp_periodic.jl +++ b/examples/dgmulti_3d/elixir_euler_fdsbp_periodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -11,19 +10,27 @@ initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test volume_flux = flux_ranocha -solver = DGMulti(element_type = Hex(), - approximation_type = periodic_derivative_operator(derivative_order = 1, - accuracy_order = 4, - xmin = 0.0, xmax = 1.0, - N = 20), - surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) - -mesh = DGMultiMesh(solver, coordinates_min = (-1.0, -1.0, -1.0), - coordinates_max = (1.0, 1.0, 1.0)) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; - source_terms = source_terms) +solver = DGMulti( + element_type = Hex(), + approximation_type = periodic_derivative_operator( + derivative_order = 1, + accuracy_order = 4, + xmin = 0.0, xmax = 1.0, + N = 20 + ), + surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) + +mesh = DGMultiMesh( + solver, coordinates_min = (-1.0, -1.0, -1.0), + coordinates_max = (1.0, 1.0, 1.0) +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver; + source_terms = source_terms +) ############################################################################### # ODE solvers, callbacks etc. @@ -34,17 +41,23 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - uEltype = real(solver)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + uEltype = real(solver) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_3d/elixir_euler_taylor_green_vortex.jl b/examples/dgmulti_3d/elixir_euler_taylor_green_vortex.jl index fea43ad4d2..b40186fc7c 100644 --- a/examples/dgmulti_3d/elixir_euler_taylor_green_vortex.jl +++ b/examples/dgmulti_3d/elixir_euler_taylor_green_vortex.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,8 +11,10 @@ equations = CompressibleEulerEquations3D(1.4) The classical inviscid Taylor-Green vortex. """ -function initial_condition_taylor_green_vortex(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_taylor_green_vortex( + x, t, + equations::CompressibleEulerEquations3D + ) A = 1.0 # magnitude of speed Ms = 0.1 # maximum Mach number @@ -24,8 +25,10 @@ function initial_condition_taylor_green_vortex(x, t, p = (A / Ms)^2 * rho / equations.gamma # scaling to get Ms p = p + 1.0 / 16.0 * A^2 * rho * - (cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + - cos(2 * x[2]) * cos(2 * x[3])) + ( + cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + + cos(2 * x[2]) * cos(2 * x[3]) + ) return prim2cons(SVector(rho, v1, v2, v3, p), equations) end @@ -33,14 +36,18 @@ initial_condition = initial_condition_taylor_green_vortex volume_flux = flux_ranocha surface_flux = flux_lax_friedrichs -solver = DGMulti(polydeg = 3, element_type = Hex(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGMulti( + polydeg = 3, element_type = Hex(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) cells_per_dimension = (8, 8, 8) -mesh = DGMultiMesh(solver, cells_per_dimension, - coordinates_min = (-pi, -pi, -pi), coordinates_max = (pi, pi, pi), - periodicity = true) +mesh = DGMultiMesh( + solver, cells_per_dimension, + coordinates_min = (-pi, -pi, -pi), coordinates_max = (pi, pi, pi), + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -53,17 +60,23 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - uEltype = real(solver)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + uEltype = real(solver) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_3d/elixir_euler_weakform.jl b/examples/dgmulti_3d/elixir_euler_weakform.jl index 6e06b35c4f..046de40e16 100644 --- a/examples/dgmulti_3d/elixir_euler_weakform.jl +++ b/examples/dgmulti_3d/elixir_euler_weakform.jl @@ -1,9 +1,10 @@ - using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Tet(), - surface_integral = SurfaceIntegralWeakForm(flux_hll), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Tet(), + surface_integral = SurfaceIntegralWeakForm(flux_hll), + volume_integral = VolumeIntegralWeakForm() +) equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test @@ -18,12 +19,16 @@ cells_per_dimension = (4, 4, 4) mesh = DGMultiMesh(dg, cells_per_dimension, is_on_boundary = is_on_boundary) boundary_condition_convergence_test = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (; :top => boundary_condition_convergence_test, - :rest => boundary_condition_convergence_test) +boundary_conditions = (; + :top => boundary_condition_convergence_test, + :rest => boundary_condition_convergence_test, +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) tspan = (0.0, 0.1) ode = semidiscretize(semi, tspan) @@ -37,6 +42,8 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_3d/elixir_euler_weakform_periodic.jl b/examples/dgmulti_3d/elixir_euler_weakform_periodic.jl index bc963a3a2f..9b779684c4 100644 --- a/examples/dgmulti_3d/elixir_euler_weakform_periodic.jl +++ b/examples/dgmulti_3d/elixir_euler_weakform_periodic.jl @@ -1,9 +1,10 @@ - using Trixi, OrdinaryDiffEq -dg = DGMulti(polydeg = 3, element_type = Tet(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_hll), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Tet(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_hll), + volume_integral = VolumeIntegralWeakForm() +) equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test @@ -12,8 +13,10 @@ source_terms = source_terms_convergence_test cells_per_dimension = (4, 4, 4) mesh = DGMultiMesh(dg, cells_per_dimension, periodicity = true) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, dg, - source_terms = source_terms) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, dg, + source_terms = source_terms +) tspan = (0.0, 0.1) ode = semidiscretize(semi, tspan) @@ -27,6 +30,8 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.5 * estimate_dt(mesh, dg), save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/dgmulti_3d/elixir_navierstokes_convergence.jl b/examples/dgmulti_3d/elixir_navierstokes_convergence.jl index 5fa0ad7ce6..fa418c9781 100644 --- a/examples/dgmulti_3d/elixir_navierstokes_convergence.jl +++ b/examples/dgmulti_3d/elixir_navierstokes_convergence.jl @@ -8,21 +8,27 @@ prandtl_number() = 0.72 mu() = 0.01 equations = CompressibleEulerEquations3D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion3D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -dg = DGMulti(polydeg = 3, element_type = Hex(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Hex(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralWeakForm() +) top_bottom(x, tol = 50 * eps()) = abs(abs(x[2]) - 1) < tol is_on_boundary = Dict(:top_bottom => top_bottom) cells_per_dimension = (8, 8, 8) -mesh = DGMultiMesh(dg, cells_per_dimension; periodicity = (true, false, true), - is_on_boundary) +mesh = DGMultiMesh( + dg, cells_per_dimension; periodicity = (true, false, true), + is_on_boundary +) # Note: the initial condition cannot be specialized to `CompressibleNavierStokesDiffusion3D` # since it is called by both the parabolic solver (which passes in `CompressibleNavierStokesDiffusion3D`) @@ -43,7 +49,7 @@ function initial_condition_navier_stokes_convergence_test(x, t, equations) rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) v1 = A2 * sin(pi_x) * log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) * sin(pi_z) * - cos(pi_t) + cos(pi_t) v2 = v1 v3 = v1 p = rho^2 @@ -74,11 +80,15 @@ end # Define auxiliary functions for the strange function of the y variable # to make expressions easier to read g = log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) - g_y = (A3 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + - (1.0 - exp(-A3 * (x[2] - 1.0))) / (x[2] + 2.0)) - g_yy = (2.0 * A3 * exp(-A3 * (x[2] - 1.0)) / (x[2] + 2.0) - + g_y = ( + A3 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + + (1.0 - exp(-A3 * (x[2] - 1.0))) / (x[2] + 2.0) + ) + g_yy = ( + 2.0 * A3 * exp(-A3 * (x[2] - 1.0)) / (x[2] + 2.0) - (1.0 - exp(-A3 * (x[2] - 1.0))) / ((x[2] + 2.0)^2) - - A3^2 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0))) + A3^2 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + ) # Density and its derivatives rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) @@ -168,52 +178,68 @@ end # Compute the source terms # Density equation - du1 = (rho_t + rho_x * v1 + rho * v1_x - + rho_y * v2 + rho * v2_y - + rho_z * v3 + rho * v3_z) + du1 = ( + rho_t + rho_x * v1 + rho * v1_x + + rho_y * v2 + rho * v2_y + + rho_z * v3 + rho * v3_z + ) # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 - + 2.0 * rho * v1 * v1_x - + rho_y * v1 * v2 - + rho * v1_y * v2 - + rho * v1 * v2_y - + rho_z * v1 * v3 - + rho * v1_z * v3 - + rho * v1 * v3_z - - mu_ * (tau11_x + tau12_y + tau13_z)) + du2 = ( + rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y + + rho_z * v1 * v3 + + rho * v1_z * v3 + + rho * v1 * v3_z - + mu_ * (tau11_x + tau12_y + tau13_z) + ) # y-momentum equation - du3 = (rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 - + rho * v1_x * v2 - + rho * v1 * v2_x - + rho_y * v2^2 - + 2.0 * rho * v2 * v2_y - + rho_z * v2 * v3 - + rho * v2_z * v3 - + rho * v2 * v3_z - - mu_ * (tau12_x + tau22_y + tau23_z)) + du3 = ( + rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y + + rho_z * v2 * v3 + + rho * v2_z * v3 + + rho * v2 * v3_z - + mu_ * (tau12_x + tau22_y + tau23_z) + ) # z-momentum equation - du4 = (rho_t * v3 + rho * v3_t + p_z + rho_x * v1 * v3 - + rho * v1_x * v3 - + rho * v1 * v3_x - + rho_y * v2 * v3 - + rho * v2_y * v3 - + rho * v2 * v3_y - + rho_z * v3^2 - + 2.0 * rho * v3 * v3_z - - mu_ * (tau13_x + tau23_y + tau33_z)) + du4 = ( + rho_t * v3 + rho * v3_t + p_z + rho_x * v1 * v3 + + rho * v1_x * v3 + + rho * v1 * v3_x + + rho_y * v2 * v3 + + rho * v2_y * v3 + + rho * v2 * v3_y + + rho_z * v3^2 + + 2.0 * rho * v3 * v3_z - + mu_ * (tau13_x + tau23_y + tau33_z) + ) # Total energy equation - du5 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + v2_y * (E + p) + v2 * (E_y + p_y) - + v3_z * (E + p) + v3 * (E_z + p_z) - - # stress tensor and temperature gradient from x-direction - mu_ * (q_xx + v1_x * tau11 + v2_x * tau12 + v3_x * tau13 - + v1 * tau11_x + v2 * tau12_x + v3 * tau13_x) - - # stress tensor and temperature gradient terms from y-direction - mu_ * (q_yy + v1_y * tau12 + v2_y * tau22 + v3_y * tau23 - + v1 * tau12_y + v2 * tau22_y + v3 * tau23_y) - - # stress tensor and temperature gradient terms from z-direction - mu_ * (q_zz + v1_z * tau13 + v2_z * tau23 + v3_z * tau33 - + v1 * tau13_z + v2 * tau23_z + v3 * tau33_z)) + du5 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) + + v3_z * (E + p) + v3 * (E_z + p_z) - + # stress tensor and temperature gradient from x-direction + mu_ * ( + q_xx + v1_x * tau11 + v2_x * tau12 + v3_x * tau13 + + v1 * tau11_x + v2 * tau12_x + v3 * tau13_x + ) - + # stress tensor and temperature gradient terms from y-direction + mu_ * ( + q_yy + v1_y * tau12 + v2_y * tau22 + v3_y * tau23 + + v1 * tau12_y + v2 * tau22_y + v3 * tau23_y + ) - + # stress tensor and temperature gradient terms from z-direction + mu_ * ( + q_zz + v1_z * tau13 + v2_z * tau23 + v3_z * tau33 + + v1 * tau13_z + v2 * tau23_z + v3 * tau33_z + ) + ) return SVector(du1, du2, du3, du4, du5) end @@ -221,12 +247,18 @@ end initial_condition = initial_condition_navier_stokes_convergence_test # BC types -velocity_bc_top_bottom = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2:4]) +velocity_bc_top_bottom = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2:4] +) heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, - heat_bc_top_bottom) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall( + velocity_bc_top_bottom, + heat_bc_top_bottom +) # define inviscid boundary conditions boundary_conditions = (; :top_bottom => boundary_condition_slip_wall) @@ -234,11 +266,15 @@ boundary_conditions = (; :top_bottom => boundary_condition_slip_wall) # define viscous boundary conditions boundary_conditions_parabolic = (; :top_bottom => boundary_condition_top_bottom) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, dg; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -256,7 +292,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_3d/elixir_navierstokes_convergence_curved.jl b/examples/dgmulti_3d/elixir_navierstokes_convergence_curved.jl index c58d78d258..8907fc84c7 100644 --- a/examples/dgmulti_3d/elixir_navierstokes_convergence_curved.jl +++ b/examples/dgmulti_3d/elixir_navierstokes_convergence_curved.jl @@ -8,14 +8,18 @@ prandtl_number() = 0.72 mu() = 0.01 equations = CompressibleEulerEquations3D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion3D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -dg = DGMulti(polydeg = 3, element_type = Hex(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralWeakForm()) +dg = DGMulti( + polydeg = 3, element_type = Hex(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralWeakForm() +) top_bottom(x, tol = 50 * eps()) = abs(abs(x[2]) - 1) < tol is_on_boundary = Dict(:top_bottom => top_bottom) @@ -27,8 +31,10 @@ function mapping(xi, eta, zeta) return SVector(x, y, z) end cells_per_dimension = (8, 8, 8) -mesh = DGMultiMesh(dg, cells_per_dimension, mapping; periodicity = (true, false, true), - is_on_boundary) +mesh = DGMultiMesh( + dg, cells_per_dimension, mapping; periodicity = (true, false, true), + is_on_boundary +) # This initial condition is taken from `examples/dgmulti_3d/elixir_navierstokes_convergence.jl` @@ -51,7 +57,7 @@ function initial_condition_navier_stokes_convergence_test(x, t, equations) rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) v1 = A2 * sin(pi_x) * log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) * sin(pi_z) * - cos(pi_t) + cos(pi_t) v2 = v1 v3 = v1 p = rho^2 @@ -82,11 +88,15 @@ end # Define auxiliary functions for the strange function of the y variable # to make expressions easier to read g = log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) - g_y = (A3 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + - (1.0 - exp(-A3 * (x[2] - 1.0))) / (x[2] + 2.0)) - g_yy = (2.0 * A3 * exp(-A3 * (x[2] - 1.0)) / (x[2] + 2.0) - + g_y = ( + A3 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + + (1.0 - exp(-A3 * (x[2] - 1.0))) / (x[2] + 2.0) + ) + g_yy = ( + 2.0 * A3 * exp(-A3 * (x[2] - 1.0)) / (x[2] + 2.0) - (1.0 - exp(-A3 * (x[2] - 1.0))) / ((x[2] + 2.0)^2) - - A3^2 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0))) + A3^2 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + ) # Density and its derivatives rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) @@ -176,52 +186,68 @@ end # Compute the source terms # Density equation - du1 = (rho_t + rho_x * v1 + rho * v1_x - + rho_y * v2 + rho * v2_y - + rho_z * v3 + rho * v3_z) + du1 = ( + rho_t + rho_x * v1 + rho * v1_x + + rho_y * v2 + rho * v2_y + + rho_z * v3 + rho * v3_z + ) # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 - + 2.0 * rho * v1 * v1_x - + rho_y * v1 * v2 - + rho * v1_y * v2 - + rho * v1 * v2_y - + rho_z * v1 * v3 - + rho * v1_z * v3 - + rho * v1 * v3_z - - mu_ * (tau11_x + tau12_y + tau13_z)) + du2 = ( + rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y + + rho_z * v1 * v3 + + rho * v1_z * v3 + + rho * v1 * v3_z - + mu_ * (tau11_x + tau12_y + tau13_z) + ) # y-momentum equation - du3 = (rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 - + rho * v1_x * v2 - + rho * v1 * v2_x - + rho_y * v2^2 - + 2.0 * rho * v2 * v2_y - + rho_z * v2 * v3 - + rho * v2_z * v3 - + rho * v2 * v3_z - - mu_ * (tau12_x + tau22_y + tau23_z)) + du3 = ( + rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y + + rho_z * v2 * v3 + + rho * v2_z * v3 + + rho * v2 * v3_z - + mu_ * (tau12_x + tau22_y + tau23_z) + ) # z-momentum equation - du4 = (rho_t * v3 + rho * v3_t + p_z + rho_x * v1 * v3 - + rho * v1_x * v3 - + rho * v1 * v3_x - + rho_y * v2 * v3 - + rho * v2_y * v3 - + rho * v2 * v3_y - + rho_z * v3^2 - + 2.0 * rho * v3 * v3_z - - mu_ * (tau13_x + tau23_y + tau33_z)) + du4 = ( + rho_t * v3 + rho * v3_t + p_z + rho_x * v1 * v3 + + rho * v1_x * v3 + + rho * v1 * v3_x + + rho_y * v2 * v3 + + rho * v2_y * v3 + + rho * v2 * v3_y + + rho_z * v3^2 + + 2.0 * rho * v3 * v3_z - + mu_ * (tau13_x + tau23_y + tau33_z) + ) # Total energy equation - du5 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + v2_y * (E + p) + v2 * (E_y + p_y) - + v3_z * (E + p) + v3 * (E_z + p_z) - - # stress tensor and temperature gradient from x-direction - mu_ * (q_xx + v1_x * tau11 + v2_x * tau12 + v3_x * tau13 - + v1 * tau11_x + v2 * tau12_x + v3 * tau13_x) - - # stress tensor and temperature gradient terms from y-direction - mu_ * (q_yy + v1_y * tau12 + v2_y * tau22 + v3_y * tau23 - + v1 * tau12_y + v2 * tau22_y + v3 * tau23_y) - - # stress tensor and temperature gradient terms from z-direction - mu_ * (q_zz + v1_z * tau13 + v2_z * tau23 + v3_z * tau33 - + v1 * tau13_z + v2 * tau23_z + v3 * tau33_z)) + du5 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) + + v3_z * (E + p) + v3 * (E_z + p_z) - + # stress tensor and temperature gradient from x-direction + mu_ * ( + q_xx + v1_x * tau11 + v2_x * tau12 + v3_x * tau13 + + v1 * tau11_x + v2 * tau12_x + v3 * tau13_x + ) - + # stress tensor and temperature gradient terms from y-direction + mu_ * ( + q_yy + v1_y * tau12 + v2_y * tau22 + v3_y * tau23 + + v1 * tau12_y + v2 * tau22_y + v3 * tau23_y + ) - + # stress tensor and temperature gradient terms from z-direction + mu_ * ( + q_zz + v1_z * tau13 + v2_z * tau23 + v3_z * tau33 + + v1 * tau13_z + v2 * tau23_z + v3 * tau33_z + ) + ) return SVector(du1, du2, du3, du4, du5) end @@ -229,12 +255,18 @@ end initial_condition = initial_condition_navier_stokes_convergence_test # BC types -velocity_bc_top_bottom = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2:4]) +velocity_bc_top_bottom = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2:4] +) heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, - heat_bc_top_bottom) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall( + velocity_bc_top_bottom, + heat_bc_top_bottom +) # define inviscid boundary conditions boundary_conditions = (; :top_bottom => boundary_condition_slip_wall) @@ -242,11 +274,15 @@ boundary_conditions = (; :top_bottom => boundary_condition_slip_wall) # define viscous boundary conditions boundary_conditions_parabolic = (; :top_bottom => boundary_condition_top_bottom) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, dg; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -264,7 +300,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/dgmulti_3d/elixir_navierstokes_taylor_green_vortex.jl b/examples/dgmulti_3d/elixir_navierstokes_taylor_green_vortex.jl index 9ae90ac47b..f3555583a8 100644 --- a/examples/dgmulti_3d/elixir_navierstokes_taylor_green_vortex.jl +++ b/examples/dgmulti_3d/elixir_navierstokes_taylor_green_vortex.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,16 +8,20 @@ prandtl_number() = 0.72 mu = 6.25e-4 # equivalent to Re = 1600 equations = CompressibleEulerEquations3D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion3D( + equations, mu = mu, + Prandtl = prandtl_number() +) """ initial_condition_taylor_green_vortex(x, t, equations::CompressibleEulerEquations3D) The classical inviscid Taylor-Green vortex. """ -function initial_condition_taylor_green_vortex(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_taylor_green_vortex( + x, t, + equations::CompressibleEulerEquations3D + ) A = 1.0 # magnitude of speed Ms = 0.1 # maximum Mach number @@ -29,27 +32,35 @@ function initial_condition_taylor_green_vortex(x, t, p = (A / Ms)^2 * rho / equations.gamma # scaling to get Ms p = p + 1.0 / 16.0 * A^2 * rho * - (cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + - cos(2 * x[2]) * cos(2 * x[3])) + ( + cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + + cos(2 * x[2]) * cos(2 * x[3]) + ) return prim2cons(SVector(rho, v1, v2, v3, p), equations) end initial_condition = initial_condition_taylor_green_vortex # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -dg = DGMulti(polydeg = 3, element_type = Hex(), approximation_type = GaussSBP(), - surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +dg = DGMulti( + polydeg = 3, element_type = Hex(), approximation_type = GaussSBP(), + surface_integral = SurfaceIntegralWeakForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) coordinates_min = (-1.0, -1.0, -1.0) .* pi coordinates_max = (1.0, 1.0, 1.0) .* pi cells_per_dimension = (8, 8, 8) -mesh = DGMultiMesh(dg, cells_per_dimension; - coordinates_min, coordinates_max, - periodicity = (true, true, true)) +mesh = DGMultiMesh( + dg, cells_per_dimension; + coordinates_min, coordinates_max, + periodicity = (true, true, true) +) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, dg) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, dg +) ############################################################################### # ODE solvers, callbacks etc. @@ -60,16 +71,22 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() alive_callback = AliveCallback(alive_interval = 10) analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, uEltype = real(dg), - extra_analysis_integrals = (energy_kinetic, - energy_internal, - enstrophy)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, uEltype = real(dg), + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + enstrophy, + ) +) callbacks = CallbackSet(summary_callback, alive_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_advection_amr_solution_independent.jl b/examples/p4est_2d_dgsem/elixir_advection_amr_solution_independent.jl index a1ddc6a314..fecd02c635 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_amr_solution_independent.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_amr_solution_independent.jl @@ -1,80 +1,87 @@ - using OrdinaryDiffEq using Trixi # define new structs inside a module to allow re-evaluating the file module TrixiExtension -using Trixi + using Trixi + + struct IndicatorSolutionIndependent{Cache <: NamedTuple} <: Trixi.AbstractIndicator + cache::Cache + end + + function IndicatorSolutionIndependent(semi) + basis = semi.solver.basis + alpha = Vector{real(basis)}() + cache = (; semi.mesh, alpha) + return IndicatorSolutionIndependent{typeof(cache)}(cache) + end -struct IndicatorSolutionIndependent{Cache <: NamedTuple} <: Trixi.AbstractIndicator - cache::Cache -end - -function IndicatorSolutionIndependent(semi) - basis = semi.solver.basis - alpha = Vector{real(basis)}() - cache = (; semi.mesh, alpha) - return IndicatorSolutionIndependent{typeof(cache)}(cache) -end - -function (indicator::IndicatorSolutionIndependent)(u::AbstractArray{<:Any, 4}, - mesh, equations, dg, cache; - t, kwargs...) - mesh = indicator.cache.mesh - alpha = indicator.cache.alpha - resize!(alpha, nelements(dg, cache)) - - #Predict the theoretical center. - advection_velocity = (0.2, -0.7) - center = t .* advection_velocity - - inner_distance = 1 - outer_distance = 1.85 - - #Iterate over all elements - for element in eachindex(alpha) - # Calculate periodic distance between cell and center. - # This requires an uncurved mesh! - coordinates = SVector(0.5 * (cache.elements.node_coordinates[1, 1, 1, element] + - cache.elements.node_coordinates[1, end, 1, element]), - 0.5 * (cache.elements.node_coordinates[2, 1, 1, element] + - cache.elements.node_coordinates[2, 1, end, element])) - - #The geometric shape of the amr should be preserved when the base_level is increased. - #This is done by looking at the original coordinates of each cell. - cell_coordinates = original_coordinates(coordinates, 5 / 8) - cell_distance = periodic_distance_2d(cell_coordinates, center, 10) - if cell_distance < (inner_distance + outer_distance) / 2 - cell_coordinates = original_coordinates(coordinates, 5 / 16) + function (indicator::IndicatorSolutionIndependent)( + u::AbstractArray{<:Any, 4}, + mesh, equations, dg, cache; + t, kwargs... + ) + mesh = indicator.cache.mesh + alpha = indicator.cache.alpha + resize!(alpha, nelements(dg, cache)) + + #Predict the theoretical center. + advection_velocity = (0.2, -0.7) + center = t .* advection_velocity + + inner_distance = 1 + outer_distance = 1.85 + + #Iterate over all elements + for element in eachindex(alpha) + # Calculate periodic distance between cell and center. + # This requires an uncurved mesh! + coordinates = SVector( + 0.5 * ( + cache.elements.node_coordinates[1, 1, 1, element] + + cache.elements.node_coordinates[1, end, 1, element] + ), + 0.5 * ( + cache.elements.node_coordinates[2, 1, 1, element] + + cache.elements.node_coordinates[2, 1, end, element] + ) + ) + + #The geometric shape of the amr should be preserved when the base_level is increased. + #This is done by looking at the original coordinates of each cell. + cell_coordinates = original_coordinates(coordinates, 5 / 8) cell_distance = periodic_distance_2d(cell_coordinates, center, 10) + if cell_distance < (inner_distance + outer_distance) / 2 + cell_coordinates = original_coordinates(coordinates, 5 / 16) + cell_distance = periodic_distance_2d(cell_coordinates, center, 10) + end + + #Set alpha according to cells position inside the circles. + target_level = (cell_distance < inner_distance) + (cell_distance < outer_distance) + alpha[element] = target_level / 2 end + return alpha + end + + # For periodic domains, distance between two points must take into account + # periodic extensions of the domain + function periodic_distance_2d(coordinates, center, domain_length) + dx = coordinates .- center + dx_shifted = abs.(dx .% domain_length) + dx_periodic = min.(dx_shifted, domain_length .- dx_shifted) + return sqrt(sum(dx_periodic .^ 2)) + end - #Set alpha according to cells position inside the circles. - target_level = (cell_distance < inner_distance) + (cell_distance < outer_distance) - alpha[element] = target_level / 2 + #This takes a cells coordinates and transforms them into the coordinates of a parent-cell it originally refined from. + #It does it so that the parent-cell has given cell_length. + function original_coordinates(coordinates, cell_length) + offset = coordinates .% cell_length + offset_sign = sign.(offset) + border = coordinates - offset + center = border + (offset_sign .* cell_length / 2) + return center end - return alpha -end - -# For periodic domains, distance between two points must take into account -# periodic extensions of the domain -function periodic_distance_2d(coordinates, center, domain_length) - dx = coordinates .- center - dx_shifted = abs.(dx .% domain_length) - dx_periodic = min.(dx_shifted, domain_length .- dx_shifted) - return sqrt(sum(dx_periodic .^ 2)) -end - -#This takes a cells coordinates and transforms them into the coordinates of a parent-cell it originally refined from. -#It does it so that the parent-cell has given cell_length. -function original_coordinates(coordinates, cell_length) - offset = coordinates .% cell_length - offset_sign = sign.(offset) - border = coordinates - offset - center = border + (offset_sign .* cell_length / 2) - return center -end end # module TrixiExtension @@ -94,9 +101,11 @@ coordinates_max = (5.0, 5.0) trees_per_dimension = (1, 1) -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 4) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 4 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -109,38 +118,50 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, - TrixiExtension.IndicatorSolutionIndependent(semi), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, + TrixiExtension.IndicatorSolutionIndependent(semi), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_advection_amr_unstructured_flag.jl b/examples/p4est_2d_dgsem/elixir_advection_amr_unstructured_flag.jl index 4bfb2d3e37..132285d539 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_amr_unstructured_flag.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_amr_unstructured_flag.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -30,15 +29,21 @@ Trixi.validate_faces(faces) mapping_flag = Trixi.transfinite_mapping(faces) # Unstructured mesh with 24 cells of the square domain [-1, 1]^n -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", - joinpath(@__DIR__, "square_unstructured_2.inp")) - -mesh = P4estMesh{2}(mesh_file, polydeg = 3, - mapping = mapping_flag, - initial_refinement_level = 1) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + joinpath(@__DIR__, "square_unstructured_2.inp") +) + +mesh = P4estMesh{2}( + mesh_file, polydeg = 3, + mapping = mapping_flag, + initial_refinement_level = 1 +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -49,40 +54,54 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 1, - med_level = 2, med_threshold = 0.1, - max_level = 3, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) + +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 1, + med_level = 2, med_threshold = 0.1, + max_level = 3, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_advection_basic.jl b/examples/p4est_2d_dgsem/elixir_advection_basic.jl index ed235bf839..ea2c9a52c6 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_basic.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_basic.jl @@ -19,13 +19,17 @@ coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) trees_per_dimension = (8, 8) # Create P4estMesh with 8 x 8 trees and 16 x 16 elements -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 1) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 1 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -41,23 +45,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_advection_diffusion_nonperiodic_amr.jl b/examples/p4est_2d_dgsem/elixir_advection_diffusion_nonperiodic_amr.jl index f87c0e056c..afdfb69c56 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_diffusion_nonperiodic_amr.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_diffusion_nonperiodic_amr.jl @@ -16,10 +16,12 @@ coordinates_min = (-1.0, -0.5) # minimum coordinates (min(x), min(y)) coordinates_max = (0.0, 0.5) # maximum coordinates (max(x), max(y)) trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = false) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = false +) # Example setup taken from # - Truman Ellis, Jesse Chan, and Leszek Demkowicz (2016). @@ -40,22 +42,30 @@ function initial_condition_eriksson_johnson(x, t, equations) end initial_condition = initial_condition_eriksson_johnson -boundary_conditions = Dict(:x_neg => BoundaryConditionDirichlet(initial_condition), - :y_neg => BoundaryConditionDirichlet(initial_condition), - :y_pos => BoundaryConditionDirichlet(initial_condition), - :x_pos => boundary_condition_do_nothing) +boundary_conditions = Dict( + :x_neg => BoundaryConditionDirichlet(initial_condition), + :y_neg => BoundaryConditionDirichlet(initial_condition), + :y_pos => BoundaryConditionDirichlet(initial_condition), + :x_pos => boundary_condition_do_nothing +) -boundary_conditions_parabolic = Dict(:x_neg => BoundaryConditionDirichlet(initial_condition), - :x_pos => BoundaryConditionDirichlet(initial_condition), - :y_neg => BoundaryConditionDirichlet(initial_condition), - :y_pos => BoundaryConditionDirichlet(initial_condition)) +boundary_conditions_parabolic = Dict( + :x_neg => BoundaryConditionDirichlet(initial_condition), + :x_pos => BoundaryConditionDirichlet(initial_condition), + :y_neg => BoundaryConditionDirichlet(initial_condition), + :y_pos => BoundaryConditionDirichlet(initial_condition) +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -75,13 +85,17 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) # The AliveCallback prints short status information in regular intervals alive_callback = AliveCallback(analysis_interval = analysis_interval) -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 1, - med_level = 2, med_threshold = 0.9, - max_level = 3, max_threshold = 1.0) +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 1, + med_level = 2, med_threshold = 0.9, + max_level = 3, max_threshold = 1.0 +) -amr_callback = AMRCallback(semi, amr_controller, - interval = 50) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 50 +) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, amr_callback) @@ -91,8 +105,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, amr # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks time_int_tol = 1.0e-11 -sol = solve(ode, dt = 1e-7, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, dt = 1.0e-7, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_advection_diffusion_nonperiodic_curved.jl b/examples/p4est_2d_dgsem/elixir_advection_diffusion_nonperiodic_curved.jl index 5497f13aa6..218d7fbbf2 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_diffusion_nonperiodic_curved.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_diffusion_nonperiodic_curved.jl @@ -28,15 +28,19 @@ function initial_condition_eriksson_johnson(x, t, equations) end initial_condition = initial_condition_eriksson_johnson -boundary_conditions = Dict(:x_neg => BoundaryConditionDirichlet(initial_condition), - :y_neg => BoundaryConditionDirichlet(initial_condition), - :y_pos => BoundaryConditionDirichlet(initial_condition), - :x_pos => boundary_condition_do_nothing) - -boundary_conditions_parabolic = Dict(:x_neg => BoundaryConditionDirichlet(initial_condition), - :x_pos => BoundaryConditionDirichlet(initial_condition), - :y_neg => BoundaryConditionDirichlet(initial_condition), - :y_pos => BoundaryConditionDirichlet(initial_condition)) +boundary_conditions = Dict( + :x_neg => BoundaryConditionDirichlet(initial_condition), + :y_neg => BoundaryConditionDirichlet(initial_condition), + :y_pos => BoundaryConditionDirichlet(initial_condition), + :x_pos => boundary_condition_do_nothing +) + +boundary_conditions_parabolic = Dict( + :x_neg => BoundaryConditionDirichlet(initial_condition), + :x_pos => BoundaryConditionDirichlet(initial_condition), + :y_neg => BoundaryConditionDirichlet(initial_condition), + :y_pos => BoundaryConditionDirichlet(initial_condition) +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -44,8 +48,8 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-1.0, -0.5) coordinates_max = (0.0, 0.5) -# This maps the domain [-1, 1]^2 to [-1, 0] x [-0.5, 0.5] while also -# introducing a curved warping to interior nodes. +# This maps the domain [-1, 1]^2 to [-1, 0] x [-0.5, 0.5] while also +# introducing a curved warping to interior nodes. function mapping(xi, eta) x = xi + 0.1 * sin(pi * xi) * sin(pi * eta) y = eta + 0.1 * sin(pi * xi) * sin(pi * eta) @@ -53,15 +57,21 @@ function mapping(xi, eta) end trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 2, - mapping = mapping, periodicity = (false, false)) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 2, + mapping = mapping, periodicity = (false, false) +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver, - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver, + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -89,8 +99,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks time_int_tol = 1.0e-11 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic.jl b/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic.jl index 0b5129e3c0..685420effc 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic.jl @@ -13,13 +13,15 @@ function x_trans_periodic(x, domain_length = SVector(2 * pi), center = SVector(0 x_normalized = x .- center x_shifted = x_normalized .% domain_length x_offset = ((x_shifted .< -0.5 * domain_length) - (x_shifted .> 0.5 * domain_length)) .* - domain_length + domain_length return center + x_shifted + x_offset end # Define initial condition (copied from "examples/tree_1d_dgsem/elixir_advection_diffusion.jl") -function initial_condition_diffusive_convergence_test(x, t, - equation::LinearScalarAdvectionEquation2D) +function initial_condition_diffusive_convergence_test( + x, t, + equation::LinearScalarAdvectionEquation2D + ) # Store translated coordinate for easy use of exact solution # Assumes that advection_velocity[2] = 0 (effectively that we are solving a 1D equation) x_trans = x_trans_periodic(x[1] - equation.advection_velocity[1] * t) @@ -40,15 +42,19 @@ coordinates_min = (-pi, -pi) # minimum coordinates (min(x), min(y)) coordinates_max = (pi, pi) # maximum coordinates (max(x), max(y)) trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = true +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -76,8 +82,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks time_int_tol = 1.0e-11 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic_amr.jl b/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic_amr.jl index fdecb05f7b..70d9fd8fc1 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic_amr.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic_amr.jl @@ -16,13 +16,17 @@ coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 1, - coordinates_min = coordinates_min, coordinates_max = coordinates_max) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 1, + coordinates_min = coordinates_min, coordinates_max = coordinates_max +) # Define initial condition -function initial_condition_diffusive_convergence_test(x, t, - equation::LinearScalarAdvectionEquation2D) +function initial_condition_diffusive_convergence_test( + x, t, + equation::LinearScalarAdvectionEquation2D + ) # Store translated coordinate for easy use of exact solution x_trans = x - equation.advection_velocity * t @@ -38,9 +42,11 @@ end initial_condition = initial_condition_diffusive_convergence_test # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -60,13 +66,17 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) # The AliveCallback prints short status information in regular intervals alive_callback = AliveCallback(analysis_interval = analysis_interval) -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 1, - med_level = 2, med_threshold = 1.25, - max_level = 3, max_threshold = 1.45) +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 1, + med_level = 2, med_threshold = 1.25, + max_level = 3, max_threshold = 1.45 +) -amr_callback = AMRCallback(semi, amr_controller, - interval = 20) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 20 +) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, amr_callback) @@ -76,8 +86,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, amr # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks time_int_tol = 1.0e-11 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) -# Print the timer summary +# Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic_curved.jl b/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic_curved.jl index 130def3799..64633c1ada 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic_curved.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_diffusion_periodic_curved.jl @@ -13,13 +13,15 @@ function x_trans_periodic(x, domain_length = SVector(2 * pi), center = SVector(0 x_normalized = x .- center x_shifted = x_normalized .% domain_length x_offset = ((x_shifted .< -0.5 * domain_length) - (x_shifted .> 0.5 * domain_length)) .* - domain_length + domain_length return center + x_shifted + x_offset end # Define initial condition (copied from "examples/tree_1d_dgsem/elixir_advection_diffusion.jl") -function initial_condition_diffusive_convergence_test(x, t, - equation::LinearScalarAdvectionEquation2D) +function initial_condition_diffusive_convergence_test( + x, t, + equation::LinearScalarAdvectionEquation2D + ) # Store translated coordinate for easy use of exact solution # Assumes that advection_velocity[2] = 0 (effectively that we are solving a 1D equation) x_trans = x_trans_periodic(x[1] - equation.advection_velocity[1] * t) @@ -36,8 +38,8 @@ initial_condition = initial_condition_diffusive_convergence_test # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) -# This maps the domain [-1, 1]^2 to [-pi, pi]^2 while also -# introducing a curved warping to interior nodes. +# This maps the domain [-1, 1]^2 to [-pi, pi]^2 while also +# introducing a curved warping to interior nodes. function mapping(xi, eta) x = xi + 0.1 * sin(pi * xi) * sin(pi * eta) y = eta + 0.1 * sin(pi * xi) * sin(pi * eta) @@ -45,15 +47,19 @@ function mapping(xi, eta) end trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 2, - mapping = mapping, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 2, + mapping = mapping, + periodicity = true +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -81,8 +87,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks time_int_tol = 1.0e-11 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_advection_extended.jl b/examples/p4est_2d_dgsem/elixir_advection_extended.jl index 66a5b7e0f5..1663859f19 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_extended.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_extended.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,10 +11,12 @@ initial_condition = initial_condition_convergence_test # BCs must be passed as Dict boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:x_neg => boundary_condition, - :x_pos => boundary_condition, - :y_neg => boundary_condition, - :y_pos => boundary_condition) +boundary_conditions = Dict( + :x_neg => boundary_condition, + :x_pos => boundary_condition, + :y_neg => boundary_condition, + :y_pos => boundary_condition +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -27,13 +28,17 @@ coordinates_max = (0.5, 5.3) # maximum coordinates (max(x), max(y)) trees_per_dimension = (19, 37) # Create curved mesh with 19 x 37 elements -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = false) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = false +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -48,38 +53,48 @@ summary_callback = SummaryCallback() # The AnalysisCallback allows to analyse the solution in regular intervals and prints the results analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) # The AliveCallback prints short status information in regular intervals alive_callback = AliveCallback(analysis_interval = analysis_interval) # The SaveRestartCallback allows to save a file from which a Trixi.jl simulation can be restarted -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_advection_nonconforming_flag.jl b/examples/p4est_2d_dgsem/elixir_advection_nonconforming_flag.jl index b47b0d6119..e3a88da69a 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_nonconforming_flag.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_nonconforming_flag.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -21,9 +20,11 @@ f4(s) = SVector(s, 1.0 + sin(0.5 * pi * s)) # Create P4estMesh with 3 x 2 trees and 6 x 4 elements, # approximate the geometry with a smaller polydeg for testing. trees_per_dimension = (3, 2) -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - faces = (f1, f2, f3, f4), - initial_refinement_level = 1) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + faces = (f1, f2, f3, f4), + initial_refinement_level = 1 +) # Refine bottom left quadrant of each tree to level 4 function refine_fn(p4est, which_tree, quadrant) @@ -39,14 +40,20 @@ end # Refine recursively until each bottom left quadrant of a tree has level 4 # The mesh will be rebalanced before the simulation starts -refine_fn_c = @cfunction(refine_fn, Cint, - (Ptr{Trixi.p4est_t}, Ptr{Trixi.p4est_topidx_t}, - Ptr{Trixi.p4est_quadrant_t})) +refine_fn_c = @cfunction( + refine_fn, Cint, + ( + Ptr{Trixi.p4est_t}, Ptr{Trixi.p4est_topidx_t}, + Ptr{Trixi.p4est_quadrant_t}, + ) +) Trixi.refine_p4est!(mesh.p4est, true, refine_fn_c, C_NULL) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -62,23 +69,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_advection_restart.jl b/examples/p4est_2d_dgsem/elixir_advection_restart.jl index 6fb14c6003..7a1b4a1d22 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_restart.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_restart.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -19,8 +18,10 @@ trixi_include(@__MODULE__, joinpath(@__DIR__, elixir_file)) restart_filename = joinpath("out", restart_file) mesh = load_mesh(restart_filename) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) tspan = (load_time(restart_filename), 2.0) dt = load_dt(restart_filename) @@ -29,9 +30,11 @@ ode = semidiscretize(semi, tspan, restart_filename); # Do not overwrite the initial snapshot written by elixir_advection_extended.jl. save_solution.condition.save_initial_solution = false -integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks, maxiters = 100_000); +integrator = init( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks, maxiters = 100_000 +); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/p4est_2d_dgsem/elixir_advection_restart_amr.jl b/examples/p4est_2d_dgsem/elixir_advection_restart_amr.jl index 6b68347493..bae3dab2db 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_restart_amr.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_restart_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -19,8 +18,10 @@ trixi_include(@__MODULE__, joinpath(@__DIR__, elixir_file)) restart_filename = joinpath("out", restart_file) mesh = load_mesh(restart_filename) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) tspan = (load_time(restart_filename), 2.0) dt = load_dt(restart_filename) @@ -30,19 +31,25 @@ ode = semidiscretize(semi, tspan, restart_filename); save_solution.condition.save_initial_solution = false # Add AMR callback -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 0, - med_level = 0, med_threshold = 0.8, - max_level = 1, max_threshold = 1.2) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 0, + med_level = 0, med_threshold = 0.8, + max_level = 1, max_threshold = 1.2 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) callbacks_ext = CallbackSet(amr_callback, callbacks.discrete_callbacks...) -integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks_ext, maxiters = 100_000); +integrator = init( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks_ext, maxiters = 100_000 +); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/p4est_2d_dgsem/elixir_advection_unstructured_flag.jl b/examples/p4est_2d_dgsem/elixir_advection_unstructured_flag.jl index 1ab96925fe..77cfe9d066 100644 --- a/examples/p4est_2d_dgsem/elixir_advection_unstructured_flag.jl +++ b/examples/p4est_2d_dgsem/elixir_advection_unstructured_flag.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -28,16 +27,22 @@ Trixi.validate_faces(faces) mapping_flag = Trixi.transfinite_mapping(faces) # Unstructured mesh with 24 cells of the square domain [-1, 1]^n -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", - joinpath(@__DIR__, "square_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + joinpath(@__DIR__, "square_unstructured_2.inp") +) -mesh = P4estMesh{2}(mesh_file, polydeg = 3, - mapping = mapping_flag, - initial_refinement_level = 2) +mesh = P4estMesh{2}( + mesh_file, polydeg = 3, + mapping = mapping_flag, + initial_refinement_level = 2 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -54,23 +59,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.4) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_euler_NACA0012airfoil_mach085.jl b/examples/p4est_2d_dgsem/elixir_euler_NACA0012airfoil_mach085.jl index 45b750728c..1e5c544188 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_NACA0012airfoil_mach085.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_NACA0012airfoil_mach085.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,8 +13,10 @@ aoa() = pi / 180.0 # 1 Degree angle of attack c_inf(equations) = sqrt(equations.gamma * p_inf() / rho_inf()) u_inf(equations) = mach_inf() * c_inf(equations) -@inline function initial_condition_mach085_flow(x, t, - equations::CompressibleEulerEquations2D) +@inline function initial_condition_mach085_flow( + x, t, + equations::CompressibleEulerEquations2D + ) v1 = u_inf(equations) * cos(aoa()) v2 = u_inf(equations) * sin(aoa()) @@ -30,44 +31,58 @@ surface_flux = flux_lax_friedrichs polydeg = 3 basis = LobattoLegendreBasis(polydeg) -shock_indicator = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(shock_indicator; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) - -mesh_file = Trixi.download("https://gist.githubusercontent.com/Arpit-Babbar/339662b4b46164a016e35c81c66383bb/raw/8bf94f5b426ba907ace87405cfcc1dcc2ef7cbda/NACA0012.inp", - joinpath(@__DIR__, "NACA0012.inp")) +shock_indicator = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + shock_indicator; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) + +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/Arpit-Babbar/339662b4b46164a016e35c81c66383bb/raw/8bf94f5b426ba907ace87405cfcc1dcc2ef7cbda/NACA0012.inp", + joinpath(@__DIR__, "NACA0012.inp") +) mesh = P4estMesh{2}(mesh_file) # The outer boundary is constant but subsonic, so we cannot compute the # boundary flux for the external information alone. Thus, we use the numerical flux to distinguish # between inflow and outflow characteristics -@inline function boundary_condition_subsonic_constant(u_inner, - normal_direction::AbstractVector, x, - t, - surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_subsonic_constant( + u_inner, + normal_direction::AbstractVector, x, + t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) u_boundary = initial_condition_mach085_flow(x, t, equations) return Trixi.flux_hll(u_inner, u_boundary, normal_direction, equations) end -boundary_conditions = Dict(:Left => boundary_condition_subsonic_constant, - :Right => boundary_condition_subsonic_constant, - :Top => boundary_condition_subsonic_constant, - :Bottom => boundary_condition_subsonic_constant, - :AirfoilBottom => boundary_condition_slip_wall, - :AirfoilTop => boundary_condition_slip_wall) +boundary_conditions = Dict( + :Left => boundary_condition_subsonic_constant, + :Right => boundary_condition_subsonic_constant, + :Top => boundary_condition_subsonic_constant, + :Bottom => boundary_condition_subsonic_constant, + :AirfoilBottom => boundary_condition_slip_wall, + :AirfoilTop => boundary_condition_slip_wall +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers @@ -85,48 +100,70 @@ analysis_interval = 2000 l_inf = 1.0 # Length of airfoil force_boundary_names = (:AirfoilBottom, :AirfoilTop) -drag_coefficient = AnalysisSurfaceIntegral(semi, force_boundary_names, - DragCoefficientPressure(aoa(), rho_inf(), - u_inf(equations), l_inf)) - -lift_coefficient = AnalysisSurfaceIntegral(semi, force_boundary_names, - LiftCoefficientPressure(aoa(), rho_inf(), - u_inf(equations), l_inf)) - -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - output_directory = "out", - save_analysis = true, - analysis_integrals = (drag_coefficient, - lift_coefficient)) +drag_coefficient = AnalysisSurfaceIntegral( + semi, force_boundary_names, + DragCoefficientPressure( + aoa(), rho_inf(), + u_inf(equations), l_inf + ) +) + +lift_coefficient = AnalysisSurfaceIntegral( + semi, force_boundary_names, + LiftCoefficientPressure( + aoa(), rho_inf(), + u_inf(equations), l_inf + ) +) + +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + output_directory = "out", + save_analysis = true, + analysis_integrals = ( + drag_coefficient, + lift_coefficient, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 500, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 500, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 1, - med_level = 3, med_threshold = 0.05, - max_level = 4, max_threshold = 0.1) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 1, + med_level = 3, med_threshold = 0.05, + max_level = 4, max_threshold = 0.1 +) amr_interval = 100 -amr_callback = AMRCallback(semi, amr_controller, - interval = amr_interval, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback, amr_callback) +amr_callback = AMRCallback( + semi, amr_controller, + interval = amr_interval, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback, amr_callback +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK54(thread = OrdinaryDiffEq.True()), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, SSPRK54(thread = OrdinaryDiffEq.True()), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_NACA6412airfoil_mach2.jl b/examples/p4est_2d_dgsem/elixir_euler_NACA6412airfoil_mach2.jl index 573d182e15..28e011d412 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_NACA6412airfoil_mach2.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_NACA6412airfoil_mach2.jl @@ -1,4 +1,3 @@ - using Trixi using OrdinaryDiffEq @@ -23,10 +22,12 @@ initial_condition = initial_condition_mach2_flow # Supersonic inflow boundary condition. # Calculate the boundary flux entirely from the external solution state, i.e., set # external solution state values for everything entering the domain. -@inline function boundary_condition_supersonic_inflow(u_inner, - normal_direction::AbstractVector, - x, t, surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_supersonic_inflow( + u_inner, + normal_direction::AbstractVector, + x, t, surface_flux_function, + equations::CompressibleEulerEquations2D + ) u_boundary = initial_condition_mach2_flow(x, t, equations) flux = Trixi.flux(u_boundary, normal_direction, equations) @@ -36,11 +37,13 @@ end # Supersonic outflow boundary condition. # Calculate the boundary flux entirely from the internal solution state. Analogous to supersonic inflow # except all the solution state values are set from the internal solution as everything leaves the domain -@inline function boundary_condition_supersonic_outflow(u_inner, - normal_direction::AbstractVector, x, - t, - surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_supersonic_outflow( + u_inner, + normal_direction::AbstractVector, x, + t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) flux = Trixi.flux(u_inner, normal_direction, equations) return flux @@ -52,37 +55,49 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(polydeg) -shock_indicator = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(shock_indicator; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +shock_indicator = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + shock_indicator; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) # DG Solver -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Mesh generated from the following gmsh geometry input file: # https://gist.githubusercontent.com/DanielDoehring/5ade6d93629f0d8c23a598812dbee2a9/raw/d2bc904fe92146eae1a36156e7f5c535dc1a80f1/NACA6412.geo mesh_file = joinpath(@__DIR__, "mesh_NACA6412.inp") isfile(mesh_file) || - Trixi.download("https://gist.githubusercontent.com/DanielDoehring/e2a389f04f1e37b33819b9637e8ee4c3/raw/4bf7607a2ce4432fdb5cb87d5e264949b11bd5d7/mesh_NACA6412.inp", - mesh_file) + Trixi.download( + "https://gist.githubusercontent.com/DanielDoehring/e2a389f04f1e37b33819b9637e8ee4c3/raw/4bf7607a2ce4432fdb5cb87d5e264949b11bd5d7/mesh_NACA6412.inp", + mesh_file +) boundary_symbols = [:PhysicalLine1, :PhysicalLine2, :PhysicalLine3, :PhysicalLine4] mesh = P4estMesh{2}(mesh_file, polydeg = polydeg, boundary_symbols = boundary_symbols) -boundary_conditions = Dict(:PhysicalLine1 => boundary_condition_supersonic_inflow, # Left boundary - :PhysicalLine2 => boundary_condition_supersonic_outflow, # Right boundary - :PhysicalLine3 => boundary_condition_supersonic_outflow, # Top and bottom boundary - :PhysicalLine4 => boundary_condition_slip_wall) # Airfoil +boundary_conditions = Dict( + :PhysicalLine1 => boundary_condition_supersonic_inflow, # Left boundary + :PhysicalLine2 => boundary_condition_supersonic_outflow, # Right boundary + :PhysicalLine3 => boundary_condition_supersonic_outflow, # Top and bottom boundary + :PhysicalLine4 => boundary_condition_slip_wall +) # Airfoil -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) tspan = (0.0, 5.0) ode = semidiscretize(semi, tspan) @@ -94,15 +109,19 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 4.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + stepsize_callback +) # Run the simulation ############################################################################### -sol = solve(ode, SSPRK104(; thread = OrdinaryDiffEq.True()); - dt = 1.0, # overwritten by the `stepsize_callback` - save_everystep = false, - callback = callbacks); +sol = solve( + ode, SSPRK104(; thread = OrdinaryDiffEq.True()); + dt = 1.0, # overwritten by the `stepsize_callback` + save_everystep = false, + callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_blast_wave_amr.jl b/examples/p4est_2d_dgsem/elixir_euler_blast_wave_amr.jl index 5db5f74a68..ddd0d82d93 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_blast_wave_amr.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_blast_wave_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -29,7 +28,7 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation rho = r > 0.5 ? 1.0 : 1.1691 v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi - p = r > 0.5 ? 1.0E-3 : 1.245 + p = r > 0.5 ? 1.0e-3 : 1.245 return prim2cons(SVector(rho, v1, v2, p), equations) end @@ -38,24 +37,32 @@ initial_condition = initial_condition_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) # Unstructured mesh with 48 cells of the square domain [-1, 1]^n -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/a075f8ec39a67fa9fad8f6f84342cbca/raw/a7206a02ed3a5d3cadacd8d9694ac154f9151db7/square_unstructured_1.inp", - joinpath(@__DIR__, "square_unstructured_1.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/a075f8ec39a67fa9fad8f6f84342cbca/raw/a7206a02ed3a5d3cadacd8d9694ac154f9151db7/square_unstructured_1.inp", + joinpath(@__DIR__, "square_unstructured_1.inp") +) mesh = P4estMesh{2}(mesh_file, polydeg = 3, initial_refinement_level = 1) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition))) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition)) +) ############################################################################### # ODE solvers, callbacks etc. @@ -70,35 +77,47 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 1, - max_level = 3, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 1, + max_level = 3, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_double_mach_amr.jl b/examples/p4est_2d_dgsem/elixir_euler_double_mach_amr.jl index fbc11e8918..961a0bb32f 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_double_mach_amr.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_double_mach_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -20,8 +19,10 @@ See Section IV c on the paper below for details. The Numerical Simulation of Two-Dimensional Fluid Flows with Strong Shocks. [DOI: 10.1016/0021-9991(84)90142-6](https://doi.org/10.1016/0021-9991(84)90142-6) """ -@inline function initial_condition_double_mach_reflection(x, t, - equations::CompressibleEulerEquations2D) +@inline function initial_condition_double_mach_reflection( + x, t, + equations::CompressibleEulerEquations2D + ) if x[1] < 1 / 6 + (x[2] + 20 * t) / sqrt(3) phi = pi / 6 sin_phi, cos_phi = sincos(phi) @@ -47,9 +48,11 @@ boundary_condition_inflow = BoundaryConditionDirichlet(initial_condition_double_ # Supersonic outflow boundary condition. Solution is taken entirely from the internal state. # See `examples/p4est_2d_dgsem/elixir_euler_forward_step_amr.jl` for complete documentation. -@inline function boundary_condition_outflow(u_inner, normal_direction::AbstractVector, x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_outflow( + u_inner, normal_direction::AbstractVector, x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) # NOTE: Only for the supersonic outflow is this strategy valid # Calculate the boundary flux entirely from the internal solution state return flux(u_inner, normal_direction, equations) @@ -57,10 +60,12 @@ end # Special mixed boundary condition type for the :Bottom of the domain. # It is Dirichlet when x < 1/6 and a slip wall when x >= 1/6 -@inline function boundary_condition_mixed_dirichlet_wall(u_inner, - normal_direction::AbstractVector, - x, t, surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_mixed_dirichlet_wall( + u_inner, + normal_direction::AbstractVector, + x, t, surface_flux_function, + equations::CompressibleEulerEquations2D + ) if x[1] < 1 / 6 # From the BoundaryConditionDirichlet # get the external value of the solution @@ -69,42 +74,56 @@ end flux = surface_flux_function(u_inner, u_boundary, normal_direction, equations) else # x[1] >= 1 / 6 # Use the free slip wall BC otherwise - flux = boundary_condition_slip_wall(u_inner, normal_direction, x, t, - surface_flux_function, equations) + flux = boundary_condition_slip_wall( + u_inner, normal_direction, x, t, + surface_flux_function, equations + ) end return flux end -boundary_conditions = Dict(:Bottom => boundary_condition_mixed_dirichlet_wall, - :Top => boundary_condition_inflow, - :Right => boundary_condition_outflow, - :Left => boundary_condition_inflow) +boundary_conditions = Dict( + :Bottom => boundary_condition_mixed_dirichlet_wall, + :Top => boundary_condition_inflow, + :Right => boundary_condition_outflow, + :Left => boundary_condition_inflow +) volume_flux = flux_ranocha surface_flux = flux_lax_friedrichs polydeg = 4 basis = LobattoLegendreBasis(polydeg) -shock_indicator = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(shock_indicator; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +shock_indicator = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + shock_indicator; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/a0806ef0d03cf5ea221af523167b6e32/raw/61ed0eb017eb432d996ed119a52fb041fe363e8c/abaqus_double_mach.inp", - joinpath(@__DIR__, "abaqus_double_mach.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/a0806ef0d03cf5ea221af523167b6e32/raw/61ed0eb017eb432d996ed119a52fb041fe363e8c/abaqus_double_mach.inp", + joinpath(@__DIR__, "abaqus_double_mach.inp") +) mesh = P4estMesh{2}(mesh_file) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -115,39 +134,53 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 0, - med_level = 3, med_threshold = 0.05, - max_level = 6, max_threshold = 0.1) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 0, + med_level = 3, med_threshold = 0.05, + max_level = 6, max_threshold = 0.1 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback +) # positivity limiter necessary for this example with strong shocks -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6), - variables = (Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (5.0e-6, 5.0e-6), + variables = (Trixi.density, pressure) +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(stage_limiter!); - ode_default_options()..., callback = callbacks); +sol = solve( + ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_forward_step_amr.jl b/examples/p4est_2d_dgsem/elixir_euler_forward_step_amr.jl index 654efd5e20..170b7f1f91 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_forward_step_amr.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_forward_step_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -43,9 +42,11 @@ boundary_condition_inflow = BoundaryConditionDirichlet(initial_condition_mach3_f # - Jan-Reneé Carlson (2011) # Inflow/Outflow Boundary Conditions with Application to FUN3D. # [NASA TM 20110022658](https://ntrs.nasa.gov/citations/20110022658) -@inline function boundary_condition_outflow(u_inner, normal_direction::AbstractVector, x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_outflow( + u_inner, normal_direction::AbstractVector, x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) # # This would be for the general case where we need to check the magnitude of the local Mach number # norm_ = norm(normal_direction) # # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later @@ -79,37 +80,49 @@ boundary_condition_inflow = BoundaryConditionDirichlet(initial_condition_mach3_f return flux end -boundary_conditions = Dict(:Bottom => boundary_condition_slip_wall, - :Step_Front => boundary_condition_slip_wall, - :Step_Top => boundary_condition_slip_wall, - :Top => boundary_condition_slip_wall, - :Right => boundary_condition_outflow, - :Left => boundary_condition_inflow) +boundary_conditions = Dict( + :Bottom => boundary_condition_slip_wall, + :Step_Front => boundary_condition_slip_wall, + :Step_Top => boundary_condition_slip_wall, + :Top => boundary_condition_slip_wall, + :Right => boundary_condition_outflow, + :Left => boundary_condition_inflow +) volume_flux = flux_ranocha surface_flux = flux_lax_friedrichs polydeg = 4 basis = LobattoLegendreBasis(polydeg) -shock_indicator = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(shock_indicator; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +shock_indicator = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + shock_indicator; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/b346ee6aa5446687f128eab8b37d52a7/raw/cd1e1d43bebd8d2631a07caec45585ec8456ca4c/abaqus_forward_step.inp", - joinpath(@__DIR__, "abaqus_forward_step.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/b346ee6aa5446687f128eab8b37d52a7/raw/cd1e1d43bebd8d2631a07caec45585ec8456ca4c/abaqus_forward_step.inp", + joinpath(@__DIR__, "abaqus_forward_step.inp") +) mesh = P4estMesh{2}(mesh_file) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -120,40 +133,54 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 2000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 2000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 0, - med_level = 2, med_threshold = 0.05, - max_level = 5, max_threshold = 0.1) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 0, + med_level = 2, med_threshold = 0.05, + max_level = 5, max_threshold = 0.1 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback +) # positivity limiter necessary for this example with strong shocks -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6), - variables = (Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (5.0e-6, 5.0e-6), + variables = (Trixi.density, pressure) +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(stage_limiter!); - maxiters = 999999, ode_default_options()..., - callback = callbacks); +sol = solve( + ode, SSPRK43(stage_limiter!); + maxiters = 999999, ode_default_options()..., + callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_free_stream.jl b/examples/p4est_2d_dgsem/elixir_euler_free_stream.jl index ab11dc1156..27e4ef6415 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_free_stream.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_free_stream.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -17,11 +16,15 @@ function mapping(xi_, eta_) xi = 1.5 * xi_ + 1.5 eta = 1.5 * eta_ + 1.5 - y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3)) + y = eta + 3 / 8 * ( + cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3) + ) - x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3)) + x = xi + 3 / 8 * ( + cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3) + ) return SVector(x, y) end @@ -30,8 +33,10 @@ end # Get the uncurved mesh from a file (downloads the file if not available locally) # Unstructured mesh with 48 cells of the square domain [-1, 1]^n -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/a075f8ec39a67fa9fad8f6f84342cbca/raw/a7206a02ed3a5d3cadacd8d9694ac154f9151db7/square_unstructured_1.inp", - joinpath(@__DIR__, "square_unstructured_1.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/a075f8ec39a67fa9fad8f6f84342cbca/raw/a7206a02ed3a5d3cadacd8d9694ac154f9151db7/square_unstructured_1.inp", + joinpath(@__DIR__, "square_unstructured_1.inp") +) # Map the unstructured mesh with the mapping above mesh = P4estMesh{2}(mesh_file, polydeg = 3, mapping = mapping, initial_refinement_level = 1) @@ -50,13 +55,19 @@ end # Refine recursively until each bottom left quadrant of a tree has level 2. # The mesh will be rebalanced before the simulation starts. -refine_fn_c = @cfunction(refine_fn, Cint, - (Ptr{Trixi.p4est_t}, Ptr{Trixi.p4est_topidx_t}, - Ptr{Trixi.p4est_quadrant_t})) +refine_fn_c = @cfunction( + refine_fn, Cint, + ( + Ptr{Trixi.p4est_t}, Ptr{Trixi.p4est_topidx_t}, + Ptr{Trixi.p4est_quadrant_t}, + ) +) Trixi.refine_p4est!(mesh.p4est, true, refine_fn_c, C_NULL) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition))) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition)) +) ############################################################################### # ODE solvers, callbacks etc. @@ -71,22 +82,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 2.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_sedov.jl b/examples/p4est_2d_dgsem/elixir_euler_sedov.jl index 539ddb4539..1b3add0bf1 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_sedov.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_sedov.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -42,17 +41,23 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) ############################################################################### @@ -60,10 +65,12 @@ coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 4, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 4, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -80,22 +87,28 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 300, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 300, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl b/examples/p4est_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl index 6a22aa262e..ccd9beed1e 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl @@ -41,16 +41,24 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -limiter_idp = SubcellLimiterIDP(equations, basis; - local_twosided_variables_cons = ["rho"], - local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, - min)], - max_iterations_newton = 40, # Default parameters are not sufficient to fulfill bounds properly. - newton_tolerances = (1.0e-14, 1.0e-15)) - -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +limiter_idp = SubcellLimiterIDP( + equations, basis; + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [ + ( + Trixi.entropy_guermond_etal, + min, + ), + ], + max_iterations_newton = 40, # Default parameters are not sufficient to fulfill bounds properly. + newton_tolerances = (1.0e-14, 1.0e-15) +) + +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) ############################################################################### @@ -59,10 +67,12 @@ coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = polydeg, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, + polydeg = polydeg, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -79,24 +89,30 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 300, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 300, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) -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); +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 +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_shockcapturing_ec.jl b/examples/p4est_2d_dgsem/elixir_euler_shockcapturing_ec.jl index 0cb18526f8..55cf3d8305 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_shockcapturing_ec.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_shockcapturing_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,17 +12,23 @@ surface_flux = flux_ranocha volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) ############################################################################### @@ -31,10 +36,12 @@ coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 4, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 4, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -51,22 +58,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_shockcapturing_ec_float32.jl b/examples/p4est_2d_dgsem/elixir_euler_shockcapturing_ec_float32.jl index 4f390f4fc2..a5d6a9e464 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_shockcapturing_ec_float32.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_shockcapturing_ec_float32.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,17 +12,23 @@ surface_flux = flux_ranocha volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(Float32, polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0f0, - alpha_min = 0.001f0, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral, RealT = Float32) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0f0, + alpha_min = 0.001f0, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral, RealT = Float32 +) ############################################################################### @@ -31,10 +36,12 @@ coordinates_min = (-1.0f0, -1.0f0) coordinates_max = (+1.0f0, +1.0f0) trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 4, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = true, RealT = Float32) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 4, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = true, RealT = Float32 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -53,14 +60,18 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.0f0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0f0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0f0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl b/examples/p4est_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl index 084fd699b8..eb0c965378 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -30,12 +29,16 @@ mapping_flag = Trixi.transfinite_mapping(faces) # Get the uncurved mesh from a file (downloads the file if not available locally) # Unstructured mesh with 24 cells of the square domain [-1, 1]^n -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", - joinpath(@__DIR__, "square_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + joinpath(@__DIR__, "square_unstructured_2.inp") +) -mesh = P4estMesh{2}(mesh_file, polydeg = 3, - mapping = mapping_flag, - initial_refinement_level = 1) +mesh = P4estMesh{2}( + mesh_file, polydeg = 3, + mapping = mapping_flag, + initial_refinement_level = 1 +) # Refine bottom left quadrant of each tree to level 2 function refine_fn(p4est, which_tree, quadrant) @@ -51,14 +54,20 @@ end # Refine recursively until each bottom left quadrant of a tree has level 2 # The mesh will be rebalanced before the simulation starts -refine_fn_c = @cfunction(refine_fn, Cint, - (Ptr{Trixi.p4est_t}, Ptr{Trixi.p4est_topidx_t}, - Ptr{Trixi.p4est_quadrant_t})) +refine_fn_c = @cfunction( + refine_fn, Cint, + ( + Ptr{Trixi.p4est_t}, Ptr{Trixi.p4est_topidx_t}, + Ptr{Trixi.p4est_quadrant_t}, + ) +) Trixi.refine_p4est!(mesh.p4est, true, refine_fn_c, C_NULL) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -73,24 +82,32 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_subsonic_cylinder.jl b/examples/p4est_2d_dgsem/elixir_euler_subsonic_cylinder.jl index 21df724da4..050fdb8b1a 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_subsonic_cylinder.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_subsonic_cylinder.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -7,8 +6,10 @@ using Trixi equations = CompressibleEulerEquations2D(1.4) -@inline function initial_condition_mach038_flow(x, t, - equations::CompressibleEulerEquations2D) +@inline function initial_condition_mach038_flow( + x, t, + equations::CompressibleEulerEquations2D + ) # set the freestream flow parameters rho_freestream = 1.4 v1 = 0.38 @@ -25,8 +26,10 @@ volume_flux = flux_ranocha_turbo # FluxRotated(flux_chandrashekar) can also be u surface_flux = flux_lax_friedrichs polydeg = 3 -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) function mapping2cylinder(xi, eta) xi_, eta_ = 0.5 * (xi + 1), 0.5 * (eta + 1.0) # Map from [-1,1] to [0,1] for simplicity @@ -49,27 +52,35 @@ cells_per_dimension = (64, 64) # conditions there. However, the image of eta = -1, +1 coincides at the line y = 0. There is no # physical boundary there so we specify `periodicity = true` there and the solver treats the # element across eta = -1, +1 as neighbours which is what we want -mesh = P4estMesh(cells_per_dimension, mapping = mapping2cylinder, polydeg = polydeg, - periodicity = (false, true)) +mesh = P4estMesh( + cells_per_dimension, mapping = mapping2cylinder, polydeg = polydeg, + periodicity = (false, true) +) # The boundary condition on the outer cylinder is constant but subsonic, so we cannot compute the # boundary flux from the external information alone. Thus, we use the numerical flux to distinguish # between inflow and outflow characteristics -@inline function boundary_condition_subsonic_constant(u_inner, - normal_direction::AbstractVector, x, - t, - surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_subsonic_constant( + u_inner, + normal_direction::AbstractVector, x, + t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) u_boundary = initial_condition_mach038_flow(x, t, equations) return surface_flux_function(u_inner, u_boundary, normal_direction, equations) end -boundary_conditions = Dict(:x_neg => boundary_condition_slip_wall, - :x_pos => boundary_condition_subsonic_constant) +boundary_conditions = Dict( + :x_neg => boundary_condition_slip_wall, + :x_pos => boundary_condition_subsonic_constant +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers @@ -89,37 +100,57 @@ rho_inf = 1.4 u_inf = 0.38 l_inf = 1.0 # Diameter of circle -drag_coefficient = AnalysisSurfaceIntegral(semi, (:x_neg,), - DragCoefficientPressure(aoa, rho_inf, u_inf, - l_inf)) - -lift_coefficient = AnalysisSurfaceIntegral(semi, (:x_neg,), - LiftCoefficientPressure(aoa, rho_inf, u_inf, - l_inf)) - -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - output_directory = "out", - save_analysis = true, - analysis_integrals = (drag_coefficient, - lift_coefficient)) +drag_coefficient = AnalysisSurfaceIntegral( + semi, (:x_neg,), + DragCoefficientPressure( + aoa, rho_inf, u_inf, + l_inf + ) +) + +lift_coefficient = AnalysisSurfaceIntegral( + semi, (:x_neg,), + LiftCoefficientPressure( + aoa, rho_inf, u_inf, + l_inf + ) +) + +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + output_directory = "out", + save_analysis = true, + analysis_integrals = ( + drag_coefficient, + lift_coefficient, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 500, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 500, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 2.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, - CarpenterKennedy2N54(williamson_condition = false; - thread = OrdinaryDiffEq.True()), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, + CarpenterKennedy2N54( + williamson_condition = false; + thread = OrdinaryDiffEq.True() + ), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder.jl b/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder.jl index 76ee96d476..efef27871e 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder.jl @@ -37,10 +37,12 @@ initial_condition = initial_condition_mach3_flow # Supersonic inflow boundary condition. # Calculate the boundary flux entirely from the external solution state, i.e., set # external solution state values for everything entering the domain. -@inline function boundary_condition_supersonic_inflow(u_inner, - normal_direction::AbstractVector, - x, t, surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_supersonic_inflow( + u_inner, + normal_direction::AbstractVector, + x, t, surface_flux_function, + equations::CompressibleEulerEquations2D + ) u_boundary = initial_condition_mach3_flow(x, t, equations) flux = Trixi.flux(u_boundary, normal_direction, equations) @@ -50,44 +52,58 @@ end # Supersonic outflow boundary condition. # Calculate the boundary flux entirely from the internal solution state. Analogous to supersonic inflow # except all the solution state values are set from the internal solution as everything leaves the domain -@inline function boundary_condition_outflow(u_inner, normal_direction::AbstractVector, x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_outflow( + u_inner, normal_direction::AbstractVector, x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) flux = Trixi.flux(u_inner, normal_direction, equations) return flux end -boundary_conditions = Dict(:Bottom => boundary_condition_slip_wall, - :Circle => boundary_condition_slip_wall, - :Top => boundary_condition_slip_wall, - :Right => boundary_condition_outflow, - :Left => boundary_condition_supersonic_inflow) +boundary_conditions = Dict( + :Bottom => boundary_condition_slip_wall, + :Circle => boundary_condition_slip_wall, + :Top => boundary_condition_slip_wall, + :Right => boundary_condition_outflow, + :Left => boundary_condition_supersonic_inflow +) volume_flux = flux_ranocha_turbo surface_flux = flux_lax_friedrichs polydeg = 3 basis = LobattoLegendreBasis(polydeg) -shock_indicator = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(shock_indicator; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +shock_indicator = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + shock_indicator; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/a08f78f6b185b63c3baeff911a63f628/raw/addac716ea0541f588b9d2bd3f92f643eb27b88f/abaqus_cylinder_in_channel.inp", - joinpath(@__DIR__, "abaqus_cylinder_in_channel.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/a08f78f6b185b63c3baeff911a63f628/raw/addac716ea0541f588b9d2bd3f92f643eb27b88f/abaqus_cylinder_in_channel.inp", + joinpath(@__DIR__, "abaqus_cylinder_in_channel.inp") +) mesh = P4estMesh{2}(mesh_file) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers @@ -104,35 +120,47 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 0, - med_level = 3, med_threshold = 0.05, - max_level = 5, max_threshold = 0.1) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 0, + med_level = 3, med_threshold = 0.05, + max_level = 5, max_threshold = 0.1 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback +) # positivity limiter necessary for this example with strong shocks. Very sensitive # to the order of the limiter variables, pressure must come first. -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-7, 1.0e-6), - variables = (pressure, Trixi.density)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (5.0e-7, 1.0e-6), + variables = (pressure, Trixi.density) +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(stage_limiter!); - ode_default_options()..., callback = callbacks); +sol = solve( + ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl b/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl index 78df12d60d..8fd0344144 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_supersonic_cylinder_sc_subcell.jl @@ -37,10 +37,12 @@ initial_condition = initial_condition_mach3_flow # Supersonic inflow boundary condition. # Calculate the boundary flux entirely from the external solution state, i.e., set # external solution state values for everything entering the domain. -@inline function boundary_condition_supersonic_inflow(u_inner, - normal_direction::AbstractVector, - x, t, surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_supersonic_inflow( + u_inner, + normal_direction::AbstractVector, + x, t, surface_flux_function, + equations::CompressibleEulerEquations2D + ) u_boundary = initial_condition_mach3_flow(x, t, equations) flux = Trixi.flux(u_boundary, normal_direction, equations) @@ -51,11 +53,13 @@ end # boundary outer state. Those functions return the boundary value for a specific boundary condition # at time `t`, for the node with spatial indices `indices` and the given `normal_direction`. # only for P4estMesh{2} -@inline function Trixi.get_boundary_outer_state(u_inner, t, - boundary_condition::typeof(boundary_condition_supersonic_inflow), - normal_direction::AbstractVector, - equations, dg, cache, - indices...) +@inline function Trixi.get_boundary_outer_state( + u_inner, t, + boundary_condition::typeof(boundary_condition_supersonic_inflow), + normal_direction::AbstractVector, + equations, dg, cache, + indices... + ) x = Trixi.get_node_coords(cache.elements.node_coordinates, equations, dg, indices...) return initial_condition_mach3_flow(x, t, equations) @@ -64,68 +68,90 @@ end # Supersonic outflow boundary condition. # Calculate the boundary flux entirely from the internal solution state. Analogous to supersonic inflow # except all the solution state values are set from the internal solution as everything leaves the domain -@inline function boundary_condition_outflow(u_inner, normal_direction::AbstractVector, x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_outflow( + u_inner, normal_direction::AbstractVector, x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) flux = Trixi.flux(u_inner, normal_direction, equations) return flux end # only for P4estMesh{2} -@inline function Trixi.get_boundary_outer_state(u_inner, t, - boundary_condition::typeof(boundary_condition_outflow), - normal_direction::AbstractVector, - equations, dg, cache, - indices...) +@inline function Trixi.get_boundary_outer_state( + u_inner, t, + boundary_condition::typeof(boundary_condition_outflow), + normal_direction::AbstractVector, + equations, dg, cache, + indices... + ) return u_inner end # only for P4estMesh{2} -@inline function Trixi.get_boundary_outer_state(u_inner, t, - boundary_condition::typeof(boundary_condition_slip_wall), - normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D, - dg, cache, indices...) +@inline function Trixi.get_boundary_outer_state( + u_inner, t, + boundary_condition::typeof(boundary_condition_slip_wall), + normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D, + dg, cache, indices... + ) factor = (normal_direction[1] * u_inner[2] + normal_direction[2] * u_inner[3]) u_normal = (factor / sum(normal_direction .^ 2)) * normal_direction - return SVector(u_inner[1], - u_inner[2] - 2 * u_normal[1], - u_inner[3] - 2 * u_normal[2], - u_inner[4]) + return SVector( + u_inner[1], + u_inner[2] - 2 * u_normal[1], + u_inner[3] - 2 * u_normal[2], + u_inner[4] + ) end -boundary_conditions = Dict(:Bottom => boundary_condition_slip_wall, - :Circle => boundary_condition_slip_wall, - :Top => boundary_condition_slip_wall, - :Right => boundary_condition_outflow, - :Left => boundary_condition_supersonic_inflow) +boundary_conditions = Dict( + :Bottom => boundary_condition_slip_wall, + :Circle => boundary_condition_slip_wall, + :Top => boundary_condition_slip_wall, + :Right => boundary_condition_outflow, + :Left => boundary_condition_supersonic_inflow +) volume_flux = flux_ranocha_turbo surface_flux = flux_lax_friedrichs polydeg = 3 basis = LobattoLegendreBasis(polydeg) -limiter_idp = SubcellLimiterIDP(equations, basis; - local_twosided_variables_cons = ["rho"], - positivity_variables_nonlinear = [pressure], - local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, - min)], - max_iterations_newton = 50) # Default value of 10 iterations is too low to fulfill bounds. - -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +limiter_idp = SubcellLimiterIDP( + equations, basis; + local_twosided_variables_cons = ["rho"], + positivity_variables_nonlinear = [pressure], + local_onesided_variables_nonlinear = [ + ( + Trixi.entropy_guermond_etal, + min, + ), + ], + max_iterations_newton = 50 +) # Default value of 10 iterations is too low to fulfill bounds. + +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/a08f78f6b185b63c3baeff911a63f628/raw/addac716ea0541f588b9d2bd3f92f643eb27b88f/abaqus_cylinder_in_channel.inp", - joinpath(@__DIR__, "abaqus_cylinder_in_channel.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/a08f78f6b185b63c3baeff911a63f628/raw/addac716ea0541f588b9d2bd3f92f643eb27b88f/abaqus_cylinder_in_channel.inp", + joinpath(@__DIR__, "abaqus_cylinder_in_channel.inp") +) mesh = P4estMesh{2}(mesh_file, initial_refinement_level = 0) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers @@ -142,21 +168,27 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) -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); +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 +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_wall_bc_amr.jl b/examples/p4est_2d_dgsem/elixir_euler_wall_bc_amr.jl index 75e60d0c78..da6580c8f4 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_wall_bc_amr.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_wall_bc_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -25,26 +24,34 @@ end initial_condition = uniform_flow_state boundary_condition_uniform_flow = BoundaryConditionDirichlet(uniform_flow_state) -boundary_conditions = Dict(:Body => boundary_condition_uniform_flow, - :Button1 => boundary_condition_slip_wall, - :Button2 => boundary_condition_slip_wall, - :Eye1 => boundary_condition_slip_wall, - :Eye2 => boundary_condition_slip_wall, - :Smile => boundary_condition_slip_wall, - :Bowtie => boundary_condition_slip_wall) +boundary_conditions = Dict( + :Body => boundary_condition_uniform_flow, + :Button1 => boundary_condition_slip_wall, + :Button2 => boundary_condition_slip_wall, + :Eye1 => boundary_condition_slip_wall, + :Eye2 => boundary_condition_slip_wall, + :Smile => boundary_condition_slip_wall, + :Bowtie => boundary_condition_slip_wall +) volume_flux = flux_ranocha -solver = DGSEM(polydeg = 5, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 5, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/0e9e990a04b5105d1d2e3096a6e41272/raw/0d924b1d7e7d3cc1070a6cc22fe1d501687aa6dd/abaqus_gingerbread_man.inp", - joinpath(@__DIR__, "abaqus_gingerbread_man.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/0e9e990a04b5105d1d2e3096a6e41272/raw/0d924b1d7e7d3cc1070a6cc22fe1d501687aa6dd/abaqus_gingerbread_man.inp", + joinpath(@__DIR__, "abaqus_gingerbread_man.inp") +) mesh = P4estMesh{2}(mesh_file) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -55,35 +62,47 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) amr_indicator = IndicatorLöhner(semi, variable = density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 0, - med_level = 1, med_threshold = 0.05, - max_level = 3, max_threshold = 0.1) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 0, + med_level = 1, med_threshold = 0.05, + max_level = 3, max_threshold = 0.1 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_euler_weak_blast_wave_amr.jl b/examples/p4est_2d_dgsem/elixir_euler_weak_blast_wave_amr.jl index 6868067371..25c49333ed 100644 --- a/examples/p4est_2d_dgsem/elixir_euler_weak_blast_wave_amr.jl +++ b/examples/p4est_2d_dgsem/elixir_euler_weak_blast_wave_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -37,17 +36,23 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) ############################################################################### @@ -63,10 +68,12 @@ end # The mesh below can be made periodic # Create P4estMesh with 8 x 8 trees trees_per_dimension = (8, 8) -mesh = P4estMesh(trees_per_dimension, polydeg = 4, - mapping = mapping_twist, - initial_refinement_level = 0, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, polydeg = 4, + mapping = mapping_twist, + initial_refinement_level = 0, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -79,39 +86,51 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 400 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(dt = 0.2, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + dt = 0.2, + save_initial_solution = true, + save_final_solution = true +) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 0, - med_level = 1, med_threshold = 0.05, - max_level = 2, max_threshold = 0.1) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 0, + med_level = 1, med_threshold = 0.05, + max_level = 2, max_threshold = 0.1 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks);#, maxiters=4); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); #, maxiters=4); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_eulergravity_convergence.jl b/examples/p4est_2d_dgsem/elixir_eulergravity_convergence.jl index 974466e3b3..fc5710b9dc 100644 --- a/examples/p4est_2d_dgsem/elixir_eulergravity_convergence.jl +++ b/examples/p4est_2d_dgsem/elixir_eulergravity_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,13 +15,17 @@ coordinates_min = (0.0, 0.0) coordinates_max = (2.0, 2.0) trees_per_dimension = (1, 1) -mesh = P4estMesh(trees_per_dimension, polydeg = 1, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 2) - -semi_euler = SemidiscretizationHyperbolic(mesh, equations_euler, initial_condition, - solver_euler, - source_terms = source_terms_eoc_test_coupled_euler_gravity) +mesh = P4estMesh( + trees_per_dimension, polydeg = 1, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 2 +) + +semi_euler = SemidiscretizationHyperbolic( + mesh, equations_euler, initial_condition, + solver_euler, + source_terms = source_terms_eoc_test_coupled_euler_gravity +) ############################################################################### # semidiscretization of the hyperbolic diffusion equations @@ -30,20 +33,24 @@ equations_gravity = HyperbolicDiffusionEquations2D() solver_gravity = DGSEM(polydeg, flux_lax_friedrichs) -semi_gravity = SemidiscretizationHyperbolic(mesh, equations_gravity, initial_condition, - solver_gravity, - source_terms = source_terms_harmonic) +semi_gravity = SemidiscretizationHyperbolic( + mesh, equations_gravity, initial_condition, + solver_gravity, + source_terms = source_terms_harmonic +) ############################################################################### # combining both semidiscretizations for Euler + self-gravity -parameters = ParametersEulerGravity(background_density = 2.0, # aka rho0 - # rho0 is (ab)used to add a "+8π" term to the source terms - # for the manufactured solution - gravitational_constant = 1.0, # aka G - cfl = 1.1, - resid_tol = 1.0e-10, - n_iterations_max = 1000, - timestep_gravity = timestep_gravity_erk52_3Sstar!) +parameters = ParametersEulerGravity( + background_density = 2.0, # aka rho0 + # rho0 is (ab)used to add a "+8π" term to the source terms + # for the manufactured solution + gravitational_constant = 1.0, # aka G + cfl = 1.1, + resid_tol = 1.0e-10, + n_iterations_max = 1000, + timestep_gravity = timestep_gravity_erk52_3Sstar! +) semi = SemidiscretizationEulerGravity(semi_euler, semi_gravity, parameters) @@ -56,28 +63,38 @@ summary_callback = SummaryCallback() stepsize_callback = StepsizeCallback(cfl = 0.8) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) analysis_interval = 100 alive_callback = AliveCallback(analysis_interval = analysis_interval) -analysis_callback = AnalysisCallback(semi_euler, interval = analysis_interval, - save_analysis = true) +analysis_callback = AnalysisCallback( + semi_euler, interval = analysis_interval, + save_analysis = true +) -callbacks = CallbackSet(summary_callback, stepsize_callback, - save_restart, save_solution, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, stepsize_callback, + save_restart, save_solution, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary println("Number of gravity subcycles: ", semi.gravity_counter.ncalls_since_readout) diff --git a/examples/p4est_2d_dgsem/elixir_linearizedeuler_gaussian_source.jl b/examples/p4est_2d_dgsem/elixir_linearizedeuler_gaussian_source.jl index 364e029644..c8a302f462 100644 --- a/examples/p4est_2d_dgsem/elixir_linearizedeuler_gaussian_source.jl +++ b/examples/p4est_2d_dgsem/elixir_linearizedeuler_gaussian_source.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -36,8 +35,10 @@ mapping(xi, eta) = rot_mat * SVector(3.0 * xi, 3.0 * eta) # Mean density and speed of sound are slightly off from 1.0 to allow proper verification of # curved LEE implementation using this elixir (some things in the LEE cancel if both are 1.0) -equations = LinearizedEulerEquations2D(v_mean_global = Tuple(rot_mat * SVector(-0.5, 0.25)), - c_mean_global = 1.02, rho_mean_global = 1.01) +equations = LinearizedEulerEquations2D( + v_mean_global = Tuple(rot_mat * SVector(-0.5, 0.25)), + c_mean_global = 1.02, rho_mean_global = 1.01 +) initial_condition = initial_condition_zero @@ -46,13 +47,17 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_godunov) # Create a uniformly refined mesh with periodic boundaries trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, polydeg = 1, - mapping = mapping, - periodicity = true, initial_refinement_level = 2) +mesh = P4estMesh( + trees_per_dimension, polydeg = 1, + mapping = mapping, + periodicity = true, initial_refinement_level = 2 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_gauss) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_gauss +) ############################################################################### # ODE solvers, callbacks etc. @@ -75,16 +80,20 @@ save_solution = SaveSolutionCallback(interval = 100) stepsize_callback = StepsizeCallback(cfl = 0.5) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/p4est_2d_dgsem/elixir_mhd_alfven_wave.jl b/examples/p4est_2d_dgsem/elixir_mhd_alfven_wave.jl index b2b402a25f..787ac85b44 100644 --- a/examples/p4est_2d_dgsem/elixir_mhd_alfven_wave.jl +++ b/examples/p4est_2d_dgsem/elixir_mhd_alfven_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,19 +11,25 @@ initial_condition = initial_condition_convergence_test # Get the DG approximation space volume_flux = (flux_central, flux_nonconservative_powell) -solver = DGSEM(polydeg = 4, - surface_flux = (flux_hlle, - flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, + surface_flux = ( + flux_hlle, + flux_nonconservative_powell, + ), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (sqrt(2.0), sqrt(2.0)) trees_per_dimension = (8, 8) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 0, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 0, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -46,16 +51,20 @@ stepsize_callback = StepsizeCallback(cfl = cfl) glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_mhd_rotor.jl b/examples/p4est_2d_dgsem/elixir_mhd_rotor.jl index 089e82580c..00e3ea0621 100644 --- a/examples/p4est_2d_dgsem/elixir_mhd_rotor.jl +++ b/examples/p4est_2d_dgsem/elixir_mhd_rotor.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -48,14 +47,18 @@ surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) # Affine type mapping to take the [-1,1]^2 domain from the mesh file @@ -68,19 +71,25 @@ function mapping_twist(xi, eta) return SVector(x, y) end -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", - joinpath(@__DIR__, "square_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + joinpath(@__DIR__, "square_unstructured_2.inp") +) -mesh = P4estMesh{2}(mesh_file, - polydeg = 4, - mapping = mapping_twist, - initial_refinement_level = 1) +mesh = P4estMesh{2}( + mesh_file, + polydeg = 4, + mapping = mapping_twist, + initial_refinement_level = 1 +) boundary_condition = BoundaryConditionDirichlet(initial_condition) boundary_conditions = Dict(:all => boundary_condition) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -95,40 +104,52 @@ 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) - -amr_indicator = IndicatorLöhner(semi, - variable = density_pressure) - -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 1, - med_level = 3, med_threshold = 0.05, - max_level = 5, max_threshold = 0.1) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorLöhner( + semi, + variable = density_pressure +) + +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 1, + med_level = 3, med_threshold = 0.05, + max_level = 5, max_threshold = 0.1 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) cfl = 0.5 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, - amr_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_navierstokes_NACA0012airfoil_mach08.jl b/examples/p4est_2d_dgsem/elixir_navierstokes_NACA0012airfoil_mach08.jl index 40baef6ef9..453b0a6e4f 100644 --- a/examples/p4est_2d_dgsem/elixir_navierstokes_NACA0012airfoil_mach08.jl +++ b/examples/p4est_2d_dgsem/elixir_navierstokes_NACA0012airfoil_mach08.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -23,8 +22,10 @@ equations = CompressibleEulerEquations2D(1.4) prandtl_number() = 0.72 mu() = 0.0031959974968701088 -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu(), - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu(), + Prandtl = prandtl_number() +) rho_inf() = 1.0 p_inf() = 2.85 @@ -51,60 +52,80 @@ surface_flux = flux_lax_friedrichs polydeg = 3 solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux) -mesh_file = Trixi.download("https://gist.githubusercontent.com/Arpit-Babbar/339662b4b46164a016e35c81c66383bb/raw/8bf94f5b426ba907ace87405cfcc1dcc2ef7cbda/NACA0012.inp", - joinpath(@__DIR__, "NACA0012.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/Arpit-Babbar/339662b4b46164a016e35c81c66383bb/raw/8bf94f5b426ba907ace87405cfcc1dcc2ef7cbda/NACA0012.inp", + joinpath(@__DIR__, "NACA0012.inp") +) mesh = P4estMesh{2}(mesh_file, initial_refinement_level = 1) # The boundary values across outer boundary are constant but subsonic, so we cannot compute the # boundary flux from the external information alone. Thus, we use the numerical flux to distinguish # between inflow and outflow characteristics -@inline function boundary_condition_subsonic_constant(u_inner, - normal_direction::AbstractVector, x, - t, - surface_flux_function, - equations::CompressibleEulerEquations2D) +@inline function boundary_condition_subsonic_constant( + u_inner, + normal_direction::AbstractVector, x, + t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) u_boundary = initial_condition_mach08_flow(x, t, equations) return Trixi.flux_hll(u_inner, u_boundary, normal_direction, equations) end -boundary_conditions = Dict(:Left => boundary_condition_subsonic_constant, - :Right => boundary_condition_subsonic_constant, - :Top => boundary_condition_subsonic_constant, - :Bottom => boundary_condition_subsonic_constant, - :AirfoilBottom => boundary_condition_slip_wall, - :AirfoilTop => boundary_condition_slip_wall) +boundary_conditions = Dict( + :Left => boundary_condition_subsonic_constant, + :Right => boundary_condition_subsonic_constant, + :Top => boundary_condition_subsonic_constant, + :Bottom => boundary_condition_subsonic_constant, + :AirfoilBottom => boundary_condition_slip_wall, + :AirfoilTop => boundary_condition_slip_wall +) velocity_airfoil = NoSlip((x, t, equations) -> SVector(0.0, 0.0)) heat_airfoil = Adiabatic((x, t, equations) -> 0.0) -boundary_conditions_airfoil = BoundaryConditionNavierStokesWall(velocity_airfoil, - heat_airfoil) +boundary_conditions_airfoil = BoundaryConditionNavierStokesWall( + velocity_airfoil, + heat_airfoil +) function momenta_initial_condition_mach08_flow(x, t, equations) u = initial_condition_mach08_flow(x, t, equations) momenta = SVector(u[2], u[3]) end -velocity_bc_square = NoSlip((x, t, equations) -> momenta_initial_condition_mach08_flow(x, t, - equations)) +velocity_bc_square = NoSlip( + (x, t, equations) -> momenta_initial_condition_mach08_flow( + x, t, + equations + ) +) heat_bc_square = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_square = BoundaryConditionNavierStokesWall(velocity_bc_square, - heat_bc_square) - -boundary_conditions_parabolic = Dict(:Left => boundary_condition_square, - :Right => boundary_condition_square, - :Top => boundary_condition_square, - :Bottom => boundary_condition_square, - :AirfoilBottom => boundary_conditions_airfoil, - :AirfoilTop => boundary_conditions_airfoil) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +boundary_condition_square = BoundaryConditionNavierStokesWall( + velocity_bc_square, + heat_bc_square +) + +boundary_conditions_parabolic = Dict( + :Left => boundary_condition_square, + :Right => boundary_condition_square, + :Top => boundary_condition_square, + :Bottom => boundary_condition_square, + :AirfoilBottom => boundary_conditions_airfoil, + :AirfoilTop => boundary_conditions_airfoil +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers @@ -120,50 +141,74 @@ summary_callback = SummaryCallback() analysis_interval = 2000 force_boundary_names = (:AirfoilBottom, :AirfoilTop) -drag_coefficient = AnalysisSurfaceIntegral(semi, force_boundary_names, - DragCoefficientPressure(aoa(), rho_inf(), - u_inf(equations), - l_inf())) - -lift_coefficient = AnalysisSurfaceIntegral(semi, force_boundary_names, - LiftCoefficientPressure(aoa(), rho_inf(), - u_inf(equations), - l_inf())) - -drag_coefficient_shear_force = AnalysisSurfaceIntegral(semi, force_boundary_names, - DragCoefficientShearStress(aoa(), - rho_inf(), - u_inf(equations), - l_inf())) - -lift_coefficient_shear_force = AnalysisSurfaceIntegral(semi, force_boundary_names, - LiftCoefficientShearStress(aoa(), - rho_inf(), - u_inf(equations), - l_inf())) - -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - output_directory = "out", - save_analysis = true, - analysis_errors = Symbol[], - analysis_integrals = (drag_coefficient, - lift_coefficient, - drag_coefficient_shear_force, - lift_coefficient_shear_force)) +drag_coefficient = AnalysisSurfaceIntegral( + semi, force_boundary_names, + DragCoefficientPressure( + aoa(), rho_inf(), + u_inf(equations), + l_inf() + ) +) + +lift_coefficient = AnalysisSurfaceIntegral( + semi, force_boundary_names, + LiftCoefficientPressure( + aoa(), rho_inf(), + u_inf(equations), + l_inf() + ) +) + +drag_coefficient_shear_force = AnalysisSurfaceIntegral( + semi, force_boundary_names, + DragCoefficientShearStress( + aoa(), + rho_inf(), + u_inf(equations), + l_inf() + ) +) + +lift_coefficient_shear_force = AnalysisSurfaceIntegral( + semi, force_boundary_names, + LiftCoefficientShearStress( + aoa(), + rho_inf(), + u_inf(equations), + l_inf() + ) +) + +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + output_directory = "out", + save_analysis = true, + analysis_errors = Symbol[], + analysis_integrals = ( + drag_coefficient, + lift_coefficient, + drag_coefficient_shear_force, + lift_coefficient_shear_force, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 500, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 500, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()); abstol = 1e-8, - reltol = 1e-8, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()); abstol = 1.0e-8, + reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_navierstokes_convergence.jl b/examples/p4est_2d_dgsem/elixir_navierstokes_convergence.jl index 54ec09d2be..3fc17aac8b 100644 --- a/examples/p4est_2d_dgsem/elixir_navierstokes_convergence.jl +++ b/examples/p4est_2d_dgsem/elixir_navierstokes_convergence.jl @@ -8,22 +8,28 @@ prandtl_number() = 0.72 mu() = 0.01 equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = (true, false)) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = (true, false) +) # Note: the initial condition cannot be specialized to `CompressibleNavierStokesDiffusion2D` # since it is called by both the parabolic solver (which passes in `CompressibleNavierStokesDiffusion2D`) @@ -79,16 +85,24 @@ end v1_t = -pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * sin(pi_t) v1_x = pi * cos(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_y = sin(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) v1_xx = -pi * pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_xy = pi * cos(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) - v1_yy = (sin(pi_x) * - (2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - - A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - - (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0))) * cos(pi_t)) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) + v1_yy = ( + sin(pi_x) * + ( + 2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - + A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - + (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0)) + ) * cos(pi_t) + ) v2 = v1 v2_t = v1_t v2_x = v1_x @@ -119,58 +133,68 @@ end du1 = rho_t + rho_x * v1 + rho * v1_x + rho_y * v2 + rho * v2_y # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 - + 2.0 * rho * v1 * v1_x - + rho_y * v1 * v2 - + rho * v1_y * v2 - + rho * v1 * v2_y - - # stress tensor from x-direction - 4.0 / 3.0 * v1_xx * mu_ + - 2.0 / 3.0 * v2_xy * mu_ - - v1_yy * mu_ - - v2_xy * mu_) + du2 = ( + rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y - + # stress tensor from x-direction + 4.0 / 3.0 * v1_xx * mu_ + + 2.0 / 3.0 * v2_xy * mu_ - + v1_yy * mu_ - + v2_xy * mu_ + ) # y-momentum equation - du3 = (rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 - + rho * v1_x * v2 - + rho * v1 * v2_x - + rho_y * v2^2 - + 2.0 * rho * v2 * v2_y - - # stress tensor from y-direction - v1_xy * mu_ - - v2_xx * mu_ - - 4.0 / 3.0 * v2_yy * mu_ + - 2.0 / 3.0 * v1_xy * mu_) + du3 = ( + rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y - + # stress tensor from y-direction + v1_xy * mu_ - + v2_xx * mu_ - + 4.0 / 3.0 * v2_yy * mu_ + + 2.0 / 3.0 * v1_xy * mu_ + ) # total energy equation - du4 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + v2_y * (E + p) + v2 * (E_y + p_y) - - # stress tensor and temperature gradient terms from x-direction - 4.0 / 3.0 * v1_xx * v1 * mu_ + - 2.0 / 3.0 * v2_xy * v1 * mu_ - - 4.0 / 3.0 * v1_x * v1_x * mu_ + - 2.0 / 3.0 * v2_y * v1_x * mu_ - - v1_xy * v2 * mu_ - - v2_xx * v2 * mu_ - - v1_y * v2_x * mu_ - - v2_x * v2_x * mu_ - - T_const * inv_rho_cubed * - (p_xx * rho * rho - - 2.0 * p_x * rho * rho_x + - 2.0 * p * rho_x * rho_x - - p * rho * rho_xx) * mu_ - - # stress tensor and temperature gradient terms from y-direction - v1_yy * v1 * mu_ - - v2_xy * v1 * mu_ - - v1_y * v1_y * mu_ - - v2_x * v1_y * mu_ - - 4.0 / 3.0 * v2_yy * v2 * mu_ + - 2.0 / 3.0 * v1_xy * v2 * mu_ - - 4.0 / 3.0 * v2_y * v2_y * mu_ + - 2.0 / 3.0 * v1_x * v2_y * mu_ - - T_const * inv_rho_cubed * - (p_yy * rho * rho - - 2.0 * p_y * rho * rho_y + - 2.0 * p * rho_y * rho_y - - p * rho * rho_yy) * mu_) + du4 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) - + # stress tensor and temperature gradient terms from x-direction + 4.0 / 3.0 * v1_xx * v1 * mu_ + + 2.0 / 3.0 * v2_xy * v1 * mu_ - + 4.0 / 3.0 * v1_x * v1_x * mu_ + + 2.0 / 3.0 * v2_y * v1_x * mu_ - + v1_xy * v2 * mu_ - + v2_xx * v2 * mu_ - + v1_y * v2_x * mu_ - + v2_x * v2_x * mu_ - + T_const * inv_rho_cubed * + ( + p_xx * rho * rho - + 2.0 * p_x * rho * rho_x + + 2.0 * p * rho_x * rho_x - + p * rho * rho_xx + ) * mu_ - + # stress tensor and temperature gradient terms from y-direction + v1_yy * v1 * mu_ - + v2_xy * v1 * mu_ - + v1_y * v1_y * mu_ - + v2_x * v1_y * mu_ - + 4.0 / 3.0 * v2_yy * v2 * mu_ + + 2.0 / 3.0 * v1_xy * v2 * mu_ - + 4.0 / 3.0 * v2_y * v2_y * mu_ + + 2.0 / 3.0 * v1_x * v2_y * mu_ - + T_const * inv_rho_cubed * + ( + p_yy * rho * rho - + 2.0 * p_y * rho * rho_y + + 2.0 * p * rho_y * rho_y - + p * rho * rho_yy + ) * mu_ + ) return SVector(du1, du2, du3, du4) end @@ -183,22 +207,32 @@ velocity_bc_top_bottom = NoSlip() do x, t, equations return SVector(u[2], u[3]) end heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, - heat_bc_top_bottom) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall( + velocity_bc_top_bottom, + heat_bc_top_bottom +) # define inviscid boundary conditions -boundary_conditions = Dict(:y_neg => boundary_condition_slip_wall, - :y_pos => boundary_condition_slip_wall) +boundary_conditions = Dict( + :y_neg => boundary_condition_slip_wall, + :y_pos => boundary_condition_slip_wall +) # define viscous boundary conditions -boundary_conditions_parabolic = Dict(:y_neg => boundary_condition_top_bottom, - :y_pos => boundary_condition_top_bottom) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +boundary_conditions_parabolic = Dict( + :y_neg => boundary_condition_top_bottom, + :y_pos => boundary_condition_top_bottom +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) # ############################################################################### # # ODE solvers, callbacks etc. @@ -216,7 +250,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1e-5, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1.0e-5, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_navierstokes_convergence_nonperiodic.jl b/examples/p4est_2d_dgsem/elixir_navierstokes_convergence_nonperiodic.jl index b4177fe853..867982c6fc 100644 --- a/examples/p4est_2d_dgsem/elixir_navierstokes_convergence_nonperiodic.jl +++ b/examples/p4est_2d_dgsem/elixir_navierstokes_convergence_nonperiodic.jl @@ -8,22 +8,28 @@ prandtl_number() = 0.72 mu() = 0.01 equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = (false, false)) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = (false, false) +) # Note: the initial condition cannot be specialized to `CompressibleNavierStokesDiffusion2D` # since it is called by both the parabolic solver (which passes in `CompressibleNavierStokesDiffusion2D`) @@ -79,16 +85,24 @@ end v1_t = -pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * sin(pi_t) v1_x = pi * cos(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_y = sin(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) v1_xx = -pi * pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_xy = pi * cos(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) - v1_yy = (sin(pi_x) * - (2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - - A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - - (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0))) * cos(pi_t)) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) + v1_yy = ( + sin(pi_x) * + ( + 2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - + A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - + (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0)) + ) * cos(pi_t) + ) v2 = v1 v2_t = v1_t v2_x = v1_x @@ -119,58 +133,68 @@ end du1 = rho_t + rho_x * v1 + rho * v1_x + rho_y * v2 + rho * v2_y # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 - + 2.0 * rho * v1 * v1_x - + rho_y * v1 * v2 - + rho * v1_y * v2 - + rho * v1 * v2_y - - # stress tensor from x-direction - 4.0 / 3.0 * v1_xx * mu_ + - 2.0 / 3.0 * v2_xy * mu_ - - v1_yy * mu_ - - v2_xy * mu_) + du2 = ( + rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y - + # stress tensor from x-direction + 4.0 / 3.0 * v1_xx * mu_ + + 2.0 / 3.0 * v2_xy * mu_ - + v1_yy * mu_ - + v2_xy * mu_ + ) # y-momentum equation - du3 = (rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 - + rho * v1_x * v2 - + rho * v1 * v2_x - + rho_y * v2^2 - + 2.0 * rho * v2 * v2_y - - # stress tensor from y-direction - v1_xy * mu_ - - v2_xx * mu_ - - 4.0 / 3.0 * v2_yy * mu_ + - 2.0 / 3.0 * v1_xy * mu_) + du3 = ( + rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y - + # stress tensor from y-direction + v1_xy * mu_ - + v2_xx * mu_ - + 4.0 / 3.0 * v2_yy * mu_ + + 2.0 / 3.0 * v1_xy * mu_ + ) # total energy equation - du4 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + v2_y * (E + p) + v2 * (E_y + p_y) - - # stress tensor and temperature gradient terms from x-direction - 4.0 / 3.0 * v1_xx * v1 * mu_ + - 2.0 / 3.0 * v2_xy * v1 * mu_ - - 4.0 / 3.0 * v1_x * v1_x * mu_ + - 2.0 / 3.0 * v2_y * v1_x * mu_ - - v1_xy * v2 * mu_ - - v2_xx * v2 * mu_ - - v1_y * v2_x * mu_ - - v2_x * v2_x * mu_ - - T_const * inv_rho_cubed * - (p_xx * rho * rho - - 2.0 * p_x * rho * rho_x + - 2.0 * p * rho_x * rho_x - - p * rho * rho_xx) * mu_ - - # stress tensor and temperature gradient terms from y-direction - v1_yy * v1 * mu_ - - v2_xy * v1 * mu_ - - v1_y * v1_y * mu_ - - v2_x * v1_y * mu_ - - 4.0 / 3.0 * v2_yy * v2 * mu_ + - 2.0 / 3.0 * v1_xy * v2 * mu_ - - 4.0 / 3.0 * v2_y * v2_y * mu_ + - 2.0 / 3.0 * v1_x * v2_y * mu_ - - T_const * inv_rho_cubed * - (p_yy * rho * rho - - 2.0 * p_y * rho * rho_y + - 2.0 * p * rho_y * rho_y - - p * rho * rho_yy) * mu_) + du4 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) - + # stress tensor and temperature gradient terms from x-direction + 4.0 / 3.0 * v1_xx * v1 * mu_ + + 2.0 / 3.0 * v2_xy * v1 * mu_ - + 4.0 / 3.0 * v1_x * v1_x * mu_ + + 2.0 / 3.0 * v2_y * v1_x * mu_ - + v1_xy * v2 * mu_ - + v2_xx * v2 * mu_ - + v1_y * v2_x * mu_ - + v2_x * v2_x * mu_ - + T_const * inv_rho_cubed * + ( + p_xx * rho * rho - + 2.0 * p_x * rho * rho_x + + 2.0 * p * rho_x * rho_x - + p * rho * rho_xx + ) * mu_ - + # stress tensor and temperature gradient terms from y-direction + v1_yy * v1 * mu_ - + v2_xy * v1 * mu_ - + v1_y * v1_y * mu_ - + v2_x * v1_y * mu_ - + 4.0 / 3.0 * v2_yy * v2 * mu_ + + 2.0 / 3.0 * v1_xy * v2 * mu_ - + 4.0 / 3.0 * v2_y * v2_y * mu_ + + 2.0 / 3.0 * v1_x * v2_y * mu_ - + T_const * inv_rho_cubed * + ( + p_yy * rho * rho - + 2.0 * p_y * rho * rho_y + + 2.0 * p * rho_y * rho_y - + p * rho * rho_yy + ) * mu_ + ) return SVector(du1, du2, du3, du4) end @@ -178,32 +202,46 @@ end initial_condition = initial_condition_navier_stokes_convergence_test # BC types -velocity_bc_top_bottom = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2:3]) +velocity_bc_top_bottom = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2:3] +) heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, - heat_bc_top_bottom) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall( + velocity_bc_top_bottom, + heat_bc_top_bottom +) boundary_condition_left_right = BoundaryConditionDirichlet(initial_condition_navier_stokes_convergence_test) # define inviscid boundary conditions -boundary_conditions = Dict(:x_neg => boundary_condition_left_right, - :x_pos => boundary_condition_left_right, - :y_neg => boundary_condition_slip_wall, - :y_pos => boundary_condition_slip_wall) +boundary_conditions = Dict( + :x_neg => boundary_condition_left_right, + :x_pos => boundary_condition_left_right, + :y_neg => boundary_condition_slip_wall, + :y_pos => boundary_condition_slip_wall +) # define viscous boundary conditions -boundary_conditions_parabolic = Dict(:x_neg => boundary_condition_left_right, - :x_pos => boundary_condition_left_right, - :y_neg => boundary_condition_top_bottom, - :y_pos => boundary_condition_top_bottom) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +boundary_conditions_parabolic = Dict( + :x_neg => boundary_condition_left_right, + :x_pos => boundary_condition_left_right, + :y_neg => boundary_condition_top_bottom, + :y_pos => boundary_condition_top_bottom +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) # ############################################################################### # # ODE solvers, callbacks etc. @@ -221,7 +259,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1e-5, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1.0e-5, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_navierstokes_lid_driven_cavity.jl b/examples/p4est_2d_dgsem/elixir_navierstokes_lid_driven_cavity.jl index 728736fe49..94f638f107 100644 --- a/examples/p4est_2d_dgsem/elixir_navierstokes_lid_driven_cavity.jl +++ b/examples/p4est_2d_dgsem/elixir_navierstokes_lid_driven_cavity.jl @@ -8,8 +8,10 @@ prandtl_number() = 0.72 mu = 0.001 equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu, + Prandtl = prandtl_number() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -19,10 +21,12 @@ coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh trees_per_dimension = (4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = (false, false)) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = (false, false) +) function initial_condition_cavity(x, t, equations::CompressibleEulerEquations2D) Ma = 0.1 @@ -40,21 +44,29 @@ heat_bc = Adiabatic((x, t, equations) -> 0.0) boundary_condition_lid = BoundaryConditionNavierStokesWall(velocity_bc_lid, heat_bc) boundary_condition_cavity = BoundaryConditionNavierStokesWall(velocity_bc_cavity, heat_bc) -boundary_conditions = Dict(:x_neg => boundary_condition_slip_wall, - :y_neg => boundary_condition_slip_wall, - :y_pos => boundary_condition_slip_wall, - :x_pos => boundary_condition_slip_wall) +boundary_conditions = Dict( + :x_neg => boundary_condition_slip_wall, + :y_neg => boundary_condition_slip_wall, + :y_pos => boundary_condition_slip_wall, + :x_pos => boundary_condition_slip_wall +) -boundary_conditions_parabolic = Dict(:x_neg => boundary_condition_cavity, - :y_neg => boundary_condition_cavity, - :y_pos => boundary_condition_lid, - :x_pos => boundary_condition_cavity) +boundary_conditions_parabolic = Dict( + :x_neg => boundary_condition_cavity, + :y_neg => boundary_condition_cavity, + :y_pos => boundary_condition_lid, + :x_pos => boundary_condition_cavity +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -72,7 +84,9 @@ callbacks = CallbackSet(summary_callback, alive_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_navierstokes_lid_driven_cavity_amr.jl b/examples/p4est_2d_dgsem/elixir_navierstokes_lid_driven_cavity_amr.jl index d9eaf4fdb7..05e723ae14 100644 --- a/examples/p4est_2d_dgsem/elixir_navierstokes_lid_driven_cavity_amr.jl +++ b/examples/p4est_2d_dgsem/elixir_navierstokes_lid_driven_cavity_amr.jl @@ -8,8 +8,10 @@ prandtl_number() = 0.72 mu = 0.001 equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu, + Prandtl = prandtl_number() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -19,10 +21,12 @@ coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh trees_per_dimension = (6, 6) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = (false, false)) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = (false, false) +) function initial_condition_cavity(x, t, equations::CompressibleEulerEquations2D) Ma = 0.1 @@ -40,21 +44,29 @@ heat_bc = Adiabatic((x, t, equations) -> 0.0) boundary_condition_lid = BoundaryConditionNavierStokesWall(velocity_bc_lid, heat_bc) boundary_condition_cavity = BoundaryConditionNavierStokesWall(velocity_bc_cavity, heat_bc) -boundary_conditions = Dict(:x_neg => boundary_condition_slip_wall, - :y_neg => boundary_condition_slip_wall, - :y_pos => boundary_condition_slip_wall, - :x_pos => boundary_condition_slip_wall) +boundary_conditions = Dict( + :x_neg => boundary_condition_slip_wall, + :y_neg => boundary_condition_slip_wall, + :y_pos => boundary_condition_slip_wall, + :x_pos => boundary_condition_slip_wall +) -boundary_conditions_parabolic = Dict(:x_neg => boundary_condition_cavity, - :y_neg => boundary_condition_cavity, - :y_pos => boundary_condition_lid, - :x_pos => boundary_condition_cavity) +boundary_conditions_parabolic = Dict( + :x_neg => boundary_condition_cavity, + :y_neg => boundary_condition_cavity, + :y_pos => boundary_condition_lid, + :x_pos => boundary_condition_cavity +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -70,15 +82,19 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 0, - med_level = 1, med_threshold = 0.005, - max_level = 2, max_threshold = 0.01) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 0, + med_level = 1, med_threshold = 0.005, + max_level = 2, max_threshold = 0.01 +) -amr_callback = AMRCallback(semi, amr_controller, - interval = 50, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 50, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback, amr_callback) # callbacks = CallbackSet(summary_callback, alive_callback) @@ -86,8 +102,10 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback, amr ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/p4est_2d_dgsem/elixir_shallowwater_source_terms.jl b/examples/p4est_2d_dgsem/elixir_shallowwater_source_terms.jl index c7922fd3b7..52b1a827de 100644 --- a/examples/p4est_2d_dgsem/elixir_shallowwater_source_terms.jl +++ b/examples/p4est_2d_dgsem/elixir_shallowwater_source_terms.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,9 +12,11 @@ initial_condition = initial_condition_convergence_test # MMS EOC test # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the P4estMesh and setup a periodic mesh @@ -25,13 +26,17 @@ coordinates_max = (sqrt(2.0), sqrt(2.0)) # maximum coordinates (max(x), max(y)) # Create P4estMesh with 8 x 8 trees and 16 x 16 elements trees_per_dimension = (8, 8) -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 1) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 1 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -47,9 +52,11 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 200, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 200, + save_initial_solution = true, + save_final_solution = true +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) @@ -57,6 +64,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, sav # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_advection_amr.jl b/examples/p4est_3d_dgsem/elixir_advection_amr.jl index d56ecc233f..8b38e60a61 100644 --- a/examples/p4est_3d_dgsem/elixir_advection_amr.jl +++ b/examples/p4est_3d_dgsem/elixir_advection_amr.jl @@ -20,9 +20,11 @@ trees_per_dimension = (1, 1, 1) # Note that it is not necessary to use mesh polydeg lower than the solver polydeg # on a Cartesian mesh. # See https://doi.org/10.1007/s10915-018-00897-9, Section 6. -mesh = P4estMesh(trees_per_dimension, polydeg = 1, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 4) +mesh = P4estMesh( + trees_per_dimension, polydeg = 1, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 4 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -35,38 +37,50 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_advection_amr_unstructured_curved.jl b/examples/p4est_3d_dgsem/elixir_advection_amr_unstructured_curved.jl index 33afd2e030..10da805c28 100644 --- a/examples/p4est_3d_dgsem/elixir_advection_amr_unstructured_curved.jl +++ b/examples/p4est_3d_dgsem/elixir_advection_amr_unstructured_curved.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -29,35 +28,47 @@ function mapping(xi, eta, zeta) # zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 4 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 4 * ( + 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 + - 1 / 4 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 4 * ( + 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 + - 1 / 4 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 4 * ( + cos(0.5 * pi * (2 * x - 3) / 3) * + cos(pi * (2 * y - 3) / 3) * + cos(0.5 * pi * (2 * zeta - 3) / 3) + ) # Transform the weird deformed cube to be approximately the size of [-5,5]^3 to match IC return SVector(5 * x, 5 * y, 5 * z) end # Unstructured mesh with 48 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", - joinpath(@__DIR__, "cube_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", + joinpath(@__DIR__, "cube_unstructured_2.inp") +) # Mesh polydeg of 2 (half the solver polydeg) to ensure FSP (see above). -mesh = P4estMesh{3}(mesh_file, polydeg = 2, - mapping = mapping, - initial_refinement_level = 1) +mesh = P4estMesh{3}( + mesh_file, polydeg = 2, + mapping = mapping, + initial_refinement_level = 1 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -68,42 +79,56 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 1, - med_level = 2, med_threshold = 0.1, - max_level = 3, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) + +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 1, + med_level = 2, med_threshold = 0.1, + max_level = 3, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_restart, - save_solution, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_restart, + save_solution, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_advection_basic.jl b/examples/p4est_3d_dgsem/elixir_advection_basic.jl index 02ffa5c7af..7e88dd64b4 100644 --- a/examples/p4est_3d_dgsem/elixir_advection_basic.jl +++ b/examples/p4est_3d_dgsem/elixir_advection_basic.jl @@ -18,13 +18,17 @@ coordinates_max = (1.0, 1.0, 1.0) # maximum coordinates (max(x), max(y), max(z)) # Create P4estMesh with 8 x 8 x 8 elements (note `refinement_level=1`) trees_per_dimension = (4, 4, 4) -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 1) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 1 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -41,27 +45,35 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveRestartCallback allows to save a file from which a Trixi.jl simulation can be restarted -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.2) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/p4est_3d_dgsem/elixir_advection_cubed_sphere.jl b/examples/p4est_3d_dgsem/elixir_advection_cubed_sphere.jl index cd641ca4af..4e279c8d15 100644 --- a/examples/p4est_3d_dgsem/elixir_advection_cubed_sphere.jl +++ b/examples/p4est_3d_dgsem/elixir_advection_cubed_sphere.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,15 +13,21 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) initial_condition = initial_condition_convergence_test boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:inside => boundary_condition, - :outside => boundary_condition) +boundary_conditions = Dict( + :inside => boundary_condition, + :outside => boundary_condition +) -mesh = Trixi.P4estMeshCubedSphere(5, 3, 0.5, 0.5, - polydeg = 3, initial_refinement_level = 0) +mesh = Trixi.P4estMeshCubedSphere( + 5, 3, 0.5, 0.5, + polydeg = 3, initial_refinement_level = 0 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -39,23 +44,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.2) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/p4est_3d_dgsem/elixir_advection_nonconforming.jl b/examples/p4est_3d_dgsem/elixir_advection_nonconforming.jl index 34b0e95834..86c8febd4e 100644 --- a/examples/p4est_3d_dgsem/elixir_advection_nonconforming.jl +++ b/examples/p4est_3d_dgsem/elixir_advection_nonconforming.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -18,15 +17,17 @@ trees_per_dimension = (1, 1, 1) # Note that it is not necessary to use mesh polydeg lower than the solver polydeg # on a Cartesian mesh. # See https://doi.org/10.1007/s10915-018-00897-9, Section 6. -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 2) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 2 +) # Refine bottom left quadrant of each tree to level 3 function refine_fn(p8est, which_tree, quadrant) quadrant_obj = unsafe_load(quadrant) if quadrant_obj.x == 0 && quadrant_obj.y == 0 && quadrant_obj.z == 0 && - quadrant_obj.level < 3 + quadrant_obj.level < 3 # return true (refine) return Cint(1) else @@ -37,14 +38,20 @@ end # Refine recursively until each bottom left quadrant of a tree has level 3 # The mesh will be rebalanced before the simulation starts -refine_fn_c = @cfunction(refine_fn, Cint, - (Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, - Ptr{Trixi.p8est_quadrant_t})) +refine_fn_c = @cfunction( + refine_fn, Cint, + ( + Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, + Ptr{Trixi.p8est_quadrant_t}, + ) +) Trixi.refine_p4est!(mesh.p4est, true, refine_fn_c, C_NULL) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -61,23 +68,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/p4est_3d_dgsem/elixir_advection_restart.jl b/examples/p4est_3d_dgsem/elixir_advection_restart.jl index 9d19d81cf4..24a7475da3 100644 --- a/examples/p4est_3d_dgsem/elixir_advection_restart.jl +++ b/examples/p4est_3d_dgsem/elixir_advection_restart.jl @@ -1,12 +1,13 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # create a restart file -trixi_include(@__MODULE__, joinpath(@__DIR__, "elixir_advection_basic.jl"), - trees_per_dimension = (2, 2, 2)) +trixi_include( + @__MODULE__, joinpath(@__DIR__, "elixir_advection_basic.jl"), + trees_per_dimension = (2, 2, 2) +) ############################################################################### # adapt the parameters that have changed compared to "elixir_advection_extended.jl" @@ -17,8 +18,10 @@ trixi_include(@__MODULE__, joinpath(@__DIR__, "elixir_advection_basic.jl"), restart_filename = joinpath("out", "restart_000000010.h5") mesh = load_mesh(restart_filename) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) tspan = (load_time(restart_filename), 2.0) dt = load_dt(restart_filename) @@ -27,9 +30,11 @@ ode = semidiscretize(semi, tspan, restart_filename); # Do not overwrite the initial snapshot written by elixir_advection_extended.jl. save_solution.condition.save_initial_solution = false -integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks, maxiters = 100_000); +integrator = init( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks, maxiters = 100_000 +); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/p4est_3d_dgsem/elixir_advection_unstructured_curved.jl b/examples/p4est_3d_dgsem/elixir_advection_unstructured_curved.jl index 83adcbf6a6..2caa745585 100644 --- a/examples/p4est_3d_dgsem/elixir_advection_unstructured_curved.jl +++ b/examples/p4est_3d_dgsem/elixir_advection_unstructured_curved.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -27,34 +26,46 @@ function mapping(xi, eta, zeta) # zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 6 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 # Unstructured mesh with 68 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", - joinpath(@__DIR__, "cube_unstructured_1.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", + joinpath(@__DIR__, "cube_unstructured_1.inp") +) -mesh = P4estMesh{3}(mesh_file, polydeg = 3, - mapping = mapping, - initial_refinement_level = 2) +mesh = P4estMesh{3}( + mesh_file, polydeg = 3, + mapping = mapping, + initial_refinement_level = 2 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -73,27 +84,35 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) # The SaveRestartCallback allows to save a file from which a Trixi.jl simulation can be restarted -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.2) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_restart, - save_solution, stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_restart, + save_solution, stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/p4est_3d_dgsem/elixir_euler_baroclinic_instability.jl b/examples/p4est_3d_dgsem/elixir_euler_baroclinic_instability.jl index 0274a89aec..4806bb97e4 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_baroclinic_instability.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_baroclinic_instability.jl @@ -22,8 +22,10 @@ equations = CompressibleEulerEquations3D(gamma) # Initial condition for an idealized baroclinic instability test # https://doi.org/10.1002/qj.2241, Section 3.2 and Appendix A -function initial_condition_baroclinic_instability(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_baroclinic_instability( + x, t, + equations::CompressibleEulerEquations3D + ) lon, lat, r = cartesian_to_sphere(x) radius_earth = 6.371229e6 # Make sure that the r is not smaller than radius_earth @@ -85,7 +87,7 @@ function basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z) half_width_parameter = 2 # b gravitational_acceleration = 9.80616 # g k = 3 # k - surface_pressure = 1e5 # p₀ + surface_pressure = 1.0e5 # p₀ gas_constant = 287 # R surface_equatorial_temperature = 310.0 # T₀ᴱ surface_polar_temperature = 240.0 # T₀ᴾ @@ -100,9 +102,9 @@ function basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z) # In the paper: A, B, C, H const_a = 1 / lapse_rate const_b = (temperature0 - surface_polar_temperature) / - (temperature0 * surface_polar_temperature) + (temperature0 * surface_polar_temperature) const_c = 0.5 * (k + 2) * (surface_equatorial_temperature - surface_polar_temperature) / - (surface_equatorial_temperature * surface_polar_temperature) + (surface_equatorial_temperature * surface_polar_temperature) const_h = gas_constant * temperature0 / gravitational_acceleration # In the paper: (r - a) / bH @@ -114,7 +116,7 @@ function basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z) # In the paper: ̃τ₁, ̃τ₂ tau1 = const_a * lapse_rate / temperature0 * temp1 + - const_b * (1 - 2 * scaled_z^2) * temp2 + const_b * (1 - 2 * scaled_z^2) * temp2 tau2 = const_c * (1 - 2 * scaled_z^2) * temp2 # In the paper: ∫τ₁(r') dr', ∫τ₂(r') dr' @@ -130,7 +132,7 @@ function basic_state_baroclinic_instability_longitudinal_velocity(lon, lat, z) # In the paper: U, u (zonal wind, first component of spherical velocity) big_u = gravitational_acceleration / radius_earth * k * temperature * inttau2 * - (temp3^(k - 1) - temp3^(k + 1)) + (temp3^(k - 1) - temp3^(k + 1)) temp5 = radius_earth * cos(lat) u = -angular_velocity * temp5 + sqrt(angular_velocity^2 * temp5^2 + temp5 * big_u) @@ -156,9 +158,11 @@ function perturbation_stream_function(lon, lat, z) # Great circle distance (d in the paper) divided by a (radius of the Earth) # because we never actually need d without dividing by a - great_circle_distance_by_a = acos(sin(perturbation_lat) * sin(lat) + - cos(perturbation_lat) * cos(lat) * - cos(lon - perturbation_lon)) + great_circle_distance_by_a = acos( + sin(perturbation_lat) * sin(lat) + + cos(perturbation_lat) * cos(lat) * + cos(lon - perturbation_lon) + ) # In the first case, the vertical taper function is per definition zero. # In the second case, the stream function is per definition zero. @@ -175,18 +179,22 @@ function perturbation_stream_function(lon, lat, z) # Common factor for both u and v factor = 16 / (3 * sqrt(3)) * perturbed_wind_amplitude * perttaper * cos_^3 * sin_ - u_perturbation = -factor * (-sin(perturbation_lat) * cos(lat) + - cos(perturbation_lat) * sin(lat) * cos(lon - perturbation_lon)) / - sin(great_circle_distance_by_a) + u_perturbation = -factor * ( + -sin(perturbation_lat) * cos(lat) + + cos(perturbation_lat) * sin(lat) * cos(lon - perturbation_lon) + ) / + sin(great_circle_distance_by_a) v_perturbation = factor * cos(perturbation_lat) * sin(lon - perturbation_lon) / - sin(great_circle_distance_by_a) + sin(great_circle_distance_by_a) return u_perturbation, v_perturbation end -@inline function source_terms_baroclinic_instability(u, x, t, - equations::CompressibleEulerEquations3D) +@inline function source_terms_baroclinic_instability( + u, x, t, + equations::CompressibleEulerEquations3D + ) radius_earth = 6.371229e6 # a gravitational_acceleration = 9.80616 # g angular_velocity = 7.29212e-5 # Ω @@ -217,24 +225,32 @@ end initial_condition = initial_condition_baroclinic_instability -boundary_conditions = Dict(:inside => boundary_condition_slip_wall, - :outside => boundary_condition_slip_wall) +boundary_conditions = Dict( + :inside => boundary_condition_slip_wall, + :outside => boundary_condition_slip_wall +) # This is a good estimate for the speed of sound in this example. # Other values between 300 and 400 should work as well. surface_flux = FluxLMARS(340) volume_flux = flux_kennedy_gruber -solver = DGSEM(polydeg = 5, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 5, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # For optimal results, use (16, 8) here trees_per_cube_face = (8, 4) -mesh = Trixi.P4estMeshCubedSphere(trees_per_cube_face..., 6.371229e6, 30000.0, - polydeg = 5, initial_refinement_level = 0) +mesh = Trixi.P4estMeshCubedSphere( + trees_per_cube_face..., 6.371229e6, 30000.0, + polydeg = 5, initial_refinement_level = 0 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_baroclinic_instability, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_baroclinic_instability, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -277,23 +293,29 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 5000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 5000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution +) ############################################################################### # run the simulation # Use a Runge-Kutta method with automatic (error based) time step size control # Enable threading of the RK method for better performance on multiple threads -sol = solve(ode, RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()); abstol = 1.0e-6, - reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()); abstol = 1.0e-6, + reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_circular_wind_nonconforming.jl b/examples/p4est_3d_dgsem/elixir_euler_circular_wind_nonconforming.jl index 34a43a5b53..1336949ff3 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_circular_wind_nonconforming.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_circular_wind_nonconforming.jl @@ -16,7 +16,7 @@ equations = CompressibleEulerEquations3D(gamma) function initial_condition_circular_wind(x, t, equations::CompressibleEulerEquations3D) radius_earth = 6.371229e6 - p = 1e5 + p = 1.0e5 rho = 1.0 v1 = -10 * x[2] / radius_earth v2 = 10 * x[1] / radius_earth @@ -25,8 +25,10 @@ function initial_condition_circular_wind(x, t, equations::CompressibleEulerEquat return prim2cons(SVector(rho, v1, v2, v3, p), equations) end -@inline function source_terms_circular_wind(u, x, t, - equations::CompressibleEulerEquations3D) +@inline function source_terms_circular_wind( + u, x, t, + equations::CompressibleEulerEquations3D + ) radius_earth = 6.371229e6 rho = 1.0 @@ -39,15 +41,19 @@ end return SVector(du1, du2, du3, du4, du5) end -function indicator_test(u::AbstractArray{<:Any, 5}, - mesh, equations, dg::DGSEM, cache; - kwargs...) +function indicator_test( + u::AbstractArray{<:Any, 5}, + mesh, equations, dg::DGSEM, cache; + kwargs... + ) alpha = zeros(Int, nelements(dg, cache)) for element in eachelement(dg, cache) for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - x = Trixi.get_node_coords(cache.elements.node_coordinates, equations, dg, i, j, - k, element) + x = Trixi.get_node_coords( + cache.elements.node_coordinates, equations, dg, i, j, + k, element + ) lambda, phi, r = cart_to_sphere(x) if 0.22 < lambda < 3.3 && 0.45 < phi < 1.3 alpha[element] = 1 @@ -69,15 +75,19 @@ function cart_to_sphere(x) return lambda, phi, r end -function Trixi.get_element_variables!(element_variables, indicator::typeof(indicator_test), - ::AMRCallback) +function Trixi.get_element_variables!( + element_variables, indicator::typeof(indicator_test), + ::AMRCallback + ) return nothing end initial_condition = initial_condition_circular_wind -boundary_conditions = Dict(:inside => boundary_condition_slip_wall, - :outside => boundary_condition_slip_wall) +boundary_conditions = Dict( + :inside => boundary_condition_slip_wall, + :outside => boundary_condition_slip_wall +) # The speed of sound in this example is 374 m/s. surface_flux = FluxLMARS(374) @@ -111,12 +121,16 @@ solver = DGSEM(polydeg = 4, surface_flux = surface_flux) # periodicity=false) trees_per_cube_face = (6, 2) -mesh = Trixi.P4estMeshCubedSphere(trees_per_cube_face..., 6.371229e6, 30000.0, - polydeg = 4, initial_refinement_level = 0) +mesh = Trixi.P4estMeshCubedSphere( + trees_per_cube_face..., 6.371229e6, 30000.0, + polydeg = 4, initial_refinement_level = 0 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_circular_wind, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_circular_wind, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -131,32 +145,42 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 5000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, indicator_test, - base_level = 0, - max_level = 1, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 0, # Only initial refinement - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - amr_callback) +save_solution = SaveSolutionCallback( + interval = 5000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, indicator_test, + base_level = 0, + max_level = 1, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 0, # Only initial refinement + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback +) ############################################################################### # run the simulation # Use a Runge-Kutta method with automatic (error based) time step size control # Enable threading of the RK method for better performance on multiple threads -sol = solve(ode, RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()); abstol = 1.0e-6, - reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()); abstol = 1.0e-6, + reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_ec.jl b/examples/p4est_3d_dgsem/elixir_euler_ec.jl index 9169854505..fc30cdd1e1 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_ec.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,8 +13,10 @@ boundary_conditions = Dict(:all => boundary_condition_slip_wall) # Get the DG approximation space volume_flux = flux_ranocha -solver = DGSEM(polydeg = 5, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 5, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Get the curved quad mesh from a file @@ -27,35 +28,47 @@ function mapping(xi_, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 y = eta + - 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 # Unstructured mesh with 48 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", - joinpath(@__DIR__, "cube_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", + joinpath(@__DIR__, "cube_unstructured_2.inp") +) -mesh = P4estMesh{3}(mesh_file, polydeg = 5, - mapping = mapping, - initial_refinement_level = 0) +mesh = P4estMesh{3}( + mesh_file, polydeg = 5, + mapping = mapping, + initial_refinement_level = 0 +) # create the semidiscretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -70,22 +83,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_free_stream.jl b/examples/p4est_3d_dgsem/elixir_euler_free_stream.jl index 6406a38186..e731cbd1a0 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_free_stream.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_free_stream.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,8 +13,10 @@ boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition) # Solver with polydeg=4 to ensure free stream preservation (FSP) on non-conforming meshes. # The polydeg of the solver must be at least twice as big as the polydeg of the mesh. # See https://doi.org/10.1007/s10915-018-00897-9, Section 6. -solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 4, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) # Mapping as described in https://arxiv.org/abs/2012.12040 but with less warping. # The mapping will be interpolated at tree level, and then refined without changing @@ -28,37 +29,47 @@ function mapping(xi_, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 6 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 # Unstructured mesh with 68 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", - joinpath(@__DIR__, "cube_unstructured_1.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", + joinpath(@__DIR__, "cube_unstructured_1.inp") +) # Mesh polydeg of 2 (half the solver polydeg) to ensure FSP (see above). -mesh = P4estMesh{3}(mesh_file, polydeg = 2, - mapping = mapping, - initial_refinement_level = 0) +mesh = P4estMesh{3}( + mesh_file, polydeg = 2, + mapping = mapping, + initial_refinement_level = 0 +) # Refine bottom left quadrant of each second tree to level 2 function refine_fn(p8est, which_tree, quadrant) quadrant_obj = unsafe_load(quadrant) if iseven(convert(Int, which_tree)) && quadrant_obj.x == 0 && quadrant_obj.y == 0 && - quadrant_obj.z == 0 && quadrant_obj.level < 2 + quadrant_obj.z == 0 && quadrant_obj.level < 2 # return true (refine) return Cint(1) else @@ -69,13 +80,19 @@ end # Refine recursively until each bottom left quadrant of every second tree has level 2. # The mesh will be rebalanced before the simulation starts. -refine_fn_c = @cfunction(refine_fn, Cint, - (Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, - Ptr{Trixi.p8est_quadrant_t})) +refine_fn_c = @cfunction( + refine_fn, Cint, + ( + Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, + Ptr{Trixi.p8est_quadrant_t}, + ) +) Trixi.refine_p4est!(mesh.p4est, true, refine_fn_c, C_NULL) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -90,22 +107,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_free_stream_boundaries.jl b/examples/p4est_3d_dgsem/elixir_euler_free_stream_boundaries.jl index 8c008fac2e..d0e0f99a7a 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_free_stream_boundaries.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_free_stream_boundaries.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -17,19 +16,25 @@ solver = DGSEM(polydeg = polydeg, surface_flux = flux_lax_friedrichs) default_mesh_file = joinpath(@__DIR__, "mesh_cube_with_boundaries.inp") isfile(default_mesh_file) || - Trixi.download("https://gist.githubusercontent.com/DanielDoehring/710eab379fe3042dc08af6f2d1076e49/raw/38e9803bc0dab9b32a61d9542feac5343c3e6f4b/mesh_cube_with_boundaries.inp", - default_mesh_file) + Trixi.download( + "https://gist.githubusercontent.com/DanielDoehring/710eab379fe3042dc08af6f2d1076e49/raw/38e9803bc0dab9b32a61d9542feac5343c3e6f4b/mesh_cube_with_boundaries.inp", + default_mesh_file +) mesh_file = default_mesh_file boundary_symbols = [:PhysicalSurface1, :PhysicalSurface2] mesh = P4estMesh{3}(mesh_file, polydeg = polydeg, boundary_symbols = boundary_symbols) -boundary_conditions = Dict(:PhysicalSurface1 => BoundaryConditionDirichlet(initial_condition), - :PhysicalSurface2 => BoundaryConditionDirichlet(initial_condition)) +boundary_conditions = Dict( + :PhysicalSurface1 => BoundaryConditionDirichlet(initial_condition), + :PhysicalSurface2 => BoundaryConditionDirichlet(initial_condition) +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -46,14 +51,18 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_free_stream_boundaries_float32.jl b/examples/p4est_3d_dgsem/elixir_euler_free_stream_boundaries_float32.jl index c41ad9a422..a8d756b66b 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_free_stream_boundaries_float32.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_free_stream_boundaries_float32.jl @@ -19,20 +19,28 @@ solver = DGSEM(polydeg = polydeg, surface_flux = flux_lax_friedrichs, RealT = Fl default_mesh_file = joinpath(@__DIR__, "mesh_cube_with_boundaries.inp") isfile(default_mesh_file) || - Trixi.download("https://gist.githubusercontent.com/DanielDoehring/710eab379fe3042dc08af6f2d1076e49/raw/38e9803bc0dab9b32a61d9542feac5343c3e6f4b/mesh_cube_with_boundaries.inp", - default_mesh_file) + Trixi.download( + "https://gist.githubusercontent.com/DanielDoehring/710eab379fe3042dc08af6f2d1076e49/raw/38e9803bc0dab9b32a61d9542feac5343c3e6f4b/mesh_cube_with_boundaries.inp", + default_mesh_file +) mesh_file = default_mesh_file boundary_symbols = [:PhysicalSurface1, :PhysicalSurface2] -mesh = P4estMesh{3}(mesh_file, polydeg = polydeg, boundary_symbols = boundary_symbols, - RealT = Float32) +mesh = P4estMesh{3}( + mesh_file, polydeg = polydeg, boundary_symbols = boundary_symbols, + RealT = Float32 +) -boundary_conditions = Dict(:PhysicalSurface1 => BoundaryConditionDirichlet(initial_condition), - :PhysicalSurface2 => BoundaryConditionDirichlet(initial_condition)) +boundary_conditions = Dict( + :PhysicalSurface1 => BoundaryConditionDirichlet(initial_condition), + :PhysicalSurface2 => BoundaryConditionDirichlet(initial_condition) +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -49,14 +57,18 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.5f0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0f0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0f0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_free_stream_extruded.jl b/examples/p4est_3d_dgsem/elixir_euler_free_stream_extruded.jl index 08307a449a..a842408fd5 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_free_stream_extruded.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_free_stream_extruded.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -11,8 +10,10 @@ initial_condition = initial_condition_constant boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition)) -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) # Mapping as described in https://arxiv.org/abs/2012.12040 but reduced to 2D. # This particular mesh is unstructured in the yz-plane, but extruded in x-direction. @@ -25,28 +26,36 @@ function mapping(xi, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 z = zeta + - 1 / 6 * (cos(1.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + cos(1.5 * pi * (2 * eta - 3) / 3) * + cos(0.5 * pi * (2 * zeta - 3) / 3) + ) - y = eta + 1 / 6 * (cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(2 * pi * (2 * z - 3) / 3)) + y = eta + 1 / 6 * ( + cos(0.5 * pi * (2 * eta - 3) / 3) * + cos(2 * pi * (2 * z - 3) / 3) + ) return SVector(xi, y, z) end # Unstructured mesh with 48 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", - joinpath(@__DIR__, "cube_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", + joinpath(@__DIR__, "cube_unstructured_2.inp") +) -mesh = P4estMesh{3}(mesh_file, polydeg = 3, - mapping = mapping, - initial_refinement_level = 0) +mesh = P4estMesh{3}( + mesh_file, polydeg = 3, + mapping = mapping, + initial_refinement_level = 0 +) # Refine quadrants in y-direction of each tree at one edge to level 2 function refine_fn(p8est, which_tree, quadrant) quadrant_obj = unsafe_load(quadrant) if convert(Int, which_tree) < 4 && quadrant_obj.x == 0 && quadrant_obj.y == 0 && - quadrant_obj.level < 2 + quadrant_obj.level < 2 # return true (refine) return Cint(1) else @@ -57,13 +66,19 @@ end # Refine recursively until each desired quadrant has level 2. # The mesh will be rebalanced before the simulation starts. -refine_fn_c = @cfunction(refine_fn, Cint, - (Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, - Ptr{Trixi.p8est_quadrant_t})) +refine_fn_c = @cfunction( + refine_fn, Cint, + ( + Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, + Ptr{Trixi.p8est_quadrant_t}, + ) +) Trixi.refine_p4est!(mesh.p4est, true, refine_fn_c, C_NULL) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -78,25 +93,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), #maxiters=1, - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), #maxiters=1, + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_sedov.jl b/examples/p4est_3d_dgsem/elixir_euler_sedov.jl index 8df95a3cc2..13d7964399 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_sedov.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_sedov.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,8 +13,10 @@ The Sedov blast wave setup based on Flash - https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node187.html#SECTION010114000000000000000 with smaller strength of the initial discontinuity. """ -function initial_condition_medium_sedov_blast_wave(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_medium_sedov_blast_wave( + x, t, + equations::CompressibleEulerEquations3D + ) # Set up polar coordinates inicenter = SVector(0.0, 0.0, 0.0) x_norm = x[1] - inicenter[1] @@ -45,26 +46,34 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 5 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) coordinates_min = (-1.0, -1.0, -1.0) coordinates_max = (1.0, 1.0, 1.0) trees_per_dimension = (4, 4, 4) -mesh = P4estMesh(trees_per_dimension, - polydeg = 4, initial_refinement_level = 0, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 4, initial_refinement_level = 0, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = true +) # create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -82,22 +91,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonconforming_earth.jl b/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonconforming_earth.jl index 28cdec12da..240ac1a2c3 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonconforming_earth.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonconforming_earth.jl @@ -11,31 +11,39 @@ using LinearAlgebra gamma = 1.4 equations = CompressibleEulerEquations3D(gamma) -function initial_condition_convergence_test_sphere(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_convergence_test_sphere( + x, t, + equations::CompressibleEulerEquations3D + ) x_scaled = x / 6.371229e6 t_scaled = t / 6.371229e6 return initial_condition_convergence_test(x_scaled, t_scaled, equations) end -@inline function source_terms_convergence_test_sphere(u, x, t, - equations::CompressibleEulerEquations3D) +@inline function source_terms_convergence_test_sphere( + u, x, t, + equations::CompressibleEulerEquations3D + ) x_scaled = x / 6.371229e6 t_scaled = t / 6.371229e6 return source_terms_convergence_test(u, x_scaled, t_scaled, equations) / 6.371229e6 end -function indicator_test(u::AbstractArray{<:Any, 5}, - mesh, equations, dg::DGSEM, cache; - kwargs...) +function indicator_test( + u::AbstractArray{<:Any, 5}, + mesh, equations, dg::DGSEM, cache; + kwargs... + ) alpha = zeros(Int, nelements(dg, cache)) for element in eachelement(dg, cache) for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - x = Trixi.get_node_coords(cache.elements.node_coordinates, equations, dg, i, j, - k, element) + x = Trixi.get_node_coords( + cache.elements.node_coordinates, equations, dg, i, j, + k, element + ) lambda, phi, r = cart_to_sphere(x) if 0.22 < lambda < 3.3 && 0.45 < phi < 1.3 alpha[element] = 1 @@ -57,16 +65,20 @@ function cart_to_sphere(x) return lambda, phi, r end -function Trixi.get_element_variables!(element_variables, indicator::typeof(indicator_test), - ::AMRCallback) +function Trixi.get_element_variables!( + element_variables, indicator::typeof(indicator_test), + ::AMRCallback + ) return nothing end initial_condition = initial_condition_convergence_test_sphere boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:inside => boundary_condition, - :outside => boundary_condition) +boundary_conditions = Dict( + :inside => boundary_condition, + :outside => boundary_condition +) surface_flux = FluxHLL(min_max_speed_naive) # Note that a free stream is not preserved if N < 2 * N_geo, where N is the @@ -86,17 +98,21 @@ solver = DGSEM(polydeg = 4, surface_flux = surface_flux) # periodicity=false) trees_per_cube_face = (6, 2) -mesh = Trixi.P4estMeshCubedSphere(trees_per_cube_face..., 6.371229e6, 30000.0, - polydeg = 4, initial_refinement_level = 0) +mesh = Trixi.P4estMeshCubedSphere( + trees_per_cube_face..., 6.371229e6, 30000.0, + polydeg = 4, initial_refinement_level = 0 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test_sphere, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test_sphere, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. -tspan = (0.0, 1e5) +tspan = (0.0, 1.0e5) ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() @@ -106,30 +122,40 @@ 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) - -amr_controller = ControllerThreeLevel(semi, indicator_test, - base_level = 0, - max_level = 1, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 0, # Only initial refinement - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - amr_callback) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, indicator_test, + base_level = 0, + max_level = 1, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 0, # Only initial refinement + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback +) ############################################################################### # run the simulation # Use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_curved.jl b/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_curved.jl index e7ca0cad4b..c1c3c89313 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_curved.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_curved.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,8 +14,10 @@ boundary_conditions = Dict(:all => boundary_condition) # Solver with polydeg=4 to ensure free stream preservation (FSP) on non-conforming meshes. # The polydeg of the solver must be at least twice as big as the polydeg of the mesh. # See https://doi.org/10.1007/s10915-018-00897-9, Section 6. -solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 4, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) # Mapping as described in https://arxiv.org/abs/2012.12040 but with less warping. # The mapping will be interpolated at tree level, and then refined without changing @@ -29,38 +30,48 @@ function mapping(xi, eta, zeta) # zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 6 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + cos(0.5 * pi * (2 * x - 3) / 3) * + cos(pi * (2 * y - 3) / 3) * + cos(0.5 * pi * (2 * zeta - 3) / 3) + ) # Transform the weird deformed cube to be approximately the cube [0,2]^3 return SVector(x + 1, y + 1, z + 1) end # Unstructured mesh with 68 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", - joinpath(@__DIR__, "cube_unstructured_1.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", + joinpath(@__DIR__, "cube_unstructured_1.inp") +) # Mesh polydeg of 2 (half the solver polydeg) to ensure FSP (see above). -mesh = P4estMesh{3}(mesh_file, polydeg = 2, - mapping = mapping, - initial_refinement_level = 0) +mesh = P4estMesh{3}( + mesh_file, polydeg = 2, + mapping = mapping, + initial_refinement_level = 0 +) # Refine bottom left quadrant of each tree to level 2 function refine_fn(p8est, which_tree, quadrant) quadrant_obj = unsafe_load(quadrant) if quadrant_obj.x == 0 && quadrant_obj.y == 0 && quadrant_obj.z == 0 && - quadrant_obj.level < 2 + quadrant_obj.level < 2 # return true (refine) return Cint(1) else @@ -71,14 +82,20 @@ end # Refine recursively until each bottom left quadrant of a tree has level 2 # The mesh will be rebalanced before the simulation starts -refine_fn_c = @cfunction(refine_fn, Cint, - (Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, - Ptr{Trixi.p8est_quadrant_t})) +refine_fn_c = @cfunction( + refine_fn, Cint, + ( + Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, + Ptr{Trixi.p8est_quadrant_t}, + ) +) Trixi.refine_p4est!(mesh.p4est, true, refine_fn_c, C_NULL) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -93,22 +110,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonperiodic.jl b/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonperiodic.jl index fc5e4da3ce..866a5fe954 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonperiodic.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,28 +9,36 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:x_neg => boundary_condition, - :x_pos => boundary_condition, - :y_neg => boundary_condition, - :y_pos => boundary_condition, - :z_neg => boundary_condition, - :z_pos => boundary_condition) - -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +boundary_conditions = Dict( + :x_neg => boundary_condition, + :x_pos => boundary_condition, + :y_neg => boundary_condition, + :y_pos => boundary_condition, + :z_neg => boundary_condition, + :z_pos => boundary_condition +) + +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (2.0, 2.0, 2.0) trees_per_dimension = (2, 2, 2) -mesh = P4estMesh(trees_per_dimension, polydeg = 1, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = false, initial_refinement_level = 1) +mesh = P4estMesh( + trees_per_dimension, polydeg = 1, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = false, initial_refinement_level = 1 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -46,22 +53,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonperiodic_hohqmesh.jl b/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonperiodic_hohqmesh.jl index 7d81d6739b..f4abf55cd2 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonperiodic_hohqmesh.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_source_terms_nonperiodic_hohqmesh.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,22 +9,28 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:Bottom => boundary_condition, - :Top => boundary_condition, - :Circle => boundary_condition, - :Cut => boundary_condition) +boundary_conditions = Dict( + :Bottom => boundary_condition, + :Top => boundary_condition, + :Circle => boundary_condition, + :Cut => boundary_condition +) solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs) # Unstructured 3D half circle mesh from HOHQMesh -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/11461efbfb02c42e06aca338b3d0b645/raw/81deeb1ebc4945952c30af5bb75fe222a18d975c/abaqus_half_circle_3d.inp", - joinpath(@__DIR__, "abaqus_half_circle_3d.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/11461efbfb02c42e06aca338b3d0b645/raw/81deeb1ebc4945952c30af5bb75fe222a18d975c/abaqus_half_circle_3d.inp", + joinpath(@__DIR__, "abaqus_half_circle_3d.inp") +) mesh = P4estMesh{3}(mesh_file, initial_refinement_level = 0) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -36,29 +41,37 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 50, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 50, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_euler_weak_blast_wave_amr.jl b/examples/p4est_3d_dgsem/elixir_euler_weak_blast_wave_amr.jl index b5b5622000..866d6ac3fa 100644 --- a/examples/p4est_3d_dgsem/elixir_euler_weak_blast_wave_amr.jl +++ b/examples/p4est_3d_dgsem/elixir_euler_weak_blast_wave_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -7,8 +6,10 @@ using Trixi equations = CompressibleEulerEquations3D(1.4) -function initial_condition_weak_blast_wave(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_weak_blast_wave( + x, t, + equations::CompressibleEulerEquations3D + ) # Set up polar coordinates inicenter = SVector(0.0, 0.0, 0.0) x_norm = x[1] - inicenter[1] @@ -37,17 +38,23 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Setup a periodic mesh with 4 x 4 x 4 trees and 8 x 8 x 8 elements trees_per_dimension = (4, 4, 4) @@ -64,11 +71,13 @@ function mapping_twist(xi, eta, zeta) return SVector(x, y, z) end -mesh = P4estMesh(trees_per_dimension, - polydeg = 2, - mapping = mapping_twist, - initial_refinement_level = 1, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 2, + mapping = mapping_twist, + initial_refinement_level = 1, + periodicity = true +) # Create the semidiscretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -82,33 +91,43 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:conservation_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = (:conservation_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 1, - med_level = 2, med_threshold = 0.05, - max_level = 3, max_threshold = 0.15) -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = false, - adapt_initial_condition_only_refine = false) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 1, + med_level = 2, med_threshold = 0.05, + max_level = 3, max_threshold = 0.15 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = false, + adapt_initial_condition_only_refine = false +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_linearizedeuler_convergence.jl b/examples/p4est_3d_dgsem/elixir_linearizedeuler_convergence.jl index fd066ef895..be7059b198 100644 --- a/examples/p4est_3d_dgsem/elixir_linearizedeuler_convergence.jl +++ b/examples/p4est_3d_dgsem/elixir_linearizedeuler_convergence.jl @@ -4,8 +4,10 @@ using Trixi ############################################################################### # semidiscretization of the linearized Euler equations -equations = LinearizedEulerEquations3D(v_mean_global = (0.0, 0.0, 0.0), c_mean_global = 1.0, - rho_mean_global = 1.0) +equations = LinearizedEulerEquations3D( + v_mean_global = (0.0, 0.0, 0.0), c_mean_global = 1.0, + rho_mean_global = 1.0 +) initial_condition = initial_condition_convergence_test @@ -14,14 +16,16 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_hll) coordinates_min = (-1.0, -1.0, -1.0) # minimum coordinates (min(x), min(y), min(z)) coordinates_max = (1.0, 1.0, 1.0) # maximum coordinates (max(x), max(y), max(z)) -# `initial_refinement_level` is provided here to allow for a +# `initial_refinement_level` is provided here to allow for a # convenient convergence test, see # https://trixi-framework.github.io/Trixi.jl/stable/#Performing-a-convergence-analysis trees_per_dimension = (4, 4, 4) -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, - coordinates_max = coordinates_max, - initial_refinement_level = 0) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, + coordinates_max = coordinates_max, + initial_refinement_level = 0 +) # A semidiscretization collects data structures and functions for the spatial discretization semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -49,16 +53,20 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.8) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # print the timer summary summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_mhd_alfven_wave_nonconforming.jl b/examples/p4est_3d_dgsem/elixir_mhd_alfven_wave_nonconforming.jl index 12ddf9e4a5..2d4072c516 100644 --- a/examples/p4est_3d_dgsem/elixir_mhd_alfven_wave_nonconforming.jl +++ b/examples/p4est_3d_dgsem/elixir_mhd_alfven_wave_nonconforming.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,27 +9,33 @@ equations = IdealGlmMhdEquations3D(5 / 3) initial_condition = initial_condition_convergence_test volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_hlle, - flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = ( + flux_hlle, + flux_nonconservative_powell, + ), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0, -1.0) coordinates_max = (1.0, 1.0, 1.0) # Create P4estMesh with 2 x 2 x 2 trees trees_per_dimension = (2, 2, 2) -mesh = P4estMesh(trees_per_dimension, - polydeg = 3, initial_refinement_level = 2, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = true) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, initial_refinement_level = 2, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = true +) # OBS! Workaround to add a refinement patch after mesh is constructed # Refine bottom left quadrant of each tree to level 4 function refine_fn(p8est, which_tree, quadrant) quadrant_obj = unsafe_load(quadrant) if quadrant_obj.x == 0 && quadrant_obj.y == 0 && quadrant_obj.z == 0 && - quadrant_obj.level < 4 + quadrant_obj.level < 4 # return true (refine) return Cint(1) else @@ -41,9 +46,13 @@ end # Refine recursively until each bottom left quadrant of a tree has level 4 # The mesh will be rebalanced before the simulation starts -refine_fn_c = @cfunction(refine_fn, Cint, - (Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, - Ptr{Trixi.p8est_quadrant_t})) +refine_fn_c = @cfunction( + refine_fn, Cint, + ( + Ptr{Trixi.p8est_t}, Ptr{Trixi.p4est_topidx_t}, + Ptr{Trixi.p8est_quadrant_t}, + ) +) Trixi.refine_p4est!(mesh.p4est, true, refine_fn_c, C_NULL) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -60,26 +69,32 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_mhd_shockcapturing_amr.jl b/examples/p4est_3d_dgsem/elixir_mhd_shockcapturing_amr.jl index 3941a40b2e..d31353de8d 100644 --- a/examples/p4est_3d_dgsem/elixir_mhd_shockcapturing_amr.jl +++ b/examples/p4est_3d_dgsem/elixir_mhd_shockcapturing_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -40,17 +39,23 @@ surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = 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 @@ -62,29 +67,37 @@ function mapping(xi_, eta_, zeta_) 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)) + 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)) + 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)) + 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) +mesh = P4estMesh( + trees_per_dimension, + polydeg = 3, + mapping = mapping, + initial_refinement_level = 2, + periodicity = true +) # create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -102,32 +115,42 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -amr_indicator = IndicatorLöhner(semi, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 2, - max_level = 4, max_threshold = 0.15) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_indicator = IndicatorLöhner( + semi, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 2, + max_level = 4, max_threshold = 0.15 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) cfl = 1.4 stepsize_callback = StepsizeCallback(cfl = cfl) glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_navierstokes_blast_wave_amr.jl b/examples/p4est_3d_dgsem/elixir_navierstokes_blast_wave_amr.jl index d556d0ab70..501235baa4 100644 --- a/examples/p4est_3d_dgsem/elixir_navierstokes_blast_wave_amr.jl +++ b/examples/p4est_3d_dgsem/elixir_navierstokes_blast_wave_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,8 +8,10 @@ prandtl_number() = 0.72 mu = 6.25e-4 # equivalent to Re = 1600 equations = CompressibleEulerEquations3D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion3D( + equations, mu = mu, + Prandtl = prandtl_number() +) function initial_condition_3d_blast_wave(x, t, equations::CompressibleEulerEquations3D) rho_c = 1.0 @@ -45,29 +46,39 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) coordinates_min = (-1.0, -1.0, -1.0) .* pi coordinates_max = (1.0, 1.0, 1.0) .* pi trees_per_dimension = (4, 4, 4) -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = (true, true, true), initial_refinement_level = 1) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = (true, true, true), initial_refinement_level = 1 +) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -79,34 +90,44 @@ summary_callback = SummaryCallback() analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = analysis_interval, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = analysis_interval, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 0, - med_level = 1, med_threshold = 0.05, - max_level = 3, max_threshold = 0.1) -amr_callback = AMRCallback(semi, amr_controller, - interval = 10, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 0, + med_level = 1, med_threshold = 0.05, + max_level = 3, max_threshold = 0.1 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 10, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + save_solution +) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_navierstokes_convergence.jl b/examples/p4est_3d_dgsem/elixir_navierstokes_convergence.jl index c640b255b0..52600ac2f5 100644 --- a/examples/p4est_3d_dgsem/elixir_navierstokes_convergence.jl +++ b/examples/p4est_3d_dgsem/elixir_navierstokes_convergence.jl @@ -8,22 +8,28 @@ prandtl_number() = 0.72 mu() = 0.01 equations = CompressibleEulerEquations3D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion3D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = (-1.0, -1.0, -1.0) # minimum coordinates (min(x), min(y), min(z)) coordinates_max = (1.0, 1.0, 1.0) # maximum coordinates (max(x), max(y), max(z)) trees_per_dimension = (2, 2, 2) -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = (true, false, true), initial_refinement_level = 2) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = (true, false, true), initial_refinement_level = 2 +) # Note: the initial condition cannot be specialized to `CompressibleNavierStokesDiffusion3D` # since it is called by both the parabolic solver (which passes in `CompressibleNavierStokesDiffusion3D`) @@ -44,7 +50,7 @@ function initial_condition_navier_stokes_convergence_test(x, t, equations) rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) v1 = A2 * sin(pi_x) * log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) * sin(pi_z) * - cos(pi_t) + cos(pi_t) v2 = v1 v3 = v1 p = rho^2 @@ -75,11 +81,15 @@ end # Define auxiliary functions for the strange function of the y variable # to make expressions easier to read g = log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) - g_y = (A3 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + - (1.0 - exp(-A3 * (x[2] - 1.0))) / (x[2] + 2.0)) - g_yy = (2.0 * A3 * exp(-A3 * (x[2] - 1.0)) / (x[2] + 2.0) - + g_y = ( + A3 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + + (1.0 - exp(-A3 * (x[2] - 1.0))) / (x[2] + 2.0) + ) + g_yy = ( + 2.0 * A3 * exp(-A3 * (x[2] - 1.0)) / (x[2] + 2.0) - (1.0 - exp(-A3 * (x[2] - 1.0))) / ((x[2] + 2.0)^2) - - A3^2 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0))) + A3^2 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + ) # Density and its derivatives rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) @@ -169,52 +179,68 @@ end # Compute the source terms # Density equation - du1 = (rho_t + rho_x * v1 + rho * v1_x - + rho_y * v2 + rho * v2_y - + rho_z * v3 + rho * v3_z) + du1 = ( + rho_t + rho_x * v1 + rho * v1_x + + rho_y * v2 + rho * v2_y + + rho_z * v3 + rho * v3_z + ) # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 - + 2.0 * rho * v1 * v1_x - + rho_y * v1 * v2 - + rho * v1_y * v2 - + rho * v1 * v2_y - + rho_z * v1 * v3 - + rho * v1_z * v3 - + rho * v1 * v3_z - - mu_ * (tau11_x + tau12_y + tau13_z)) + du2 = ( + rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y + + rho_z * v1 * v3 + + rho * v1_z * v3 + + rho * v1 * v3_z - + mu_ * (tau11_x + tau12_y + tau13_z) + ) # y-momentum equation - du3 = (rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 - + rho * v1_x * v2 - + rho * v1 * v2_x - + rho_y * v2^2 - + 2.0 * rho * v2 * v2_y - + rho_z * v2 * v3 - + rho * v2_z * v3 - + rho * v2 * v3_z - - mu_ * (tau12_x + tau22_y + tau23_z)) + du3 = ( + rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y + + rho_z * v2 * v3 + + rho * v2_z * v3 + + rho * v2 * v3_z - + mu_ * (tau12_x + tau22_y + tau23_z) + ) # z-momentum equation - du4 = (rho_t * v3 + rho * v3_t + p_z + rho_x * v1 * v3 - + rho * v1_x * v3 - + rho * v1 * v3_x - + rho_y * v2 * v3 - + rho * v2_y * v3 - + rho * v2 * v3_y - + rho_z * v3^2 - + 2.0 * rho * v3 * v3_z - - mu_ * (tau13_x + tau23_y + tau33_z)) + du4 = ( + rho_t * v3 + rho * v3_t + p_z + rho_x * v1 * v3 + + rho * v1_x * v3 + + rho * v1 * v3_x + + rho_y * v2 * v3 + + rho * v2_y * v3 + + rho * v2 * v3_y + + rho_z * v3^2 + + 2.0 * rho * v3 * v3_z - + mu_ * (tau13_x + tau23_y + tau33_z) + ) # Total energy equation - du5 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + v2_y * (E + p) + v2 * (E_y + p_y) - + v3_z * (E + p) + v3 * (E_z + p_z) - - # stress tensor and temperature gradient from x-direction - mu_ * (q_xx + v1_x * tau11 + v2_x * tau12 + v3_x * tau13 - + v1 * tau11_x + v2 * tau12_x + v3 * tau13_x) - - # stress tensor and temperature gradient terms from y-direction - mu_ * (q_yy + v1_y * tau12 + v2_y * tau22 + v3_y * tau23 - + v1 * tau12_y + v2 * tau22_y + v3 * tau23_y) - - # stress tensor and temperature gradient terms from z-direction - mu_ * (q_zz + v1_z * tau13 + v2_z * tau23 + v3_z * tau33 - + v1 * tau13_z + v2 * tau23_z + v3 * tau33_z)) + du5 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) + + v3_z * (E + p) + v3 * (E_z + p_z) - + # stress tensor and temperature gradient from x-direction + mu_ * ( + q_xx + v1_x * tau11 + v2_x * tau12 + v3_x * tau13 + + v1 * tau11_x + v2 * tau12_x + v3 * tau13_x + ) - + # stress tensor and temperature gradient terms from y-direction + mu_ * ( + q_yy + v1_y * tau12 + v2_y * tau22 + v3_y * tau23 + + v1 * tau12_y + v2 * tau22_y + v3 * tau23_y + ) - + # stress tensor and temperature gradient terms from z-direction + mu_ * ( + q_zz + v1_z * tau13 + v2_z * tau23 + v3_z * tau33 + + v1 * tau13_z + v2 * tau23_z + v3 * tau33_z + ) + ) return SVector(du1, du2, du3, du4, du5) end @@ -227,22 +253,32 @@ velocity_bc_top_bottom = NoSlip() do x, t, equations return SVector(u[2], u[3], u[4]) end heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, - heat_bc_top_bottom) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall( + velocity_bc_top_bottom, + heat_bc_top_bottom +) # define inviscid boundary conditions -boundary_conditions = Dict(:y_neg => boundary_condition_slip_wall, - :y_pos => boundary_condition_slip_wall) +boundary_conditions = Dict( + :y_neg => boundary_condition_slip_wall, + :y_pos => boundary_condition_slip_wall +) # define viscous boundary conditions -boundary_conditions_parabolic = Dict(:y_neg => boundary_condition_top_bottom, - :y_pos => boundary_condition_top_bottom) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +boundary_conditions_parabolic = Dict( + :y_neg => boundary_condition_top_bottom, + :y_pos => boundary_condition_top_bottom +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -260,7 +296,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1e-5, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1.0e-5, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl b/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl index 9c90e4d321..97d2be0748 100644 --- a/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl +++ b/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,16 +8,20 @@ prandtl_number() = 0.72 mu = 6.25e-4 # equivalent to Re = 1600 equations = CompressibleEulerEquations3D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion3D( + equations, mu = mu, + Prandtl = prandtl_number() +) """ initial_condition_taylor_green_vortex(x, t, equations::CompressibleEulerEquations3D) The classical inviscid Taylor-Green vortex. """ -function initial_condition_taylor_green_vortex(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_taylor_green_vortex( + x, t, + equations::CompressibleEulerEquations3D + ) A = 1.0 # magnitude of speed Ms = 0.1 # maximum Mach number @@ -29,28 +32,36 @@ function initial_condition_taylor_green_vortex(x, t, p = (A / Ms)^2 * rho / equations.gamma # scaling to get Ms p = p + 1.0 / 16.0 * A^2 * rho * - (cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + - cos(2 * x[2]) * cos(2 * x[3])) + ( + cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + + cos(2 * x[2]) * cos(2 * x[3]) + ) return prim2cons(SVector(rho, v1, v2, v3, p), equations) end initial_condition = initial_condition_taylor_green_vortex volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_hll, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hll, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0, -1.0) .* pi coordinates_max = (1.0, 1.0, 1.0) .* pi trees_per_dimension = (2, 2, 2) -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = (true, true, true), initial_refinement_level = 2) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = (true, true, true), initial_refinement_level = 2 +) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -61,25 +72,35 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 50 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (energy_kinetic, - energy_internal, - enstrophy)) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + enstrophy, + ) +) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, save_solution +) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex_amr.jl b/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex_amr.jl index 2741f0df17..023f1274cc 100644 --- a/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex_amr.jl +++ b/examples/p4est_3d_dgsem/elixir_navierstokes_taylor_green_vortex_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,16 +8,20 @@ prandtl_number() = 0.72 mu = 6.25e-4 # equivalent to Re = 1600 equations = CompressibleEulerEquations3D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion3D( + equations, mu = mu, + Prandtl = prandtl_number() +) """ initial_condition_taylor_green_vortex(x, t, equations::CompressibleEulerEquations3D) The classical Taylor-Green vortex. """ -function initial_condition_taylor_green_vortex(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_taylor_green_vortex( + x, t, + equations::CompressibleEulerEquations3D + ) A = 1.0 # magnitude of speed Ms = 0.1 # maximum Mach number @@ -29,8 +32,10 @@ function initial_condition_taylor_green_vortex(x, t, p = (A / Ms)^2 * rho / equations.gamma # scaling to get Ms p = p + 1.0 / 16.0 * A^2 * rho * - (cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + - cos(2 * x[2]) * cos(2 * x[3])) + ( + cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + + cos(2 * x[2]) * cos(2 * x[3]) + ) return prim2cons(SVector(rho, v1, v2, v3, p), equations) end @@ -42,20 +47,26 @@ initial_condition = initial_condition_taylor_green_vortex end volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0, -1.0) .* pi coordinates_max = (1.0, 1.0, 1.0) .* pi trees_per_dimension = (2, 2, 2) -mesh = P4estMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = (true, true, true), initial_refinement_level = 0) +mesh = P4estMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = (true, true, true), initial_refinement_level = 0 +) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -66,40 +77,54 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 50 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (energy_kinetic, - energy_internal, - enstrophy)) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + enstrophy, + ) +) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) amr_indicator = IndicatorLöhner(semi, variable = vel_mag) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 0, - med_level = 1, med_threshold = 0.1, - max_level = 3, max_threshold = 0.2) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 0, + med_level = 1, med_threshold = 0.1, + max_level = 3, max_threshold = 0.2 +) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = false, - adapt_initial_condition_only_refine = false) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = false, + adapt_initial_condition_only_refine = false +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + save_solution +) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/paper_self_gravitating_gas_dynamics/elixir_euler_convergence.jl b/examples/paper_self_gravitating_gas_dynamics/elixir_euler_convergence.jl index 4f44d7b12a..b5162f3198 100644 --- a/examples/paper_self_gravitating_gas_dynamics/elixir_euler_convergence.jl +++ b/examples/paper_self_gravitating_gas_dynamics/elixir_euler_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,12 +11,16 @@ solver = DGSEM(polydeg = 3, surface_flux = FluxHLL(min_max_speed_naive)) coordinates_min = (0.0, 0.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_eoc_test_euler) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_eoc_test_euler +) ############################################################################### # ODE solvers, callbacks etc. @@ -29,23 +32,29 @@ summary_callback = SummaryCallback() stepsize_callback = StepsizeCallback(cfl = 0.8) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) analysis_interval = 100 alive_callback = AliveCallback(analysis_interval = analysis_interval) analysis_callback = AnalysisCallback(semi, interval = analysis_interval) -callbacks = CallbackSet(summary_callback, stepsize_callback, - save_solution, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, stepsize_callback, + save_solution, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_convergence.jl b/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_convergence.jl index 49b9880357..9c78f91735 100644 --- a/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_convergence.jl +++ b/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,13 +13,17 @@ solver_euler = DGSEM(polydeg, FluxHLL(min_max_speed_naive)) coordinates_min = (0.0, 0.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) - -semi_euler = SemidiscretizationHyperbolic(mesh, equations_euler, initial_condition, - solver_euler, - source_terms = source_terms_eoc_test_coupled_euler_gravity) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) + +semi_euler = SemidiscretizationHyperbolic( + mesh, equations_euler, initial_condition, + solver_euler, + source_terms = source_terms_eoc_test_coupled_euler_gravity +) ############################################################################### # semidiscretization of the hyperbolic diffusion equations @@ -28,20 +31,24 @@ equations_gravity = HyperbolicDiffusionEquations2D() solver_gravity = DGSEM(polydeg, flux_lax_friedrichs) -semi_gravity = SemidiscretizationHyperbolic(mesh, equations_gravity, initial_condition, - solver_gravity, - source_terms = source_terms_harmonic) +semi_gravity = SemidiscretizationHyperbolic( + mesh, equations_gravity, initial_condition, + solver_gravity, + source_terms = source_terms_harmonic +) ############################################################################### # combining both semidiscretizations for Euler + self-gravity -parameters = ParametersEulerGravity(background_density = 2.0, # aka rho0 - # rho0 is (ab)used to add a "+8π" term to the source terms - # for the manufactured solution - gravitational_constant = 1.0, # aka G - cfl = 1.1, - resid_tol = 1.0e-10, - n_iterations_max = 1000, - timestep_gravity = timestep_gravity_erk52_3Sstar!) +parameters = ParametersEulerGravity( + background_density = 2.0, # aka rho0 + # rho0 is (ab)used to add a "+8π" term to the source terms + # for the manufactured solution + gravitational_constant = 1.0, # aka G + cfl = 1.1, + resid_tol = 1.0e-10, + n_iterations_max = 1000, + timestep_gravity = timestep_gravity_erk52_3Sstar! +) semi = SemidiscretizationEulerGravity(semi_euler, semi_gravity, parameters) @@ -54,25 +61,33 @@ summary_callback = SummaryCallback() stepsize_callback = StepsizeCallback(cfl = 0.8) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) analysis_interval = 100 alive_callback = AliveCallback(analysis_interval = analysis_interval) -analysis_callback = AnalysisCallback(semi_euler, interval = analysis_interval, - save_analysis = true) +analysis_callback = AnalysisCallback( + semi_euler, interval = analysis_interval, + save_analysis = true +) -callbacks = CallbackSet(summary_callback, stepsize_callback, - save_solution, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, stepsize_callback, + save_solution, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary println("Number of gravity subcycles: ", semi.gravity_counter.ncalls_since_readout) diff --git a/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_jeans_instability.jl b/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_jeans_instability.jl index 7461198fbb..4c82e1cb77 100644 --- a/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_jeans_instability.jl +++ b/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_jeans_instability.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -17,8 +16,10 @@ The classical Jeans instability taken from - Flash manual https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node189.html#SECTION010131000000000000000 in CGS (centimeter, gram, second) units. """ -function initial_condition_jeans_instability(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_jeans_instability( + x, t, + equations::CompressibleEulerEquations2D + ) # Jeans gravitational instability test case # see Derigs et al. https://arxiv.org/abs/1605.03572; Sec. 4.6 # OBS! this uses cgs (centimeter, gram, second) units @@ -29,7 +30,7 @@ function initial_condition_jeans_instability(x, t, # gamma = 5/3 dens0 = 1.5e7 # g/cm^3 pres0 = 1.5e7 # dyn/cm^2 - delta0 = 1e-3 + delta0 = 1.0e-3 # set wave vector values for perturbation (units 1/cm) # see FLASH manual: https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node189.html#SECTION010131000000000000000 kx = 2.0 * pi / 0.5 # 2π/λ_x, λ_x = 0.5 @@ -44,13 +45,15 @@ function initial_condition_jeans_instability(x, t, return prim2cons((dens, velx, vely, pres), equations) end -function initial_condition_jeans_instability(x, t, - equations::HyperbolicDiffusionEquations2D) +function initial_condition_jeans_instability( + x, t, + equations::HyperbolicDiffusionEquations2D + ) # gravity equation: -Δϕ = -4πGρ # Constants taken from the FLASH manual # https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node189.html#SECTION010131000000000000000 rho0 = 1.5e7 - delta0 = 1e-3 + delta0 = 1.0e-3 phi = rho0 * delta0 # constant background perturbation magnitude q1 = 0.0 @@ -70,12 +73,16 @@ solver_euler = DGSEM(polydeg, FluxHLL(min_max_speed_naive)) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) -semi_euler = SemidiscretizationHyperbolic(mesh, equations_euler, initial_condition, - solver_euler) +semi_euler = SemidiscretizationHyperbolic( + mesh, equations_euler, initial_condition, + solver_euler +) ############################################################################### # semidiscretization of the hyperbolic diffusion equations @@ -83,18 +90,22 @@ equations_gravity = HyperbolicDiffusionEquations2D() solver_gravity = DGSEM(polydeg, flux_lax_friedrichs) -semi_gravity = SemidiscretizationHyperbolic(mesh, equations_gravity, initial_condition, - solver_gravity, - source_terms = source_terms_harmonic) +semi_gravity = SemidiscretizationHyperbolic( + mesh, equations_gravity, initial_condition, + solver_gravity, + source_terms = source_terms_harmonic +) ############################################################################### # combining both semidiscretizations for Euler + self-gravity -parameters = ParametersEulerGravity(background_density = 1.5e7, # aka rho0 - gravitational_constant = 6.674e-8, # aka G - cfl = 1.6, - resid_tol = 1.0e-4, - n_iterations_max = 1000, - timestep_gravity = timestep_gravity_carpenter_kennedy_erk54_2N!) +parameters = ParametersEulerGravity( + background_density = 1.5e7, # aka rho0 + gravitational_constant = 6.674e-8, # aka G + cfl = 1.6, + resid_tol = 1.0e-4, + n_iterations_max = 1000, + timestep_gravity = timestep_gravity_carpenter_kennedy_erk54_2N! +) semi = SemidiscretizationEulerGravity(semi_euler, semi_gravity, parameters) @@ -107,13 +118,17 @@ summary_callback = SummaryCallback() stepsize_callback = StepsizeCallback(cfl = 1.0) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) analysis_interval = 100 alive_callback = AliveCallback(analysis_interval = analysis_interval) @@ -121,42 +136,56 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) Trixi.pretty_form_utf(::Val{:energy_potential}) = "∑e_potential" Trixi.pretty_form_ascii(::Val{:energy_potential}) = "e_potential" -function Trixi.analyze(::Val{:energy_potential}, du, u_euler, t, - semi::SemidiscretizationEulerGravity) +function Trixi.analyze( + ::Val{:energy_potential}, du, u_euler, t, + semi::SemidiscretizationEulerGravity + ) u_gravity = Trixi.wrap_array(semi.cache.u_ode, semi.semi_gravity) mesh, equations_euler, dg, cache = Trixi.mesh_equations_solver_cache(semi.semi_euler) _, equations_gravity, _, _ = Trixi.mesh_equations_solver_cache(semi.semi_gravity) - e_potential = Trixi.integrate_via_indices(u_euler, mesh, equations_euler, dg, cache, - equations_gravity, - u_gravity) do u, i, j, element, - equations_euler, dg, - equations_gravity, u_gravity + e_potential = Trixi.integrate_via_indices( + u_euler, mesh, equations_euler, dg, cache, + equations_gravity, + u_gravity + ) do u, i, j, element, + equations_euler, dg, + equations_gravity, u_gravity u_euler_local = Trixi.get_node_vars(u_euler, equations_euler, dg, i, j, element) - u_gravity_local = Trixi.get_node_vars(u_gravity, equations_gravity, dg, i, j, - element) + u_gravity_local = Trixi.get_node_vars( + u_gravity, equations_gravity, dg, i, j, + element + ) # OBS! subtraction is specific to Jeans instability test where rho0 = 1.5e7 return (u_euler_local[1] - 1.5e7) * u_gravity_local[1] end return e_potential end -analysis_callback = AnalysisCallback(semi_euler, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (energy_total, - energy_kinetic, - energy_internal, - Val(:energy_potential))) - -callbacks = CallbackSet(summary_callback, stepsize_callback, - save_restart, save_solution, - analysis_callback, alive_callback) +analysis_callback = AnalysisCallback( + semi_euler, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + energy_total, + energy_kinetic, + energy_internal, + Val(:energy_potential), + ) +) + +callbacks = CallbackSet( + summary_callback, stepsize_callback, + save_restart, save_solution, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary println("Number of gravity subcycles: ", semi.gravity_counter.ncalls_since_readout) diff --git a/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_sedov_blast_wave.jl b/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_sedov_blast_wave.jl index bc7ceb97c8..36b177a3ff 100644 --- a/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_sedov_blast_wave.jl +++ b/examples/paper_self_gravitating_gas_dynamics/elixir_eulergravity_sedov_blast_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -26,7 +25,7 @@ function initial_condition_sedov_self_gravity(x, t, equations::CompressibleEuler r0 = 0.125 # = 4.0 * smallest dx (for domain length=8 and max-ref=8) E = 1.0 p_inner = (equations.gamma - 1) * E / (pi * r0^2) - p_ambient = 1e-5 # = true Sedov setup + p_ambient = 1.0e-5 # = true Sedov setup # Calculate primitive variables # use a logistic function to transfer density value smoothly @@ -34,7 +33,7 @@ function initial_condition_sedov_self_gravity(x, t, equations::CompressibleEuler x0 = 1.0 # center point of function k = -150.0 # sharpness of transfer logistic_function_rho = L / (1.0 + exp(-k * (r - x0))) - rho_ambient = 1e-5 + rho_ambient = 1.0e-5 rho = max(logistic_function_rho, rho_ambient) # clip background density to not be so tiny # velocities are zero @@ -62,15 +61,17 @@ based on - https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node187.html#SECTION010114100000000000000 Should be used together with [`initial_condition_sedov_self_gravity`](@ref). """ -function boundary_condition_sedov_self_gravity(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) +function boundary_condition_sedov_self_gravity( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) # velocities are zero, density/pressure are ambient values according to # initial_condition_sedov_self_gravity - rho = 1e-5 + rho = 1.0e-5 v1 = 0.0 v2 = 0.0 - p = 1e-5 + p = 1.0e-5 u_boundary = prim2cons(SVector(rho, v1, v2, p), equations) @@ -89,26 +90,34 @@ surface_flux = FluxHLL(min_max_speed_naive) volume_flux = flux_chandrashekar polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations_euler, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations_euler, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver_euler = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-4, -4) coordinates_max = (4, 4) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 100_000, - periodicity = false) - -semi_euler = SemidiscretizationHyperbolic(mesh, equations_euler, initial_condition, - solver_euler, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 100_000, + periodicity = false +) + +semi_euler = SemidiscretizationHyperbolic( + mesh, equations_euler, initial_condition, + solver_euler, + boundary_conditions = boundary_conditions +) ############################################################################### # semidiscretization of the hyperbolic diffusion equations @@ -125,8 +134,10 @@ based on - https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node187.html#SECTION010114100000000000000 Should be used together with [`boundary_condition_sedov_self_gravity`](@ref). """ -function initial_condition_sedov_self_gravity(x, t, - equations::HyperbolicDiffusionEquations2D) +function initial_condition_sedov_self_gravity( + x, t, + equations::HyperbolicDiffusionEquations2D + ) # for now just use constant initial condition for sedov blast wave (can likely be improved) phi = 0.0 q1 = 0.0 @@ -147,9 +158,11 @@ based on - https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node187.html#SECTION010114100000000000000 Should be used together with [`initial_condition_sedov_self_gravity`](@ref). """ -function boundary_condition_sedov_self_gravity(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::HyperbolicDiffusionEquations2D) +function boundary_condition_sedov_self_gravity( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::HyperbolicDiffusionEquations2D + ) u_boundary = initial_condition_sedov_self_gravity(x, t, equations) # Calculate boundary flux @@ -164,19 +177,23 @@ end solver_gravity = DGSEM(polydeg, flux_lax_friedrichs) -semi_gravity = SemidiscretizationHyperbolic(mesh, equations_gravity, initial_condition, - solver_gravity, - boundary_conditions = boundary_conditions, - source_terms = source_terms_harmonic) +semi_gravity = SemidiscretizationHyperbolic( + mesh, equations_gravity, initial_condition, + solver_gravity, + boundary_conditions = boundary_conditions, + source_terms = source_terms_harmonic +) ############################################################################### # combining both semidiscretizations for Euler + self-gravity -parameters = ParametersEulerGravity(background_density = 0.0, # aka rho0 - gravitational_constant = 6.674e-8, # aka G - cfl = 2.4, - resid_tol = 1.0e-4, - n_iterations_max = 100, - timestep_gravity = timestep_gravity_erk52_3Sstar!) +parameters = ParametersEulerGravity( + background_density = 0.0, # aka rho0 + gravitational_constant = 6.674e-8, # aka G + cfl = 2.4, + resid_tol = 1.0e-4, + n_iterations_max = 100, + timestep_gravity = timestep_gravity_erk52_3Sstar! +) semi = SemidiscretizationEulerGravity(semi_euler, semi_gravity, parameters) @@ -187,43 +204,59 @@ ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 1.0, - alpha_min = 0.0, - alpha_smooth = false, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 2, - max_level = 8, max_threshold = 0.0003) -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 1.0, + alpha_min = 0.0, + alpha_smooth = false, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 2, + max_level = 8, max_threshold = 0.0003 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) analysis_interval = 100 alive_callback = AliveCallback(analysis_interval = analysis_interval) -analysis_callback = AnalysisCallback(semi_euler, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (energy_total, - energy_kinetic, - energy_internal)) - -callbacks = CallbackSet(summary_callback, amr_callback, stepsize_callback, - save_solution, - analysis_callback, alive_callback) +analysis_callback = AnalysisCallback( + semi_euler, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + energy_total, + energy_kinetic, + energy_internal, + ) +) + +callbacks = CallbackSet( + summary_callback, amr_callback, stepsize_callback, + save_solution, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary println("Number of gravity subcycles: ", semi.gravity_counter.ncalls_since_readout) diff --git a/examples/paper_self_gravitating_gas_dynamics/elixir_hypdiff_convergence.jl b/examples/paper_self_gravitating_gas_dynamics/elixir_hypdiff_convergence.jl index df6e4e6349..b701a5fcf9 100644 --- a/examples/paper_self_gravitating_gas_dynamics/elixir_hypdiff_convergence.jl +++ b/examples/paper_self_gravitating_gas_dynamics/elixir_hypdiff_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,10 +8,12 @@ equations = HyperbolicDiffusionEquations2D() initial_condition = initial_condition_poisson_nonperiodic # 1 => -x, 2 => +x, 3 => -y, 4 => +y as usual for orientations -boundary_conditions = (x_neg = boundary_condition_poisson_nonperiodic, - x_pos = boundary_condition_poisson_nonperiodic, - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) +boundary_conditions = ( + x_neg = boundary_condition_poisson_nonperiodic, + x_pos = boundary_condition_poisson_nonperiodic, + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) polydeg = 3 surface_flux = flux_lax_friedrichs @@ -20,14 +21,18 @@ solver = DGSEM(polydeg, surface_flux) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 30_000, - periodicity = (false, true)) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_poisson_nonperiodic, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 30_000, + periodicity = (false, true) +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_poisson_nonperiodic, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -42,24 +47,32 @@ steady_state_callback = SteadyStateCallback(abstol = resid_tol, reltol = 0.0) stepsize_callback = StepsizeCallback(cfl = 1.0) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) analysis_interval = 500 alive_callback = AliveCallback(analysis_interval = analysis_interval) -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) -callbacks = CallbackSet(summary_callback, steady_state_callback, stepsize_callback, - save_solution, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, stepsize_callback, + save_solution, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/special_elixirs/elixir_euler_ad.jl b/examples/special_elixirs/elixir_euler_ad.jl index 3aa44f9a77..0c6d7f2d92 100644 --- a/examples/special_elixirs/elixir_euler_ad.jl +++ b/examples/special_elixirs/elixir_euler_ad.jl @@ -1,15 +1,18 @@ - # This example is described in more detail in the documentation of Trixi.jl using Trixi, LinearAlgebra, ForwardDiff equations = CompressibleEulerEquations2D(1.4) -mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), - initial_refinement_level = 2, n_cells_max = 10^5) +mesh = TreeMesh( + (-1.0, -1.0), (1.0, 1.0), + initial_refinement_level = 2, n_cells_max = 10^5 +) -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha) +) """ initial_condition_isentropic_vortex(x, t, equations::CompressibleEulerEquations2D) @@ -52,14 +55,20 @@ function initial_condition_isentropic_vortex(x, t, equations::CompressibleEulerE prim = SVector(rho, v1, v2, p) return prim2cons(prim, equations) end -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_isentropic_vortex, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_isentropic_vortex, + solver +) u0_ode = compute_coefficients(0.0, semi) -J = ForwardDiff.jacobian((du_ode, γ) -> begin - equations_inner = CompressibleEulerEquations2D(first(γ)) - semi_inner = Trixi.remake(semi, equations = equations_inner, - uEltype = eltype(γ)) - Trixi.rhs!(du_ode, u0_ode, semi_inner, 0.0) - end, similar(u0_ode), [1.4]); # γ needs to be an `AbstractArray` +J = ForwardDiff.jacobian( + (du_ode, γ) -> begin + equations_inner = CompressibleEulerEquations2D(first(γ)) + semi_inner = Trixi.remake( + semi, equations = equations_inner, + uEltype = eltype(γ) + ) + Trixi.rhs!(du_ode, u0_ode, semi_inner, 0.0) + end, similar(u0_ode), [1.4] +); # γ needs to be an `AbstractArray` diff --git a/examples/structured_1d_dgsem/elixir_advection_basic.jl b/examples/structured_1d_dgsem/elixir_advection_basic.jl index cdabeb4c61..abcde7db0e 100644 --- a/examples/structured_1d_dgsem/elixir_advection_basic.jl +++ b/examples/structured_1d_dgsem/elixir_advection_basic.jl @@ -21,8 +21,10 @@ cells_per_dimension = (16,) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -38,23 +40,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_1d_dgsem/elixir_advection_nonperiodic.jl b/examples/structured_1d_dgsem/elixir_advection_nonperiodic.jl index 1e0c579ffc..a4da198d37 100644 --- a/examples/structured_1d_dgsem/elixir_advection_nonperiodic.jl +++ b/examples/structured_1d_dgsem/elixir_advection_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -17,10 +16,12 @@ coordinates_min = (0.0,) coordinates_max = (5.0,) mesh = StructuredMesh((16,), coordinates_min, coordinates_max, periodicity = false) -semi = SemidiscretizationHyperbolic(mesh, equations, - initial_condition, - solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, + initial_condition, + solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -31,30 +32,40 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_1d_dgsem/elixir_advection_shockcapturing.jl b/examples/structured_1d_dgsem/elixir_advection_shockcapturing.jl index 96566bc237..6f875efab7 100644 --- a/examples/structured_1d_dgsem/elixir_advection_shockcapturing.jl +++ b/examples/structured_1d_dgsem/elixir_advection_shockcapturing.jl @@ -51,22 +51,28 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_central polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = Trixi.first) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = Trixi.first +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) # Create curved mesh cells_per_dimension = (120,) coordinates_min = (-1.0,) # minimum coordinate coordinates_max = (1.0,) # maximum coordinate -mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, - periodicity = true) +mesh = StructuredMesh( + cells_per_dimension, coordinates_min, coordinates_max, + periodicity = true +) # A semidiscretization collects data structures and functions for the spatial discretization semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -92,14 +98,18 @@ save_solution = SaveSolutionCallback(interval = 100, solution_variables = cons2p stepsize_callback = StepsizeCallback(cfl = 0.5) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_1d_dgsem/elixir_euler_sedov.jl b/examples/structured_1d_dgsem/elixir_euler_sedov.jl index fc974cb94d..153599c91f 100644 --- a/examples/structured_1d_dgsem/elixir_euler_sedov.jl +++ b/examples/structured_1d_dgsem/elixir_euler_sedov.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -41,21 +40,27 @@ volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) shock_indicator_variable = density_pressure -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = shock_indicator_variable) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = shock_indicator_variable +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) cells_per_dimension = (64,) coordinates_min = (-2.0,) coordinates_max = (2.0,) -mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, - periodicity = true) +mesh = StructuredMesh( + cells_per_dimension, coordinates_min, coordinates_max, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -72,23 +77,29 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_1d_dgsem/elixir_euler_source_terms.jl b/examples/structured_1d_dgsem/elixir_euler_source_terms.jl index 97767f3e12..53a41986f7 100644 --- a/examples/structured_1d_dgsem/elixir_euler_source_terms.jl +++ b/examples/structured_1d_dgsem/elixir_euler_source_terms.jl @@ -21,8 +21,10 @@ cells_per_dimension = (16,) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -33,28 +35,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_1d_dgsem/elixir_euler_source_terms_nonperiodic.jl b/examples/structured_1d_dgsem/elixir_euler_source_terms_nonperiodic.jl index d506383813..79108e1d7f 100644 --- a/examples/structured_1d_dgsem/elixir_euler_source_terms_nonperiodic.jl +++ b/examples/structured_1d_dgsem/elixir_euler_source_terms_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,8 +14,10 @@ source_terms = source_terms_convergence_test # 1*ndims == 2 directions or you can pass a tuple containing BCs for # each direction boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (x_neg = boundary_condition, - x_pos = boundary_condition) +boundary_conditions = ( + x_neg = boundary_condition, + x_pos = boundary_condition, +) solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -24,9 +25,11 @@ f1() = SVector(0.0) f2() = SVector(2.0) mesh = StructuredMesh((16,), (f1, f2), periodicity = false) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -41,25 +44,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_1d_dgsem/elixir_linearizedeuler_characteristic_system.jl b/examples/structured_1d_dgsem/elixir_linearizedeuler_characteristic_system.jl index 663b25b18c..8d033d3152 100644 --- a/examples/structured_1d_dgsem/elixir_linearizedeuler_characteristic_system.jl +++ b/examples/structured_1d_dgsem/elixir_linearizedeuler_characteristic_system.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using LinearAlgebra: dot using Trixi @@ -23,12 +22,14 @@ mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) # https://www.nas.nasa.gov/assets/nas/pdf/ams/2018/introtocfd/Intro2CFD_Lecture1_Pulliam_Euler_WaveEQ.pdf # Linearized Euler: Eigensystem lin_euler_eigvals = [v_0 - c_0; v_0; v_0 + c_0] -lin_euler_eigvecs = [-rho_0/c_0 1 rho_0/c_0; - 1 0 1; - -rho_0*c_0 0 rho_0*c_0] +lin_euler_eigvecs = [ + -rho_0 / c_0 1 rho_0 / c_0; + 1 0 1; + -rho_0 * c_0 0 rho_0 * c_0 +] lin_euler_eigvecs_inv = inv(lin_euler_eigvecs) -# Trace back characteristics. +# Trace back characteristics. # See https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf, p.95 function compute_char_initial_pos(x, t) return SVector(x[1], x[1], x[1]) .- t * lin_euler_eigvals @@ -94,8 +95,10 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback +) stepsize_callback = StepsizeCallback(cfl = 1.0) @@ -105,8 +108,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_1d_dgsem/elixir_traffic_flow_lwr_greenlight.jl b/examples/structured_1d_dgsem/elixir_traffic_flow_lwr_greenlight.jl index e5badf1445..9d12d8b122 100644 --- a/examples/structured_1d_dgsem/elixir_traffic_flow_lwr_greenlight.jl +++ b/examples/structured_1d_dgsem/elixir_traffic_flow_lwr_greenlight.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,8 +11,10 @@ coordinates_min = (-1.0,) # minimum coordinate coordinates_max = (1.0,) # maximum coordinate cells_per_dimension = (64,) -mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, - periodicity = false) +mesh = StructuredMesh( + cells_per_dimension, coordinates_min, coordinates_max, + periodicity = false +) # Example inspired from http://www.clawpack.org/riemann_book/html/Traffic_flow.html#Example:-green-light # Green light that at x = 0 which switches at t = 0 from red to green. @@ -27,29 +28,35 @@ end ############################################################################### # Specify non-periodic boundary conditions -# Assume that there are always cars waiting at the left +# Assume that there are always cars waiting at the left function inflow(x, t, equations::TrafficFlowLWREquations1D) return initial_condition_greenlight(coordinates_min, t, equations) end boundary_condition_inflow = BoundaryConditionDirichlet(inflow) # Cars may leave the modeled domain -function boundary_condition_outflow(u_inner, orientation, normal_direction, x, t, - surface_flux_function, - equations::TrafficFlowLWREquations1D) +function boundary_condition_outflow( + u_inner, orientation, normal_direction, x, t, + surface_flux_function, + equations::TrafficFlowLWREquations1D + ) # Calculate the boundary flux entirely from the internal solution state flux = Trixi.flux(u_inner, orientation, equations) return flux end -boundary_conditions = (x_neg = boundary_condition_inflow, - x_pos = boundary_condition_outflow) +boundary_conditions = ( + x_neg = boundary_condition_inflow, + x_pos = boundary_condition_outflow, +) initial_condition = initial_condition_greenlight -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -66,15 +73,19 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_advection_basic.jl b/examples/structured_2d_dgsem/elixir_advection_basic.jl index 1d7235fc21..4214289f58 100644 --- a/examples/structured_2d_dgsem/elixir_advection_basic.jl +++ b/examples/structured_2d_dgsem/elixir_advection_basic.jl @@ -22,8 +22,10 @@ cells_per_dimension = (16, 16) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -39,23 +41,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_advection_coupled.jl b/examples/structured_2d_dgsem/elixir_advection_coupled.jl index 0002bb8d37..d18f935103 100644 --- a/examples/structured_2d_dgsem/elixir_advection_coupled.jl +++ b/examples/structured_2d_dgsem/elixir_advection_coupled.jl @@ -53,30 +53,44 @@ cells_per_dimension = (8, 8) coordinates_min1 = (-1.0, 0.0) # minimum coordinates (min(x), min(y)) coordinates_max1 = (0.0, 1.0) # maximum coordinates (max(x), max(y)) -mesh1 = StructuredMesh(cells_per_dimension, coordinates_min1, coordinates_max1, - periodicity = false) +mesh1 = StructuredMesh( + cells_per_dimension, coordinates_min1, coordinates_max1, + periodicity = false +) # Define the coupling functions coupling_function12 = (x, u, equations_other, equations_own) -> u coupling_function13 = (x, u, equations_other, equations_own) -> u # Define the coupling boundary conditions and the system it is coupled to. -boundary_conditions_x_neg1 = BoundaryConditionCoupled(2, (:end, :i_forward), Float64, - coupling_function12) -boundary_conditions_x_pos1 = BoundaryConditionCoupled(2, (:begin, :i_forward), Float64, - coupling_function12) -boundary_conditions_y_neg1 = BoundaryConditionCoupled(3, (:i_forward, :end), Float64, - coupling_function13) -boundary_conditions_y_pos1 = BoundaryConditionCoupled(3, (:i_forward, :begin), Float64, - coupling_function13) +boundary_conditions_x_neg1 = BoundaryConditionCoupled( + 2, (:end, :i_forward), Float64, + coupling_function12 +) +boundary_conditions_x_pos1 = BoundaryConditionCoupled( + 2, (:begin, :i_forward), Float64, + coupling_function12 +) +boundary_conditions_y_neg1 = BoundaryConditionCoupled( + 3, (:i_forward, :end), Float64, + coupling_function13 +) +boundary_conditions_y_pos1 = BoundaryConditionCoupled( + 3, (:i_forward, :begin), Float64, + coupling_function13 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi1 = SemidiscretizationHyperbolic(mesh1, equations, initial_condition_convergence_test, - solver, - boundary_conditions = (x_neg = boundary_conditions_x_neg1, - x_pos = boundary_conditions_x_pos1, - y_neg = boundary_conditions_y_neg1, - y_pos = boundary_conditions_y_pos1)) +semi1 = SemidiscretizationHyperbolic( + mesh1, equations, initial_condition_convergence_test, + solver, + boundary_conditions = ( + x_neg = boundary_conditions_x_neg1, + x_pos = boundary_conditions_x_pos1, + y_neg = boundary_conditions_y_neg1, + y_pos = boundary_conditions_y_pos1, + ) +) ########### # system #2 @@ -85,30 +99,44 @@ semi1 = SemidiscretizationHyperbolic(mesh1, equations, initial_condition_converg coordinates_min2 = (0.0, 0.0) # minimum coordinates (min(x), min(y)) coordinates_max2 = (1.0, 1.0) # maximum coordinates (max(x), max(y)) -mesh2 = StructuredMesh(cells_per_dimension, coordinates_min2, coordinates_max2, - periodicity = false) +mesh2 = StructuredMesh( + cells_per_dimension, coordinates_min2, coordinates_max2, + periodicity = false +) # Define the coupling functions coupling_function21 = (x, u, equations_other, equations_own) -> u coupling_function24 = (x, u, equations_other, equations_own) -> u # Define the coupling boundary conditions and the system it is coupled to. -boundary_conditions_x_neg2 = BoundaryConditionCoupled(1, (:end, :i_forward), Float64, - coupling_function21) -boundary_conditions_x_pos2 = BoundaryConditionCoupled(1, (:begin, :i_forward), Float64, - coupling_function21) -boundary_conditions_y_neg2 = BoundaryConditionCoupled(4, (:i_forward, :end), Float64, - coupling_function24) -boundary_conditions_y_pos2 = BoundaryConditionCoupled(4, (:i_forward, :begin), Float64, - coupling_function24) +boundary_conditions_x_neg2 = BoundaryConditionCoupled( + 1, (:end, :i_forward), Float64, + coupling_function21 +) +boundary_conditions_x_pos2 = BoundaryConditionCoupled( + 1, (:begin, :i_forward), Float64, + coupling_function21 +) +boundary_conditions_y_neg2 = BoundaryConditionCoupled( + 4, (:i_forward, :end), Float64, + coupling_function24 +) +boundary_conditions_y_pos2 = BoundaryConditionCoupled( + 4, (:i_forward, :begin), Float64, + coupling_function24 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi2 = SemidiscretizationHyperbolic(mesh2, equations, initial_condition_convergence_test, - solver, - boundary_conditions = (x_neg = boundary_conditions_x_neg2, - x_pos = boundary_conditions_x_pos2, - y_neg = boundary_conditions_y_neg2, - y_pos = boundary_conditions_y_pos2)) +semi2 = SemidiscretizationHyperbolic( + mesh2, equations, initial_condition_convergence_test, + solver, + boundary_conditions = ( + x_neg = boundary_conditions_x_neg2, + x_pos = boundary_conditions_x_pos2, + y_neg = boundary_conditions_y_neg2, + y_pos = boundary_conditions_y_pos2, + ) +) ########### # system #3 @@ -117,30 +145,44 @@ semi2 = SemidiscretizationHyperbolic(mesh2, equations, initial_condition_converg coordinates_min3 = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max3 = (0.0, 0.0) # maximum coordinates (max(x), max(y)) -mesh3 = StructuredMesh(cells_per_dimension, coordinates_min3, coordinates_max3, - periodicity = false) +mesh3 = StructuredMesh( + cells_per_dimension, coordinates_min3, coordinates_max3, + periodicity = false +) # Define the coupling functions coupling_function34 = (x, u, equations_other, equations_own) -> u coupling_function31 = (x, u, equations_other, equations_own) -> u # Define the coupling boundary conditions and the system it is coupled to. -boundary_conditions_x_neg3 = BoundaryConditionCoupled(4, (:end, :i_forward), Float64, - coupling_function34) -boundary_conditions_x_pos3 = BoundaryConditionCoupled(4, (:begin, :i_forward), Float64, - coupling_function34) -boundary_conditions_y_neg3 = BoundaryConditionCoupled(1, (:i_forward, :end), Float64, - coupling_function31) -boundary_conditions_y_pos3 = BoundaryConditionCoupled(1, (:i_forward, :begin), Float64, - coupling_function31) +boundary_conditions_x_neg3 = BoundaryConditionCoupled( + 4, (:end, :i_forward), Float64, + coupling_function34 +) +boundary_conditions_x_pos3 = BoundaryConditionCoupled( + 4, (:begin, :i_forward), Float64, + coupling_function34 +) +boundary_conditions_y_neg3 = BoundaryConditionCoupled( + 1, (:i_forward, :end), Float64, + coupling_function31 +) +boundary_conditions_y_pos3 = BoundaryConditionCoupled( + 1, (:i_forward, :begin), Float64, + coupling_function31 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi3 = SemidiscretizationHyperbolic(mesh3, equations, initial_condition_convergence_test, - solver, - boundary_conditions = (x_neg = boundary_conditions_x_neg3, - x_pos = boundary_conditions_x_pos3, - y_neg = boundary_conditions_y_neg3, - y_pos = boundary_conditions_y_pos3)) +semi3 = SemidiscretizationHyperbolic( + mesh3, equations, initial_condition_convergence_test, + solver, + boundary_conditions = ( + x_neg = boundary_conditions_x_neg3, + x_pos = boundary_conditions_x_pos3, + y_neg = boundary_conditions_y_neg3, + y_pos = boundary_conditions_y_pos3, + ) +) ########### # system #4 @@ -149,30 +191,44 @@ semi3 = SemidiscretizationHyperbolic(mesh3, equations, initial_condition_converg coordinates_min4 = (0.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max4 = (1.0, 0.0) # maximum coordinates (max(x), max(y)) -mesh4 = StructuredMesh(cells_per_dimension, coordinates_min4, coordinates_max4, - periodicity = false) +mesh4 = StructuredMesh( + cells_per_dimension, coordinates_min4, coordinates_max4, + periodicity = false +) # Define the coupling functions coupling_function43 = (x, u, equations_other, equations_own) -> u coupling_function42 = (x, u, equations_other, equations_own) -> u # Define the coupling boundary conditions and the system it is coupled to. -boundary_conditions_x_neg4 = BoundaryConditionCoupled(3, (:end, :i_forward), Float64, - coupling_function43) -boundary_conditions_x_pos4 = BoundaryConditionCoupled(3, (:begin, :i_forward), Float64, - coupling_function43) -boundary_conditions_y_neg4 = BoundaryConditionCoupled(2, (:i_forward, :end), Float64, - coupling_function42) -boundary_conditions_y_pos4 = BoundaryConditionCoupled(2, (:i_forward, :begin), Float64, - coupling_function42) +boundary_conditions_x_neg4 = BoundaryConditionCoupled( + 3, (:end, :i_forward), Float64, + coupling_function43 +) +boundary_conditions_x_pos4 = BoundaryConditionCoupled( + 3, (:begin, :i_forward), Float64, + coupling_function43 +) +boundary_conditions_y_neg4 = BoundaryConditionCoupled( + 2, (:i_forward, :end), Float64, + coupling_function42 +) +boundary_conditions_y_pos4 = BoundaryConditionCoupled( + 2, (:i_forward, :begin), Float64, + coupling_function42 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi4 = SemidiscretizationHyperbolic(mesh4, equations, initial_condition_convergence_test, - solver, - boundary_conditions = (x_neg = boundary_conditions_x_neg4, - x_pos = boundary_conditions_x_pos4, - y_neg = boundary_conditions_y_neg4, - y_pos = boundary_conditions_y_pos4)) +semi4 = SemidiscretizationHyperbolic( + mesh4, equations, initial_condition_convergence_test, + solver, + boundary_conditions = ( + x_neg = boundary_conditions_x_neg4, + x_pos = boundary_conditions_x_pos4, + y_neg = boundary_conditions_y_neg4, + y_pos = boundary_conditions_y_pos4, + ) +) # Create a semidiscretization that bundles all the semidiscretizations. semi = SemidiscretizationCoupled(semi1, semi2, semi3, semi4) @@ -192,27 +248,35 @@ analysis_callback1 = AnalysisCallback(semi1, interval = 100) analysis_callback2 = AnalysisCallback(semi2, interval = 100) analysis_callback3 = AnalysisCallback(semi3, interval = 100) analysis_callback4 = AnalysisCallback(semi4, interval = 100) -analysis_callback = AnalysisCallbackCoupled(semi, analysis_callback1, analysis_callback2, - analysis_callback3, analysis_callback4) +analysis_callback = AnalysisCallbackCoupled( + semi, analysis_callback1, analysis_callback2, + analysis_callback3, analysis_callback4 +) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_advection_extended.jl b/examples/structured_2d_dgsem/elixir_advection_extended.jl index df7e1f375a..df093c511c 100644 --- a/examples/structured_2d_dgsem/elixir_advection_extended.jl +++ b/examples/structured_2d_dgsem/elixir_advection_extended.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -30,8 +29,10 @@ cells_per_dimension = (19, 37) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -46,38 +47,48 @@ summary_callback = SummaryCallback() # The AnalysisCallback allows to analyse the solution in regular intervals and prints the results analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) # The AliveCallback prints short status information in regular intervals alive_callback = AliveCallback(analysis_interval = analysis_interval) # The SaveRestartCallback allows to save a file from which a Trixi.jl simulation can be restarted -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_advection_float32.jl b/examples/structured_2d_dgsem/elixir_advection_float32.jl index 20c30d4aeb..56071a3b25 100644 --- a/examples/structured_2d_dgsem/elixir_advection_float32.jl +++ b/examples/structured_2d_dgsem/elixir_advection_float32.jl @@ -22,8 +22,10 @@ cells_per_dimension = (16, 16) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -39,23 +41,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6f0) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0f0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0f0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_advection_free_stream.jl b/examples/structured_2d_dgsem/elixir_advection_free_stream.jl index 7785b4f9e1..532a7e44cd 100644 --- a/examples/structured_2d_dgsem/elixir_advection_free_stream.jl +++ b/examples/structured_2d_dgsem/elixir_advection_free_stream.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -19,11 +18,15 @@ function mapping(xi_, eta_) xi = 1.5 * xi_ + 1.5 eta = 1.5 * eta_ + 1.5 - y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3)) + y = eta + 3 / 8 * ( + cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3) + ) - x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3)) + x = xi + 3 / 8 * ( + cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3) + ) return SVector(x, y) end @@ -50,27 +53,35 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The SaveRestartCallback allows to save a file from which a Trixi.jl simulation can be restarted -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 2.0) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_advection_meshview.jl b/examples/structured_2d_dgsem/elixir_advection_meshview.jl index d8d2703109..6589ba97d8 100644 --- a/examples/structured_2d_dgsem/elixir_advection_meshview.jl +++ b/examples/structured_2d_dgsem/elixir_advection_meshview.jl @@ -54,29 +54,43 @@ coupling_function = (x, u, equations_other, equations_own) -> u # The indices (:end, :i_forward) and (:begin, :i_forward) denote the interface indexing. # For a system with coupling in x and y see examples/structured_2d_dgsem/elixir_advection_coupled.jl. boundary_conditions1 = ( - # Connect left boundary with right boundary of left mesh - x_neg = BoundaryConditionCoupled(2, (:end, :i_forward), Float64, - coupling_function), - x_pos = BoundaryConditionCoupled(2, (:begin, :i_forward), Float64, - coupling_function), - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) + # Connect left boundary with right boundary of left mesh + x_neg = BoundaryConditionCoupled( + 2, (:end, :i_forward), Float64, + coupling_function + ), + x_pos = BoundaryConditionCoupled( + 2, (:begin, :i_forward), Float64, + coupling_function + ), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) boundary_conditions2 = ( - # Connect left boundary with right boundary of left mesh - x_neg = BoundaryConditionCoupled(1, (:end, :i_forward), Float64, - coupling_function), - x_pos = BoundaryConditionCoupled(1, (:begin, :i_forward), Float64, - coupling_function), - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) + # Connect left boundary with right boundary of left mesh + x_neg = BoundaryConditionCoupled( + 1, (:end, :i_forward), Float64, + coupling_function + ), + x_pos = BoundaryConditionCoupled( + 1, (:begin, :i_forward), Float64, + coupling_function + ), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) # A semidiscretization collects data structures and functions for the spatial discretization -semi1 = SemidiscretizationHyperbolic(mesh1, equations, initial_condition_convergence_test, - solver, - boundary_conditions = boundary_conditions1) -semi2 = SemidiscretizationHyperbolic(mesh2, equations, initial_condition_convergence_test, - solver, - boundary_conditions = boundary_conditions2) +semi1 = SemidiscretizationHyperbolic( + mesh1, equations, initial_condition_convergence_test, + solver, + boundary_conditions = boundary_conditions1 +) +semi2 = SemidiscretizationHyperbolic( + mesh2, equations, initial_condition_convergence_test, + solver, + boundary_conditions = boundary_conditions2 +) semi = SemidiscretizationCoupled(semi1, semi2) ############################################################################### @@ -98,25 +112,31 @@ analysis_interval = 100 alive_callback = AliveCallback(analysis_interval = analysis_interval) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 5.0e-2, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 5.0e-2, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_advection_nonperiodic.jl b/examples/structured_2d_dgsem/elixir_advection_nonperiodic.jl index e2e113b168..a85246920c 100644 --- a/examples/structured_2d_dgsem/elixir_advection_nonperiodic.jl +++ b/examples/structured_2d_dgsem/elixir_advection_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -19,8 +18,10 @@ coordinates_min = (-5.0, -5.0) coordinates_max = (5.0, 5.0) mesh = StructuredMesh((16, 16), coordinates_min, coordinates_max, periodicity = false) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -31,26 +32,34 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_advection_parallelogram.jl b/examples/structured_2d_dgsem/elixir_advection_parallelogram.jl index b70deb1231..7c27743c9d 100644 --- a/examples/structured_2d_dgsem/elixir_advection_parallelogram.jl +++ b/examples/structured_2d_dgsem/elixir_advection_parallelogram.jl @@ -1,6 +1,6 @@ # This elixir transforms the setup of elixir_advection_basic to a parallelogram. # The nodal values of the initial condition and the exact solution are the same as -# in elixir_advection_basic. +# in elixir_advection_basic. # However, on this non-rectangular mesh, the metric terms are non-trivial. # The same errors as with elixir_advection_basic are expected. @@ -51,8 +51,10 @@ cells_per_dimension = (16, 16) mesh = StructuredMesh(cells_per_dimension, mapping; periodicity = (true, true)) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_parallelogram, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_parallelogram, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -68,23 +70,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_advection_restart.jl b/examples/structured_2d_dgsem/elixir_advection_restart.jl index 896eda4f84..5defe20294 100644 --- a/examples/structured_2d_dgsem/elixir_advection_restart.jl +++ b/examples/structured_2d_dgsem/elixir_advection_restart.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -28,9 +27,11 @@ ode = semidiscretize(semi, tspan, restart_filename); # Do not overwrite the initial snapshot written by elixir_advection_extended.jl. save_solution.condition.save_initial_solution = false -integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks, maxiters = 100_000); +integrator = init( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks, maxiters = 100_000 +); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/structured_2d_dgsem/elixir_advection_rotated.jl b/examples/structured_2d_dgsem/elixir_advection_rotated.jl index 826c7ebaac..43a2394a27 100644 --- a/examples/structured_2d_dgsem/elixir_advection_rotated.jl +++ b/examples/structured_2d_dgsem/elixir_advection_rotated.jl @@ -1,6 +1,6 @@ # This elixir transforms the setup of elixir_advection_basic to a rotated square. # The nodal values of the initial condition and the exact solution are the same as -# in elixir_advection_basic. +# in elixir_advection_basic. # However, on this rotated mesh, the metric terms are non-trivial. # The same errors as with elixir_advection_basic are expected (except for rounding errors). @@ -12,46 +12,48 @@ using Trixi # if multiple test cases using the same module name are run in the same session. module TrixiExtensionAdvectionRotated -using Trixi + using Trixi -# initial_condition_convergence_test transformed to the rotated rectangle -struct InitialConditionConvergenceTestRotated - sin_alpha::Float64 - cos_alpha::Float64 -end + # initial_condition_convergence_test transformed to the rotated rectangle + struct InitialConditionConvergenceTestRotated + sin_alpha::Float64 + cos_alpha::Float64 + end -function InitialConditionConvergenceTestRotated(alpha) - sin_alpha, cos_alpha = sincos(alpha) + function InitialConditionConvergenceTestRotated(alpha) + sin_alpha, cos_alpha = sincos(alpha) - InitialConditionConvergenceTestRotated(sin_alpha, cos_alpha) -end + InitialConditionConvergenceTestRotated(sin_alpha, cos_alpha) + end -function (initial_condition::InitialConditionConvergenceTestRotated)(x, t, - equation::LinearScalarAdvectionEquation2D) - sin_ = initial_condition.sin_alpha - cos_ = initial_condition.cos_alpha + function (initial_condition::InitialConditionConvergenceTestRotated)( + x, t, + equation::LinearScalarAdvectionEquation2D + ) + sin_ = initial_condition.sin_alpha + cos_ = initial_condition.cos_alpha - # Rotate back to unit square + # Rotate back to unit square - # Clockwise rotation by α and translation by 1 - # Multiply with [ cos(α) sin(α); - # -sin(α) cos(α)] - x_rot = SVector(cos_ * x[1] + sin_ * x[2], -sin_ * x[1] + cos_ * x[2]) - a = equation.advection_velocity - a_rot = SVector(cos_ * a[1] + sin_ * a[2], -sin_ * a[1] + cos_ * a[2]) + # Clockwise rotation by α and translation by 1 + # Multiply with [ cos(α) sin(α); + # -sin(α) cos(α)] + x_rot = SVector(cos_ * x[1] + sin_ * x[2], -sin_ * x[1] + cos_ * x[2]) + a = equation.advection_velocity + a_rot = SVector(cos_ * a[1] + sin_ * a[2], -sin_ * a[1] + cos_ * a[2]) - # Store translated coordinate for easy use of exact solution - x_trans = x_rot - a_rot * t + # Store translated coordinate for easy use of exact solution + x_trans = x_rot - a_rot * t - c = 1.0 - A = 0.5 - L = 2 - f = 1 / L - omega = 2 * pi * f - scalar = c + A * sin(omega * sum(x_trans)) + c = 1.0 + A = 0.5 + L = 2 + f = 1 / L + omega = 2 * pi * f + scalar = c + A * sin(omega * sum(x_trans)) - return SVector(scalar) -end + return SVector(scalar) + end end # module TrixiExtensionAdvectionRotated @@ -96,23 +98,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_advection_waving_flag.jl b/examples/structured_2d_dgsem/elixir_advection_waving_flag.jl index 50393d50a1..d11b53ba20 100644 --- a/examples/structured_2d_dgsem/elixir_advection_waving_flag.jl +++ b/examples/structured_2d_dgsem/elixir_advection_waving_flag.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -42,27 +41,35 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveRestartCallback allows to save a file from which a Trixi.jl simulation can be restarted -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.4) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_euler_ec.jl b/examples/structured_2d_dgsem/elixir_euler_ec.jl index 29686bd3cb..ea3b1ac450 100644 --- a/examples/structured_2d_dgsem/elixir_euler_ec.jl +++ b/examples/structured_2d_dgsem/elixir_euler_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,8 +12,10 @@ initial_condition = initial_condition_weak_blast_wave # Get the DG approximation space volume_flux = flux_ranocha -solver = DGSEM(polydeg = 4, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the curved quad mesh from a mapping function @@ -25,11 +26,15 @@ function mapping(xi_, eta_) xi = 1.5 * xi_ + 1.5 eta = 1.5 * eta_ + 1.5 - y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3)) + y = eta + 3 / 8 * ( + cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3) + ) - x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3)) + x = xi + 3 / 8 * ( + cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3) + ) return SVector(x, y) end @@ -57,22 +62,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_free_stream.jl b/examples/structured_2d_dgsem/elixir_euler_free_stream.jl index 3aab4be979..56f1bba11d 100644 --- a/examples/structured_2d_dgsem/elixir_euler_free_stream.jl +++ b/examples/structured_2d_dgsem/elixir_euler_free_stream.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -17,11 +16,15 @@ function mapping(xi_, eta_) xi = 1.5 * xi_ + 1.5 eta = 1.5 * eta_ + 1.5 - y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3)) + y = eta + 3 / 8 * ( + cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3) + ) - x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3)) + x = xi + 3 / 8 * ( + cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3) + ) return SVector(x, y) end @@ -45,22 +48,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 2.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_rayleigh_taylor_instability.jl b/examples/structured_2d_dgsem/elixir_euler_rayleigh_taylor_instability.jl index dd0cc339b2..300fbab5f6 100644 --- a/examples/structured_2d_dgsem/elixir_euler_rayleigh_taylor_instability.jl +++ b/examples/structured_2d_dgsem/elixir_euler_rayleigh_taylor_instability.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -27,10 +26,12 @@ all boundaries or This should be used together with `source_terms_rayleigh_taylor_instability`, which is defined below. """ -@inline function initial_condition_rayleigh_taylor_instability(x, t, - equations::CompressibleEulerEquations2D, - slope = 1000) - tol = 1e2 * eps() +@inline function initial_condition_rayleigh_taylor_instability( + x, t, + equations::CompressibleEulerEquations2D, + slope = 1000 + ) + tol = 1.0e2 * eps() if x[2] < 0.5 p = 2 * x[2] + 1 @@ -51,8 +52,10 @@ defined below. return prim2cons(SVector(rho, u, v, p), equations) end -@inline function source_terms_rayleigh_taylor_instability(u, x, t, - equations::CompressibleEulerEquations2D) +@inline function source_terms_rayleigh_taylor_instability( + u, x, t, + equations::CompressibleEulerEquations2D + ) g = 1.0 rho, rho_v1, rho_v2, rho_e = u @@ -61,8 +64,10 @@ end # numerical parameters volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_hll, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hll, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # The domain is [0, 0.25] x [0, 1] mapping(xi, eta) = SVector(0.25 * 0.5 * (1.0 + xi), 0.5 * (1.0 + eta)) @@ -72,10 +77,12 @@ cells_per_dimension = (num_elements_per_dimension, num_elements_per_dimension * mesh = StructuredMesh(cells_per_dimension, mapping, periodicity = false) initial_condition = initial_condition_rayleigh_taylor_instability -boundary_conditions = (x_neg = boundary_condition_slip_wall, - x_pos = boundary_condition_slip_wall, - y_neg = boundary_condition_slip_wall, - y_pos = boundary_condition_slip_wall) +boundary_conditions = ( + x_neg = boundary_condition_slip_wall, + x_pos = boundary_condition_slip_wall, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall, +) # # Alternative setup: left/right periodic BCs and Dirichlet BCs on the top/bottom. # boundary_conditions = ( @@ -85,9 +92,11 @@ boundary_conditions = (x_neg = boundary_condition_slip_wall, # y_pos=BoundaryConditionDirichlet(initial_condition), # ) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; - source_terms = source_terms_rayleigh_taylor_instability, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver; + source_terms = source_terms_rayleigh_taylor_instability, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -102,14 +111,18 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_sedov.jl b/examples/structured_2d_dgsem/elixir_euler_sedov.jl index 42094d7191..41a0fbb75d 100644 --- a/examples/structured_2d_dgsem/elixir_euler_sedov.jl +++ b/examples/structured_2d_dgsem/elixir_euler_sedov.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -42,17 +41,23 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Get the curved quad mesh from a mapping function # Mapping as described in https://arxiv.org/abs/2012.12040 @@ -84,22 +89,28 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 300, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 300, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl b/examples/structured_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl index 5c11a7d15a..c76aac5d32 100644 --- a/examples/structured_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl +++ b/examples/structured_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl @@ -38,28 +38,38 @@ end initial_condition = initial_condition_sedov_blast_wave boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (x_neg = boundary_condition, - x_pos = boundary_condition, - y_neg = boundary_condition, - y_pos = boundary_condition) +boundary_conditions = ( + x_neg = boundary_condition, + x_pos = boundary_condition, + y_neg = boundary_condition, + y_pos = boundary_condition, +) surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -limiter_idp = SubcellLimiterIDP(equations, basis; - local_twosided_variables_cons = ["rho"], - local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, - min)], - max_iterations_newton = 40, # Default value of 10 iterations is too low to fulfill bounds. - positivity_variables_cons = [], - positivity_variables_nonlinear = []) +limiter_idp = SubcellLimiterIDP( + equations, basis; + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [ + ( + Trixi.entropy_guermond_etal, + min, + ), + ], + max_iterations_newton = 40, # Default value of 10 iterations is too low to fulfill bounds. + positivity_variables_cons = [], + positivity_variables_nonlinear = [] +) # Variables for global limiting (`positivity_variables_cons` and # `positivity_variables_nonlinear`) are overwritten and used in the tests. -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) # Get the curved quad mesh from a mapping function @@ -75,8 +85,10 @@ end cells_per_dimension = (16, 16) mesh = StructuredMesh(cells_per_dimension, mapping, periodicity = false) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -91,24 +103,30 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) -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 - save_everystep = false, callback = callbacks); +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 + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_source_terms.jl b/examples/structured_2d_dgsem/elixir_euler_source_terms.jl index 6827848e18..7e8c414929 100644 --- a/examples/structured_2d_dgsem/elixir_euler_source_terms.jl +++ b/examples/structured_2d_dgsem/elixir_euler_source_terms.jl @@ -20,8 +20,10 @@ cells_per_dimension = (16, 16) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -36,22 +38,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_source_terms_nonperiodic.jl b/examples/structured_2d_dgsem/elixir_euler_source_terms_nonperiodic.jl index f42d223611..3198bb456f 100644 --- a/examples/structured_2d_dgsem/elixir_euler_source_terms_nonperiodic.jl +++ b/examples/structured_2d_dgsem/elixir_euler_source_terms_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,10 +11,12 @@ initial_condition = initial_condition_convergence_test # you can either use a single function to impose the BCs weakly in all # 2*ndims == 4 directions or you can pass a tuple containing BCs for each direction boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (x_neg = boundary_condition, - x_pos = boundary_condition, - y_neg = boundary_condition, - y_pos = boundary_condition) +boundary_conditions = ( + x_neg = boundary_condition, + x_pos = boundary_condition, + y_neg = boundary_condition, + y_pos = boundary_condition, +) solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -23,9 +24,11 @@ coordinates_min = (0.0, 0.0) coordinates_max = (2.0, 2.0) mesh = StructuredMesh((16, 16), coordinates_min, coordinates_max, periodicity = false) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -40,24 +43,32 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_source_terms_parallelogram.jl b/examples/structured_2d_dgsem/elixir_euler_source_terms_parallelogram.jl index 5d6b090838..001ea58192 100644 --- a/examples/structured_2d_dgsem/elixir_euler_source_terms_parallelogram.jl +++ b/examples/structured_2d_dgsem/elixir_euler_source_terms_parallelogram.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -24,8 +23,10 @@ cells_per_dimension = (16, 16) mesh = StructuredMesh(cells_per_dimension, mapping) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -40,22 +41,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_source_terms_rotated.jl b/examples/structured_2d_dgsem/elixir_euler_source_terms_rotated.jl index f2e3c44889..08d9c68d3b 100644 --- a/examples/structured_2d_dgsem/elixir_euler_source_terms_rotated.jl +++ b/examples/structured_2d_dgsem/elixir_euler_source_terms_rotated.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -7,68 +6,74 @@ using Trixi # if multiple test cases using the same module name are run in the same session. module TrixiExtensionEulerRotated -using Trixi + using Trixi -# initial_condition_convergence_test transformed to the rotated rectangle -struct InitialConditionSourceTermsRotated - sin_alpha::Float64 - cos_alpha::Float64 -end + # initial_condition_convergence_test transformed to the rotated rectangle + struct InitialConditionSourceTermsRotated + sin_alpha::Float64 + cos_alpha::Float64 + end -function InitialConditionSourceTermsRotated(alpha) - sin_alpha, cos_alpha = sincos(alpha) + function InitialConditionSourceTermsRotated(alpha) + sin_alpha, cos_alpha = sincos(alpha) - InitialConditionSourceTermsRotated(sin_alpha, cos_alpha) -end + InitialConditionSourceTermsRotated(sin_alpha, cos_alpha) + end -function (initial_condition::InitialConditionSourceTermsRotated)(x, t, - equations::CompressibleEulerEquations2D) - sin_ = initial_condition.sin_alpha - cos_ = initial_condition.cos_alpha + function (initial_condition::InitialConditionSourceTermsRotated)( + x, t, + equations::CompressibleEulerEquations2D + ) + sin_ = initial_condition.sin_alpha + cos_ = initial_condition.cos_alpha - # Rotate back to unit square and translate from [-1, 1]^2 to [0, 2]^2 + # Rotate back to unit square and translate from [-1, 1]^2 to [0, 2]^2 - # Clockwise rotation by α and translation by 1 - # Multiply with [ cos(α) sin(α); - # -sin(α) cos(α)] - x1 = cos_ * x[1] + sin_ * x[2] + 1 - x2 = -sin_ * x[1] + cos_ * x[2] + 1 + # Clockwise rotation by α and translation by 1 + # Multiply with [ cos(α) sin(α); + # -sin(α) cos(α)] + x1 = cos_ * x[1] + sin_ * x[2] + 1 + x2 = -sin_ * x[1] + cos_ * x[2] + 1 - rho, rho_v1, rho_v2, rho_e = initial_condition_convergence_test(SVector(x1, x2), t, - equations) + rho, rho_v1, rho_v2, rho_e = initial_condition_convergence_test( + SVector(x1, x2), t, + equations + ) - # Rotate velocity vector counterclockwise - # Multiply with [ cos(α) -sin(α); - # sin(α) cos(α)] - rho_v1_rot = cos_ * rho_v1 - sin_ * rho_v2 - rho_v2_rot = sin_ * rho_v1 + cos_ * rho_v2 + # Rotate velocity vector counterclockwise + # Multiply with [ cos(α) -sin(α); + # sin(α) cos(α)] + rho_v1_rot = cos_ * rho_v1 - sin_ * rho_v2 + rho_v2_rot = sin_ * rho_v1 + cos_ * rho_v2 - return SVector(rho, rho_v1_rot, rho_v2_rot, rho_e) -end + return SVector(rho, rho_v1_rot, rho_v2_rot, rho_e) + end -@inline function (source_terms::InitialConditionSourceTermsRotated)(u, x, t, - equations::CompressibleEulerEquations2D) - sin_ = source_terms.sin_alpha - cos_ = source_terms.cos_alpha + @inline function (source_terms::InitialConditionSourceTermsRotated)( + u, x, t, + equations::CompressibleEulerEquations2D + ) + sin_ = source_terms.sin_alpha + cos_ = source_terms.cos_alpha - # Rotate back to unit square and translate from [-1, 1]^2 to [0, 2]^2 + # Rotate back to unit square and translate from [-1, 1]^2 to [0, 2]^2 - # Clockwise rotation by α and translation by 1 - # Multiply with [ cos(α) sin(α); - # -sin(α) cos(α)] - x1 = cos_ * x[1] + sin_ * x[2] + 1 - x2 = -sin_ * x[1] + cos_ * x[2] + 1 + # Clockwise rotation by α and translation by 1 + # Multiply with [ cos(α) sin(α); + # -sin(α) cos(α)] + x1 = cos_ * x[1] + sin_ * x[2] + 1 + x2 = -sin_ * x[1] + cos_ * x[2] + 1 - du1, du2, du3, du4 = source_terms_convergence_test(u, SVector(x1, x2), t, equations) + du1, du2, du3, du4 = source_terms_convergence_test(u, SVector(x1, x2), t, equations) - # Rotate velocity vector counterclockwise - # Multiply with [ cos(α) -sin(α); - # sin(α) cos(α)] - du2_rotated = cos_ * du2 - sin_ * du3 - du3_rotated = sin_ * du2 + cos_ * du3 + # Rotate velocity vector counterclockwise + # Multiply with [ cos(α) -sin(α); + # sin(α) cos(α)] + du2_rotated = cos_ * du2 - sin_ * du3 + du3_rotated = sin_ * du2 + cos_ * du3 - return SVector(du1, du2_rotated, du3_rotated, du4) -end + return SVector(du1, du2_rotated, du3_rotated, du4) + end end # module TrixiExtensionEulerRotated @@ -93,8 +98,10 @@ cells_per_dimension = (16, 16) mesh = StructuredMesh(cells_per_dimension, mapping) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_source_terms, solver, - source_terms = initial_condition_source_terms) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_source_terms, solver, + source_terms = initial_condition_source_terms +) ############################################################################### # ODE solvers, callbacks etc. @@ -109,22 +116,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_source_terms_waving_flag.jl b/examples/structured_2d_dgsem/elixir_euler_source_terms_waving_flag.jl index 4a78b65ae0..6c367f984e 100644 --- a/examples/structured_2d_dgsem/elixir_euler_source_terms_waving_flag.jl +++ b/examples/structured_2d_dgsem/elixir_euler_source_terms_waving_flag.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -22,8 +21,10 @@ cells_per_dimension = (16, 16) mesh = StructuredMesh(cells_per_dimension, (f1, f2, f3, f4)) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -38,22 +39,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_euler_warm_bubble.jl b/examples/structured_2d_dgsem/elixir_euler_warm_bubble.jl index 38b9386e94..f1c351953e 100644 --- a/examples/structured_2d_dgsem/elixir_euler_warm_bubble.jl +++ b/examples/structured_2d_dgsem/elixir_euler_warm_bubble.jl @@ -3,7 +3,7 @@ using Trixi # Warm bubble test case from # - Wicker, L. J., and Skamarock, W. C. (1998) -# A time-splitting scheme for the elastic equations incorporating +# A time-splitting scheme for the elastic equations incorporating # second-order Runge–Kutta time differencing # [DOI: 10.1175/1520-0493(1998)126%3C1992:ATSSFT%3E2.0.CO;2](https://doi.org/10.1175/1520-0493(1998)126%3C1992:ATSSFT%3E2.0.CO;2) # See also @@ -11,7 +11,7 @@ using Trixi # A Benchmark Simulation for Moist Nonhydrostatic Numerical Models # [DOI: 10.1175/1520-0493(2002)130<2917:ABSFMN>2.0.CO;2](https://doi.org/10.1175/1520-0493(2002)130<2917:ABSFMN>2.0.CO;2) # - Carpenter, Droegemeier, Woodward, Hane (1990) -# Application of the Piecewise Parabolic Method (PPM) to +# Application of the Piecewise Parabolic Method (PPM) to # Meteorological Modeling # [DOI: 10.1175/1520-0493(1990)118<0586:AOTPPM>2.0.CO;2](https://doi.org/10.1175/1520-0493(1990)118<0586:AOTPPM>2.0.CO;2) struct WarmBubbleSetup @@ -79,10 +79,12 @@ warm_bubble_setup = WarmBubbleSetup() equations = CompressibleEulerEquations2D(warm_bubble_setup.gamma) -boundary_conditions = (x_neg = boundary_condition_periodic, - x_pos = boundary_condition_periodic, - y_neg = boundary_condition_slip_wall, - y_pos = boundary_condition_slip_wall) +boundary_conditions = ( + x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall, +) polydeg = 3 basis = LobattoLegendreBasis(polydeg) @@ -100,12 +102,16 @@ coordinates_min = (0.0, 0.0) coordinates_max = (20_000.0, 10_000.0) cells_per_dimension = (64, 32) -mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, - periodicity = (true, false)) +mesh = StructuredMesh( + cells_per_dimension, coordinates_min, coordinates_max, + periodicity = (true, false) +) -semi = SemidiscretizationHyperbolic(mesh, equations, warm_bubble_setup, solver, - source_terms = warm_bubble_setup, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, warm_bubble_setup, solver, + source_terms = warm_bubble_setup, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -118,30 +124,38 @@ summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:entropy_conservation_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = analysis_interval, - save_initial_solution = true, - save_final_solution = true, - output_directory = "out", - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = analysis_interval, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - maxiters = 1.0e7, - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + maxiters = 1.0e7, + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_eulermulti_convergence_ec.jl b/examples/structured_2d_dgsem/elixir_eulermulti_convergence_ec.jl index 95f71f3813..7d30807dca 100644 --- a/examples/structured_2d_dgsem/elixir_eulermulti_convergence_ec.jl +++ b/examples/structured_2d_dgsem/elixir_eulermulti_convergence_ec.jl @@ -1,25 +1,30 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler multicomponent equations -equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.4), - gas_constants = (0.4, 0.4)) +equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.4), + gas_constants = (0.4, 0.4) +) initial_condition = initial_condition_convergence_test volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) cells_per_dimension = (16, 16) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -34,22 +39,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_eulerpolytropic_convergence.jl b/examples/structured_2d_dgsem/elixir_eulerpolytropic_convergence.jl index 4fc9281e7a..4f30db0a15 100644 --- a/examples/structured_2d_dgsem/elixir_eulerpolytropic_convergence.jl +++ b/examples/structured_2d_dgsem/elixir_eulerpolytropic_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,20 +11,26 @@ equations = PolytropicEulerEquations2D(gamma, kappa) initial_condition = initial_condition_convergence_test volume_flux = flux_winters_etal -solver = DGSEM(polydeg = 3, surface_flux = flux_hll, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hll, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) cells_per_dimension = (4, 4) -mesh = StructuredMesh(cells_per_dimension, - coordinates_min, - coordinates_max) +mesh = StructuredMesh( + cells_per_dimension, + coordinates_min, + coordinates_max +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -36,28 +41,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_eulerpolytropic_ec.jl b/examples/structured_2d_dgsem/elixir_eulerpolytropic_ec.jl index b96f1d3cd6..8969358ed9 100644 --- a/examples/structured_2d_dgsem/elixir_eulerpolytropic_ec.jl +++ b/examples/structured_2d_dgsem/elixir_eulerpolytropic_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,8 +14,10 @@ initial_condition = initial_condition_weak_blast_wave # Get the DG approximation space volume_flux = flux_winters_etal -solver = DGSEM(polydeg = 4, surface_flux = flux_winters_etal, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = flux_winters_etal, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the curved quad mesh from a mapping function @@ -27,11 +28,15 @@ function mapping(xi_, eta_) xi = 1.5 * xi_ + 1.5 eta = 1.5 * eta_ + 1.5 - y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3)) + y = eta + 3 / 8 * ( + cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3) + ) - x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3)) + x = xi + 3 / 8 * ( + cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3) + ) return SVector(x, y) end @@ -59,22 +64,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_eulerpolytropic_isothermal_wave.jl b/examples/structured_2d_dgsem/elixir_eulerpolytropic_isothermal_wave.jl index 4ab9095757..8aa511291e 100644 --- a/examples/structured_2d_dgsem/elixir_eulerpolytropic_isothermal_wave.jl +++ b/examples/structured_2d_dgsem/elixir_eulerpolytropic_isothermal_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -24,25 +23,33 @@ end initial_condition = initial_condition_wave volume_flux = flux_winters_etal -solver = DGSEM(polydeg = 2, surface_flux = flux_hll, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 2, surface_flux = flux_hll, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -1.0) coordinates_max = (2.0, 1.0) cells_per_dimension = (64, 32) -boundary_conditions = (x_neg = boundary_condition_periodic, - x_pos = boundary_condition_periodic, - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) +boundary_conditions = ( + x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) -mesh = StructuredMesh(cells_per_dimension, - coordinates_min, - coordinates_max) +mesh = StructuredMesh( + cells_per_dimension, + coordinates_min, + coordinates_max +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -57,27 +64,35 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 50, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 50, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (1.0e-4, 1.0e-4), - variables = (Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (1.0e-4, 1.0e-4), + variables = (Trixi.density, pressure) +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_eulerpolytropic_wave.jl b/examples/structured_2d_dgsem/elixir_eulerpolytropic_wave.jl index fd332b20ae..3cdbca56cb 100644 --- a/examples/structured_2d_dgsem/elixir_eulerpolytropic_wave.jl +++ b/examples/structured_2d_dgsem/elixir_eulerpolytropic_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -24,25 +23,33 @@ end initial_condition = initial_condition_wave volume_flux = flux_winters_etal -solver = DGSEM(polydeg = 2, surface_flux = flux_hll, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 2, surface_flux = flux_hll, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -1.0) coordinates_max = (2.0, 1.0) cells_per_dimension = (64, 32) -boundary_conditions = (x_neg = boundary_condition_periodic, - x_pos = boundary_condition_periodic, - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) +boundary_conditions = ( + x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) -mesh = StructuredMesh(cells_per_dimension, - coordinates_min, - coordinates_max) +mesh = StructuredMesh( + cells_per_dimension, + coordinates_min, + coordinates_max +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -57,24 +64,30 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 50, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 50, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_2d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl b/examples/structured_2d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl index ccb74fa4b6..a29d5ebd5c 100644 --- a/examples/structured_2d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl +++ b/examples/structured_2d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -7,8 +6,10 @@ using Trixi equations = HyperbolicDiffusionEquations2D() -@inline function initial_condition_harmonic_nonperiodic(x, t, - equations::HyperbolicDiffusionEquations2D) +@inline function initial_condition_harmonic_nonperiodic( + x, t, + equations::HyperbolicDiffusionEquations2D + ) # elliptic equation: -ν Δϕ = 0 in Ω, u = g on ∂Ω if t == 0.0 phi = 1.0 @@ -37,12 +38,16 @@ solver = DGSEM(polydeg = 4, surface_flux = flux_godunov) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) cells_per_dimension = (8, 8) -mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max, - periodicity = false) +mesh = StructuredMesh( + cells_per_dimension, coordinates_min, coordinates_max, + periodicity = false +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions, - source_terms = source_terms_harmonic) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions, + source_terms = source_terms_harmonic +) ############################################################################### # ODE solvers, callbacks etc. @@ -60,22 +65,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_hypdiff_nonperiodic.jl b/examples/structured_2d_dgsem/elixir_hypdiff_nonperiodic.jl index 681b3bd781..1f2a247a8b 100644 --- a/examples/structured_2d_dgsem/elixir_hypdiff_nonperiodic.jl +++ b/examples/structured_2d_dgsem/elixir_hypdiff_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -11,10 +10,12 @@ initial_condition = initial_condition_poisson_nonperiodic solver = DGSEM(polydeg = 6, surface_flux = flux_lax_friedrichs) -boundary_conditions = (x_neg = boundary_condition_poisson_nonperiodic, - x_pos = boundary_condition_poisson_nonperiodic, - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) +boundary_conditions = ( + x_neg = boundary_condition_poisson_nonperiodic, + x_pos = boundary_condition_poisson_nonperiodic, + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) ############################################################################### # Get the curved quad mesh from a mapping function @@ -25,11 +26,15 @@ function mapping(xi_, eta_) xi = 1.5 * xi_ + 1.5 eta = 1.5 * eta_ + 1.5 - y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3)) + y = eta + 3 / 8 * ( + cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3) + ) - x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3)) + x = xi + 3 / 8 * ( + cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3) + ) return SVector(x, y) end @@ -38,9 +43,11 @@ end cells_per_dimension = (8, 8) mesh = StructuredMesh(cells_per_dimension, mapping, periodicity = (false, true)) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_poisson_nonperiodic, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_poisson_nonperiodic, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -54,27 +61,35 @@ resid_tol = 5.0e-12 steady_state_callback = SteadyStateCallback(abstol = resid_tol, reltol = 0.0) analysis_interval = 4000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 4000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 4000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.9) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = Trixi.solve(ode, Trixi.HypDiffN3Erk3Sstar52(), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = Trixi.solve( + ode, Trixi.HypDiffN3Erk3Sstar52(), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_mhd_alfven_wave.jl b/examples/structured_2d_dgsem/elixir_mhd_alfven_wave.jl index e8f2b2ecc3..c53d1803a0 100644 --- a/examples/structured_2d_dgsem/elixir_mhd_alfven_wave.jl +++ b/examples/structured_2d_dgsem/elixir_mhd_alfven_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,9 +11,11 @@ initial_condition = initial_condition_convergence_test # Get the DG approximation space volume_flux = (flux_central, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Get the curved quad mesh from a mapping function # Mapping as described in https://arxiv.org/abs/1809.01178 @@ -25,12 +26,16 @@ function mapping(xi_, eta_) eta = 0.5 * sqrt(2) * eta_ + 0.5 * sqrt(2) y = eta + - sqrt(2) / 12 * (cos(1.5 * pi * (2 * xi - sqrt(2)) / sqrt(2)) * - cos(0.5 * pi * (2 * eta - sqrt(2)) / sqrt(2))) + sqrt(2) / 12 * ( + cos(1.5 * pi * (2 * xi - sqrt(2)) / sqrt(2)) * + cos(0.5 * pi * (2 * eta - sqrt(2)) / sqrt(2)) + ) x = xi + - sqrt(2) / 12 * (cos(0.5 * pi * (2 * xi - sqrt(2)) / sqrt(2)) * - cos(2 * pi * (2 * y - sqrt(2)) / sqrt(2))) + sqrt(2) / 12 * ( + cos(0.5 * pi * (2 * xi - sqrt(2)) / sqrt(2)) * + cos(2 * pi * (2 * y - sqrt(2)) / sqrt(2)) + ) return SVector(x, y) end @@ -50,36 +55,46 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = false, - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal, - energy_magnetic, - cross_helicity)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = false, + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + energy_magnetic, + cross_helicity, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 2.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_mhd_coupled.jl b/examples/structured_2d_dgsem/elixir_mhd_coupled.jl index d3aa4ecf58..f32faad3a1 100644 --- a/examples/structured_2d_dgsem/elixir_mhd_coupled.jl +++ b/examples/structured_2d_dgsem/elixir_mhd_coupled.jl @@ -33,16 +33,22 @@ cells_per_dimension = (32, 64) # Extend the definition of the non-conservative Powell flux functions. import Trixi.flux_nonconservative_powell -function flux_nonconservative_powell(u_ll, u_rr, - normal_direction_ll::AbstractVector, - equations::IdealGlmMhdEquations2D) - flux_nonconservative_powell(u_ll, u_rr, normal_direction_ll, normal_direction_ll, - equations) +function flux_nonconservative_powell( + u_ll, u_rr, + normal_direction_ll::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + flux_nonconservative_powell( + u_ll, u_rr, normal_direction_ll, normal_direction_ll, + equations + ) end volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ########### # system #1 @@ -51,21 +57,31 @@ solver = DGSEM(polydeg = 3, initial_condition1 = initial_condition_convergence_test coordinates_min1 = (-1 / sin(pi / 4), -1 / sin(pi / 4)) coordinates_max1 = (0.0, 1 / sin(pi / 4)) -mesh1 = StructuredMesh(cells_per_dimension, - coordinates_min1, - coordinates_max1, - periodicity = (false, true)) +mesh1 = StructuredMesh( + cells_per_dimension, + coordinates_min1, + coordinates_max1, + periodicity = (false, true) +) coupling_function1 = (x, u, equations_other, equations_own) -> u -boundary_conditions1 = (x_neg = BoundaryConditionCoupled(2, (:end, :i_forward), Float64, - coupling_function1), - x_pos = BoundaryConditionCoupled(2, (:begin, :i_forward), Float64, - coupling_function1), - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) - -semi1 = SemidiscretizationHyperbolic(mesh1, equations, initial_condition1, solver, - boundary_conditions = boundary_conditions1) +boundary_conditions1 = ( + x_neg = BoundaryConditionCoupled( + 2, (:end, :i_forward), Float64, + coupling_function1 + ), + x_pos = BoundaryConditionCoupled( + 2, (:begin, :i_forward), Float64, + coupling_function1 + ), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) + +semi1 = SemidiscretizationHyperbolic( + mesh1, equations, initial_condition1, solver, + boundary_conditions = boundary_conditions1 +) ########### # system #2 @@ -74,21 +90,31 @@ semi1 = SemidiscretizationHyperbolic(mesh1, equations, initial_condition1, solve initial_condition2 = initial_condition_convergence_test coordinates_min2 = (0.0, -1 / sin(pi / 4)) coordinates_max2 = (1 / sin(pi / 4), 1 / sin(pi / 4)) -mesh2 = StructuredMesh(cells_per_dimension, - coordinates_min2, - coordinates_max2, - periodicity = (false, true)) +mesh2 = StructuredMesh( + cells_per_dimension, + coordinates_min2, + coordinates_max2, + periodicity = (false, true) +) coupling_function2 = (x, u, equations_other, equations_own) -> u -boundary_conditions2 = (x_neg = BoundaryConditionCoupled(1, (:end, :i_forward), Float64, - coupling_function2), - x_pos = BoundaryConditionCoupled(1, (:begin, :i_forward), Float64, - coupling_function2), - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) - -semi2 = SemidiscretizationHyperbolic(mesh2, equations, initial_condition2, solver, - boundary_conditions = boundary_conditions2) +boundary_conditions2 = ( + x_neg = BoundaryConditionCoupled( + 1, (:end, :i_forward), Float64, + coupling_function2 + ), + x_pos = BoundaryConditionCoupled( + 1, (:begin, :i_forward), Float64, + coupling_function2 + ), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) + +semi2 = SemidiscretizationHyperbolic( + mesh2, equations, initial_condition2, solver, + boundary_conditions = boundary_conditions2 +) # Create a semidiscretization that bundles all the semidiscretizations. semi = SemidiscretizationCoupled(semi1, semi2) @@ -109,28 +135,36 @@ analysis_callback = AnalysisCallbackCoupled(semi, analysis_callback1, analysis_c alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 50, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 50, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 stepsize_callback = StepsizeCallback(cfl = cfl) -glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl, - semi_indices = [1, 2]) +glm_speed_callback = GlmSpeedCallback( + glm_scale = 0.5, cfl = cfl, + semi_indices = [1, 2] +) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 0.01, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 0.01, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_mhd_ec.jl b/examples/structured_2d_dgsem/elixir_mhd_ec.jl index a6c31744ca..4171b5d19b 100644 --- a/examples/structured_2d_dgsem/elixir_mhd_ec.jl +++ b/examples/structured_2d_dgsem/elixir_mhd_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -30,9 +29,11 @@ initial_condition = initial_condition_shifted_weak_blast_wave # Get the DG approximation space volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 5, - surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 5, + surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Get the curved quad mesh from a mapping function # Mapping as described in https://arxiv.org/abs/2012.12040, but reduced to 2D @@ -41,11 +42,15 @@ function mapping(xi_, eta_) xi = 1.5 * xi_ + 1.5 eta = 1.5 * eta_ + 1.5 - y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3)) + y = eta + 3 / 8 * ( + cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3) + ) - x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3)) + x = xi + 3 / 8 * ( + cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3) + ) return SVector(x, y) end @@ -66,36 +71,46 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = false, - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal, - energy_magnetic, - cross_helicity)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = false, + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + energy_magnetic, + cross_helicity, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_mhd_ec_shockcapturing.jl b/examples/structured_2d_dgsem/elixir_mhd_ec_shockcapturing.jl index 6668014a0b..0d8df51237 100644 --- a/examples/structured_2d_dgsem/elixir_mhd_ec_shockcapturing.jl +++ b/examples/structured_2d_dgsem/elixir_mhd_ec_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,14 +11,18 @@ surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) # Get the curved quad mesh from a mapping function @@ -56,16 +59,20 @@ stepsize_callback = StepsizeCallback(cfl = cfl) glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_shallowwater_source_terms.jl b/examples/structured_2d_dgsem/elixir_shallowwater_source_terms.jl index 532fe8dbe7..f661bacb50 100644 --- a/examples/structured_2d_dgsem/elixir_shallowwater_source_terms.jl +++ b/examples/structured_2d_dgsem/elixir_shallowwater_source_terms.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,9 +9,11 @@ equations = ShallowWaterEquations2D(gravity_constant = 9.81) initial_condition = initial_condition_convergence_test volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (sqrt(2.0), sqrt(2.0)) @@ -21,8 +22,10 @@ cells_per_dimension = (8, 8) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -37,22 +40,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced.jl b/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced.jl index 09abdf3384..1dae8e9eed 100644 --- a/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced.jl +++ b/examples/structured_2d_dgsem/elixir_shallowwater_well_balanced.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -18,8 +17,10 @@ function initial_condition_well_balancedness(x, t, equations::ShallowWaterEquati v2 = 0.0 # bottom topography taken from Pond.control in [HOHQMesh](https://github.com/trixi-framework/HOHQMesh) x1, x2 = x - b = (1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + - 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2))) + b = ( + 1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + + 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2)) + ) return prim2cons(SVector(H, v1, v2, b), equations) end @@ -29,11 +30,17 @@ initial_condition = initial_condition_well_balancedness # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -surface_flux = (FluxHydrostaticReconstruction(flux_lax_friedrichs, - hydrostatic_reconstruction_audusse_etal), - flux_nonconservative_audusse_etal) -solver = DGSEM(polydeg = 4, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +surface_flux = ( + FluxHydrostaticReconstruction( + flux_lax_friedrichs, + hydrostatic_reconstruction_audusse_etal + ), + flux_nonconservative_audusse_etal, +) +solver = DGSEM( + polydeg = 4, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # This setup a structured periodic mesh @@ -64,8 +71,10 @@ ode = semidiscretize(semi, tspan) # In contrast to the usual signature of initial conditions, this one get passed the # `element_id` explicitly. In particular, this initial conditions works as intended # only for the specific mesh loaded above! -function initial_condition_discontinuous_well_balancedness(x, t, element_id, - equations::ShallowWaterEquations2D) +function initial_condition_discontinuous_well_balancedness( + x, t, element_id, + equations::ShallowWaterEquations2D + ) # Set the background values H = equations.H0 v1 = 0.0 @@ -85,10 +94,14 @@ u = Trixi.wrap_array(ode.u0, semi) # reset the initial condition for element in eachelement(semi.solver, semi.cache) for j in eachnode(semi.solver), i in eachnode(semi.solver) - x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, - semi.solver, i, j, element) - u_node = initial_condition_discontinuous_well_balancedness(x_node, first(tspan), - element, equations) + x_node = Trixi.get_node_coords( + semi.cache.elements.node_coordinates, equations, + semi.solver, i, j, element + ) + u_node = initial_condition_discontinuous_well_balancedness( + x_node, first(tspan), + element, equations + ) Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) end end @@ -99,24 +112,32 @@ end summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (lake_at_rest_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (lake_at_rest_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_3d_dgsem/elixir_advection_basic.jl b/examples/structured_3d_dgsem/elixir_advection_basic.jl index 5b0bb371fe..2899f58863 100644 --- a/examples/structured_3d_dgsem/elixir_advection_basic.jl +++ b/examples/structured_3d_dgsem/elixir_advection_basic.jl @@ -21,8 +21,10 @@ cells_per_dimension = (8, 8, 8) mesh = StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -38,27 +40,35 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The SaveRestartCallback allows to save a file from which a Trixi.jl simulation can be restarted -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.2) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, save_restart, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, save_restart, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_3d_dgsem/elixir_advection_free_stream.jl b/examples/structured_3d_dgsem/elixir_advection_free_stream.jl index 12d52f1516..33059de1f5 100644 --- a/examples/structured_3d_dgsem/elixir_advection_free_stream.jl +++ b/examples/structured_3d_dgsem/elixir_advection_free_stream.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -19,19 +18,25 @@ function mapping(xi_, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 y = eta + - 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 @@ -58,23 +63,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 2.0) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/structured_3d_dgsem/elixir_advection_nonperiodic_curved.jl b/examples/structured_3d_dgsem/elixir_advection_nonperiodic_curved.jl index 1a20a9c853..69151f7b35 100644 --- a/examples/structured_3d_dgsem/elixir_advection_nonperiodic_curved.jl +++ b/examples/structured_3d_dgsem/elixir_advection_nonperiodic_curved.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -21,19 +20,25 @@ function mapping(xi, eta, zeta) # zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 6 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 @@ -41,8 +46,10 @@ end cells_per_dimension = (8, 8, 8) mesh = StructuredMesh(cells_per_dimension, mapping, periodicity = false) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -53,32 +60,42 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_restart, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_restart, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_3d_dgsem/elixir_advection_restart.jl b/examples/structured_3d_dgsem/elixir_advection_restart.jl index e70a83cee6..e12e251d41 100644 --- a/examples/structured_3d_dgsem/elixir_advection_restart.jl +++ b/examples/structured_3d_dgsem/elixir_advection_restart.jl @@ -1,12 +1,13 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # create a restart file -trixi_include(@__MODULE__, joinpath(@__DIR__, "elixir_advection_basic.jl"), - cells_per_dimension = (4, 4, 4)) +trixi_include( + @__MODULE__, joinpath(@__DIR__, "elixir_advection_basic.jl"), + cells_per_dimension = (4, 4, 4) +) ############################################################################### # adapt the parameters that have changed compared to "elixir_advection_extended.jl" @@ -17,8 +18,10 @@ trixi_include(@__MODULE__, joinpath(@__DIR__, "elixir_advection_basic.jl"), restart_filename = joinpath("out", "restart_000000010.h5") mesh = load_mesh(restart_filename) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) tspan = (load_time(restart_filename), 2.0) dt = load_dt(restart_filename) @@ -27,9 +30,11 @@ ode = semidiscretize(semi, tspan, restart_filename); # Do not overwrite the initial snapshot written by elixir_advection_extended.jl. save_solution.condition.save_initial_solution = false -integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks, maxiters = 100_000); +integrator = init( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks, maxiters = 100_000 +); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/structured_3d_dgsem/elixir_euler_ec.jl b/examples/structured_3d_dgsem/elixir_euler_ec.jl index 1330006760..322fe87a28 100644 --- a/examples/structured_3d_dgsem/elixir_euler_ec.jl +++ b/examples/structured_3d_dgsem/elixir_euler_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,8 +12,10 @@ initial_condition = initial_condition_weak_blast_wave # Get the DG approximation space volume_flux = flux_ranocha -solver = DGSEM(polydeg = 5, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 5, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the curved quad mesh from a file @@ -27,19 +28,25 @@ function mapping(xi_, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 y = eta + - 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 @@ -66,22 +73,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_3d_dgsem/elixir_euler_free_stream.jl b/examples/structured_3d_dgsem/elixir_euler_free_stream.jl index 1b01287100..797592d181 100644 --- a/examples/structured_3d_dgsem/elixir_euler_free_stream.jl +++ b/examples/structured_3d_dgsem/elixir_euler_free_stream.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,8 +8,10 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_constant -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) # Mapping as described in https://arxiv.org/abs/2012.12040 function mapping(xi_, eta_, zeta_) @@ -20,19 +21,25 @@ function mapping(xi_, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 y = eta + - 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 @@ -56,25 +63,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.3) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_3d_dgsem/elixir_euler_sedov.jl b/examples/structured_3d_dgsem/elixir_euler_sedov.jl index 1f2d9d2eeb..c9e061b50f 100644 --- a/examples/structured_3d_dgsem/elixir_euler_sedov.jl +++ b/examples/structured_3d_dgsem/elixir_euler_sedov.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,8 +13,10 @@ The Sedov blast wave setup based on Flash - https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node187.html#SECTION010114000000000000000 with smaller strength of the initial discontinuity. """ -function initial_condition_medium_sedov_blast_wave(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_medium_sedov_blast_wave( + x, t, + equations::CompressibleEulerEquations3D + ) # Set up polar coordinates inicenter = SVector(0.0, 0.0, 0.0) x_norm = x[1] - inicenter[1] @@ -45,17 +46,23 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Mapping as described in https://arxiv.org/abs/2012.12040 function mapping(xi, eta, zeta) @@ -88,22 +95,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_3d_dgsem/elixir_euler_source_terms.jl b/examples/structured_3d_dgsem/elixir_euler_source_terms.jl index ebf1336c12..48d963c0af 100644 --- a/examples/structured_3d_dgsem/elixir_euler_source_terms.jl +++ b/examples/structured_3d_dgsem/elixir_euler_source_terms.jl @@ -11,8 +11,10 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) # coordinates_min = (0.0, 0.0, 0.0) # coordinates_max = (2.0, 2.0, 2.0) @@ -27,8 +29,10 @@ cells_per_dimension = (4, 4, 4) mesh = StructuredMesh(cells_per_dimension, (f1, f2, f3, f4, f5, f6)) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -43,22 +47,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_3d_dgsem/elixir_euler_source_terms_nonperiodic_curved.jl b/examples/structured_3d_dgsem/elixir_euler_source_terms_nonperiodic_curved.jl index eb358fa5da..836075b692 100644 --- a/examples/structured_3d_dgsem/elixir_euler_source_terms_nonperiodic_curved.jl +++ b/examples/structured_3d_dgsem/elixir_euler_source_terms_nonperiodic_curved.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,15 +11,19 @@ initial_condition = initial_condition_convergence_test # you can either use a single function to impose the BCs weakly in all # 2*ndims == 4 directions or you can pass a tuple containing BCs for each direction boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (x_neg = boundary_condition, - x_pos = boundary_condition, - y_neg = boundary_condition, - y_pos = boundary_condition, - z_neg = boundary_condition, - z_pos = boundary_condition) - -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +boundary_conditions = ( + x_neg = boundary_condition, + x_pos = boundary_condition, + y_neg = boundary_condition, + y_pos = boundary_condition, + z_neg = boundary_condition, + z_pos = boundary_condition, +) + +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) # Mapping as described in https://arxiv.org/abs/2012.12040 but with less warping. function mapping(xi, eta, zeta) @@ -30,19 +33,25 @@ function mapping(xi, eta, zeta) # zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 6 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + cos(0.5 * pi * (2 * x - 3) / 3) * + cos(pi * (2 * y - 3) / 3) * + cos(0.5 * pi * (2 * zeta - 3) / 3) + ) # Transform the weird deformed cube to be approximately the cube [0,2]^3 return SVector(x + 1, y + 1, z + 1) @@ -51,9 +60,11 @@ end cells_per_dimension = (4, 4, 4) mesh = StructuredMesh(cells_per_dimension, mapping, periodicity = false) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -68,22 +79,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_3d_dgsem/elixir_mhd_alfven_wave.jl b/examples/structured_3d_dgsem/elixir_mhd_alfven_wave.jl index 03f50ff3e5..c963bd57b9 100644 --- a/examples/structured_3d_dgsem/elixir_mhd_alfven_wave.jl +++ b/examples/structured_3d_dgsem/elixir_mhd_alfven_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,10 +9,14 @@ equations = IdealGlmMhdEquations3D(5 / 3) initial_condition = initial_condition_convergence_test volume_flux = (flux_central, flux_nonconservative_powell) -solver = DGSEM(polydeg = 5, - surface_flux = (flux_hlle, - flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 5, + surface_flux = ( + flux_hlle, + flux_nonconservative_powell, + ), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Create the mesh # Note, we use the domain [-1, 1]^3 for the Alfvén wave convergence test case so the @@ -48,26 +51,32 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.2 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_3d_dgsem/elixir_mhd_ec.jl b/examples/structured_3d_dgsem/elixir_mhd_ec.jl index 5b3cd6f371..f57a1d7337 100644 --- a/examples/structured_3d_dgsem/elixir_mhd_ec.jl +++ b/examples/structured_3d_dgsem/elixir_mhd_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,9 +9,11 @@ equations = IdealGlmMhdEquations3D(1.4) initial_condition = initial_condition_weak_blast_wave volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Create a heavily warped curved mesh @@ -24,19 +25,25 @@ function mapping(xi_, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 y = eta + - 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 @@ -60,26 +67,32 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.4 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/structured_3d_dgsem/elixir_mhd_ec_shockcapturing.jl b/examples/structured_3d_dgsem/elixir_mhd_ec_shockcapturing.jl index 084e2ee962..9fd46906f0 100644 --- a/examples/structured_3d_dgsem/elixir_mhd_ec_shockcapturing.jl +++ b/examples/structured_3d_dgsem/elixir_mhd_ec_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,17 +12,23 @@ surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Create a heavily warped curved mesh @@ -35,19 +40,25 @@ function mapping(xi_, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 y = eta + - 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 @@ -76,15 +87,19 @@ stepsize_callback = StepsizeCallback(cfl = cfl) glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl b/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl index 618be7f896..546cec455b 100644 --- a/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl +++ b/examples/t8code_2d_dgsem/elixir_advection_amr_solution_independent.jl @@ -4,77 +4,85 @@ using Trixi # Define new structs inside a module to allow re-evaluating the file. module TrixiExtension -using Trixi + using Trixi + + struct IndicatorSolutionIndependent{Cache <: NamedTuple} <: Trixi.AbstractIndicator + cache::Cache + end + + function IndicatorSolutionIndependent(semi) + basis = semi.solver.basis + alpha = Vector{real(basis)}() + cache = (; semi.mesh, alpha) + return IndicatorSolutionIndependent{typeof(cache)}(cache) + end -struct IndicatorSolutionIndependent{Cache <: NamedTuple} <: Trixi.AbstractIndicator - cache::Cache -end - -function IndicatorSolutionIndependent(semi) - basis = semi.solver.basis - alpha = Vector{real(basis)}() - cache = (; semi.mesh, alpha) - return IndicatorSolutionIndependent{typeof(cache)}(cache) -end - -function (indicator::IndicatorSolutionIndependent)(u::AbstractArray{<:Any, 4}, - mesh, equations, dg, cache; - t, kwargs...) - mesh = indicator.cache.mesh - alpha = indicator.cache.alpha - resize!(alpha, nelements(dg, cache)) - - # Predict the theoretical center. - advection_velocity = (0.2, -0.7) - center = t .* advection_velocity - - inner_distance = 1 - outer_distance = 1.85 - - # Iterate over all elements. - for element in eachindex(alpha) - # Calculate periodic distance between cell and center. - # This requires an uncurved mesh! - coordinates = SVector(0.5 * (cache.elements.node_coordinates[1, 1, 1, element] + - cache.elements.node_coordinates[1, end, 1, element]), - 0.5 * (cache.elements.node_coordinates[2, 1, 1, element] + - cache.elements.node_coordinates[2, 1, end, element])) - - # The geometric shape of the amr should be preserved when the base_level is increased. - # This is done by looking at the original coordinates of each cell. - cell_coordinates = original_coordinates(coordinates, 5 / 8) - cell_distance = periodic_distance_2d(cell_coordinates, center, 10) - if cell_distance < (inner_distance + outer_distance) / 2 - cell_coordinates = original_coordinates(coordinates, 5 / 16) + function (indicator::IndicatorSolutionIndependent)( + u::AbstractArray{<:Any, 4}, + mesh, equations, dg, cache; + t, kwargs... + ) + mesh = indicator.cache.mesh + alpha = indicator.cache.alpha + resize!(alpha, nelements(dg, cache)) + + # Predict the theoretical center. + advection_velocity = (0.2, -0.7) + center = t .* advection_velocity + + inner_distance = 1 + outer_distance = 1.85 + + # Iterate over all elements. + for element in eachindex(alpha) + # Calculate periodic distance between cell and center. + # This requires an uncurved mesh! + coordinates = SVector( + 0.5 * ( + cache.elements.node_coordinates[1, 1, 1, element] + + cache.elements.node_coordinates[1, end, 1, element] + ), + 0.5 * ( + cache.elements.node_coordinates[2, 1, 1, element] + + cache.elements.node_coordinates[2, 1, end, element] + ) + ) + + # The geometric shape of the amr should be preserved when the base_level is increased. + # This is done by looking at the original coordinates of each cell. + cell_coordinates = original_coordinates(coordinates, 5 / 8) cell_distance = periodic_distance_2d(cell_coordinates, center, 10) + if cell_distance < (inner_distance + outer_distance) / 2 + cell_coordinates = original_coordinates(coordinates, 5 / 16) + cell_distance = periodic_distance_2d(cell_coordinates, center, 10) + end + + # Set alpha according to cells position inside the circles. + target_level = (cell_distance < inner_distance) + (cell_distance < outer_distance) + alpha[element] = target_level / 2 end + return alpha + end + + # For periodic domains, distance between two points must take into account + # periodic extensions of the domain. + function periodic_distance_2d(coordinates, center, domain_length) + dx = coordinates .- center + dx_shifted = abs.(dx .% domain_length) + dx_periodic = min.(dx_shifted, domain_length .- dx_shifted) + return sqrt(sum(dx_periodic .^ 2)) + end - # Set alpha according to cells position inside the circles. - target_level = (cell_distance < inner_distance) + (cell_distance < outer_distance) - alpha[element] = target_level / 2 + # This takes a cells coordinates and transforms them into the coordinates of a + # parent-cell it originally refined from. It does it so that the parent-cell + # has given cell_length. + function original_coordinates(coordinates, cell_length) + offset = coordinates .% cell_length + offset_sign = sign.(offset) + border = coordinates - offset + center = border + (offset_sign .* cell_length / 2) + return center end - return alpha -end - -# For periodic domains, distance between two points must take into account -# periodic extensions of the domain. -function periodic_distance_2d(coordinates, center, domain_length) - dx = coordinates .- center - dx_shifted = abs.(dx .% domain_length) - dx_periodic = min.(dx_shifted, domain_length .- dx_shifted) - return sqrt(sum(dx_periodic .^ 2)) -end - -# This takes a cells coordinates and transforms them into the coordinates of a -# parent-cell it originally refined from. It does it so that the parent-cell -# has given cell_length. -function original_coordinates(coordinates, cell_length) - offset = coordinates .% cell_length - offset_sign = sign.(offset) - border = coordinates - offset - center = border + (offset_sign .* cell_length / 2) - return center -end end # module TrixiExtension @@ -95,9 +103,11 @@ coordinates_max = (5.0, 5.0) trees_per_dimension = (1, 1) -mesh = T8codeMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 1) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 1 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -110,34 +120,44 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -amr_controller = ControllerThreeLevel(semi, - TrixiExtension.IndicatorSolutionIndependent(semi), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, + TrixiExtension.IndicatorSolutionIndependent(semi), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + amr_callback, stepsize_callback +); ############################################################################### # Run the simulation. -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_2d_dgsem/elixir_advection_amr_unstructured_flag.jl b/examples/t8code_2d_dgsem/elixir_advection_amr_unstructured_flag.jl index c9c831d347..d29970d59f 100644 --- a/examples/t8code_2d_dgsem/elixir_advection_amr_unstructured_flag.jl +++ b/examples/t8code_2d_dgsem/elixir_advection_amr_unstructured_flag.jl @@ -30,15 +30,21 @@ Trixi.validate_faces(faces) mapping_flag = Trixi.transfinite_mapping(faces) # Unstructured mesh with 24 cells of the square domain [-1, 1]^n -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", - joinpath(@__DIR__, "square_unstructured_2.inp")) - -mesh = T8codeMesh(mesh_file, 2; - mapping = mapping_flag, polydeg = 3, - initial_refinement_level = 1) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + joinpath(@__DIR__, "square_unstructured_2.inp") +) + +mesh = T8codeMesh( + mesh_file, 2; + mapping = mapping_flag, polydeg = 3, + initial_refinement_level = 1 +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -49,33 +55,43 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 1, - med_level = 2, med_threshold = 0.1, - max_level = 3, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true, - dynamic_load_balancing = true) +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 1, + med_level = 2, med_threshold = 0.1, + max_level = 3, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true, + dynamic_load_balancing = true +) stepsize_callback = StepsizeCallback(cfl = 0.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + amr_callback, stepsize_callback +) ############################################################################### # Run the simulation. -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/t8code_2d_dgsem/elixir_advection_basic.jl b/examples/t8code_2d_dgsem/elixir_advection_basic.jl index 2c19ace1de..35821a2a02 100644 --- a/examples/t8code_2d_dgsem/elixir_advection_basic.jl +++ b/examples/t8code_2d_dgsem/elixir_advection_basic.jl @@ -18,13 +18,17 @@ coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) trees_per_dimension = (8, 8) -mesh = T8codeMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 1) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 1 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -49,9 +53,11 @@ callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/t8code_2d_dgsem/elixir_advection_nonconforming_flag.jl b/examples/t8code_2d_dgsem/elixir_advection_nonconforming_flag.jl index a8808f9ab7..4510a7e3ee 100644 --- a/examples/t8code_2d_dgsem/elixir_advection_nonconforming_flag.jl +++ b/examples/t8code_2d_dgsem/elixir_advection_nonconforming_flag.jl @@ -22,10 +22,12 @@ faces = (f1, f2, f3, f4) # Create T8codeMesh with 3 x 2 trees and 6 x 4 elements, # approximate the geometry with a smaller polydeg for testing. trees_per_dimension = (3, 2) -mesh = T8codeMesh(trees_per_dimension, polydeg = 3, - faces = faces, - initial_refinement_level = 1, - periodicity = (true, true)) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 3, + faces = faces, + initial_refinement_level = 1, + periodicity = (true, true) +) # Note: This is actually a `p4est_quadrant_t` which is much bigger than the # following struct. But we only need the first three fields for our purpose. @@ -37,8 +39,10 @@ struct t8_dquad_t end # Refine quadrants of each tree at lower left edge to level 4. -function adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, - user_data) +function adapt_callback( + forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, + user_data + ) el = unsafe_load(Ptr{t8_dquad_t}(elements[1])) if el.x == 0 && el.y == 0 && el.level < 4 @@ -53,8 +57,10 @@ end Trixi.adapt!(mesh, adapt_callback) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -79,9 +85,11 @@ callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/t8code_2d_dgsem/elixir_advection_unstructured_flag.jl b/examples/t8code_2d_dgsem/elixir_advection_unstructured_flag.jl index f7917fb03b..4cfb493431 100644 --- a/examples/t8code_2d_dgsem/elixir_advection_unstructured_flag.jl +++ b/examples/t8code_2d_dgsem/elixir_advection_unstructured_flag.jl @@ -27,16 +27,22 @@ Trixi.validate_faces(faces) mapping_flag = Trixi.transfinite_mapping(faces) # Unstructured mesh with 24 cells of the square domain [-1, 1]^n. -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", - joinpath(@__DIR__, "square_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + joinpath(@__DIR__, "square_unstructured_2.inp") +) -mesh = T8codeMesh(mesh_file, 2; - mapping = mapping_flag, polydeg = 3, - initial_refinement_level = 2) +mesh = T8codeMesh( + mesh_file, 2; + mapping = mapping_flag, polydeg = 3, + initial_refinement_level = 2 +) # A semidiscretization collects data structures and functions for the spatial discretization. -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -65,9 +71,11 @@ callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) # Run the simulation. # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks. -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # Solve needs some value here but it will be overwritten by the stepsize_callback. - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # Solve needs some value here but it will be overwritten by the stepsize_callback. + save_everystep = false, callback = callbacks +); # Print the timer summary. summary_callback() diff --git a/examples/t8code_2d_dgsem/elixir_euler_free_stream.jl b/examples/t8code_2d_dgsem/elixir_euler_free_stream.jl index 34e6d7d8c0..afb74148c9 100644 --- a/examples/t8code_2d_dgsem/elixir_euler_free_stream.jl +++ b/examples/t8code_2d_dgsem/elixir_euler_free_stream.jl @@ -16,11 +16,15 @@ function mapping(xi_, eta_) xi = 1.5 * xi_ + 1.5 eta = 1.5 * eta_ + 1.5 - y = eta + 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3)) + y = eta + 3 / 8 * ( + cos(1.5 * pi * (2 * xi - 3) / 3) * + cos(0.5 * pi * (2 * eta - 3) / 3) + ) - x = xi + 3 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3)) + x = xi + 3 / 8 * ( + cos(0.5 * pi * (2 * xi - 3) / 3) * + cos(2 * pi * (2 * y - 3) / 3) + ) return SVector(x, y) end @@ -29,15 +33,21 @@ end # Get the uncurved mesh from a file (downloads the file if not available locally) # Unstructured mesh with 48 cells of the square domain [-1, 1]^n -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/a075f8ec39a67fa9fad8f6f84342cbca/raw/a7206a02ed3a5d3cadacd8d9694ac154f9151db7/square_unstructured_1.inp", - joinpath(@__DIR__, "square_unstructured_1.inp")) - -mesh = T8codeMesh(mesh_file, 2; polydeg = 3, - mapping = mapping, - initial_refinement_level = 1) - -function adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, - user_data) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/a075f8ec39a67fa9fad8f6f84342cbca/raw/a7206a02ed3a5d3cadacd8d9694ac154f9151db7/square_unstructured_1.inp", + joinpath(@__DIR__, "square_unstructured_1.inp") +) + +mesh = T8codeMesh( + mesh_file, 2; polydeg = 3, + mapping = mapping, + initial_refinement_level = 1 +) + +function adapt_callback( + forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, + user_data + ) vertex = Vector{Cdouble}(undef, 3) Trixi.t8_element_vertex_reference_coords(eclass_scheme, elements[1], 0, vertex) @@ -45,7 +55,7 @@ function adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_ level = Trixi.t8_element_level(eclass_scheme, elements[1]) # TODO: Make this condition more general. - if vertex[1] < 1e-8 && vertex[2] < 1e-8 && level < 3 + if vertex[1] < 1.0e-8 && vertex[2] < 1.0e-8 && level < 3 # return true (refine) return 1 else @@ -56,8 +66,10 @@ end Trixi.adapt!(mesh, adapt_callback) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition))) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition)) +) ############################################################################### # ODE solvers, callbacks etc. @@ -74,16 +86,20 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 2.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_2d_dgsem/elixir_euler_sedov.jl b/examples/t8code_2d_dgsem/elixir_euler_sedov.jl index e1c51c1f96..2f15f6bd78 100644 --- a/examples/t8code_2d_dgsem/elixir_euler_sedov.jl +++ b/examples/t8code_2d_dgsem/elixir_euler_sedov.jl @@ -41,17 +41,23 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) ############################################################################### @@ -60,9 +66,11 @@ coordinates_max = (1.0, 1.0) trees_per_dimension = (4, 4) -mesh = T8codeMesh(trees_per_dimension, polydeg = 4, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 2, periodicity = true) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 4, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 2, periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -81,17 +89,21 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_2d_dgsem/elixir_euler_shockcapturing_ec.jl b/examples/t8code_2d_dgsem/elixir_euler_shockcapturing_ec.jl index 92ed169756..e7c772b043 100644 --- a/examples/t8code_2d_dgsem/elixir_euler_shockcapturing_ec.jl +++ b/examples/t8code_2d_dgsem/elixir_euler_shockcapturing_ec.jl @@ -12,17 +12,23 @@ surface_flux = flux_ranocha volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) ############################################################################### @@ -31,9 +37,11 @@ coordinates_max = (1.0, 1.0) trees_per_dimension = (4, 4) -mesh = T8codeMesh(trees_per_dimension, polydeg = 4, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 2, periodicity = true) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 4, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 2, periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -52,17 +60,21 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl b/examples/t8code_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl index 59e5f99691..8a49159771 100644 --- a/examples/t8code_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl +++ b/examples/t8code_2d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_flag.jl @@ -29,15 +29,21 @@ mapping_flag = Trixi.transfinite_mapping(faces) # Get the uncurved mesh from a file (downloads the file if not available locally) # Unstructured mesh with 24 cells of the square domain [-1, 1]^n -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", - joinpath(@__DIR__, "square_unstructured_2.inp")) - -mesh = T8codeMesh(mesh_file, 2; polydeg = 3, - mapping = mapping_flag, - initial_refinement_level = 1) - -function adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, - user_data) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + joinpath(@__DIR__, "square_unstructured_2.inp") +) + +mesh = T8codeMesh( + mesh_file, 2; polydeg = 3, + mapping = mapping_flag, + initial_refinement_level = 1 +) + +function adapt_callback( + forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, + user_data + ) vertex = Vector{Cdouble}(undef, 3) Trixi.t8_element_vertex_reference_coords(eclass_scheme, elements[1], 0, pointer(vertex)) @@ -45,7 +51,7 @@ function adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_ level = Trixi.t8_element_level(eclass_scheme, elements[1]) # TODO: Make this condition more general. - if vertex[1] < 1e-8 && vertex[2] < 1e-8 && level < 2 + if vertex[1] < 1.0e-8 && vertex[2] < 1.0e-8 && level < 2 # return true (refine) return 1 else @@ -56,9 +62,11 @@ end Trixi.adapt!(mesh, adapt_callback) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -75,15 +83,19 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_2d_dgsem/elixir_euler_weak_blast_wave_amr.jl b/examples/t8code_2d_dgsem/elixir_euler_weak_blast_wave_amr.jl index a3366caa31..fd1fa948b8 100644 --- a/examples/t8code_2d_dgsem/elixir_euler_weak_blast_wave_amr.jl +++ b/examples/t8code_2d_dgsem/elixir_euler_weak_blast_wave_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -37,17 +36,23 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Affine type mapping to take the [-1,1]^2 domain # and warp it as described in https://arxiv.org/abs/2012.12040 @@ -61,10 +66,12 @@ end # The mesh below can be made periodic # Create T8codeMesh with 8 x 8 trees trees_per_dimension = (8, 8) -mesh = T8codeMesh(trees_per_dimension, polydeg = 4, - mapping = mapping_twist, - initial_refinement_level = 0, - periodicity = true) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 4, + mapping = mapping_twist, + initial_refinement_level = 0, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -77,36 +84,46 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 400 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 0, - med_level = 1, med_threshold = 0.05, - max_level = 2, max_threshold = 0.1) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 0, + med_level = 1, med_threshold = 0.05, + max_level = 2, max_threshold = 0.1 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks);#, maxiters=4); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); #, maxiters=4); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_2d_dgsem/elixir_eulergravity_convergence.jl b/examples/t8code_2d_dgsem/elixir_eulergravity_convergence.jl index 3fafbf8a4c..ee52a38c36 100644 --- a/examples/t8code_2d_dgsem/elixir_eulergravity_convergence.jl +++ b/examples/t8code_2d_dgsem/elixir_eulergravity_convergence.jl @@ -16,13 +16,17 @@ coordinates_max = (2.0, 2.0) trees_per_dimension = (1, 1) -mesh = T8codeMesh(trees_per_dimension, polydeg = 1, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 2) - -semi_euler = SemidiscretizationHyperbolic(mesh, equations_euler, initial_condition, - solver_euler, - source_terms = source_terms_eoc_test_coupled_euler_gravity) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 1, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 2 +) + +semi_euler = SemidiscretizationHyperbolic( + mesh, equations_euler, initial_condition, + solver_euler, + source_terms = source_terms_eoc_test_coupled_euler_gravity +) ############################################################################### # semidiscretization of the hyperbolic diffusion equations @@ -30,20 +34,24 @@ equations_gravity = HyperbolicDiffusionEquations2D() solver_gravity = DGSEM(polydeg, flux_lax_friedrichs) -semi_gravity = SemidiscretizationHyperbolic(mesh, equations_gravity, initial_condition, - solver_gravity, - source_terms = source_terms_harmonic) +semi_gravity = SemidiscretizationHyperbolic( + mesh, equations_gravity, initial_condition, + solver_gravity, + source_terms = source_terms_harmonic +) ############################################################################### # combining both semidiscretizations for Euler + self-gravity -parameters = ParametersEulerGravity(background_density = 2.0, # aka rho0 - # rho0 is (ab)used to add a "+8π" term to the source terms - # for the manufactured solution - gravitational_constant = 1.0, # aka G - cfl = 1.1, - resid_tol = 1.0e-10, - n_iterations_max = 1000, - timestep_gravity = timestep_gravity_erk52_3Sstar!) +parameters = ParametersEulerGravity( + background_density = 2.0, # aka rho0 + # rho0 is (ab)used to add a "+8π" term to the source terms + # for the manufactured solution + gravitational_constant = 1.0, # aka G + cfl = 1.1, + resid_tol = 1.0e-10, + n_iterations_max = 1000, + timestep_gravity = timestep_gravity_erk52_3Sstar! +) semi = SemidiscretizationEulerGravity(semi_euler, semi_gravity, parameters) @@ -59,17 +67,23 @@ stepsize_callback = StepsizeCallback(cfl = 0.8) analysis_interval = 100 alive_callback = AliveCallback(analysis_interval = analysis_interval) -analysis_callback = AnalysisCallback(semi_euler, interval = analysis_interval, - save_analysis = true) +analysis_callback = AnalysisCallback( + semi_euler, interval = analysis_interval, + save_analysis = true +) -callbacks = CallbackSet(summary_callback, stepsize_callback, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, stepsize_callback, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary println("Number of gravity subcycles: ", semi.gravity_counter.ncalls_since_readout) diff --git a/examples/t8code_2d_dgsem/elixir_mhd_alfven_wave.jl b/examples/t8code_2d_dgsem/elixir_mhd_alfven_wave.jl index 04c36dd864..acf8e4fbdf 100644 --- a/examples/t8code_2d_dgsem/elixir_mhd_alfven_wave.jl +++ b/examples/t8code_2d_dgsem/elixir_mhd_alfven_wave.jl @@ -12,17 +12,21 @@ initial_condition = initial_condition_convergence_test # Get the DG approximation space volume_flux = (flux_central, flux_nonconservative_powell) -solver = DGSEM(polydeg = 4, surface_flux = (flux_hlle, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = (flux_hlle, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (sqrt(2.0), sqrt(2.0)) trees_per_dimension = (8, 8) -mesh = T8codeMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 0, periodicity = true) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 0, periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -44,18 +48,22 @@ stepsize_callback = StepsizeCallback(cfl = cfl) glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_2d_dgsem/elixir_mhd_rotor.jl b/examples/t8code_2d_dgsem/elixir_mhd_rotor.jl index 82e6d8ca4a..4e8fc15b69 100644 --- a/examples/t8code_2d_dgsem/elixir_mhd_rotor.jl +++ b/examples/t8code_2d_dgsem/elixir_mhd_rotor.jl @@ -47,14 +47,18 @@ surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) # Affine type mapping to take the [-1,1]^2 domain from the mesh file @@ -67,18 +71,24 @@ function mapping_twist(xi, eta) return SVector(x, y) end -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", - joinpath(@__DIR__, "square_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/63ff2ea224409e55ee8423b3a33e316a/raw/7db58af7446d1479753ae718930741c47a3b79b7/square_unstructured_2.inp", + joinpath(@__DIR__, "square_unstructured_2.inp") +) -mesh = T8codeMesh(mesh_file, 2; polydeg = 4, - mapping = mapping_twist, - initial_refinement_level = 1) +mesh = T8codeMesh( + mesh_file, 2; polydeg = 4, + mapping = mapping_twist, + initial_refinement_level = 1 +) boundary_condition = BoundaryConditionDirichlet(initial_condition) boundary_conditions = Dict(:all => boundary_condition) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -93,36 +103,46 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -amr_indicator = IndicatorLöhner(semi, - variable = density_pressure) - -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 1, - med_level = 3, med_threshold = 0.05, - max_level = 5, max_threshold = 0.1) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_indicator = IndicatorLöhner( + semi, + variable = density_pressure +) + +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 1, + med_level = 3, med_threshold = 0.05, + max_level = 5, max_threshold = 0.1 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) cfl = 0.5 stepsize_callback = StepsizeCallback(cfl = cfl) glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_2d_dgsem/elixir_shallowwater_source_terms.jl b/examples/t8code_2d_dgsem/elixir_shallowwater_source_terms.jl index 9ce248ff3b..f3260bd1a0 100644 --- a/examples/t8code_2d_dgsem/elixir_shallowwater_source_terms.jl +++ b/examples/t8code_2d_dgsem/elixir_shallowwater_source_terms.jl @@ -12,9 +12,11 @@ initial_condition = initial_condition_convergence_test # MMS EOC test # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the P4estMesh and setup a periodic mesh @@ -24,13 +26,17 @@ coordinates_max = (sqrt(2.0), sqrt(2.0)) # maximum coordinates (max(x), max(y)) trees_per_dimension = (8, 8) -mesh = T8codeMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 1) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 1 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -52,8 +58,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_3d_dgsem/elixir_advection_amr.jl b/examples/t8code_3d_dgsem/elixir_advection_amr.jl index 83f897dac7..f60e2e5496 100644 --- a/examples/t8code_3d_dgsem/elixir_advection_amr.jl +++ b/examples/t8code_3d_dgsem/elixir_advection_amr.jl @@ -20,9 +20,11 @@ trees_per_dimension = (1, 1, 1) # Note that it is not necessary to use mesh polydeg lower than the solver polydeg # on a Cartesian mesh. # See https://doi.org/10.1007/s10915-018-00897-9, Section 6. -mesh = T8codeMesh(trees_per_dimension, polydeg = 1, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 4) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 1, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 4 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -35,34 +37,44 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_3d_dgsem/elixir_advection_amr_unstructured_curved.jl b/examples/t8code_3d_dgsem/elixir_advection_amr_unstructured_curved.jl index ae95fa6c4d..6dab8598e8 100644 --- a/examples/t8code_3d_dgsem/elixir_advection_amr_unstructured_curved.jl +++ b/examples/t8code_3d_dgsem/elixir_advection_amr_unstructured_curved.jl @@ -28,34 +28,46 @@ function mapping(xi, eta, zeta) # zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 4 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 4 * ( + 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 + - 1 / 4 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 4 * ( + 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 + - 1 / 4 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 4 * ( + cos(0.5 * pi * (2 * x - 3) / 3) * + cos(pi * (2 * y - 3) / 3) * + cos(0.5 * pi * (2 * zeta - 3) / 3) + ) # Transform the weird deformed cube to be approximately the size of [-5,5]^3 to match IC return SVector(5 * x, 5 * y, 5 * z) end # Unstructured mesh with 48 cells of the cube domain [-1, 1]^3. -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", - joinpath(@__DIR__, "cube_unstructured_2.inp")) - -mesh = T8codeMesh(mesh_file, 3; polydeg = 2, - mapping = mapping, - initial_refinement_level = 1) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", + joinpath(@__DIR__, "cube_unstructured_2.inp") +) + +mesh = T8codeMesh( + mesh_file, 3; polydeg = 2, + mapping = mapping, + initial_refinement_level = 1 +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -66,34 +78,44 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 1, - med_level = 2, med_threshold = 0.1, - max_level = 3, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 1, + med_level = 2, med_threshold = 0.1, + max_level = 3, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_3d_dgsem/elixir_advection_basic.jl b/examples/t8code_3d_dgsem/elixir_advection_basic.jl index b647994697..0f0065b5f2 100644 --- a/examples/t8code_3d_dgsem/elixir_advection_basic.jl +++ b/examples/t8code_3d_dgsem/elixir_advection_basic.jl @@ -18,13 +18,17 @@ coordinates_max = (1.0, 1.0, 1.0) # maximum coordinates (max(x), max(y), max(z)) # Create P4estMesh with 8 x 8 x 8 elements (note `refinement_level=1`) trees_per_dimension = (4, 4, 4) -mesh = T8codeMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 1) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 1 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -44,16 +48,20 @@ analysis_callback = AnalysisCallback(semi, interval = 100) stepsize_callback = StepsizeCallback(cfl = 1.2) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/t8code_3d_dgsem/elixir_advection_nonconforming.jl b/examples/t8code_3d_dgsem/elixir_advection_nonconforming.jl index cac6e30aa7..4ce7f7c814 100644 --- a/examples/t8code_3d_dgsem/elixir_advection_nonconforming.jl +++ b/examples/t8code_3d_dgsem/elixir_advection_nonconforming.jl @@ -17,9 +17,11 @@ trees_per_dimension = (1, 1, 1) # Note that it is not necessary to use mesh polydeg lower than the solver polydeg # on a Cartesian mesh. # See https://doi.org/10.1007/s10915-018-00897-9, Section 6. -mesh = T8codeMesh(trees_per_dimension, polydeg = 3, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - initial_refinement_level = 2) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 3, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + initial_refinement_level = 2 +) # Note: This is actually a `p8est_quadrant_t` which is much bigger than the # following struct. But we only need the first four fields for our purpose. @@ -32,12 +34,14 @@ struct t8_dhex_t end # Refine bottom left quadrant of each second tree to level 2 -function adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, - user_data) +function adapt_callback( + forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, + user_data + ) el = unsafe_load(Ptr{t8_dhex_t}(elements[1])) if iseven(convert(Int, ltreeid)) && el.x == 0 && el.y == 0 && el.z == 0 && - el.level < 3 + el.level < 3 # return true (refine) return 1 else @@ -49,8 +53,10 @@ end Trixi.adapt!(mesh, adapt_callback) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -70,16 +76,20 @@ analysis_callback = AnalysisCallback(semi, interval = 100) stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/t8code_3d_dgsem/elixir_advection_unstructured_curved.jl b/examples/t8code_3d_dgsem/elixir_advection_unstructured_curved.jl index dd8e7f2103..95bc966783 100644 --- a/examples/t8code_3d_dgsem/elixir_advection_unstructured_curved.jl +++ b/examples/t8code_3d_dgsem/elixir_advection_unstructured_curved.jl @@ -26,34 +26,46 @@ function mapping(xi, eta, zeta) # zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 6 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 # Unstructured mesh with 68 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", - joinpath(@__DIR__, "cube_unstructured_1.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", + joinpath(@__DIR__, "cube_unstructured_1.inp") +) -mesh = T8codeMesh(mesh_file, 3; polydeg = 3, - mapping = mapping, - initial_refinement_level = 2) +mesh = T8codeMesh( + mesh_file, 3; polydeg = 3, + mapping = mapping, + initial_refinement_level = 2 +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -75,16 +87,20 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.2) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/t8code_3d_dgsem/elixir_euler_ec.jl b/examples/t8code_3d_dgsem/elixir_euler_ec.jl index ad23e3440d..12f6558884 100644 --- a/examples/t8code_3d_dgsem/elixir_euler_ec.jl +++ b/examples/t8code_3d_dgsem/elixir_euler_ec.jl @@ -13,8 +13,10 @@ boundary_conditions = Dict(:all => boundary_condition_slip_wall) # Get the DG approximation space volume_flux = flux_ranocha -solver = DGSEM(polydeg = 5, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 5, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Get the curved quad mesh from a file @@ -26,34 +28,46 @@ function mapping(xi_, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 y = eta + - 3 / 8 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 / 8 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 3 / 8 * ( + 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 # Unstructured mesh with 48 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", - joinpath(@__DIR__, "cube_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", + joinpath(@__DIR__, "cube_unstructured_2.inp") +) -mesh = T8codeMesh(mesh_file, 3; polydeg = 5, - mapping = mapping, - initial_refinement_level = 0) +mesh = T8codeMesh( + mesh_file, 3; polydeg = 5, + mapping = mapping, + initial_refinement_level = 0 +) # Create the semidiscretization object. -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -70,17 +84,21 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_3d_dgsem/elixir_euler_free_stream.jl b/examples/t8code_3d_dgsem/elixir_euler_free_stream.jl index 11f3ba94e2..6f7d79c704 100644 --- a/examples/t8code_3d_dgsem/elixir_euler_free_stream.jl +++ b/examples/t8code_3d_dgsem/elixir_euler_free_stream.jl @@ -13,8 +13,10 @@ boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition) # Solver with polydeg=4 to ensure free stream preservation (FSP) on non-conforming meshes. # The polydeg of the solver must be at least twice as big as the polydeg of the mesh. # See https://doi.org/10.1007/s10915-018-00897-9, Section 6. -solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 4, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) # Mapping as described in https://arxiv.org/abs/2012.12040 but with less warping. # The mapping will be interpolated at tree level, and then refined without changing @@ -27,30 +29,40 @@ function mapping(xi_, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 6 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 # Unstructured mesh with 68 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", - joinpath(@__DIR__, "cube_unstructured_1.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", + joinpath(@__DIR__, "cube_unstructured_1.inp") +) -mesh = T8codeMesh(mesh_file, 3; polydeg = 2, - mapping = mapping, - initial_refinement_level = 0) +mesh = T8codeMesh( + mesh_file, 3; polydeg = 2, + mapping = mapping, + initial_refinement_level = 0 +) # Note: This is actually a `p8est_quadrant_t` which is much bigger than the # following struct. But we only need the first four fields for our purpose. @@ -63,12 +75,14 @@ struct t8_dhex_t end # Refine bottom left quadrant of each second tree to level 2 -function adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, - user_data) +function adapt_callback( + forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, + user_data + ) el = unsafe_load(Ptr{t8_dhex_t}(elements[1])) if iseven(convert(Int, ltreeid)) && el.x == 0 && el.y == 0 && el.z == 0 && - el.level < 2 + el.level < 2 # return true (refine) return 1 else @@ -79,8 +93,10 @@ end Trixi.adapt!(mesh, adapt_callback) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -97,16 +113,20 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_3d_dgsem/elixir_euler_free_stream_extruded.jl b/examples/t8code_3d_dgsem/elixir_euler_free_stream_extruded.jl index ca3722e625..3cae8bca62 100644 --- a/examples/t8code_3d_dgsem/elixir_euler_free_stream_extruded.jl +++ b/examples/t8code_3d_dgsem/elixir_euler_free_stream_extruded.jl @@ -10,8 +10,10 @@ initial_condition = initial_condition_constant boundary_conditions = Dict(:all => BoundaryConditionDirichlet(initial_condition)) -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) # Mapping as described in https://arxiv.org/abs/2012.12040 but reduced to 2D. # This particular mesh is unstructured in the yz-plane, but extruded in x-direction. @@ -24,22 +26,30 @@ function mapping(xi, eta_, zeta_) zeta = 1.5 * zeta_ + 1.5 z = zeta + - 1 / 6 * (cos(1.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + cos(1.5 * pi * (2 * eta - 3) / 3) * + cos(0.5 * pi * (2 * zeta - 3) / 3) + ) - y = eta + 1 / 6 * (cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(2 * pi * (2 * z - 3) / 3)) + y = eta + 1 / 6 * ( + cos(0.5 * pi * (2 * eta - 3) / 3) * + cos(2 * pi * (2 * z - 3) / 3) + ) return SVector(xi, y, z) end # Unstructured mesh with 48 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", - joinpath(@__DIR__, "cube_unstructured_2.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/b8df0033798e4926dec515fc045e8c2c/raw/b9254cde1d1fb64b6acc8416bc5ccdd77a240227/cube_unstructured_2.inp", + joinpath(@__DIR__, "cube_unstructured_2.inp") +) -mesh = T8codeMesh(mesh_file, 3; polydeg = 3, - mapping = mapping, - initial_refinement_level = 0) +mesh = T8codeMesh( + mesh_file, 3; polydeg = 3, + mapping = mapping, + initial_refinement_level = 0 +) # Note: This is actually a `p8est_quadrant_t` which is much bigger than the # following struct. But we only need the first four fields for our purpose. @@ -52,8 +62,10 @@ struct t8_dhex_t end # Refine quadrants in y-direction of each tree at one edge to level 2 -function adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, - user_data) +function adapt_callback( + forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, + user_data + ) el = unsafe_load(Ptr{t8_dhex_t}(elements[1])) if convert(Int, ltreeid) < 4 && el.x == 0 && el.y == 0 && el.level < 2 @@ -67,8 +79,10 @@ end Trixi.adapt!(mesh, adapt_callback) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -85,16 +99,20 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), #maxiters=1, - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), #maxiters=1, + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_3d_dgsem/elixir_euler_sedov.jl b/examples/t8code_3d_dgsem/elixir_euler_sedov.jl index 55369fa14f..f47c7295a7 100644 --- a/examples/t8code_3d_dgsem/elixir_euler_sedov.jl +++ b/examples/t8code_3d_dgsem/elixir_euler_sedov.jl @@ -13,8 +13,10 @@ The Sedov blast wave setup based on Flash - https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node187.html#SECTION010114000000000000000 with smaller strength of the initial discontinuity. """ -function initial_condition_medium_sedov_blast_wave(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_medium_sedov_blast_wave( + x, t, + equations::CompressibleEulerEquations3D + ) # Set up polar coordinates inicenter = SVector(0.0, 0.0, 0.0) x_norm = x[1] - inicenter[1] @@ -44,26 +46,34 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 5 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) coordinates_min = (-1.0, -1.0, -1.0) coordinates_max = (1.0, 1.0, 1.0) trees_per_dimension = (4, 4, 4) -mesh = T8codeMesh(trees_per_dimension, - polydeg = 4, initial_refinement_level = 0, - coordinates_min = coordinates_min, coordinates_max = coordinates_max, - periodicity = true) +mesh = T8codeMesh( + trees_per_dimension, + polydeg = 4, initial_refinement_level = 0, + coordinates_min = coordinates_min, coordinates_max = coordinates_max, + periodicity = true +) # create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -83,17 +93,21 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_3d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_curved.jl b/examples/t8code_3d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_curved.jl index 9a16b104e6..06719079da 100644 --- a/examples/t8code_3d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_curved.jl +++ b/examples/t8code_3d_dgsem/elixir_euler_source_terms_nonconforming_unstructured_curved.jl @@ -14,8 +14,10 @@ boundary_conditions = Dict(:all => boundary_condition) # Solver with polydeg=4 to ensure free stream preservation (FSP) on non-conforming meshes. # The polydeg of the solver must be at least twice as big as the polydeg of the mesh. # See https://doi.org/10.1007/s10915-018-00897-9, Section 6. -solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 4, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) # Mapping as described in https://arxiv.org/abs/2012.12040 but with less warping. # The mapping will be interpolated at tree level, and then refined without changing @@ -28,32 +30,42 @@ function mapping(xi, eta, zeta) # zeta = 1.5 * zeta_ + 1.5 y = eta + - 1 / 6 * (cos(1.5 * pi * (2 * xi - 3) / 3) * - cos(0.5 * pi * (2 * eta - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * xi - 3) / 3) * - cos(2 * pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + 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 + - 1 / 6 * (cos(0.5 * pi * (2 * x - 3) / 3) * - cos(pi * (2 * y - 3) / 3) * - cos(0.5 * pi * (2 * zeta - 3) / 3)) + 1 / 6 * ( + cos(0.5 * pi * (2 * x - 3) / 3) * + cos(pi * (2 * y - 3) / 3) * + cos(0.5 * pi * (2 * zeta - 3) / 3) + ) # Transform the weird deformed cube to be approximately the cube [0,2]^3 return SVector(x + 1, y + 1, z + 1) end # Unstructured mesh with 68 cells of the cube domain [-1, 1]^3 -mesh_file = Trixi.download("https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", - joinpath(@__DIR__, "cube_unstructured_1.inp")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/efaulhaber/d45c8ac1e248618885fa7cc31a50ab40/raw/37fba24890ab37cfa49c39eae98b44faf4502882/cube_unstructured_1.inp", + joinpath(@__DIR__, "cube_unstructured_1.inp") +) # Mesh polydeg of 2 (half the solver polydeg) to ensure FSP (see above). -mesh = T8codeMesh(mesh_file, 3; polydeg = 2, - mapping = mapping, - initial_refinement_level = 0) +mesh = T8codeMesh( + mesh_file, 3; polydeg = 2, + mapping = mapping, + initial_refinement_level = 0 +) # Note: This is actually a `p8est_quadrant_t` which is much bigger than the # following struct. But we only need the first four fields for our purpose. @@ -65,8 +77,10 @@ struct t8_dhex_t # [...] # See `p8est.h` in `p4est` for more info. end -function adapt_callback(forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, - user_data) +function adapt_callback( + forest, ltreeid, eclass_scheme, lelemntid, elements, is_family, + user_data + ) el = unsafe_load(Ptr{t8_dhex_t}(elements[1])) if el.x == 0 && el.y == 0 && el.z == 0 && el.level < 2 @@ -80,9 +94,11 @@ end Trixi.adapt!(mesh, adapt_callback) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -99,16 +115,20 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_3d_dgsem/elixir_euler_source_terms_nonperiodic.jl b/examples/t8code_3d_dgsem/elixir_euler_source_terms_nonperiodic.jl index 3962ce798c..8584b9c694 100644 --- a/examples/t8code_3d_dgsem/elixir_euler_source_terms_nonperiodic.jl +++ b/examples/t8code_3d_dgsem/elixir_euler_source_terms_nonperiodic.jl @@ -9,15 +9,19 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:x_neg => boundary_condition, - :x_pos => boundary_condition, - :y_neg => boundary_condition, - :y_pos => boundary_condition, - :z_neg => boundary_condition, - :z_pos => boundary_condition) - -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +boundary_conditions = Dict( + :x_neg => boundary_condition, + :x_pos => boundary_condition, + :y_neg => boundary_condition, + :y_pos => boundary_condition, + :z_neg => boundary_condition, + :z_pos => boundary_condition +) + +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (2.0, 2.0, 2.0) @@ -26,13 +30,17 @@ trees_per_dimension = (2, 2, 2) mapping = Trixi.coordinates2mapping(coordinates_min, coordinates_max) -mesh = T8codeMesh(trees_per_dimension, polydeg = 1, - mapping = mapping, - periodicity = false, initial_refinement_level = 1) +mesh = T8codeMesh( + trees_per_dimension, polydeg = 1, + mapping = mapping, + periodicity = false, initial_refinement_level = 1 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -49,16 +57,20 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/t8code_3d_dgsem/elixir_euler_weak_blast_wave_amr.jl b/examples/t8code_3d_dgsem/elixir_euler_weak_blast_wave_amr.jl index 106b482114..1d2a67e5a9 100644 --- a/examples/t8code_3d_dgsem/elixir_euler_weak_blast_wave_amr.jl +++ b/examples/t8code_3d_dgsem/elixir_euler_weak_blast_wave_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -7,8 +6,10 @@ using Trixi equations = CompressibleEulerEquations3D(1.4) -function initial_condition_weak_blast_wave(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_weak_blast_wave( + x, t, + equations::CompressibleEulerEquations3D + ) # Set up polar coordinates inicenter = SVector(0.0, 0.0, 0.0) x_norm = x[1] - inicenter[1] @@ -37,17 +38,23 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Setup a periodic mesh with 4 x 4 x 4 trees and 8 x 8 x 8 elements trees_per_dimension = (4, 4, 4) @@ -62,11 +69,13 @@ function mapping_twist(xi, eta, zeta) return SVector(x, y, z) end -mesh = T8codeMesh(trees_per_dimension, - polydeg = 2, - mapping = mapping_twist, - initial_refinement_level = 1, - periodicity = true) +mesh = T8codeMesh( + trees_per_dimension, + polydeg = 2, + mapping = mapping_twist, + initial_refinement_level = 1, + periodicity = true +) # Create the semidiscretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -80,35 +89,45 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:conservation_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = (:conservation_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) amr_indicator = IndicatorLöhner(semi, variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 1, - med_level = 2, med_threshold = 0.05, - max_level = 3, max_threshold = 0.15) -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = false, - adapt_initial_condition_only_refine = false) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 1, + med_level = 2, med_threshold = 0.05, + max_level = 3, max_threshold = 0.15 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = false, + adapt_initial_condition_only_refine = false +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Finalize `T8codeMesh` to make sure MPI related objects in t8code are diff --git a/examples/tree_1d_dgsem/elixir_advection_amr.jl b/examples/tree_1d_dgsem/elixir_advection_amr.jl index 1071c98ab7..f19b6b6ceb 100644 --- a/examples/tree_1d_dgsem/elixir_advection_amr.jl +++ b/examples/tree_1d_dgsem/elixir_advection_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,9 +13,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0,) coordinates_max = (5.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -29,36 +30,48 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_advection_amr_nonperiodic.jl b/examples/tree_1d_dgsem/elixir_advection_amr_nonperiodic.jl index ff62e90542..aff52ed3ca 100644 --- a/examples/tree_1d_dgsem/elixir_advection_amr_nonperiodic.jl +++ b/examples/tree_1d_dgsem/elixir_advection_amr_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,15 +14,19 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0,) coordinates_max = (5.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, - initial_condition, - solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, + initial_condition, + solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -34,39 +37,53 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) + +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_advection_basic.jl b/examples/tree_1d_dgsem/elixir_advection_basic.jl index cba522f636..00550ad003 100644 --- a/examples/tree_1d_dgsem/elixir_advection_basic.jl +++ b/examples/tree_1d_dgsem/elixir_advection_basic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,13 +14,17 @@ coordinates_min = -1.0 # minimum coordinate coordinates_max = 1.0 # maximum coordinate # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -37,23 +40,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_1d_dgsem/elixir_advection_diffusion.jl b/examples/tree_1d_dgsem/elixir_advection_diffusion.jl index b75e3622ac..106fb9e598 100644 --- a/examples/tree_1d_dgsem/elixir_advection_diffusion.jl +++ b/examples/tree_1d_dgsem/elixir_advection_diffusion.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -17,22 +16,26 @@ coordinates_min = -pi # minimum coordinate coordinates_max = pi # maximum coordinate # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000, # set maximum capacity of tree data structure - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000, # set maximum capacity of tree data structure + periodicity = true +) function x_trans_periodic(x, domain_length = SVector(2 * pi), center = SVector(0.0)) x_normalized = x .- center x_shifted = x_normalized .% domain_length x_offset = ((x_shifted .< -0.5 * domain_length) - (x_shifted .> 0.5 * domain_length)) .* - domain_length + domain_length return center + x_shifted + x_offset end # Define initial condition -function initial_condition_diffusive_convergence_test(x, t, - equation::LinearScalarAdvectionEquation1D) +function initial_condition_diffusive_convergence_test( + x, t, + equation::LinearScalarAdvectionEquation1D + ) # Store translated coordinate for easy use of exact solution x_trans = x_trans_periodic(x - equation.advection_velocity * t) @@ -52,11 +55,15 @@ boundary_conditions = boundary_condition_periodic boundary_conditions_parabolic = boundary_condition_periodic # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, - solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, + solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -84,8 +91,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks time_int_tol = 1.0e-10 time_abs_tol = 1.0e-10 -sol = solve(ode, KenCarp4(autodiff = false), abstol = time_abs_tol, reltol = time_int_tol, - save_everystep = false, callback = callbacks) +sol = solve( + ode, KenCarp4(autodiff = false), abstol = time_abs_tol, reltol = time_int_tol, + save_everystep = false, callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/tree_1d_dgsem/elixir_advection_extended.jl b/examples/tree_1d_dgsem/elixir_advection_extended.jl index df18583470..a01c906b85 100644 --- a/examples/tree_1d_dgsem/elixir_advection_extended.jl +++ b/examples/tree_1d_dgsem/elixir_advection_extended.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -24,14 +23,18 @@ coordinates_min = -1.0 # minimum coordinate coordinates_max = 1.0 # maximum coordinate # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000, # set maximum capacity of tree data structure - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000, # set maximum capacity of tree data structure + periodicity = true +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -46,38 +49,48 @@ summary_callback = SummaryCallback() # The AnalysisCallback allows to analyse the solution in regular intervals and prints the results analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) # The AliveCallback prints short status information in regular intervals alive_callback = AliveCallback(analysis_interval = analysis_interval) # The SaveRestartCallback allows to save a file from which a Trixi.jl simulation can be restarted -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_1d_dgsem/elixir_advection_finite_volume.jl b/examples/tree_1d_dgsem/elixir_advection_finite_volume.jl index 62701f3ecf..76becf7ab9 100644 --- a/examples/tree_1d_dgsem/elixir_advection_finite_volume.jl +++ b/examples/tree_1d_dgsem/elixir_advection_finite_volume.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,13 +15,17 @@ coordinates_min = -1.0 # minimum coordinate coordinates_max = 1.0 # maximum coordinate # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -47,9 +50,11 @@ callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, Euler(), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, Euler(), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_1d_dgsem/elixir_advection_perk2.jl b/examples/tree_1d_dgsem/elixir_advection_perk2.jl index 7db461a079..0592952827 100644 --- a/examples/tree_1d_dgsem/elixir_advection_perk2.jl +++ b/examples/tree_1d_dgsem/elixir_advection_perk2.jl @@ -1,4 +1,3 @@ - using Convex, ECOS using OrdinaryDiffEq using Trixi @@ -16,13 +15,17 @@ coordinates_min = -1.0 # minimum coordinate coordinates_max = 1.0 # maximum coordinate # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -44,29 +47,35 @@ stepsize_callback = StepsizeCallback(cfl = 2.5) alive_callback = AliveCallback(alive_interval = analysis_interval) -save_solution = SaveSolutionCallback(dt = 0.1, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + dt = 0.1, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, - alive_callback, - save_solution, - analysis_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + alive_callback, + save_solution, + analysis_callback, + stepsize_callback +) ############################################################################### # run the simulation # Construct second order paired explicit Runge-Kutta method with 6 stages for given simulation setup. -# Pass `tspan` to calculate maximum time step allowed for the bisection algorithm used +# Pass `tspan` to calculate maximum time step allowed for the bisection algorithm used # in calculating the polynomial coefficients in the ODE algorithm. ode_algorithm = Trixi.PairedExplicitRK2(6, tspan, semi) -sol = Trixi.solve(ode, ode_algorithm, - dt = 1.0, # Manual time step value, will be overwritten by the stepsize_callback when it is specified. - save_everystep = false, callback = callbacks); +sol = Trixi.solve( + ode, ode_algorithm, + dt = 1.0, # Manual time step value, will be overwritten by the stepsize_callback when it is specified. + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_1d_dgsem/elixir_burgers_basic.jl b/examples/tree_1d_dgsem/elixir_burgers_basic.jl index f57b8e730f..96b3f8ae20 100644 --- a/examples/tree_1d_dgsem/elixir_burgers_basic.jl +++ b/examples/tree_1d_dgsem/elixir_burgers_basic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,12 +12,16 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -29,28 +32,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_burgers_linear_stability.jl b/examples/tree_1d_dgsem/elixir_burgers_linear_stability.jl index ae2039edde..412cda2601 100644 --- a/examples/tree_1d_dgsem/elixir_burgers_linear_stability.jl +++ b/examples/tree_1d_dgsem/elixir_burgers_linear_stability.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,17 +12,23 @@ function initial_condition_linear_stability(x, t, equation::InviscidBurgersEquat end volume_flux = flux_ec -solver = DGSEM(polydeg = 3, surface_flux = flux_ec, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ec, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_linear_stability, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_linear_stability, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -34,22 +39,30 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_burgers_rarefaction.jl b/examples/tree_1d_dgsem/elixir_burgers_rarefaction.jl index d32b9d6f1f..1a769ad43c 100644 --- a/examples/tree_1d_dgsem/elixir_burgers_rarefaction.jl +++ b/examples/tree_1d_dgsem/elixir_burgers_rarefaction.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,18 +8,22 @@ equations = InviscidBurgersEquation1D() basis = LobattoLegendreBasis(3) # Use shock capturing techniques to suppress oscillations at discontinuities -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = first) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = first +) volume_flux = flux_ec surface_flux = flux_lax_friedrichs -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) @@ -28,10 +31,12 @@ coordinate_min = 0.0 coordinate_max = 1.0 # Make sure to turn periodicity explicitly off as special boundary conditions are specified -mesh = TreeMesh(coordinate_min, coordinate_max, - initial_refinement_level = 6, - n_cells_max = 10_000, - periodicity = false) +mesh = TreeMesh( + coordinate_min, coordinate_max, + initial_refinement_level = 6, + n_cells_max = 10_000, + periodicity = false +) # Discontinuous initial condition (Riemann Problem) leading to a rarefaction fan. function initial_condition_rarefaction(x, t, equation::InviscidBurgersEquation1D) @@ -48,22 +53,28 @@ function inflow(x, t, equations::InviscidBurgersEquation1D) end boundary_condition_inflow = BoundaryConditionDirichlet(inflow) -function boundary_condition_outflow(u_inner, orientation, normal_direction, x, t, - surface_flux_function, - equations::InviscidBurgersEquation1D) +function boundary_condition_outflow( + u_inner, orientation, normal_direction, x, t, + surface_flux_function, + equations::InviscidBurgersEquation1D + ) # Calculate the boundary flux entirely from the internal solution state flux = Trixi.flux(u_inner, normal_direction, equations) return flux end -boundary_conditions = (x_neg = boundary_condition_inflow, - x_pos = boundary_condition_outflow) +boundary_conditions = ( + x_neg = boundary_condition_inflow, + x_pos = boundary_condition_outflow, +) initial_condition = initial_condition_rarefaction -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -80,15 +91,19 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_burgers_shock.jl b/examples/tree_1d_dgsem/elixir_burgers_shock.jl index 1f0b0e7e04..9eb8fd5c1b 100644 --- a/examples/tree_1d_dgsem/elixir_burgers_shock.jl +++ b/examples/tree_1d_dgsem/elixir_burgers_shock.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,18 +8,22 @@ equations = InviscidBurgersEquation1D() basis = LobattoLegendreBasis(3) # Use shock capturing techniques to suppress oscillations at discontinuities -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = first) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = first +) volume_flux = flux_ec surface_flux = flux_lax_friedrichs -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = surface_flux, - volume_flux_fv = surface_flux) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = surface_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) @@ -28,10 +31,12 @@ coordinate_min = 0.0 coordinate_max = 1.0 # Make sure to turn periodicity explicitly off as special boundary conditions are specified -mesh = TreeMesh(coordinate_min, coordinate_max, - initial_refinement_level = 6, - n_cells_max = 10_000, - periodicity = false) +mesh = TreeMesh( + coordinate_min, coordinate_max, + initial_refinement_level = 6, + n_cells_max = 10_000, + periodicity = false +) # Discontinuous initial condition (Riemann Problem) leading to a shock to test e.g. correct shock speed. function initial_condition_shock(x, t, equation::InviscidBurgersEquation1D) @@ -48,22 +53,28 @@ function inflow(x, t, equations::InviscidBurgersEquation1D) end boundary_condition_inflow = BoundaryConditionDirichlet(inflow) -function boundary_condition_outflow(u_inner, orientation, normal_direction, x, t, - surface_flux_function, - equations::InviscidBurgersEquation1D) +function boundary_condition_outflow( + u_inner, orientation, normal_direction, x, t, + surface_flux_function, + equations::InviscidBurgersEquation1D + ) # Calculate the boundary flux entirely from the internal solution state flux = Trixi.flux(u_inner, normal_direction, equations) return flux end -boundary_conditions = (x_neg = boundary_condition_inflow, - x_pos = boundary_condition_outflow) +boundary_conditions = ( + x_neg = boundary_condition_inflow, + x_pos = boundary_condition_outflow, +) initial_condition = initial_condition_shock -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -80,15 +91,19 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.85) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_blast_wave.jl b/examples/tree_1d_dgsem/elixir_euler_blast_wave.jl index 9cba4936d2..05906b3bb2 100644 --- a/examples/tree_1d_dgsem/elixir_euler_blast_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_blast_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -30,7 +29,7 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation # Calculate primitive variables rho = r > 0.5 ? 1.0 : 1.1691 v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi - p = r > 0.5 ? 1.0E-3 : 1.245 + p = r > 0.5 ? 1.0e-3 : 1.245 return prim2cons(SVector(rho, v1, p), equations) end @@ -39,21 +38,27 @@ initial_condition = initial_condition_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0,) coordinates_max = (2.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -71,22 +76,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_convergence_pure_fv.jl b/examples/tree_1d_dgsem/elixir_euler_convergence_pure_fv.jl index 1fa07d4edd..4765f8a61a 100644 --- a/examples/tree_1d_dgsem/elixir_euler_convergence_pure_fv.jl +++ b/examples/tree_1d_dgsem/elixir_euler_convergence_pure_fv.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,17 +8,23 @@ equations = CompressibleEulerEquations1D(1.4) initial_condition = initial_condition_convergence_test -solver = DGSEM(polydeg = 3, surface_flux = flux_hllc, - volume_integral = VolumeIntegralPureLGLFiniteVolume(flux_hllc)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hllc, + volume_integral = VolumeIntegralPureLGLFiniteVolume(flux_hllc) +) coordinates_min = 0.0 coordinates_max = 2.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -30,28 +35,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_density_wave.jl b/examples/tree_1d_dgsem/elixir_euler_density_wave.jl index 01ccbb2b51..4238310f52 100644 --- a/examples/tree_1d_dgsem/elixir_euler_density_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_density_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,9 +11,11 @@ solver = DGSEM(polydeg = 5, surface_flux = flux_central) coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -31,22 +32,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_ec.jl b/examples/tree_1d_dgsem/elixir_euler_ec.jl index 0be9c8fbf4..f7ac8e272c 100644 --- a/examples/tree_1d_dgsem/elixir_euler_ec.jl +++ b/examples/tree_1d_dgsem/elixir_euler_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,14 +8,18 @@ equations = CompressibleEulerEquations1D(1.4) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0,) coordinates_max = (2.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -33,22 +36,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_positivity.jl b/examples/tree_1d_dgsem/elixir_euler_positivity.jl index 3fb96fb807..acbcb94540 100644 --- a/examples/tree_1d_dgsem/elixir_euler_positivity.jl +++ b/examples/tree_1d_dgsem/elixir_euler_positivity.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -39,21 +38,27 @@ initial_condition = initial_condition_sedov_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0,) coordinates_max = (2.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -70,36 +75,50 @@ 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) - -amr_indicator = IndicatorLöhner(semi, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - med_level = 0, med_threshold = 0.1, # med_level = current level - max_level = 6, max_threshold = 0.3) -amr_callback = AMRCallback(semi, amr_controller, - interval = 2, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorLöhner( + semi, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + med_level = 0, med_threshold = 0.1, # med_level = current level + max_level = 6, max_threshold = 0.3 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 2, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6), - variables = (Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (5.0e-6, 5.0e-6), + variables = (Trixi.density, pressure) +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_quasi_1d_discontinuous.jl b/examples/tree_1d_dgsem/elixir_euler_quasi_1d_discontinuous.jl index cc4535be02..2378901441 100644 --- a/examples/tree_1d_dgsem/elixir_euler_quasi_1d_discontinuous.jl +++ b/examples/tree_1d_dgsem/elixir_euler_quasi_1d_discontinuous.jl @@ -16,8 +16,10 @@ A discontinuous initial condition taken from shallow water and compressible Euler equations [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) """ -function initial_condition_discontinuity(x, t, - equations::CompressibleEulerEquationsQuasi1D) +function initial_condition_discontinuity( + x, t, + equations::CompressibleEulerEquationsQuasi1D + ) rho = (x[1] < 0) ? 3.4718 : 2.0 v1 = (x[1] < 0) ? -2.5923 : -3.0 p = (x[1] < 0) ? 5.7118 : 2.639 @@ -32,21 +34,27 @@ surface_flux = (flux_lax_friedrichs, flux_nonconservative_chan_etal) volume_flux = (flux_chan_etal, flux_nonconservative_chan_etal) basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-1.0,) coordinates_max = (1.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -64,22 +72,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_quasi_1d_ec.jl b/examples/tree_1d_dgsem/elixir_euler_quasi_1d_ec.jl index ae1b2b24b6..309d8f11ad 100644 --- a/examples/tree_1d_dgsem/elixir_euler_quasi_1d_ec.jl +++ b/examples/tree_1d_dgsem/elixir_euler_quasi_1d_ec.jl @@ -27,14 +27,18 @@ initial_condition = initial_condition_ec surface_flux = (flux_chan_etal, flux_nonconservative_chan_etal) volume_flux = surface_flux -solver = DGSEM(polydeg = 4, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0,) coordinates_max = (1.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -52,22 +56,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_quasi_1d_source_terms.jl b/examples/tree_1d_dgsem/elixir_euler_quasi_1d_source_terms.jl index 91bb1ba6e8..cdf3fb2536 100644 --- a/examples/tree_1d_dgsem/elixir_euler_quasi_1d_source_terms.jl +++ b/examples/tree_1d_dgsem/elixir_euler_quasi_1d_source_terms.jl @@ -12,17 +12,23 @@ initial_condition = initial_condition_convergence_test surface_flux = (flux_chan_etal, flux_nonconservative_chan_etal) volume_flux = surface_flux -solver = DGSEM(polydeg = 4, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -33,28 +39,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_sedov_blast_wave.jl b/examples/tree_1d_dgsem/elixir_euler_sedov_blast_wave.jl index b67b2bc602..7fda0fa267 100644 --- a/examples/tree_1d_dgsem/elixir_euler_sedov_blast_wave.jl +++ b/examples/tree_1d_dgsem/elixir_euler_sedov_blast_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -40,21 +39,27 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_chandrashekar basis = LobattoLegendreBasis(3) shock_indicator_variable = density_pressure -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = shock_indicator_variable) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = shock_indicator_variable +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0,) coordinates_max = (2.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -71,35 +76,47 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 6, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 6, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_sedov_blast_wave_pure_fv.jl b/examples/tree_1d_dgsem/elixir_euler_sedov_blast_wave_pure_fv.jl index 8a0241680b..a5746819b8 100644 --- a/examples/tree_1d_dgsem/elixir_euler_sedov_blast_wave_pure_fv.jl +++ b/examples/tree_1d_dgsem/elixir_euler_sedov_blast_wave_pure_fv.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -43,9 +42,11 @@ solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0,) coordinates_max = (2.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 7, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 7, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -62,35 +63,47 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 7, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 7, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.25) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_shockcapturing.jl b/examples/tree_1d_dgsem/elixir_euler_shockcapturing.jl index 0836750537..bdf25938f6 100644 --- a/examples/tree_1d_dgsem/elixir_euler_shockcapturing.jl +++ b/examples/tree_1d_dgsem/elixir_euler_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,21 +11,27 @@ initial_condition = initial_condition_weak_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_shima_etal basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = -2.0 coordinates_max = 2.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -43,22 +48,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_source_terms.jl b/examples/tree_1d_dgsem/elixir_euler_source_terms.jl index cb8a09057d..f0d6cecd64 100644 --- a/examples/tree_1d_dgsem/elixir_euler_source_terms.jl +++ b/examples/tree_1d_dgsem/elixir_euler_source_terms.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,12 +14,16 @@ solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs) coordinates_min = 0.0 coordinates_max = 2.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -31,31 +34,41 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) time_series = TimeSeriesCallback(semi, [0.0, 0.33, 1.0], interval = 10) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - time_series, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + time_series, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_euler_source_terms_nonperiodic.jl b/examples/tree_1d_dgsem/elixir_euler_source_terms_nonperiodic.jl index 922ac3dd97..fc8628d204 100644 --- a/examples/tree_1d_dgsem/elixir_euler_source_terms_nonperiodic.jl +++ b/examples/tree_1d_dgsem/elixir_euler_source_terms_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,21 +12,27 @@ initial_condition = initial_condition_convergence_test # 1*ndims == 2 directions or you can pass a tuple containing BCs for # each direction boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (x_neg = boundary_condition, - x_pos = boundary_condition) +boundary_conditions = ( + x_neg = boundary_condition, + x_pos = boundary_condition, +) solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0,) coordinates_max = (2.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -42,25 +47,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_eulergravity_convergence.jl b/examples/tree_1d_dgsem/elixir_eulergravity_convergence.jl index acde04780e..1496802576 100644 --- a/examples/tree_1d_dgsem/elixir_eulergravity_convergence.jl +++ b/examples/tree_1d_dgsem/elixir_eulergravity_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,12 +13,16 @@ solver_euler = DGSEM(polydeg, flux_hll) coordinates_min = 0.0 coordinates_max = 2.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) -semi_euler = SemidiscretizationHyperbolic(mesh, equations_euler, initial_condition, - solver_euler) +semi_euler = SemidiscretizationHyperbolic( + mesh, equations_euler, initial_condition, + solver_euler +) ############################################################################### # semidiscretization of the hyperbolic diffusion equations @@ -27,18 +30,22 @@ equations_gravity = HyperbolicDiffusionEquations1D() solver_gravity = DGSEM(polydeg, flux_lax_friedrichs) -semi_gravity = SemidiscretizationHyperbolic(mesh, equations_gravity, initial_condition, - solver_gravity, - source_terms = source_terms_harmonic) +semi_gravity = SemidiscretizationHyperbolic( + mesh, equations_gravity, initial_condition, + solver_gravity, + source_terms = source_terms_harmonic +) ############################################################################### # combining both semidiscretizations for Euler + self-gravity -parameters = ParametersEulerGravity(background_density = 2.0, # aka rho0 - gravitational_constant = 1.0, # aka G - cfl = 1.5, - resid_tol = 1.0e-10, - n_iterations_max = 1000, - timestep_gravity = timestep_gravity_erk52_3Sstar!) +parameters = ParametersEulerGravity( + background_density = 2.0, # aka rho0 + gravitational_constant = 1.0, # aka G + cfl = 1.5, + resid_tol = 1.0e-10, + n_iterations_max = 1000, + timestep_gravity = timestep_gravity_erk52_3Sstar! +) semi = SemidiscretizationEulerGravity(semi_euler, semi_gravity, parameters) @@ -50,32 +57,42 @@ ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi_euler, interval = analysis_interval, - save_analysis = true) +analysis_callback = AnalysisCallback( + semi_euler, interval = analysis_interval, + save_analysis = true +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_restart, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_restart, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary println("Number of gravity subcycles: ", semi.gravity_counter.ncalls_since_readout) diff --git a/examples/tree_1d_dgsem/elixir_eulermulti_convergence_ec.jl b/examples/tree_1d_dgsem/elixir_eulermulti_convergence_ec.jl index 6d6316898b..c29f069d0d 100644 --- a/examples/tree_1d_dgsem/elixir_eulermulti_convergence_ec.jl +++ b/examples/tree_1d_dgsem/elixir_eulermulti_convergence_ec.jl @@ -1,26 +1,33 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler multicomponent equations -equations = CompressibleEulerMulticomponentEquations1D(gammas = (1.4, 1.4), - gas_constants = (0.4, 0.4)) +equations = CompressibleEulerMulticomponentEquations1D( + gammas = (1.4, 1.4), + gas_constants = (0.4, 0.4) +) initial_condition = initial_condition_convergence_test volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0,) coordinates_max = (1.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -35,22 +42,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_eulermulti_convergence_es.jl b/examples/tree_1d_dgsem/elixir_eulermulti_convergence_es.jl index 1b37a8a227..25ebf0ad99 100644 --- a/examples/tree_1d_dgsem/elixir_eulermulti_convergence_es.jl +++ b/examples/tree_1d_dgsem/elixir_eulermulti_convergence_es.jl @@ -1,26 +1,33 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler multicomponent equations -equations = CompressibleEulerMulticomponentEquations1D(gammas = (1.4, 1.4, 1.4, 1.4), - gas_constants = (0.4, 0.4, 0.4, 0.4)) +equations = CompressibleEulerMulticomponentEquations1D( + gammas = (1.4, 1.4, 1.4, 1.4), + gas_constants = (0.4, 0.4, 0.4, 0.4) +) initial_condition = initial_condition_convergence_test volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0,) coordinates_max = (1.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -35,22 +42,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_eulermulti_ec.jl b/examples/tree_1d_dgsem/elixir_eulermulti_ec.jl index 73f8de15d8..d369235912 100644 --- a/examples/tree_1d_dgsem/elixir_eulermulti_ec.jl +++ b/examples/tree_1d_dgsem/elixir_eulermulti_ec.jl @@ -1,23 +1,28 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler multicomponent equations -equations = CompressibleEulerMulticomponentEquations1D(gammas = (1.4, 1.4, 1.4), - gas_constants = (0.4, 0.4, 0.4)) +equations = CompressibleEulerMulticomponentEquations1D( + gammas = (1.4, 1.4, 1.4), + gas_constants = (0.4, 0.4, 0.4) +) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0,) coordinates_max = (2.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -31,27 +36,35 @@ summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (Trixi.density,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (Trixi.density,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_eulermulti_es.jl b/examples/tree_1d_dgsem/elixir_eulermulti_es.jl index 7fbec0c005..18575783c7 100644 --- a/examples/tree_1d_dgsem/elixir_eulermulti_es.jl +++ b/examples/tree_1d_dgsem/elixir_eulermulti_es.jl @@ -1,23 +1,28 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler multicomponent equations -equations = CompressibleEulerMulticomponentEquations1D(gammas = (1.4, 1.4), - gas_constants = (0.4, 0.4)) +equations = CompressibleEulerMulticomponentEquations1D( + gammas = (1.4, 1.4), + gas_constants = (0.4, 0.4) +) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0,) coordinates_max = (2.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -31,30 +36,40 @@ summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (Trixi.density,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (Trixi.density,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_eulermulti_two_interacting_blast_waves.jl b/examples/tree_1d_dgsem/elixir_eulermulti_two_interacting_blast_waves.jl index c093420cc7..f3a4ba4183 100644 --- a/examples/tree_1d_dgsem/elixir_eulermulti_two_interacting_blast_waves.jl +++ b/examples/tree_1d_dgsem/elixir_eulermulti_two_interacting_blast_waves.jl @@ -1,12 +1,13 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler equations -equations = CompressibleEulerMulticomponentEquations1D(gammas = (1.4, 1.4, 1.4), - gas_constants = (0.4, 0.4, 0.4)) +equations = CompressibleEulerMulticomponentEquations1D( + gammas = (1.4, 1.4, 1.4), + gas_constants = (0.4, 0.4, 0.4) +) """ initial_condition_two_interacting_blast_waves(x, t, equations::CompressibleEulerMulticomponentEquations1D) @@ -16,8 +17,10 @@ A multicomponent two interacting blast wave test taken from The consistent multi-fluid advection method [arXiv: 9807241](https://arxiv.org/pdf/astro-ph/9807241.pdf) """ -function initial_condition_two_interacting_blast_waves(x, t, - equations::CompressibleEulerMulticomponentEquations1D) +function initial_condition_two_interacting_blast_waves( + x, t, + equations::CompressibleEulerMulticomponentEquations1D + ) rho1 = 0.5 * x[1]^2 rho2 = 0.5 * (sin(20 * x[1]))^2 rho3 = 1 - rho1 - rho2 @@ -40,9 +43,11 @@ function initial_condition_two_interacting_blast_waves(x, t, end initial_condition = initial_condition_two_interacting_blast_waves -function boundary_condition_two_interacting_blast_waves(u_inner, orientation, direction, - x, t, surface_flux_function, - equations::CompressibleEulerMulticomponentEquations1D) +function boundary_condition_two_interacting_blast_waves( + u_inner, orientation, direction, + x, t, surface_flux_function, + equations::CompressibleEulerMulticomponentEquations1D + ) u_inner_reflect = SVector(-u_inner[1], u_inner[2], u_inner[3], u_inner[4], u_inner[5]) # Calculate boundary flux if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary @@ -58,25 +63,33 @@ boundary_conditions = boundary_condition_two_interacting_blast_waves surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.8, - alpha_min = 0.0, - alpha_smooth = true, - variable = pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.8, + alpha_min = 0.0, + alpha_smooth = true, + variable = pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (0.0,) coordinates_max = (1.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 9, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 9, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -92,22 +105,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl b/examples/tree_1d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl index b9173ec9f4..8fb63c43ba 100644 --- a/examples/tree_1d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl +++ b/examples/tree_1d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,8 +15,10 @@ A non-priodic harmonic function used in combination with !!! note The only harmonic functions in 1D have the form phi(x) = A + Bx """ -function initial_condition_harmonic_nonperiodic(x, t, - equations::HyperbolicDiffusionEquations1D) +function initial_condition_harmonic_nonperiodic( + x, t, + equations::HyperbolicDiffusionEquations1D + ) # elliptic equation: -νΔϕ = f if t == 0.0 phi = 5.0 @@ -38,14 +39,18 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = -1.0 coordinates_max = 2.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 30_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions, - source_terms = source_terms_harmonic) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 30_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions, + source_terms = source_terms_harmonic +) ############################################################################### # ODE solvers, callbacks etc. @@ -63,22 +68,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.75) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = Trixi.solve(ode, Trixi.HypDiffN3Erk3Sstar52(), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = Trixi.solve( + ode, Trixi.HypDiffN3Erk3Sstar52(), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_hypdiff_nonperiodic.jl b/examples/tree_1d_dgsem/elixir_hypdiff_nonperiodic.jl index 4da3b33a46..8ebc33eff4 100644 --- a/examples/tree_1d_dgsem/elixir_hypdiff_nonperiodic.jl +++ b/examples/tree_1d_dgsem/elixir_hypdiff_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,14 +14,18 @@ solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions, - source_terms = source_terms_poisson_nonperiodic) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions, + source_terms = source_terms_poisson_nonperiodic +) ############################################################################### # ODE solvers, callbacks etc. @@ -36,27 +39,35 @@ resid_tol = 5.0e-12 steady_state_callback = SteadyStateCallback(abstol = resid_tol, reltol = 0.0) analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_linearizedeuler_convergence.jl b/examples/tree_1d_dgsem/elixir_linearizedeuler_convergence.jl index 5b17ab4f3d..290d9435b7 100644 --- a/examples/tree_1d_dgsem/elixir_linearizedeuler_convergence.jl +++ b/examples/tree_1d_dgsem/elixir_linearizedeuler_convergence.jl @@ -4,8 +4,10 @@ using Trixi ############################################################################### # semidiscretization of the linearized Euler equations -equations = LinearizedEulerEquations1D(v_mean_global = 0.0, c_mean_global = 1.0, - rho_mean_global = 1.0) +equations = LinearizedEulerEquations1D( + v_mean_global = 0.0, c_mean_global = 1.0, + rho_mean_global = 1.0 +) initial_condition = initial_condition_convergence_test @@ -16,9 +18,11 @@ coordinates_min = (-1.0) coordinates_max = (1.0) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # A semidiscretization collects data structures and functions for the spatial discretization semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -45,15 +49,19 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.8) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_linearizedeuler_gauss_wall.jl b/examples/tree_1d_dgsem/elixir_linearizedeuler_gauss_wall.jl index 0884249559..96ea4422b7 100644 --- a/examples/tree_1d_dgsem/elixir_linearizedeuler_gauss_wall.jl +++ b/examples/tree_1d_dgsem/elixir_linearizedeuler_gauss_wall.jl @@ -1,22 +1,25 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the linearized Euler equations -equations = LinearizedEulerEquations1D(v_mean_global = 0.5, c_mean_global = 1.0, - rho_mean_global = 1.0) +equations = LinearizedEulerEquations1D( + v_mean_global = 0.5, c_mean_global = 1.0, + rho_mean_global = 1.0 +) solver = DGSEM(polydeg = 5, surface_flux = flux_hll) coordinates_min = (0.0,) coordinates_max = (90.0,) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 100_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 100_000, + periodicity = false +) # Initialize density and pressure perturbation with a Gaussian bump # that is advected to left with v - c and to the right with v + c. @@ -29,8 +32,10 @@ end initial_condition = initial_condition_gauss_wall # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition_wall) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition_wall +) ############################################################################### # ODE solvers, callbacks etc. @@ -50,16 +55,20 @@ analysis_callback = AnalysisCallback(semi, interval = 100) stepsize_callback = StepsizeCallback(cfl = 0.7) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks) +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/tree_1d_dgsem/elixir_maxwell_E_excitation.jl b/examples/tree_1d_dgsem/elixir_maxwell_E_excitation.jl index b5478331a7..d3770f5d7c 100644 --- a/examples/tree_1d_dgsem/elixir_maxwell_E_excitation.jl +++ b/examples/tree_1d_dgsem/elixir_maxwell_E_excitation.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,9 +12,11 @@ coordinates_min = 0.0 coordinates_max = 1.0 # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # Excite the electric field which causes a standing wave. # The solution is an undamped exchange between electric and magnetic energy. @@ -35,7 +36,7 @@ semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) # As the wave speed is equal to the speed of light which is on the order of 3 * 10^8 # we consider only a small time horizon. -ode = semidiscretize(semi, (0.0, 1e-7)); +ode = semidiscretize(semi, (0.0, 1.0e-7)); summary_callback = SummaryCallback() @@ -51,9 +52,11 @@ callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_1d_dgsem/elixir_maxwell_convergence.jl b/examples/tree_1d_dgsem/elixir_maxwell_convergence.jl index e62f7215ac..a0c0a9db73 100644 --- a/examples/tree_1d_dgsem/elixir_maxwell_convergence.jl +++ b/examples/tree_1d_dgsem/elixir_maxwell_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,19 +12,23 @@ coordinates_min = 0.0 coordinates_max = 1.0 # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. # As the wave speed is equal to the speed of light which is on the order of 3 * 10^8 # we consider only a small time horizon. -ode = semidiscretize(semi, (0.0, 1e-8)); +ode = semidiscretize(semi, (0.0, 1.0e-8)); summary_callback = SummaryCallback() @@ -41,9 +44,11 @@ callbacks = CallbackSet(summary_callback, analysis_callback, stepsize_callback) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_1d_dgsem/elixir_mhd_alfven_wave.jl b/examples/tree_1d_dgsem/elixir_mhd_alfven_wave.jl index 1a66ac60b9..bc1ea59da9 100644 --- a/examples/tree_1d_dgsem/elixir_mhd_alfven_wave.jl +++ b/examples/tree_1d_dgsem/elixir_mhd_alfven_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,14 +9,18 @@ equations = IdealGlmMhdEquations1D(gamma) initial_condition = initial_condition_convergence_test volume_flux = flux_hindenlang_gassner -solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -30,33 +33,45 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal, - energy_magnetic, - cross_helicity)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + energy_magnetic, + cross_helicity, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_mhd_briowu_shock_tube.jl b/examples/tree_1d_dgsem/elixir_mhd_briowu_shock_tube.jl index 4d537508b4..d0a910c39d 100644 --- a/examples/tree_1d_dgsem/elixir_mhd_briowu_shock_tube.jl +++ b/examples/tree_1d_dgsem/elixir_mhd_briowu_shock_tube.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -36,25 +35,33 @@ surface_flux = flux_hlle volume_flux = flux_derigs_etal basis = LobattoLegendreBasis(4) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -65,44 +72,62 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 6, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) + +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 6, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.65) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_mhd_ec.jl b/examples/tree_1d_dgsem/elixir_mhd_ec.jl index e5da808f69..480369cfce 100644 --- a/examples/tree_1d_dgsem/elixir_mhd_ec.jl +++ b/examples/tree_1d_dgsem/elixir_mhd_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,14 +9,18 @@ equations = IdealGlmMhdEquations1D(gamma) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_hindenlang_gassner -solver = DGSEM(polydeg = 3, surface_flux = flux_hindenlang_gassner, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hindenlang_gassner, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -30,28 +33,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_mhd_ryujones_shock_tube.jl b/examples/tree_1d_dgsem/elixir_mhd_ryujones_shock_tube.jl index a7d7689a80..78a22a5d20 100644 --- a/examples/tree_1d_dgsem/elixir_mhd_ryujones_shock_tube.jl +++ b/examples/tree_1d_dgsem/elixir_mhd_ryujones_shock_tube.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -43,25 +42,33 @@ surface_flux = flux_hlle volume_flux = flux_hindenlang_gassner basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = Trixi.density) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = Trixi.density +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 7, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 7, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -76,22 +83,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_mhd_shu_osher_shock_tube.jl b/examples/tree_1d_dgsem/elixir_mhd_shu_osher_shock_tube.jl index 689537ebdd..969823cc85 100644 --- a/examples/tree_1d_dgsem/elixir_mhd_shu_osher_shock_tube.jl +++ b/examples/tree_1d_dgsem/elixir_mhd_shu_osher_shock_tube.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -41,8 +40,10 @@ but shock propagates from right to left. !!! note This is useful to exercise some of the components of the HLL flux. """ -function initial_condition_shu_osher_shock_tube_flipped(x, t, - equations::IdealGlmMhdEquations1D) +function initial_condition_shu_osher_shock_tube_flipped( + x, t, + equations::IdealGlmMhdEquations1D + ) # domain must be set to [-5, 5], γ = 5/3, final time = 0.7 # initial shock location is taken to be at x = 4 x_0 = 4.0 @@ -65,25 +66,33 @@ surface_flux = flux_hlle volume_flux = flux_hindenlang_gassner basis = LobattoLegendreBasis(4) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = -5.0 coordinates_max = 5.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -94,43 +103,59 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (energy_kinetic, - energy_internal, - energy_magnetic, - cross_helicity)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + energy_magnetic, + cross_helicity, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 7, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 7, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_mhd_torrilhon_shock_tube.jl b/examples/tree_1d_dgsem/elixir_mhd_torrilhon_shock_tube.jl index 3b366c35e0..4a036acf81 100644 --- a/examples/tree_1d_dgsem/elixir_mhd_torrilhon_shock_tube.jl +++ b/examples/tree_1d_dgsem/elixir_mhd_torrilhon_shock_tube.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -35,25 +34,33 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_central basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = -1.0 coordinates_max = 1.5 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 7, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 7, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -64,28 +71,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_mhdmulti_briowu_shock_tube.jl b/examples/tree_1d_dgsem/elixir_mhdmulti_briowu_shock_tube.jl index 831fa7afed..745e7e29e8 100644 --- a/examples/tree_1d_dgsem/elixir_mhdmulti_briowu_shock_tube.jl +++ b/examples/tree_1d_dgsem/elixir_mhdmulti_briowu_shock_tube.jl @@ -1,11 +1,12 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the ideal MHD equations -equations = IdealGlmMhdMulticomponentEquations1D(gammas = (2.0, 2.0), - gas_constants = (2.0, 2.0)) +equations = IdealGlmMhdMulticomponentEquations1D( + gammas = (2.0, 2.0), + gas_constants = (2.0, 2.0) +) """ initial_condition_briowu_shock_tube(x, t, equations::IdealGlmMhdMulticomponentEquations1D) @@ -16,23 +17,33 @@ MHD extension of the Sod shock tube. Taken from Section V of the article An Upwind Differencing Scheme for the Equations of Ideal Magnetohydrodynamics [DOI: 10.1016/0021-9991(88)90120-9](https://doi.org/10.1016/0021-9991(88)90120-9) """ -function initial_condition_briowu_shock_tube(x, t, - equations::IdealGlmMhdMulticomponentEquations1D) +function initial_condition_briowu_shock_tube( + x, t, + equations::IdealGlmMhdMulticomponentEquations1D + ) # domain must be set to [0, 1], γ = 2, final time = 0.12 if x[1] < 0.5 rho = 1.0 - prim_rho = SVector{ncomponents(equations), real(equations)}(2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - rho - for i in eachcomponent(equations)) + prim_rho = SVector{ncomponents(equations), real(equations)}( + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + rho + for i in eachcomponent(equations) + ) else rho = 0.125 - prim_rho = SVector{ncomponents(equations), real(equations)}(2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - rho - for i in eachcomponent(equations)) + prim_rho = SVector{ncomponents(equations), real(equations)}( + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + rho + for i in eachcomponent(equations) + ) end v1 = 0.0 v2 = 0.0 @@ -53,25 +64,33 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_hindenlang_gassner basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.8, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.8, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -82,44 +101,62 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 6, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) + +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 6, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_mhdmulti_convergence.jl b/examples/tree_1d_dgsem/elixir_mhdmulti_convergence.jl index a1636c0847..4875c17fa7 100644 --- a/examples/tree_1d_dgsem/elixir_mhdmulti_convergence.jl +++ b/examples/tree_1d_dgsem/elixir_mhdmulti_convergence.jl @@ -1,24 +1,29 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible ideal GLM-MHD equations -equations = IdealGlmMhdMulticomponentEquations1D(gammas = (5 / 3, 5 / 3, 5 / 3), - gas_constants = (2.08, 2.08, 2.08)) +equations = IdealGlmMhdMulticomponentEquations1D( + gammas = (5 / 3, 5 / 3, 5 / 3), + gas_constants = (2.08, 2.08, 2.08) +) initial_condition = initial_condition_convergence_test volume_flux = flux_hindenlang_gassner -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -31,29 +36,37 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 0.5 stepsize_callback = StepsizeCallback(cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_mhdmulti_ec.jl b/examples/tree_1d_dgsem/elixir_mhdmulti_ec.jl index 71466f3138..9dd3f133e3 100644 --- a/examples/tree_1d_dgsem/elixir_mhdmulti_ec.jl +++ b/examples/tree_1d_dgsem/elixir_mhdmulti_ec.jl @@ -1,23 +1,28 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the ideal MHD equations -equations = IdealGlmMhdMulticomponentEquations1D(gammas = (2.0, 2.0, 2.0), - gas_constants = (2.0, 2.0, 2.0)) +equations = IdealGlmMhdMulticomponentEquations1D( + gammas = (2.0, 2.0, 2.0), + gas_constants = (2.0, 2.0, 2.0) +) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_hindenlang_gassner -solver = DGSEM(polydeg = 3, surface_flux = flux_hindenlang_gassner, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hindenlang_gassner, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -33,22 +38,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_mhdmulti_es.jl b/examples/tree_1d_dgsem/elixir_mhdmulti_es.jl index 37623e048e..d2044b3679 100644 --- a/examples/tree_1d_dgsem/elixir_mhdmulti_es.jl +++ b/examples/tree_1d_dgsem/elixir_mhdmulti_es.jl @@ -1,23 +1,28 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the ideal MHD equations -equations = IdealGlmMhdMulticomponentEquations1D(gammas = (2.0, 2.0, 2.0), - gas_constants = (2.0, 2.0, 2.0)) +equations = IdealGlmMhdMulticomponentEquations1D( + gammas = (2.0, 2.0, 2.0), + gas_constants = (2.0, 2.0, 2.0) +) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_hindenlang_gassner -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -30,28 +35,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_navierstokes_convergence_periodic.jl b/examples/tree_1d_dgsem/elixir_navierstokes_convergence_periodic.jl index eab0840f38..fb2f25f5b2 100644 --- a/examples/tree_1d_dgsem/elixir_navierstokes_convergence_periodic.jl +++ b/examples/tree_1d_dgsem/elixir_navierstokes_convergence_periodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,8 +8,10 @@ prandtl_number() = 0.72 mu() = 6.25e-4 # equivalent to Re = 1600 equations = CompressibleEulerEquations1D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion1D(equations, mu = mu(), - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion1D( + equations, mu = mu(), + Prandtl = prandtl_number() +) # This convergence test setup was originally derived by Andrew Winters (@andrewwinters5000) # (Simplified version of the 2D) @@ -76,38 +77,50 @@ initial_condition = initial_condition_navier_stokes_convergence_test du1 = rho_t + rho_x * v1 + rho * v1_x # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t - + p_x + rho_x * v1^2 + 2.0 * rho * v1 * v1_x - - # stress tensor from x-direction - v1_xx * mu_) + du2 = ( + rho_t * v1 + rho * v1_t + + p_x + rho_x * v1^2 + 2.0 * rho * v1 * v1_x - + # stress tensor from x-direction + v1_xx * mu_ + ) # total energy equation - du3 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - - # stress tensor and temperature gradient terms from x-direction - v1_xx * v1 * mu_ - - v1_x * v1_x * mu_ - - T_const * inv_rho_cubed * - (p_xx * rho * rho - - 2.0 * p_x * rho * rho_x + - 2.0 * p * rho_x * rho_x - - p * rho * rho_xx) * mu_) + du3 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + # stress tensor and temperature gradient terms from x-direction + v1_xx * v1 * mu_ - + v1_x * v1_x * mu_ - + T_const * inv_rho_cubed * + ( + p_xx * rho * rho - + 2.0 * p_x * rho * rho_x + + 2.0 * p * rho_x * rho_x - + p * rho * rho_xx + ) * mu_ + ) return SVector(du1, du2, du3) end volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_hllc, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hllc, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 100_000) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver, - source_terms = source_terms_navier_stokes_convergence_test) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 100_000 +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver, + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -122,14 +135,18 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -time_int_tol = 1e-9 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-9 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_navierstokes_convergence_walls.jl b/examples/tree_1d_dgsem/elixir_navierstokes_convergence_walls.jl index 40030d5334..d71593ad12 100644 --- a/examples/tree_1d_dgsem/elixir_navierstokes_convergence_walls.jl +++ b/examples/tree_1d_dgsem/elixir_navierstokes_convergence_walls.jl @@ -8,22 +8,28 @@ prandtl_number() = 0.72 mu() = 0.01 equations = CompressibleEulerEquations1D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion1D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion1D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = -1.0 coordinates_max = 1.0 # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - periodicity = false, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + periodicity = false, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # Note: the initial condition cannot be specialized to `CompressibleNavierStokesDiffusion1D` # since it is called by both the parabolic solver (which passes in `CompressibleNavierStokesDiffusion1D`) @@ -72,11 +78,17 @@ end v1 = log(x + 2.0) * (1.0 - exp(-A * (x - 1.0))) * cos(pi_t) v1_t = -pi * log(x + 2.0) * (1.0 - exp(-A * (x - 1.0))) * sin(pi_t) - v1_x = (A * log(x + 2.0) * exp(-A * (x - 1.0)) + - (1.0 - exp(-A * (x - 1.0))) / (x + 2.0)) * cos(pi_t) - v1_xx = ((2.0 * A * exp(-A * (x - 1.0)) / (x + 2.0) - - A * A * log(x + 2.0) * exp(-A * (x - 1.0)) - - (1.0 - exp(-A * (x - 1.0))) / ((x + 2.0) * (x + 2.0))) * cos(pi_t)) + v1_x = ( + A * log(x + 2.0) * exp(-A * (x - 1.0)) + + (1.0 - exp(-A * (x - 1.0))) / (x + 2.0) + ) * cos(pi_t) + v1_xx = ( + ( + 2.0 * A * exp(-A * (x - 1.0)) / (x + 2.0) - + A * A * log(x + 2.0) * exp(-A * (x - 1.0)) - + (1.0 - exp(-A * (x - 1.0))) / ((x + 2.0) * (x + 2.0)) + ) * cos(pi_t) + ) p = rho * rho p_t = 2.0 * rho * rho_t @@ -97,21 +109,27 @@ end du1 = rho_t + rho_x * v1 + rho * v1_x # y-momentum equation - du2 = (rho_t * v1 + rho * v1_t - + p_x + rho_x * v1^2 + 2.0 * rho * v1 * v1_x - - # stress tensor from y-direction - v1_xx * mu_) + du2 = ( + rho_t * v1 + rho * v1_t + + p_x + rho_x * v1^2 + 2.0 * rho * v1 * v1_x - + # stress tensor from y-direction + v1_xx * mu_ + ) # total energy equation - du3 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - - # stress tensor and temperature gradient terms from x-direction - v1_xx * v1 * mu_ - - v1_x * v1_x * mu_ - - T_const * inv_rho_cubed * - (p_xx * rho * rho - - 2.0 * p_x * rho * rho_x + - 2.0 * p * rho_x * rho_x - - p * rho * rho_xx) * mu_) + du3 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + # stress tensor and temperature gradient terms from x-direction + v1_xx * v1 * mu_ - + v1_x * v1_x * mu_ - + T_const * inv_rho_cubed * + ( + p_xx * rho * rho - + 2.0 * p_x * rho * rho_x + + 2.0 * p * rho_x * rho_x - + p * rho * rho_xx + ) * mu_ + ) return SVector(du1, du2, du3) end @@ -119,34 +137,56 @@ end initial_condition = initial_condition_navier_stokes_convergence_test # BC types -velocity_bc_left_right = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2]) - -heat_bc_left = Isothermal((x, t, equations) -> Trixi.temperature(initial_condition_navier_stokes_convergence_test(x, - t, - equations), - equations_parabolic)) +velocity_bc_left_right = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2] +) + +heat_bc_left = Isothermal( + (x, t, equations) -> Trixi.temperature( + initial_condition_navier_stokes_convergence_test( + x, + t, + equations + ), + equations_parabolic + ) +) heat_bc_right = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_left = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_left) -boundary_condition_right = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_right) +boundary_condition_left = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_left +) +boundary_condition_right = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_right +) # define inviscid boundary conditions -boundary_conditions = (; x_neg = boundary_condition_slip_wall, - x_pos = boundary_condition_slip_wall) +boundary_conditions = (; + x_neg = boundary_condition_slip_wall, + x_pos = boundary_condition_slip_wall, +) # define viscous boundary conditions -boundary_conditions_parabolic = (; x_neg = boundary_condition_left, - x_pos = boundary_condition_right) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +boundary_conditions_parabolic = (; + x_neg = boundary_condition_left, + x_pos = boundary_condition_right, +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -164,7 +204,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1e-5, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1.0e-5, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_navierstokes_convergence_walls_amr.jl b/examples/tree_1d_dgsem/elixir_navierstokes_convergence_walls_amr.jl index e833155a68..37394b436d 100644 --- a/examples/tree_1d_dgsem/elixir_navierstokes_convergence_walls_amr.jl +++ b/examples/tree_1d_dgsem/elixir_navierstokes_convergence_walls_amr.jl @@ -8,22 +8,28 @@ prandtl_number() = 0.72 mu() = 0.01 equations = CompressibleEulerEquations1D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion1D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesEntropy()) +equations_parabolic = CompressibleNavierStokesDiffusion1D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesEntropy() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = -1.0 coordinates_max = 1.0 # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - periodicity = false, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + periodicity = false, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # Note: the initial condition cannot be specialized to `CompressibleNavierStokesDiffusion1D` # since it is called by both the parabolic solver (which passes in `CompressibleNavierStokesDiffusion1D`) @@ -72,11 +78,17 @@ end v1 = log(x + 2.0) * (1.0 - exp(-A * (x - 1.0))) * cos(pi_t) v1_t = -pi * log(x + 2.0) * (1.0 - exp(-A * (x - 1.0))) * sin(pi_t) - v1_x = (A * log(x + 2.0) * exp(-A * (x - 1.0)) + - (1.0 - exp(-A * (x - 1.0))) / (x + 2.0)) * cos(pi_t) - v1_xx = ((2.0 * A * exp(-A * (x - 1.0)) / (x + 2.0) - - A * A * log(x + 2.0) * exp(-A * (x - 1.0)) - - (1.0 - exp(-A * (x - 1.0))) / ((x + 2.0) * (x + 2.0))) * cos(pi_t)) + v1_x = ( + A * log(x + 2.0) * exp(-A * (x - 1.0)) + + (1.0 - exp(-A * (x - 1.0))) / (x + 2.0) + ) * cos(pi_t) + v1_xx = ( + ( + 2.0 * A * exp(-A * (x - 1.0)) / (x + 2.0) - + A * A * log(x + 2.0) * exp(-A * (x - 1.0)) - + (1.0 - exp(-A * (x - 1.0))) / ((x + 2.0) * (x + 2.0)) + ) * cos(pi_t) + ) p = rho * rho p_t = 2.0 * rho * rho_t @@ -97,21 +109,27 @@ end du1 = rho_t + rho_x * v1 + rho * v1_x # y-momentum equation - du2 = (rho_t * v1 + rho * v1_t - + p_x + rho_x * v1^2 + 2.0 * rho * v1 * v1_x - - # stress tensor from y-direction - v1_xx * mu_) + du2 = ( + rho_t * v1 + rho * v1_t + + p_x + rho_x * v1^2 + 2.0 * rho * v1 * v1_x - + # stress tensor from y-direction + v1_xx * mu_ + ) # total energy equation - du3 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - - # stress tensor and temperature gradient terms from x-direction - v1_xx * v1 * mu_ - - v1_x * v1_x * mu_ - - T_const * inv_rho_cubed * - (p_xx * rho * rho - - 2.0 * p_x * rho * rho_x + - 2.0 * p * rho_x * rho_x - - p * rho * rho_xx) * mu_) + du3 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + # stress tensor and temperature gradient terms from x-direction + v1_xx * v1 * mu_ - + v1_x * v1_x * mu_ - + T_const * inv_rho_cubed * + ( + p_xx * rho * rho - + 2.0 * p_x * rho * rho_x + + 2.0 * p * rho_x * rho_x - + p * rho * rho_xx + ) * mu_ + ) return SVector(du1, du2, du3) end @@ -119,34 +137,56 @@ end initial_condition = initial_condition_navier_stokes_convergence_test # BC types -velocity_bc_left_right = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2]) - -heat_bc_left = Isothermal((x, t, equations) -> Trixi.temperature(initial_condition_navier_stokes_convergence_test(x, - t, - equations), - equations_parabolic)) +velocity_bc_left_right = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2] +) + +heat_bc_left = Isothermal( + (x, t, equations) -> Trixi.temperature( + initial_condition_navier_stokes_convergence_test( + x, + t, + equations + ), + equations_parabolic + ) +) heat_bc_right = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_left = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_left) -boundary_condition_right = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_right) +boundary_condition_left = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_left +) +boundary_condition_right = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_right +) # define inviscid boundary conditions -boundary_conditions = (; x_neg = boundary_condition_slip_wall, - x_pos = boundary_condition_slip_wall) +boundary_conditions = (; + x_neg = boundary_condition_slip_wall, + x_pos = boundary_condition_slip_wall, +) # define viscous boundary conditions -boundary_conditions_parabolic = (; x_neg = boundary_condition_left, - x_pos = boundary_condition_right) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +boundary_conditions_parabolic = (; + x_neg = boundary_condition_left, + x_pos = boundary_condition_right, +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -160,15 +200,19 @@ alive_callback = AliveCallback(alive_interval = 10) analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval) -amr_controller = ControllerThreeLevel(semi, - IndicatorLöhner(semi, variable = Trixi.density), - base_level = 3, - med_level = 4, med_threshold = 0.005, - max_level = 5, max_threshold = 0.01) +amr_controller = ControllerThreeLevel( + semi, + IndicatorLöhner(semi, variable = Trixi.density), + base_level = 3, + med_level = 4, med_threshold = 0.005, + max_level = 5, max_threshold = 0.01 +) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true +) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, amr_callback) @@ -176,7 +220,9 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, amr ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1e-5, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1.0e-5, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallow_water_quasi_1d_source_terms.jl b/examples/tree_1d_dgsem/elixir_shallow_water_quasi_1d_source_terms.jl index 72747c669e..0e9b60ad64 100644 --- a/examples/tree_1d_dgsem/elixir_shallow_water_quasi_1d_source_terms.jl +++ b/examples/tree_1d_dgsem/elixir_shallow_water_quasi_1d_source_terms.jl @@ -13,24 +13,32 @@ initial_condition = initial_condition_convergence_test # Get the DG approximation space volume_flux = (flux_chan_etal, flux_nonconservative_chan_etal) -surface_flux = (FluxPlusDissipation(flux_chan_etal, DissipationLocalLaxFriedrichs()), - flux_nonconservative_chan_etal) -solver = DGSEM(polydeg = 3, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +surface_flux = ( + FluxPlusDissipation(flux_chan_etal, DissipationLocalLaxFriedrichs()), + flux_nonconservative_chan_etal, +) +solver = DGSEM( + polydeg = 3, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = 0.0 coordinates_max = sqrt(2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000, + periodicity = true +) # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -45,9 +53,11 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 200, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 200, + save_initial_solution = true, + save_final_solution = true +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) @@ -55,6 +65,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, sav # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_ec.jl b/examples/tree_1d_dgsem/elixir_shallowwater_ec.jl index af0da5d176..7e607ed621 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_ec.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -41,18 +40,22 @@ initial_condition = initial_condition_ec_discontinuous_bottom # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 4, - surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, + surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) # Create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -73,19 +76,25 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_discontinuous.jl b/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_discontinuous.jl index 76c0475938..a40aa98711 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_discontinuous.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_discontinuous.jl @@ -29,18 +29,22 @@ initial_condition = initial_condition_discontinuity volume_flux = (flux_chan_etal, flux_nonconservative_chan_etal) surface_flux = (flux_lax_friedrichs, flux_nonconservative_chan_etal) -solver = DGSEM(polydeg = 3, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = -0.5 coordinates_max = 0.5 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000, + periodicity = true +) # create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -58,9 +62,11 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 200, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 200, + save_initial_solution = true, + save_final_solution = true +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) @@ -68,6 +74,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, sav # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_well_balanced.jl b/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_well_balanced.jl index a4f4b0189b..78f15f003b 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_well_balanced.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_quasi_1d_well_balanced.jl @@ -14,12 +14,14 @@ equations = ShallowWaterEquationsQuasi1D(gravity_constant = 9.81, H0 = 2.0) # Works as intended for TreeMesh1D with `initial_refinement_level=3`. If the mesh # refinement level is changed the initial condition below may need changed as well to # ensure that the discontinuities lie on an element interface. -function initial_condition_discontinuous_well_balancedness(x, t, - equations::ShallowWaterEquationsQuasi1D) +function initial_condition_discontinuous_well_balancedness( + x, t, + equations::ShallowWaterEquationsQuasi1D + ) H = equations.H0 v = 0.0 - # for a periodic domain, this choice of `b` and `a` mimic + # for a periodic domain, this choice of `b` and `a` mimic # discontinuity across the periodic boundary. b = 0.5 * (x[1] + 1) a = 2 + x[1] @@ -34,17 +36,21 @@ initial_condition = initial_condition_discontinuous_well_balancedness volume_flux = (flux_chan_etal, flux_nonconservative_chan_etal) surface_flux = volume_flux -solver = DGSEM(polydeg = 4, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000 +) # Create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -61,24 +67,32 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (lake_at_rest_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (lake_at_rest_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_shock_capturing.jl b/examples/tree_1d_dgsem/elixir_shallowwater_shock_capturing.jl index 511f33d110..0daf1606cf 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_shock_capturing.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_shock_capturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -11,8 +10,10 @@ equations = ShallowWaterEquations1D(gravity_constant = 9.812, H0 = 1.75) # Works as intended for TreeMesh1D with `initial_refinement_level=3`. If the mesh # refinement level is changed the initial condition below may need changed as well to # ensure that the discontinuities lie on an element interface. -function initial_condition_stone_throw_discontinuous_bottom(x, t, - equations::ShallowWaterEquations1D) +function initial_condition_stone_throw_discontinuous_bottom( + x, t, + equations::ShallowWaterEquations1D + ) # Calculate primitive variables @@ -27,8 +28,10 @@ function initial_condition_stone_throw_discontinuous_bottom(x, t, v = 1.0 end - b = (1.5 / exp(0.5 * ((x[1] - 1.0)^2)) + - 0.75 / exp(0.5 * ((x[1] + 1.0)^2))) + b = ( + 1.5 / exp(0.5 * ((x[1] - 1.0)^2)) + + 0.75 / exp(0.5 * ((x[1] + 1.0)^2)) + ) # Force a discontinuous bottom topography if x[1] >= -1.5 && x[1] <= 0.0 @@ -46,19 +49,27 @@ boundary_condition = boundary_condition_slip_wall # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -surface_flux = (FluxHydrostaticReconstruction(flux_lax_friedrichs, - hydrostatic_reconstruction_audusse_etal), - flux_nonconservative_audusse_etal) +surface_flux = ( + FluxHydrostaticReconstruction( + flux_lax_friedrichs, + hydrostatic_reconstruction_audusse_etal + ), + flux_nonconservative_audusse_etal, +) basis = LobattoLegendreBasis(4) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = waterheight_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = waterheight_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) @@ -67,14 +78,18 @@ solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = -3.0 coordinates_max = 3.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000, + periodicity = false +) # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition +) ############################################################################### # ODE solver @@ -88,11 +103,15 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = false, - extra_analysis_integrals = (energy_kinetic, - energy_internal, - lake_at_rest_error)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = false, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + lake_at_rest_error, + ) +) # Enable in-situ visualization with a new plot generated every 50 time steps # and we explicitly pass that the plot data will be one-dimensional @@ -100,17 +119,21 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution)#, +callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) #, # visualization) ############################################################################### # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-7, reltol = 1.0e-7, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_source_terms.jl b/examples/tree_1d_dgsem/elixir_shallowwater_source_terms.jl index af596a377f..2f58c2656a 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_source_terms.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_source_terms.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,23 +12,29 @@ initial_condition = initial_condition_convergence_test # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = 0.0 coordinates_max = sqrt(2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000, + periodicity = true +) # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -44,9 +49,11 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 200, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 200, + save_initial_solution = true, + save_final_solution = true +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) @@ -54,6 +61,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, sav # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_source_terms_dirichlet.jl b/examples/tree_1d_dgsem/elixir_shallowwater_source_terms_dirichlet.jl index cbc98a30f9..0f9229fc7a 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_source_terms_dirichlet.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_source_terms_dirichlet.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,23 +15,29 @@ boundary_condition = BoundaryConditionDirichlet(initial_condition) volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal) -solver = DGSEM(polydeg = 3, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = 0.0 coordinates_max = sqrt(2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000, + periodicity = false +) # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -47,9 +52,11 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 200, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 200, + save_initial_solution = true, + save_final_solution = true +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) @@ -57,6 +64,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, sav # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced.jl b/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced.jl index 5851530e23..b518ec57ab 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,8 +14,10 @@ equations = ShallowWaterEquations1D(gravity_constant = 9.81, H0 = 3.25) # Works as intended for TreeMesh1D with `initial_refinement_level=3`. If the mesh # refinement level is changed the initial condition below may need changed as well to # ensure that the discontinuities lie on an element interface. -function initial_condition_discontinuous_well_balancedness(x, t, - equations::ShallowWaterEquations1D) +function initial_condition_discontinuous_well_balancedness( + x, t, + equations::ShallowWaterEquations1D + ) # Set the background values H = equations.H0 v = 0.0 @@ -37,17 +38,21 @@ initial_condition = initial_condition_discontinuous_well_balancedness volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal) -solver = DGSEM(polydeg = 4, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000 +) # Create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -64,24 +69,32 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (lake_at_rest_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (lake_at_rest_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced_nonperiodic.jl b/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced_nonperiodic.jl index 9ed02c0e37..f198f073ca 100644 --- a/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced_nonperiodic.jl +++ b/examples/tree_1d_dgsem/elixir_shallowwater_well_balanced_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -26,24 +25,32 @@ boundary_condition = BoundaryConditionDirichlet(initial_condition) # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 4, - surface_flux = (flux_hll, - flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, + surface_flux = ( + flux_hll, + flux_nonconservative_fjordholm_etal, + ), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = 0.0 coordinates_max = sqrt(2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000, + periodicity = false +) # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition +) ############################################################################### # ODE solvers, callbacks etc. @@ -54,25 +61,33 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (lake_at_rest_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = (lake_at_rest_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_traffic_flow_lwr_convergence.jl b/examples/tree_1d_dgsem/elixir_traffic_flow_lwr_convergence.jl index 59258018f8..946c12afec 100644 --- a/examples/tree_1d_dgsem/elixir_traffic_flow_lwr_convergence.jl +++ b/examples/tree_1d_dgsem/elixir_traffic_flow_lwr_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,16 +12,20 @@ coordinates_min = 0.0 # minimum coordinate coordinates_max = 2.0 # maximum coordinate # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) ############################################################################### # Specify non-periodic boundary conditions initial_condition = initial_condition_convergence_test -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -39,16 +42,20 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(), - dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(), + dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_dgsem/elixir_traffic_flow_lwr_trafficjam.jl b/examples/tree_1d_dgsem/elixir_traffic_flow_lwr_trafficjam.jl index d3a17b513f..ef96be12c7 100644 --- a/examples/tree_1d_dgsem/elixir_traffic_flow_lwr_trafficjam.jl +++ b/examples/tree_1d_dgsem/elixir_traffic_flow_lwr_trafficjam.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,12 +11,14 @@ solver = DGSEM(polydeg = 0, surface_flux = flux_lax_friedrichs) coordinates_min = -1.0 # minimum coordinate coordinates_max = 1.0 # maximum coordinate -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 9, - n_cells_max = 30_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 9, + n_cells_max = 30_000, + periodicity = false +) -# Example taken from http://www.clawpack.org/riemann_book/html/Traffic_flow.html#Example:-Traffic-jam +# Example taken from http://www.clawpack.org/riemann_book/html/Traffic_flow.html#Example:-Traffic-jam # Discontinuous initial condition (Riemann Problem) leading to a shock that moves to the left. # The shock corresponds to the traffic congestion. function initial_condition_traffic_jam(x, t, equation::TrafficFlowLWREquations1D) @@ -34,22 +35,28 @@ function outflow(x, t, equations::TrafficFlowLWREquations1D) end boundary_condition_outflow = BoundaryConditionDirichlet(outflow) -function boundary_condition_inflow(u_inner, orientation, normal_direction, x, t, - surface_flux_function, - equations::TrafficFlowLWREquations1D) +function boundary_condition_inflow( + u_inner, orientation, normal_direction, x, t, + surface_flux_function, + equations::TrafficFlowLWREquations1D + ) # Calculate the boundary flux entirely from the internal solution state flux = Trixi.flux(u_inner, orientation, equations) return flux end -boundary_conditions = (x_neg = boundary_condition_outflow, - x_pos = boundary_condition_inflow) +boundary_conditions = ( + x_neg = boundary_condition_outflow, + x_pos = boundary_condition_inflow, +) initial_condition = initial_condition_traffic_jam -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -66,17 +73,21 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -# Note: Be careful when increasing the polynomial degree and switching from first order finite volume +# Note: Be careful when increasing the polynomial degree and switching from first order finite volume # to some actual DG method - in that case, you should also exchange the ODE solver. -sol = solve(ode, Euler(), - dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, Euler(), + dt = 42, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_fdsbp/elixir_advection_upwind.jl b/examples/tree_1d_fdsbp/elixir_advection_upwind.jl index 389a8566c9..cceb922a11 100644 --- a/examples/tree_1d_fdsbp/elixir_advection_upwind.jl +++ b/examples/tree_1d_fdsbp/elixir_advection_upwind.jl @@ -13,22 +13,28 @@ function initial_condition_sin(x, t, equation::LinearScalarAdvectionEquation1D) return SVector(sinpi(x[1] - equations.advection_velocity[1] * t)) end -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 16) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 16 +) flux_splitting = splitting_lax_friedrichs -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sin, solver) @@ -45,12 +51,16 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_fdsbp/elixir_advection_upwind_periodic.jl b/examples/tree_1d_fdsbp/elixir_advection_upwind_periodic.jl index 2d7c13d7e5..f9e21f12bc 100644 --- a/examples/tree_1d_fdsbp/elixir_advection_upwind_periodic.jl +++ b/examples/tree_1d_fdsbp/elixir_advection_upwind_periodic.jl @@ -13,21 +13,27 @@ function initial_condition_sin(x, t, equation::LinearScalarAdvectionEquation1D) return SVector(sinpi(x[1] - equations.advection_velocity[1] * t)) end -D_upw = upwind_operators(SummationByPartsOperators.periodic_derivative_operator, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 64) +D_upw = upwind_operators( + SummationByPartsOperators.periodic_derivative_operator, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 64 +) flux_splitting = splitting_lax_friedrichs -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 0, - n_cells_max = 10_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 0, + n_cells_max = 10_000, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_sin, solver) @@ -44,12 +50,16 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_fdsbp/elixir_burgers_basic.jl b/examples/tree_1d_fdsbp/elixir_burgers_basic.jl index c58fc497e1..6bbd101b43 100644 --- a/examples/tree_1d_fdsbp/elixir_burgers_basic.jl +++ b/examples/tree_1d_fdsbp/elixir_burgers_basic.jl @@ -11,24 +11,32 @@ equations = InviscidBurgersEquation1D() initial_condition = initial_condition_convergence_test -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 32) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 32 +) flux_splitting = splitting_lax_friedrichs -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = 0.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -39,24 +47,34 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 200 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(); abstol = 1.0e-9, reltol = 1.0e-9, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, SSPRK43(); abstol = 1.0e-9, reltol = 1.0e-9, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl b/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl index eeaae7a784..99f2b9baf5 100644 --- a/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl +++ b/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl @@ -14,24 +14,32 @@ function initial_condition_linear_stability(x, t, equation::InviscidBurgersEquat 2 + sinpi(k * (x[1] - 0.7)) |> SVector end -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 16) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 16 +) flux_splitting = splitting_lax_friedrichs -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_linear_stability, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_linear_stability, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -42,18 +50,26 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_1d_fdsbp/elixir_euler_convergence.jl b/examples/tree_1d_fdsbp/elixir_euler_convergence.jl index 7b6bfee946..563465d45a 100644 --- a/examples/tree_1d_fdsbp/elixir_euler_convergence.jl +++ b/examples/tree_1d_fdsbp/elixir_euler_convergence.jl @@ -11,24 +11,32 @@ equations = CompressibleEulerEquations1D(1.4) initial_condition = initial_condition_convergence_test -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 32) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 32 +) flux_splitting = splitting_steger_warming -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = 0.0 coordinates_max = 2.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 1, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 1, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -39,24 +47,34 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl b/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl index a28cd01120..20418aa532 100644 --- a/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl +++ b/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl @@ -10,21 +10,27 @@ equations = CompressibleEulerEquations1D(1.4) initial_condition = initial_condition_density_wave -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 16) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 16 +) flux_splitting = splitting_coirier_vanleer -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = -1.0 coordinates_max = 1.0 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -41,18 +47,24 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_acoustics_convergence.jl b/examples/tree_2d_dgsem/elixir_acoustics_convergence.jl index 615da95187..d4250dbeca 100644 --- a/examples/tree_2d_dgsem/elixir_acoustics_convergence.jl +++ b/examples/tree_2d_dgsem/elixir_acoustics_convergence.jl @@ -4,8 +4,10 @@ using Trixi ############################################################################### # semidiscretization of the acoustic perturbation equations -equations = AcousticPerturbationEquations2D(v_mean_global = (0.5, 0.3), c_mean_global = 2.0, - rho_mean_global = 0.9) +equations = AcousticPerturbationEquations2D( + v_mean_global = (0.5, 0.3), c_mean_global = 2.0, + rho_mean_global = 0.9 +) initial_condition = initial_condition_convergence_test @@ -16,13 +18,17 @@ coordinates_min = (0.0, 0.0) # minimum coordinates (min(x), min(y)) coordinates_max = (2.0, 2.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -39,23 +45,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 0.5) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_acoustics_gauss.jl b/examples/tree_2d_dgsem/elixir_acoustics_gauss.jl index b3fe55dcce..f905714455 100644 --- a/examples/tree_2d_dgsem/elixir_acoustics_gauss.jl +++ b/examples/tree_2d_dgsem/elixir_acoustics_gauss.jl @@ -16,9 +16,11 @@ coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # A semidiscretization collects data structures and functions for the spatial discretization semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_gauss, solver) @@ -38,23 +40,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 0.5) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_acoustics_gauss_wall.jl b/examples/tree_2d_dgsem/elixir_acoustics_gauss_wall.jl index 918c0831fc..503cbe30f4 100644 --- a/examples/tree_2d_dgsem/elixir_acoustics_gauss_wall.jl +++ b/examples/tree_2d_dgsem/elixir_acoustics_gauss_wall.jl @@ -4,8 +4,10 @@ using Trixi ############################################################################### # semidiscretization of the acoustic perturbation equations -equations = AcousticPerturbationEquations2D(v_mean_global = (0.5, 0.0), c_mean_global = 1.0, - rho_mean_global = 1.0) +equations = AcousticPerturbationEquations2D( + v_mean_global = (0.5, 0.0), c_mean_global = 1.0, + rho_mean_global = 1.0 +) # Create DG solver with polynomial degree = 5 and (local) Lax-Friedrichs/Rusanov flux as surface flux solver = DGSEM(polydeg = 5, surface_flux = flux_lax_friedrichs) @@ -14,10 +16,12 @@ coordinates_min = (-100.0, 0.0) # minimum coordinates (min(x), min(y)) coordinates_max = (100.0, 200.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 100_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 100_000, + periodicity = false +) """ initial_condition_gauss_wall(x, t, equations::AcousticPerturbationEquations2D) @@ -37,8 +41,10 @@ end initial_condition = initial_condition_gauss_wall # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition_wall) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition_wall +) ############################################################################### # ODE solvers, callbacks etc. @@ -61,16 +67,20 @@ save_solution = SaveSolutionCallback(interval = 100, solution_variables = cons2s stepsize_callback = StepsizeCallback(cfl = 0.7) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks) +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_acoustics_gaussian_source.jl b/examples/tree_2d_dgsem/elixir_acoustics_gaussian_source.jl index 71d4f1a9f6..5d3e821fb6 100644 --- a/examples/tree_2d_dgsem/elixir_acoustics_gaussian_source.jl +++ b/examples/tree_2d_dgsem/elixir_acoustics_gaussian_source.jl @@ -22,9 +22,11 @@ end ############################################################################### # semidiscretization of the acoustic perturbation equations -equations = AcousticPerturbationEquations2D(v_mean_global = (-0.5, 0.25), - c_mean_global = 1.0, - rho_mean_global = 1.0) +equations = AcousticPerturbationEquations2D( + v_mean_global = (-0.5, 0.25), + c_mean_global = 1.0, + rho_mean_global = 1.0 +) initial_condition = initial_condition_constant @@ -35,13 +37,17 @@ coordinates_min = (-3.0, -3.0) # minimum coordinates (min(x), min(y)) coordinates_max = (3.0, 3.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_gauss) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_gauss +) ############################################################################### # ODE solvers, callbacks etc. @@ -58,8 +64,10 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The TimeSeriesCallback records the solution at the given points over time time_series = TimeSeriesCallback(semi, [(0.0, 0.0), (-1.0, 0.5)]) @@ -68,16 +76,20 @@ time_series = TimeSeriesCallback(semi, [(0.0, 0.0), (-1.0, 0.5)]) stepsize_callback = StepsizeCallback(cfl = 0.5) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, time_series, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, time_series, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_acoustics_monopole.jl b/examples/tree_2d_dgsem/elixir_acoustics_monopole.jl index d726577511..47f440e21a 100644 --- a/examples/tree_2d_dgsem/elixir_acoustics_monopole.jl +++ b/examples/tree_2d_dgsem/elixir_acoustics_monopole.jl @@ -4,8 +4,10 @@ using Trixi ############################################################################### # semidiscretization of the acoustic perturbation equations -equations = AcousticPerturbationEquations2D(v_mean_global = (0.0, 0.0), c_mean_global = 0.0, - rho_mean_global = 0.0) +equations = AcousticPerturbationEquations2D( + v_mean_global = (0.0, 0.0), c_mean_global = 0.0, + rho_mean_global = 0.0 +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -45,9 +47,11 @@ Boundary condition for a monopole in a boundary layer at the -y boundary, i.e. ` This will return an error for any other direction. This boundary condition is used in combination with [`initial_condition_monopole`](@ref). """ -function boundary_condition_monopole(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::AcousticPerturbationEquations2D) +function boundary_condition_monopole( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::AcousticPerturbationEquations2D + ) if direction != 3 error("expected direction = 3, got $direction instead") end @@ -60,14 +64,18 @@ function boundary_condition_monopole(u_inner, orientation, direction, x, t, v1_prime = 0.0 v2_prime = p_prime = sin(2 * pi * t) - prim_boundary = SVector(v1_prime, v2_prime, p_prime, u_inner[4], u_inner[5], - u_inner[6], u_inner[7]) + prim_boundary = SVector( + v1_prime, v2_prime, p_prime, u_inner[4], u_inner[5], + u_inner[6], u_inner[7] + ) u_boundary = prim2cons(prim_boundary, equations) else # Wall - u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3], u_inner[4], u_inner[5], - u_inner[6], - u_inner[7]) + u_boundary = SVector( + u_inner[1], -u_inner[2], u_inner[3], u_inner[4], u_inner[5], + u_inner[6], + u_inner[7] + ) end # Calculate boundary flux @@ -83,9 +91,11 @@ end Boundary condition that uses a boundary state where the state variables are zero and the mean variables are the same as in `u_inner`. """ -function boundary_condition_zero(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::AcousticPerturbationEquations2D) +function boundary_condition_zero( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::AcousticPerturbationEquations2D + ) value = zero(eltype(u_inner)) u_boundary = SVector(value, value, value, cons2mean(u_inner, equations)...) @@ -99,20 +109,26 @@ function boundary_condition_zero(u_inner, orientation, direction, x, t, return flux end -boundary_conditions = (x_neg = boundary_condition_zero, - x_pos = boundary_condition_zero, - y_neg = boundary_condition_monopole, - y_pos = boundary_condition_zero) +boundary_conditions = ( + x_neg = boundary_condition_zero, + x_pos = boundary_condition_zero, + y_neg = boundary_condition_monopole, + y_pos = boundary_condition_zero, +) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 100_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 100_000, + periodicity = false +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -135,16 +151,20 @@ save_solution = SaveSolutionCallback(interval = 100, solution_variables = cons2p stepsize_callback = StepsizeCallback(cfl = 0.8) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks) +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_advection_amr.jl b/examples/tree_2d_dgsem/elixir_advection_amr.jl index c3f971d2ff..20d3c418af 100644 --- a/examples/tree_2d_dgsem/elixir_advection_amr.jl +++ b/examples/tree_2d_dgsem/elixir_advection_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,9 +12,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0) coordinates_max = (5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -28,36 +29,48 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_advection_amr_coarsen_twice.jl b/examples/tree_2d_dgsem/elixir_advection_amr_coarsen_twice.jl index aa042e7500..c0c7329321 100644 --- a/examples/tree_2d_dgsem/elixir_advection_amr_coarsen_twice.jl +++ b/examples/tree_2d_dgsem/elixir_advection_amr_coarsen_twice.jl @@ -8,30 +8,32 @@ using Trixi # if multiple test cases using the same module name are run in the same session. module TrixiExtensionCoarsen -using Trixi + using Trixi -struct IndicatorAlwaysCoarsen{Cache <: NamedTuple} <: Trixi.AbstractIndicator - cache::Cache -end + struct IndicatorAlwaysCoarsen{Cache <: NamedTuple} <: Trixi.AbstractIndicator + cache::Cache + end -function IndicatorAlwaysCoarsen(semi) - basis = semi.solver.basis - alpha = Vector{real(basis)}() - cache = (; semi.mesh, alpha) + function IndicatorAlwaysCoarsen(semi) + basis = semi.solver.basis + alpha = Vector{real(basis)}() + cache = (; semi.mesh, alpha) - return IndicatorAlwaysCoarsen{typeof(cache)}(cache) -end + return IndicatorAlwaysCoarsen{typeof(cache)}(cache) + end -function (indicator::IndicatorAlwaysCoarsen)(u::AbstractArray{<:Any, 4}, - mesh, equations, dg, cache; - t, kwargs...) - alpha = indicator.cache.alpha - resize!(alpha, nelements(dg, cache)) + function (indicator::IndicatorAlwaysCoarsen)( + u::AbstractArray{<:Any, 4}, + mesh, equations, dg, cache; + t, kwargs... + ) + alpha = indicator.cache.alpha + resize!(alpha, nelements(dg, cache)) - alpha .= -1.0 + alpha .= -1.0 - return alpha -end + return alpha + end end # module TrixiExtensionCoarsen @@ -49,9 +51,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0) coordinates_max = (5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -64,37 +68,49 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, - TrixiExtensionCoarsen.IndicatorAlwaysCoarsen(semi), - base_level = 2, max_level = 2, - med_threshold = 0.1, max_threshold = 0.6) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, + TrixiExtensionCoarsen.IndicatorAlwaysCoarsen(semi), + base_level = 2, max_level = 2, + med_threshold = 0.1, max_threshold = 0.6 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_advection_amr_nonperiodic.jl b/examples/tree_2d_dgsem/elixir_advection_amr_nonperiodic.jl index abb8a5035b..0cf5d4f65b 100644 --- a/examples/tree_2d_dgsem/elixir_advection_amr_nonperiodic.jl +++ b/examples/tree_2d_dgsem/elixir_advection_amr_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -18,13 +17,17 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0) coordinates_max = (5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -35,35 +38,47 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = stepsize_callback(ode), # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_advection_amr_refine_twice.jl b/examples/tree_2d_dgsem/elixir_advection_amr_refine_twice.jl index 7b44177520..7f734ab8de 100644 --- a/examples/tree_2d_dgsem/elixir_advection_amr_refine_twice.jl +++ b/examples/tree_2d_dgsem/elixir_advection_amr_refine_twice.jl @@ -8,30 +8,32 @@ using Trixi # if multiple test cases using the same module name are run in the same session. module TrixiExtensionRefine -using Trixi + using Trixi -struct IndicatorAlwaysRefine{Cache <: NamedTuple} <: Trixi.AbstractIndicator - cache::Cache -end + struct IndicatorAlwaysRefine{Cache <: NamedTuple} <: Trixi.AbstractIndicator + cache::Cache + end -function IndicatorAlwaysRefine(semi) - basis = semi.solver.basis - alpha = Vector{real(basis)}() - cache = (; semi.mesh, alpha) + function IndicatorAlwaysRefine(semi) + basis = semi.solver.basis + alpha = Vector{real(basis)}() + cache = (; semi.mesh, alpha) - return IndicatorAlwaysRefine{typeof(cache)}(cache) -end + return IndicatorAlwaysRefine{typeof(cache)}(cache) + end -function (indicator::IndicatorAlwaysRefine)(u::AbstractArray{<:Any, 4}, - mesh, equations, dg, cache; - t, kwargs...) - alpha = indicator.cache.alpha - resize!(alpha, nelements(dg, cache)) + function (indicator::IndicatorAlwaysRefine)( + u::AbstractArray{<:Any, 4}, + mesh, equations, dg, cache; + t, kwargs... + ) + alpha = indicator.cache.alpha + resize!(alpha, nelements(dg, cache)) - alpha .= 1.0 + alpha .= 1.0 - return alpha -end + return alpha + end end # module TrixiExtensionRefine @@ -49,9 +51,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0) coordinates_max = (5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -64,37 +68,49 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, - TrixiExtensionRefine.IndicatorAlwaysRefine(semi), - base_level = 4, max_level = 4, - med_threshold = 0.1, max_threshold = 0.6) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, + TrixiExtensionRefine.IndicatorAlwaysRefine(semi), + base_level = 4, max_level = 4, + med_threshold = 0.1, max_threshold = 0.6 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_advection_amr_solution_independent.jl b/examples/tree_2d_dgsem/elixir_advection_amr_solution_independent.jl index c7412660b0..ad266fd96f 100644 --- a/examples/tree_2d_dgsem/elixir_advection_amr_solution_independent.jl +++ b/examples/tree_2d_dgsem/elixir_advection_amr_solution_independent.jl @@ -1,75 +1,76 @@ - using OrdinaryDiffEq using Trixi # define new structs inside a module to allow re-evaluating the file module TrixiExtension -using Trixi + using Trixi -struct IndicatorSolutionIndependent{Cache <: NamedTuple} <: Trixi.AbstractIndicator - cache::Cache -end -function IndicatorSolutionIndependent(semi) - basis = semi.solver.basis - alpha = Vector{real(basis)}() - cache = (; semi.mesh, alpha) - return IndicatorSolutionIndependent{typeof(cache)}(cache) -end -function (indicator::IndicatorSolutionIndependent)(u::AbstractArray{<:Any, 4}, - mesh, equations, dg, cache; - t, kwargs...) - mesh = indicator.cache.mesh - alpha = indicator.cache.alpha - resize!(alpha, nelements(dg, cache)) - - #Predict the theoretical center. - advection_velocity = (0.2, -0.7) - center = t .* advection_velocity - - inner_distance = 1 - outer_distance = 1.85 - - #Iterate over all elements - for element in eachindex(alpha) - #Calculate periodic distance between cell and center. - cell_id = cache.elements.cell_ids[element] - coordinates = mesh.tree.coordinates[1:2, cell_id] - - #The geometric shape of the amr should be preserved when the base_level is increased. - #This is done by looking at the original coordinates of each cell. - cell_coordinates = original_coordinates(coordinates, 5 / 8) - cell_distance = periodic_distance_2d(cell_coordinates, center, 10) - if cell_distance < (inner_distance + outer_distance) / 2 - cell_coordinates = original_coordinates(coordinates, 5 / 16) + struct IndicatorSolutionIndependent{Cache <: NamedTuple} <: Trixi.AbstractIndicator + cache::Cache + end + function IndicatorSolutionIndependent(semi) + basis = semi.solver.basis + alpha = Vector{real(basis)}() + cache = (; semi.mesh, alpha) + return IndicatorSolutionIndependent{typeof(cache)}(cache) + end + function (indicator::IndicatorSolutionIndependent)( + u::AbstractArray{<:Any, 4}, + mesh, equations, dg, cache; + t, kwargs... + ) + mesh = indicator.cache.mesh + alpha = indicator.cache.alpha + resize!(alpha, nelements(dg, cache)) + + #Predict the theoretical center. + advection_velocity = (0.2, -0.7) + center = t .* advection_velocity + + inner_distance = 1 + outer_distance = 1.85 + + #Iterate over all elements + for element in eachindex(alpha) + #Calculate periodic distance between cell and center. + cell_id = cache.elements.cell_ids[element] + coordinates = mesh.tree.coordinates[1:2, cell_id] + + #The geometric shape of the amr should be preserved when the base_level is increased. + #This is done by looking at the original coordinates of each cell. + cell_coordinates = original_coordinates(coordinates, 5 / 8) cell_distance = periodic_distance_2d(cell_coordinates, center, 10) + if cell_distance < (inner_distance + outer_distance) / 2 + cell_coordinates = original_coordinates(coordinates, 5 / 16) + cell_distance = periodic_distance_2d(cell_coordinates, center, 10) + end + + #Set alpha according to cells position inside the circles. + target_level = (cell_distance < inner_distance) + (cell_distance < outer_distance) + alpha[element] = target_level / 2 end + return alpha + end - #Set alpha according to cells position inside the circles. - target_level = (cell_distance < inner_distance) + (cell_distance < outer_distance) - alpha[element] = target_level / 2 + # For periodic domains, distance between two points must take into account + # periodic extensions of the domain + function periodic_distance_2d(coordinates, center, domain_length) + dx = coordinates .- center + dx_shifted = abs.(dx .% domain_length) + dx_periodic = min.(dx_shifted, domain_length .- dx_shifted) + return sqrt(sum(dx_periodic .^ 2)) + end + + #This takes a cells coordinates and transforms them into the coordinates of a parent-cell it originally refined from. + #It does it so that the parent-cell has given cell_length. + function original_coordinates(coordinates, cell_length) + offset = coordinates .% cell_length + offset_sign = sign.(offset) + border = coordinates - offset + center = border + (offset_sign .* cell_length / 2) + return center end - return alpha -end - -# For periodic domains, distance between two points must take into account -# periodic extensions of the domain -function periodic_distance_2d(coordinates, center, domain_length) - dx = coordinates .- center - dx_shifted = abs.(dx .% domain_length) - dx_periodic = min.(dx_shifted, domain_length .- dx_shifted) - return sqrt(sum(dx_periodic .^ 2)) -end - -#This takes a cells coordinates and transforms them into the coordinates of a parent-cell it originally refined from. -#It does it so that the parent-cell has given cell_length. -function original_coordinates(coordinates, cell_length) - offset = coordinates .% cell_length - offset_sign = sign.(offset) - border = coordinates - offset - center = border + (offset_sign .* cell_length / 2) - return center -end end # module TrixiExtension @@ -86,9 +87,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0) coordinates_max = (5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -101,38 +104,50 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, - TrixiExtension.IndicatorSolutionIndependent(semi), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, + TrixiExtension.IndicatorSolutionIndependent(semi), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_advection_amr_visualization.jl b/examples/tree_2d_dgsem/elixir_advection_amr_visualization.jl index 7b67b81117..231e95d219 100644 --- a/examples/tree_2d_dgsem/elixir_advection_amr_visualization.jl +++ b/examples/tree_2d_dgsem/elixir_advection_amr_visualization.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi using Plots @@ -9,8 +8,10 @@ using Plots advection_velocity = (0.2, -0.7) equations = LinearScalarAdvectionEquation2D(advection_velocity) -function initial_condition_gauss_largedomain(x, t, - equation::LinearScalarAdvectionEquation2D) +function initial_condition_gauss_largedomain( + x, t, + equation::LinearScalarAdvectionEquation2D + ) # Store translated coordinate for easy use of exact solution domain_length = SVector(10, 10) x_trans = Trixi.x_trans_periodic_2d(x - equation.advection_velocity * t, domain_length) @@ -23,9 +24,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0) coordinates_max = (5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -38,40 +41,52 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) # Enable in-situ visualization with a new plot generated every 20 time steps # and additional plotting options passed as keyword arguments visualization = VisualizationCallback(interval = 20, clims = (0, 1)) -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 3, - med_level = 4, med_threshold = 0.1, - max_level = 5, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 3, + med_level = 4, med_threshold = 0.1, + max_level = 5, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, visualization, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, visualization, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_advection_basic.jl b/examples/tree_2d_dgsem/elixir_advection_basic.jl index 0ec0bc3629..e4faac4334 100644 --- a/examples/tree_2d_dgsem/elixir_advection_basic.jl +++ b/examples/tree_2d_dgsem/elixir_advection_basic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,13 +14,17 @@ coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -37,23 +40,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_advection_callbacks.jl b/examples/tree_2d_dgsem/elixir_advection_callbacks.jl index 708cd0aa3a..f4db5dc0cb 100644 --- a/examples/tree_2d_dgsem/elixir_advection_callbacks.jl +++ b/examples/tree_2d_dgsem/elixir_advection_callbacks.jl @@ -1,94 +1,95 @@ - using OrdinaryDiffEq using Trixi # define new structs inside a module to allow re-evaluating the file module TrixiExtensionExample -using Trixi -using OrdinaryDiffEq: DiscreteCallback, u_modified! - -# This is an example implementation for a simple stage callback (i.e., a callable -# that is executed after each Runge-Kutta *stage*), which records some values -# each time it is called. Its sole purpose here is to showcase how to implement -# a stage callback for Trixi.jl. -struct ExampleStageCallback - times::Vector{Float64} - min_values::Vector{Float64} - max_values::Vector{Float64} - - # You can optionally define an inner constructor like the one below to set up - # some required stuff. You can also create outer constructors (not demonstrated - # here) for further customization options. - function ExampleStageCallback() - new(Float64[], Float64[], Float64[]) + using Trixi + using OrdinaryDiffEq: DiscreteCallback, u_modified! + + # This is an example implementation for a simple stage callback (i.e., a callable + # that is executed after each Runge-Kutta *stage*), which records some values + # each time it is called. Its sole purpose here is to showcase how to implement + # a stage callback for Trixi.jl. + struct ExampleStageCallback + times::Vector{Float64} + min_values::Vector{Float64} + max_values::Vector{Float64} + + # You can optionally define an inner constructor like the one below to set up + # some required stuff. You can also create outer constructors (not demonstrated + # here) for further customization options. + function ExampleStageCallback() + new(Float64[], Float64[], Float64[]) + end + end + + # This method is called when the `ExampleStageCallback` is used as `stage_limiter!` + # which gets called after every RK stage. There is no specific initialization + # method for such `stage_limiter!`s in OrdinaryDiffEq.jl. + function (example_stage_callback::ExampleStageCallback)(u_ode, _, semi, t) + min_val, max_val = extrema(u_ode) + push!(example_stage_callback.times, t) + push!(example_stage_callback.min_values, min_val) + push!(example_stage_callback.max_values, max_val) + + return nothing + end + + # This is an example implementation for a simple step callback (i.e., a callable + # that is potentially executed after each Runge-Kutta *step*), which records + # some values each time it is called. Its sole purpose here is to showcase + # how to implement a step callback for Trixi.jl. + struct ExampleStepCallback + message::String + times::Vector{Float64} + min_values::Vector{Float64} + max_values::Vector{Float64} + + # You can optionally define an inner constructor like the one below to set up + # some required stuff. You can also create outer constructors (not demonstrated + # here) for further customization options. + function ExampleStepCallback(message::String) + new(message, Float64[], Float64[], Float64[]) + end end -end - -# This method is called when the `ExampleStageCallback` is used as `stage_limiter!` -# which gets called after every RK stage. There is no specific initialization -# method for such `stage_limiter!`s in OrdinaryDiffEq.jl. -function (example_stage_callback::ExampleStageCallback)(u_ode, _, semi, t) - min_val, max_val = extrema(u_ode) - push!(example_stage_callback.times, t) - push!(example_stage_callback.min_values, min_val) - push!(example_stage_callback.max_values, max_val) - - return nothing -end - -# This is an example implementation for a simple step callback (i.e., a callable -# that is potentially executed after each Runge-Kutta *step*), which records -# some values each time it is called. Its sole purpose here is to showcase -# how to implement a step callback for Trixi.jl. -struct ExampleStepCallback - message::String - times::Vector{Float64} - min_values::Vector{Float64} - max_values::Vector{Float64} - - # You can optionally define an inner constructor like the one below to set up - # some required stuff. You can also create outer constructors (not demonstrated - # here) for further customization options. - function ExampleStepCallback(message::String) - new(message, Float64[], Float64[], Float64[]) + + # This method is called when the `ExampleStepCallback` is used as callback + # which gets called after RK steps. + function (example_callback::ExampleStepCallback)(integrator) + u_ode = integrator.u + t = integrator.t + # You can also access semi = integrator.p + + min_val, max_val = extrema(u_ode) + push!(example_callback.times, t) + push!(example_callback.min_values, min_val) + push!(example_callback.max_values, max_val) + + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) + return nothing + end + + # This method is used to wrap an `ExampleStepCallback` inside a `DiscreteCallback` + # which gets called after every RK step. You can pass an additional initialization + # method and a separate condition specifying whether the callback shall be called. + function ExampleStepCallback(; message::String) + # Call the `ExampleStepCallback` after every RK step. + condition = (u_ode, t, integrator) -> true + + # You can optionally pass an initialization method. There, you can access the + # `ExampleStepCallback` as `cb.affect!`. + initialize = (cb, u_ode, t, integrator) -> println(cb.affect!.message) + + example_callback = ExampleStepCallback(message) + + DiscreteCallback( + condition, example_callback, + save_positions = (false, false), + initialize = initialize + ) end -end - -# This method is called when the `ExampleStepCallback` is used as callback -# which gets called after RK steps. -function (example_callback::ExampleStepCallback)(integrator) - u_ode = integrator.u - t = integrator.t - # You can also access semi = integrator.p - - min_val, max_val = extrema(u_ode) - push!(example_callback.times, t) - push!(example_callback.min_values, min_val) - push!(example_callback.max_values, max_val) - - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) - return nothing -end - -# This method is used to wrap an `ExampleStepCallback` inside a `DiscreteCallback` -# which gets called after every RK step. You can pass an additional initialization -# method and a separate condition specifying whether the callback shall be called. -function ExampleStepCallback(; message::String) - # Call the `ExampleStepCallback` after every RK step. - condition = (u_ode, t, integrator) -> true - - # You can optionally pass an initialization method. There, you can access the - # `ExampleStepCallback` as `cb.affect!`. - initialize = (cb, u_ode, t, integrator) -> println(cb.affect!.message) - - example_callback = ExampleStepCallback(message) - - DiscreteCallback(condition, example_callback, - save_positions = (false, false), - initialize = initialize) -end end # module TrixiExtensionExample @@ -105,9 +106,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -120,25 +123,31 @@ ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2cons) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2cons +) example_callback = TrixiExtensionExample.ExampleStepCallback(message = "안녕하세요?") stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - example_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + example_callback, + stepsize_callback +) # In OrdinaryDiffEq.jl, the `step_limiter!` is called after every Runge-Kutta step # but before possible RHS evaluations of the new value occur. Hence, it's possible @@ -153,10 +162,12 @@ example_stage_callback! = TrixiExtensionExample.ExampleStageCallback() ############################################################################### # run the simulation -sol = solve(ode, - CarpenterKennedy2N54(example_stage_callback!, williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, + CarpenterKennedy2N54(example_stage_callback!, williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary # Check whether we recorded the same values. diff --git a/examples/tree_2d_dgsem/elixir_advection_diffusion.jl b/examples/tree_2d_dgsem/elixir_advection_diffusion.jl index 1f765ff356..e2523e22ad 100644 --- a/examples/tree_2d_dgsem/elixir_advection_diffusion.jl +++ b/examples/tree_2d_dgsem/elixir_advection_diffusion.jl @@ -16,14 +16,18 @@ coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - periodicity = true, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + periodicity = true, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # Define initial condition -function initial_condition_diffusive_convergence_test(x, t, - equation::LinearScalarAdvectionEquation2D) +function initial_condition_diffusive_convergence_test( + x, t, + equation::LinearScalarAdvectionEquation2D + ) # Store translated coordinate for easy use of exact solution x_trans = x - equation.advection_velocity * t @@ -43,11 +47,15 @@ boundary_conditions = boundary_condition_periodic boundary_conditions_parabolic = boundary_condition_periodic # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -76,8 +84,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks alg = RDPK3SpFSAL49() time_int_tol = 1.0e-11 -sol = solve(ode, alg; abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, alg; abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_advection_diffusion_nonperiodic.jl b/examples/tree_2d_dgsem/elixir_advection_diffusion_nonperiodic.jl index 8da542b0b5..d4b95d3f8f 100644 --- a/examples/tree_2d_dgsem/elixir_advection_diffusion_nonperiodic.jl +++ b/examples/tree_2d_dgsem/elixir_advection_diffusion_nonperiodic.jl @@ -16,10 +16,12 @@ coordinates_min = (-1.0, -0.5) # minimum coordinates (min(x), min(y)) coordinates_max = (0.0, 0.5) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - periodicity = false, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + periodicity = false, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # Example setup taken from # - Truman Ellis, Jesse Chan, and Leszek Demkowicz (2016). @@ -40,19 +42,25 @@ function initial_condition_eriksson_johnson(x, t, equations) end initial_condition = initial_condition_eriksson_johnson -boundary_conditions = (; x_neg = BoundaryConditionDirichlet(initial_condition), - y_neg = BoundaryConditionDirichlet(initial_condition), - y_pos = BoundaryConditionDirichlet(initial_condition), - x_pos = boundary_condition_do_nothing) +boundary_conditions = (; + x_neg = BoundaryConditionDirichlet(initial_condition), + y_neg = BoundaryConditionDirichlet(initial_condition), + y_pos = BoundaryConditionDirichlet(initial_condition), + x_pos = boundary_condition_do_nothing, +) boundary_conditions_parabolic = BoundaryConditionDirichlet(initial_condition) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -80,8 +88,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks time_int_tol = 1.0e-11 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_advection_extended.jl b/examples/tree_2d_dgsem/elixir_advection_extended.jl index 50a509c072..150e7ce4a9 100644 --- a/examples/tree_2d_dgsem/elixir_advection_extended.jl +++ b/examples/tree_2d_dgsem/elixir_advection_extended.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -24,14 +23,18 @@ coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000, # set maximum capacity of tree data structure - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000, # set maximum capacity of tree data structure + periodicity = true +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -46,40 +49,50 @@ summary_callback = SummaryCallback() # The AnalysisCallback allows to analyse the solution in regular intervals and prints the results analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) # The AliveCallback prints short status information in regular intervals alive_callback = AliveCallback(analysis_interval = analysis_interval) # The SaveRestartCallback allows to save a file from which a Trixi.jl simulation can be restarted -save_restart = SaveRestartCallback(interval = 40, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 40, + save_final_restart = true +) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks alg = CarpenterKennedy2N54(williamson_condition = false) -sol = solve(ode, alg, - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - callback = callbacks; - ode_default_options()...); # default options because an adaptive time stepping method is used in test_mpi_tree.jl +sol = solve( + ode, alg, + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + callback = callbacks; + ode_default_options()... +); # default options because an adaptive time stepping method is used in test_mpi_tree.jl # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_advection_mortar.jl b/examples/tree_2d_dgsem/elixir_advection_mortar.jl index 645c55ba43..dfc4a1f8de 100644 --- a/examples/tree_2d_dgsem/elixir_advection_mortar.jl +++ b/examples/tree_2d_dgsem/elixir_advection_mortar.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,12 +12,18 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -refinement_patches = ((type = "box", coordinates_min = (0.0, -1.0), - coordinates_max = (1.0, 1.0)),) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - refinement_patches = refinement_patches, - n_cells_max = 10_000) +refinement_patches = ( + ( + type = "box", coordinates_min = (0.0, -1.0), + coordinates_max = (1.0, 1.0), + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + refinement_patches = refinement_patches, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -31,27 +36,35 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_advection_restart.jl b/examples/tree_2d_dgsem/elixir_advection_restart.jl index c1a0cb8a8c..b68611198e 100644 --- a/examples/tree_2d_dgsem/elixir_advection_restart.jl +++ b/examples/tree_2d_dgsem/elixir_advection_restart.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -6,8 +5,10 @@ using Trixi # Define time integration algorithm alg = CarpenterKennedy2N54(williamson_condition = false) # Create a restart file -trixi_include(@__MODULE__, joinpath(@__DIR__, "elixir_advection_extended.jl"), alg = alg, - tspan = (0.0, 10.0)) +trixi_include( + @__MODULE__, joinpath(@__DIR__, "elixir_advection_extended.jl"), alg = alg, + tspan = (0.0, 10.0) +) ############################################################################### # adapt the parameters that have changed compared to "elixir_advection_extended.jl" @@ -27,10 +28,12 @@ ode = semidiscretize(semi, tspan, restart_filename); # Do not overwrite the initial snapshot written by elixir_advection_extended.jl. save_solution.condition.save_initial_solution = false -integrator = init(ode, alg, - dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - callback = callbacks; - ode_default_options()...); # default options because an adaptive time stepping method is used in test_mpi_tree.jl +integrator = init( + ode, alg, + dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback + callback = callbacks; + ode_default_options()... +); # default options because an adaptive time stepping method is used in test_mpi_tree.jl # Load saved context for adaptive time integrator if integrator.opts.adaptive diff --git a/examples/tree_2d_dgsem/elixir_advection_restart_amr.jl b/examples/tree_2d_dgsem/elixir_advection_restart_amr.jl index 96b62f4f4c..5f1c80a3a8 100644 --- a/examples/tree_2d_dgsem/elixir_advection_restart_amr.jl +++ b/examples/tree_2d_dgsem/elixir_advection_restart_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -6,8 +5,10 @@ using Trixi # Define time integration algorithm alg = CarpenterKennedy2N54(williamson_condition = false) # Create a restart file -trixi_include(@__MODULE__, joinpath(@__DIR__, "elixir_advection_extended.jl"), alg = alg, - tspan = (0.0, 3.0)) +trixi_include( + @__MODULE__, joinpath(@__DIR__, "elixir_advection_extended.jl"), alg = alg, + tspan = (0.0, 3.0) +) ############################################################################### # adapt the parameters that have changed compared to "elixir_advection_extended.jl" @@ -28,19 +29,25 @@ ode = semidiscretize(semi, tspan, restart_filename); save_solution.condition.save_initial_solution = false # Add AMR callback -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 3, - med_level = 4, med_threshold = 0.8, - max_level = 5, max_threshold = 1.2) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 3, + med_level = 4, med_threshold = 0.8, + max_level = 5, max_threshold = 1.2 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) callbacks_ext = CallbackSet(amr_callback, callbacks.discrete_callbacks...) -integrator = init(ode, alg, - dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks_ext, maxiters = 100_000) +integrator = init( + ode, alg, + dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks_ext, maxiters = 100_000 +) # Load saved context for adaptive time integrator if integrator.opts.adaptive diff --git a/examples/tree_2d_dgsem/elixir_advection_timeintegration.jl b/examples/tree_2d_dgsem/elixir_advection_timeintegration.jl index 06982bb9a2..f94e4380d6 100644 --- a/examples/tree_2d_dgsem/elixir_advection_timeintegration.jl +++ b/examples/tree_2d_dgsem/elixir_advection_timeintegration.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,9 +12,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0) coordinates_max = (5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -28,38 +29,50 @@ ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2cons) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2cons +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation # sol = solve(ode, CarpenterKennedy2N54(williamson_condition=false), ode_algorithm = Trixi.CarpenterKennedy2N54() -sol = Trixi.solve(ode, ode_algorithm, - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = Trixi.solve( + ode, ode_algorithm, + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_astro_jet_amr.jl b/examples/tree_2d_dgsem/elixir_euler_astro_jet_amr.jl index 1393f4a6cc..fb5eb16aad 100644 --- a/examples/tree_2d_dgsem/elixir_euler_astro_jet_amr.jl +++ b/examples/tree_2d_dgsem/elixir_euler_astro_jet_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -7,7 +6,7 @@ using Trixi gamma = 5 / 3 equations = CompressibleEulerEquations2D(gamma) -# Initial condition adopted from +# Initial condition adopted from # - Yong Liu, Jianfang Lu, and Chi-Wang Shu # An oscillation free discontinuous Galerkin method for hyperbolic systems # https://tinyurl.com/c76fjtx4 @@ -30,10 +29,12 @@ function initial_condition_astro_jet(x, t, equations::CompressibleEulerEquations end initial_condition = initial_condition_astro_jet -boundary_conditions = (x_neg = BoundaryConditionDirichlet(initial_condition_astro_jet), - x_pos = BoundaryConditionDirichlet(initial_condition_astro_jet), - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) +boundary_conditions = ( + x_neg = BoundaryConditionDirichlet(initial_condition_astro_jet), + x_pos = BoundaryConditionDirichlet(initial_condition_astro_jet), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) surface_flux = flux_lax_friedrichs # HLLC needs more shock capturing (alpha_max) volume_flux = flux_ranocha # works with Chandrashekar flux as well @@ -41,26 +42,34 @@ polydeg = 3 basis = LobattoLegendreBasis(polydeg) # shock capturing necessary for this tough example -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.3, - alpha_min = 0.0001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.3, + alpha_min = 0.0001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-0.5, -0.5) coordinates_max = (0.5, 0.5) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - periodicity = (false, true), - n_cells_max = 100_000) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + periodicity = (false, true), + n_cells_max = 100_000 +) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -75,39 +84,53 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 5000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 1.0, - alpha_min = 0.0001, - alpha_smooth = false, - variable = Trixi.density) - -amr_controller = ControllerThreeLevelCombined(semi, amr_indicator, indicator_sc, - base_level = 2, - med_level = 0, med_threshold = 0.0003, # med_level = current level - max_level = 8, max_threshold = 0.003, - max_threshold_secondary = indicator_sc.alpha_max) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - amr_callback, save_solution) +save_solution = SaveSolutionCallback( + interval = 5000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 1.0, + alpha_min = 0.0001, + alpha_smooth = false, + variable = Trixi.density +) + +amr_controller = ControllerThreeLevelCombined( + semi, amr_indicator, indicator_sc, + base_level = 2, + med_level = 0, med_threshold = 0.0003, # med_level = current level + max_level = 8, max_threshold = 0.003, + max_threshold_secondary = indicator_sc.alpha_max +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + amr_callback, save_solution +) # positivity limiter necessary for this tough example -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6), - variables = (Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (5.0e-6, 5.0e-6), + variables = (Trixi.density, pressure) +) ############################################################################### # run the simulation # use adaptive time stepping based on error estimates, time step roughly dt = 1e-7 -sol = solve(ode, SSPRK43(stage_limiter!); - ode_default_options()..., callback = callbacks); +sol = solve( + ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave.jl index ccd7b54086..a6c98cea6c 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -29,7 +28,7 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation rho = r > 0.5 ? 1.0 : 1.1691 v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi - p = r > 0.5 ? 1.0E-3 : 1.245 + p = r > 0.5 ? 1.0e-3 : 1.245 return prim2cons(SVector(rho, v1, v2, p), equations) end @@ -38,21 +37,27 @@ initial_condition = initial_condition_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -69,22 +74,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr.jl index d32c2e51b0..d027db8e66 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -29,7 +28,7 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation rho = r > 0.5 ? 1.0 : 1.1691 v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi - p = r > 0.5 ? 1.0E-3 : 1.245 + p = r > 0.5 ? 1.0e-3 : 1.245 return prim2cons(SVector(rho, v1, v2, p), equations) end @@ -38,21 +37,27 @@ initial_condition = initial_condition_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -69,35 +74,47 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 6, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 6, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_pure_fv.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_pure_fv.jl index a2392d05e5..ae21138995 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_pure_fv.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_pure_fv.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -29,7 +28,7 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation rho = r > 0.5 ? 1.0 : 1.1691 v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi - p = r > 0.5 ? 1.0E-3 : 1.245 + p = r > 0.5 ? 1.0e-3 : 1.245 return prim2cons(SVector(rho, v1, v2, p), equations) end @@ -42,9 +41,11 @@ solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -61,22 +62,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell_nonperiodic.jl b/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell_nonperiodic.jl index 00d3c69f2e..5feecaeb40 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell_nonperiodic.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blast_wave_sc_subcell_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -29,7 +28,7 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation rho = r > 0.5 ? 1.0 : 1.1691 v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi - p = r > 0.5 ? 1.0E-3 : 1.245 + p = r > 0.5 ? 1.0e-3 : 1.245 return prim2cons(SVector(rho, v1, v2, p), equations) end @@ -40,24 +39,36 @@ boundary_condition = BoundaryConditionDirichlet(initial_condition) surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -limiter_idp = SubcellLimiterIDP(equations, basis; - local_twosided_variables_cons = ["rho"], - local_onesided_variables_nonlinear = [(Trixi.entropy_math, - max)]) -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +limiter_idp = SubcellLimiterIDP( + equations, basis; + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [ + ( + Trixi.entropy_math, + max, + ), + ] +) +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition +) ############################################################################### # ODE solvers, callbacks etc. @@ -72,24 +83,30 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.3) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = false)) -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 - save_everystep = false, callback = callbacks); +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 + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_blob_amr.jl b/examples/tree_2d_dgsem/elixir_euler_blob_amr.jl index 2b3659017a..0070f8c903 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blob_amr.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blob_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -45,7 +44,7 @@ function initial_condition_blob(x, t, equations::CompressibleEulerEquations2D) slope = 2 # density blob dens = dens0 + - (Chi - 1) * 0.5 * (1 + (tanh(slope * (r + R)) - (tanh(slope * (r - R)) + 1))) + (Chi - 1) * 0.5 * (1 + (tanh(slope * (r + R)) - (tanh(slope * (r - R)) + 1))) # velocity blob is zero velx = velx0 - velx0 * 0.5 * (1 + (tanh(slope * (r + R)) - (tanh(slope * (r - R)) + 1))) return prim2cons(SVector(dens, velx, vely0, p0), equations) @@ -56,24 +55,30 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(4) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.4, - alpha_min = 0.0001, - alpha_smooth = true, - variable = pressure) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.4, + alpha_min = 0.0001, + alpha_smooth = true, + variable = pressure +) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-20.0, -20.0) coordinates_max = (20.0, 20.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -90,37 +95,49 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 1.0, - alpha_min = 0.0001, - alpha_smooth = false, - variable = Trixi.density) -amr_controller = ControllerThreeLevelCombined(semi, amr_indicator, indicator_sc, - base_level = 4, - med_level = 0, med_threshold = 0.0003, # med_level = current level - max_level = 7, max_threshold = 0.003, - max_threshold_secondary = indicator_sc.alpha_max) -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 1.0, + alpha_min = 0.0001, + alpha_smooth = false, + variable = Trixi.density +) +amr_controller = ControllerThreeLevelCombined( + semi, amr_indicator, indicator_sc, + base_level = 4, + med_level = 0, med_threshold = 0.0003, # med_level = current level + max_level = 7, max_threshold = 0.003, + max_threshold_secondary = indicator_sc.alpha_max +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.25) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_blob_mortar.jl b/examples/tree_2d_dgsem/elixir_euler_blob_mortar.jl index 8bd5db00c9..e159610264 100644 --- a/examples/tree_2d_dgsem/elixir_euler_blob_mortar.jl +++ b/examples/tree_2d_dgsem/elixir_euler_blob_mortar.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -45,7 +44,7 @@ function initial_condition_blob(x, t, equations::CompressibleEulerEquations2D) slope = 2 # density blob dens = dens0 + - (Chi - 1) * 0.5 * (1 + (tanh(slope * (r + R)) - (tanh(slope * (r - R)) + 1))) + (Chi - 1) * 0.5 * (1 + (tanh(slope * (r + R)) - (tanh(slope * (r - R)) + 1))) # velocity blob is zero velx = velx0 - velx0 * 0.5 * (1 + (tanh(slope * (r + R)) - (tanh(slope * (r - R)) + 1))) return prim2cons(SVector(dens, velx, vely0, p0), equations) @@ -56,28 +55,40 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.05, - alpha_min = 0.0001, - alpha_smooth = true, - variable = pressure) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.05, + alpha_min = 0.0001, + alpha_smooth = true, + variable = pressure +) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-32.0, -32.0) coordinates_max = (32.0, 32.0) -refinement_patches = ((type = "box", coordinates_min = (-40.0, -5.0), - coordinates_max = (40.0, 5.0)), - (type = "box", coordinates_min = (-40.0, -5.0), - coordinates_max = (40.0, 5.0))) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - refinement_patches = refinement_patches, - n_cells_max = 100_000) +refinement_patches = ( + ( + type = "box", coordinates_min = (-40.0, -5.0), + coordinates_max = (40.0, 5.0), + ), + ( + type = "box", coordinates_min = (-40.0, -5.0), + coordinates_max = (40.0, 5.0), + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + refinement_patches = refinement_patches, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -94,22 +105,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_colliding_flow.jl b/examples/tree_2d_dgsem/elixir_euler_colliding_flow.jl index 984ac3ff1f..98645edf11 100644 --- a/examples/tree_2d_dgsem/elixir_euler_colliding_flow.jl +++ b/examples/tree_2d_dgsem/elixir_euler_colliding_flow.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,8 +9,10 @@ equations = CompressibleEulerEquations2D(gamma) # This is a hand made colliding flow setup without reference. Features Mach=70 inflow from both # sides, with relative low temperature, such that pressure keeps relatively small # Computed with gamma close to 1, to simulate isothermal gas -function initial_condition_colliding_flow_astro(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_colliding_flow_astro( + x, t, + equations::CompressibleEulerEquations2D + ) # change discontinuity to tanh # resolution 128^2 elements (refined close to the interface) and polydeg=3 (total of 512^2 DOF) # domain size is [-64,+64]^2 @@ -33,10 +34,12 @@ function initial_condition_colliding_flow_astro(x, t, end initial_condition = initial_condition_colliding_flow_astro -boundary_conditions = (x_neg = BoundaryConditionDirichlet(initial_condition_colliding_flow_astro), - x_pos = BoundaryConditionDirichlet(initial_condition_colliding_flow_astro), - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) +boundary_conditions = ( + x_neg = BoundaryConditionDirichlet(initial_condition_colliding_flow_astro), + x_pos = BoundaryConditionDirichlet(initial_condition_colliding_flow_astro), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) surface_flux = flux_lax_friedrichs # HLLC needs more shock capturing (alpha_max) volume_flux = flux_ranocha # works with Chandrashekar flux as well @@ -44,37 +47,54 @@ polydeg = 3 basis = LobattoLegendreBasis(polydeg) # shock capturing necessary for this tough example, however alpha_max = 0.5 is fine -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.0001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.0001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-64.0, -64.0) coordinates_max = (64.0, 64.0) # only refinement in a patch. Needs x=-17/+17 to trigger refinement due to coarse base mesh -refinement_patches = ((type = "box", coordinates_min = (-17, -64), - coordinates_max = (17, 64)), - (type = "box", coordinates_min = (-17, -64), - coordinates_max = (17, 64)), - (type = "box", coordinates_min = (-17, -64), - coordinates_max = (17, 64)), - (type = "box", coordinates_min = (-17, -64), - coordinates_max = (17, 64)) - #(type="box", coordinates_min=(-17, -64), coordinates_max=(17, 64)), # very high resolution, takes about 1000s on 2 cores - ) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - refinement_patches = refinement_patches, - periodicity = (false, true), - n_cells_max = 100_000) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +refinement_patches = ( + ( + type = "box", coordinates_min = (-17, -64), + coordinates_max = (17, 64), + ), + ( + type = "box", coordinates_min = (-17, -64), + coordinates_max = (17, 64), + ), + ( + type = "box", coordinates_min = (-17, -64), + coordinates_max = (17, 64), + ), + ( + type = "box", coordinates_min = (-17, -64), + coordinates_max = (17, 64), + ), + #(type="box", coordinates_min=(-17, -64), coordinates_max=(17, 64)), # very high resolution, takes about 1000s on 2 cores +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + refinement_patches = refinement_patches, + periodicity = (false, true), + n_cells_max = 100_000 +) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -89,22 +109,30 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution +) # positivity limiter necessary for this tough example -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6), - variables = (Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (5.0e-6, 5.0e-6), + variables = (Trixi.density, pressure) +) ############################################################################### # run the simulation # use adaptive time stepping based on error estimates, time step roughly dt = 5e-3 -sol = solve(ode, SSPRK43(stage_limiter!); - ode_default_options()..., callback = callbacks); +sol = solve( + ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_colliding_flow_amr.jl b/examples/tree_2d_dgsem/elixir_euler_colliding_flow_amr.jl index a9eb671929..28efb03aec 100644 --- a/examples/tree_2d_dgsem/elixir_euler_colliding_flow_amr.jl +++ b/examples/tree_2d_dgsem/elixir_euler_colliding_flow_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,8 +9,10 @@ equations = CompressibleEulerEquations2D(gamma) # This is a hand made colliding flow setup without reference. Features Mach=70 inflow from both # sides, with relative low temperature, such that pressure keeps relatively small # Computed with gamma close to 1, to simulate isothermal gas -function initial_condition_colliding_flow_astro(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_colliding_flow_astro( + x, t, + equations::CompressibleEulerEquations2D + ) # change discontinuity to tanh # resolution 128^2 elements (refined close to the interface) and polydeg=3 (total of 512^2 DOF) # domain size is [-64,+64]^2 @@ -33,10 +34,12 @@ function initial_condition_colliding_flow_astro(x, t, end initial_condition = initial_condition_colliding_flow_astro -boundary_conditions = (x_neg = BoundaryConditionDirichlet(initial_condition_colliding_flow_astro), - x_pos = BoundaryConditionDirichlet(initial_condition_colliding_flow_astro), - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) +boundary_conditions = ( + x_neg = BoundaryConditionDirichlet(initial_condition_colliding_flow_astro), + x_pos = BoundaryConditionDirichlet(initial_condition_colliding_flow_astro), + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) surface_flux = flux_lax_friedrichs # HLLC needs more shock capturing (alpha_max) volume_flux = flux_ranocha # works with Chandrashekar flux as well @@ -44,25 +47,33 @@ polydeg = 3 basis = LobattoLegendreBasis(polydeg) # shock capturing necessary for this tough example, however alpha_max = 0.5 is fine -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.0001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.0001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-64.0, -64.0) coordinates_max = (64.0, 64.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - periodicity = (false, true), - n_cells_max = 100_000) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + periodicity = (false, true), + n_cells_max = 100_000 +) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -77,40 +88,54 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) # Simulation also feasible without AMR: AMR reduces CPU time by a factor of about 2 -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 1.0, - alpha_min = 0.0001, - alpha_smooth = false, - variable = Trixi.density) - -amr_controller = ControllerThreeLevelCombined(semi, amr_indicator, indicator_sc, - base_level = 2, - med_level = 0, med_threshold = 0.0003, # med_level = current level - max_level = 8, max_threshold = 0.003, - max_threshold_secondary = indicator_sc.alpha_max) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - amr_callback, save_solution) +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 1.0, + alpha_min = 0.0001, + alpha_smooth = false, + variable = Trixi.density +) + +amr_controller = ControllerThreeLevelCombined( + semi, amr_indicator, indicator_sc, + base_level = 2, + med_level = 0, med_threshold = 0.0003, # med_level = current level + max_level = 8, max_threshold = 0.003, + max_threshold_secondary = indicator_sc.alpha_max +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + amr_callback, save_solution +) # positivity limiter necessary for this tough example -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6), - variables = (Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (5.0e-6, 5.0e-6), + variables = (Trixi.density, pressure) +) ############################################################################### # run the simulation # use adaptive time stepping based on error estimates, time step roughly dt = 5e-3 -sol = solve(ode, SSPRK43(stage_limiter!); - ode_default_options()..., callback = callbacks); +sol = solve( + ode, SSPRK43(stage_limiter!); + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_convergence_pure_fv.jl b/examples/tree_2d_dgsem/elixir_euler_convergence_pure_fv.jl index 96184b5ba4..bfda94b9b2 100644 --- a/examples/tree_2d_dgsem/elixir_euler_convergence_pure_fv.jl +++ b/examples/tree_2d_dgsem/elixir_euler_convergence_pure_fv.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -17,12 +16,16 @@ solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (0.0, 0.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -37,22 +40,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_density_wave.jl b/examples/tree_2d_dgsem/elixir_euler_density_wave.jl index 0f9e232fa1..1d070f054d 100644 --- a/examples/tree_2d_dgsem/elixir_euler_density_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_density_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,9 +12,11 @@ solver = DGSEM(polydeg = 5, surface_flux = flux_central) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -32,22 +33,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_ec.jl b/examples/tree_2d_dgsem/elixir_euler_ec.jl index e634a383cd..b1325fab87 100644 --- a/examples/tree_2d_dgsem/elixir_euler_ec.jl +++ b/examples/tree_2d_dgsem/elixir_euler_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,18 +8,24 @@ equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 10_000, - periodicity = true) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition_periodic) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000, + periodicity = true +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition_periodic +) ############################################################################### # ODE solvers, callbacks etc. @@ -35,22 +40,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability.jl index 5e6b1e0cc0..9b782211c9 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,8 +15,10 @@ A version of the classical Kelvin-Helmholtz instability based on of the Euler Equations [arXiv: 2102.06017](https://arxiv.org/abs/2102.06017) """ -function initial_condition_kelvin_helmholtz_instability(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_kelvin_helmholtz_instability( + x, t, + equations::CompressibleEulerEquations2D + ) # change discontinuity to tanh # typical resolution 128^2, 256^2 # domain size is [-1,+1]^2 @@ -36,21 +37,27 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.002, - alpha_min = 0.0001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.002, + alpha_min = 0.0001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) ############################################################################### @@ -66,22 +73,28 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 20, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 20, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.3) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr.jl index 5c237835cc..a7e269db72 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,8 +15,10 @@ A version of the classical Kelvin-Helmholtz instability based on of the Euler Equations [arXiv: 2102.06017](https://arxiv.org/abs/2102.06017) """ -function initial_condition_kelvin_helmholtz_instability(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_kelvin_helmholtz_instability( + x, t, + equations::CompressibleEulerEquations2D + ) # change discontinuity to tanh # typical resolution 128^2, 256^2 # domain size is [-1,+1]^2 @@ -36,21 +37,27 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.002, - alpha_min = 0.0001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.002, + alpha_min = 0.0001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -67,36 +74,48 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 1.0, - alpha_min = 0.0001, - alpha_smooth = false, - variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - med_level = 0, med_threshold = 0.0003, # med_level = current level - max_level = 6, max_threshold = 0.003) -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 1.0, + alpha_min = 0.0001, + alpha_smooth = false, + variable = Trixi.density +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + med_level = 0, med_threshold = 0.0003, # med_level = current level + max_level = 6, max_threshold = 0.003 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.3) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_fjordholm_etal.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_fjordholm_etal.jl index 5b2b80d84f..c9d8ed7e06 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_fjordholm_etal.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_fjordholm_etal.jl @@ -15,8 +15,10 @@ A version of the classical Kelvin-Helmholtz instability based on solutions for hyperbolic systems of conservation laws [arXiv: 1402.0909](https://arxiv.org/abs/1402.0909) """ -function initial_condition_kelvin_helmholtz_instability_fjordholm_etal(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_kelvin_helmholtz_instability_fjordholm_etal( + x, t, + equations::CompressibleEulerEquations2D + ) # typical resolution 128^2, 256^2 # domain size is [0,+1]^2 # interface is sharp, but randomly perturbed @@ -34,20 +36,28 @@ function initial_condition_kelvin_helmholtz_instability_fjordholm_etal(x, t, # b2 = (rand(rng, m) .- 0.5) .* pi m = 10 - a1 = [0.04457096674422902, 0.03891512410182607, 0.0030191053979293433, + a1 = [ + 0.04457096674422902, 0.03891512410182607, 0.0030191053979293433, 0.0993913172320319, 0.1622302137588842, 0.1831383653456182, 0.11758003014101702, 0.07964318348142958, - 0.0863245324711805, 0.18518716132585408] - a2 = [0.061688440856337096, 0.23000237877135882, 0.04453793881833177, + 0.0863245324711805, 0.18518716132585408, + ] + a2 = [ + 0.061688440856337096, 0.23000237877135882, 0.04453793881833177, 0.19251530387370916, 0.11107917357941084, 0.05898041974649702, 0.09949312336096268, 0.07022276346006465, - 0.10670366489014596, 0.02477679264318211] - b1 = [0.06582340543754152, 0.9857886297001535, 0.8450452205037154, -1.279648120993805, + 0.10670366489014596, 0.02477679264318211, + ] + b1 = [ + 0.06582340543754152, 0.9857886297001535, 0.8450452205037154, -1.279648120993805, 0.45454198915209526, -0.13359370986823993, 0.07062615913363897, -1.0097986278512623, - 1.0810669017430343, -0.14207309803877177] - b2 = [-1.1376882185131414, -1.4798197129947765, 0.6139290513283818, -0.3319087388365522, + 1.0810669017430343, -0.14207309803877177, + ] + b2 = [ + -1.1376882185131414, -1.4798197129947765, 0.6139290513283818, -0.3319087388365522, 0.14633328999192285, -0.06373231463100072, -0.6270101051216724, 0.13941252226261905, - -1.0337526453303645, 1.0441408867083155] + -1.0337526453303645, 1.0441408867083155, + ] Y1 = 0.0 Y2 = 0.0 for n in 1:m @@ -79,22 +89,28 @@ surface_flux = flux_hllc volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.001, - alpha_min = 0.0001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.001, + alpha_min = 0.0001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -111,17 +127,23 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 400, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 400, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(); - ode_default_options()..., callback = callbacks); +sol = solve( + ode, SSPRK43(); + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl index 9e9fb45e7d..2986728d89 100644 --- a/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,8 +15,10 @@ A version of the classical Kelvin-Helmholtz instability based on of the Euler Equations [arXiv: 2102.06017](https://arxiv.org/abs/2102.06017) """ -function initial_condition_kelvin_helmholtz_instability(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_kelvin_helmholtz_instability( + x, t, + equations::CompressibleEulerEquations2D + ) # change discontinuity to tanh # typical resolution 128^2, 256^2 # domain size is [-1,+1]^2 @@ -37,19 +38,25 @@ volume_flux = flux_ranocha 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) +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) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) ############################################################################### @@ -65,29 +72,39 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -save_restart = SaveRestartCallback(interval = 1000, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 1000, + save_final_restart = true +) stepsize_callback = StepsizeCallback(cfl = 0.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback, - save_restart, save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback, + save_restart, save_solution +) ############################################################################### # run the simulation -stage_callbacks = (SubcellLimiterIDPCorrection(), - BoundsCheckCallback(save_errors = false, interval = 100)) +stage_callbacks = ( + SubcellLimiterIDPCorrection(), + BoundsCheckCallback(save_errors = false, interval = 100), +) # `interval` is used when calling this elixir in the tests with `save_errors=true`. -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); +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 +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_positivity.jl b/examples/tree_2d_dgsem/elixir_euler_positivity.jl index 6fec4c1bf9..cb12144654 100644 --- a/examples/tree_2d_dgsem/elixir_euler_positivity.jl +++ b/examples/tree_2d_dgsem/elixir_euler_positivity.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -41,21 +40,27 @@ initial_condition = initial_condition_sedov_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -72,36 +77,50 @@ 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) - -amr_indicator = IndicatorLöhner(semi, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - med_level = 0, med_threshold = 0.1, # med_level = current level - max_level = 6, max_threshold = 0.3) -amr_callback = AMRCallback(semi, amr_controller, - interval = 2, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorLöhner( + semi, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + med_level = 0, med_threshold = 0.1, # med_level = current level + max_level = 6, max_threshold = 0.3 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 2, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (5.0e-6, 5.0e-6), - variables = (Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (5.0e-6, 5.0e-6), + variables = (Trixi.density, pressure) +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave.jl index 5b8959b97d..1b4e285192 100644 --- a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -41,21 +40,27 @@ initial_condition = initial_condition_sedov_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_chandrashekar basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -72,34 +77,46 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 6, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 6, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl index 2089d35397..0a19888f3b 100644 --- a/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_sedov_blast_wave_sc_subcell.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -41,20 +40,30 @@ initial_condition = initial_condition_sedov_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_chandrashekar basis = LobattoLegendreBasis(3) -limiter_idp = SubcellLimiterIDP(equations, basis; - local_twosided_variables_cons = ["rho"], - local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, - min)]) -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +limiter_idp = SubcellLimiterIDP( + equations, basis; + local_twosided_variables_cons = ["rho"], + local_onesided_variables_nonlinear = [ + ( + Trixi.entropy_guermond_etal, + min, + ), + ] +) +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -71,25 +80,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback, + save_solution +) ############################################################################### # run the simulation -stage_callbacks = (SubcellLimiterIDPCorrection(), - BoundsCheckCallback(save_errors = false, interval = 100)) +stage_callbacks = ( + SubcellLimiterIDPCorrection(), + BoundsCheckCallback(save_errors = false, interval = 100), +) # `interval` is used when calling this elixir in the tests with `save_errors=true`. -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 - save_everystep = false, callback = callbacks); +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 + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_shockcapturing.jl b/examples/tree_2d_dgsem/elixir_euler_shockcapturing.jl index 40f19cbd4e..1b6faa7266 100644 --- a/examples/tree_2d_dgsem/elixir_euler_shockcapturing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,21 +11,27 @@ initial_condition = initial_condition_weak_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_shima_etal basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -43,21 +48,27 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, stepsize_callback, - save_solution, - analysis_callback, alive_callback) +callbacks = CallbackSet( + summary_callback, stepsize_callback, + save_solution, + analysis_callback, alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_shockcapturing_subcell.jl b/examples/tree_2d_dgsem/elixir_euler_shockcapturing_subcell.jl index 44e63a0872..51f02772ec 100644 --- a/examples/tree_2d_dgsem/elixir_euler_shockcapturing_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_euler_shockcapturing_subcell.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -29,7 +28,7 @@ function initial_condition_blast_wave(x, t, equations::CompressibleEulerEquation rho = r > 0.5 ? 0.1 : 0.2691 # rho = r > 0.5 ? 1 : 1.1691 v1 = r > 0.5 ? 0.0 : 0.1882 * cos_phi v2 = r > 0.5 ? 0.0 : 0.1882 * sin_phi - p = r > 0.5 ? 1.0E-1 : 1.245 # p = r > 0.5 ? 1.0E-3 : 1.245 + p = r > 0.5 ? 1.0e-1 : 1.245 # p = r > 0.5 ? 1.0E-3 : 1.245 return prim2cons(SVector(rho, v1, v2, p), equations) end @@ -38,19 +37,25 @@ initial_condition = initial_condition_blast_wave surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -limiter_idp = SubcellLimiterIDP(equations, basis; - positivity_variables_cons = ["rho"], - positivity_correction_factor = 0.5) -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +limiter_idp = SubcellLimiterIDP( + equations, basis; + positivity_variables_cons = ["rho"], + positivity_correction_factor = 0.5 +) +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -67,24 +72,30 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(dt = 0.1, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + dt = 0.1, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = false)) -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 - save_everystep = false, callback = callbacks); +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 + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_source_terms.jl b/examples/tree_2d_dgsem/elixir_euler_source_terms.jl index ec23014553..fc2cb7b6fc 100644 --- a/examples/tree_2d_dgsem/elixir_euler_source_terms.jl +++ b/examples/tree_2d_dgsem/elixir_euler_source_terms.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,12 +11,16 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -32,22 +35,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_source_terms_amr_refine_coarsen.jl b/examples/tree_2d_dgsem/elixir_euler_source_terms_amr_refine_coarsen.jl index 28ab4cec1d..ef3bbe30ef 100644 --- a/examples/tree_2d_dgsem/elixir_euler_source_terms_amr_refine_coarsen.jl +++ b/examples/tree_2d_dgsem/elixir_euler_source_terms_amr_refine_coarsen.jl @@ -8,38 +8,40 @@ using Trixi # if multiple test cases using the same module name are run in the same session. module TrixiExtensionEulerAMR -using Trixi + using Trixi -struct IndicatorRefineCoarsen{Cache <: NamedTuple} <: Trixi.AbstractIndicator - cache::Cache -end - -function IndicatorRefineCoarsen(semi) - basis = semi.solver.basis - alpha = Vector{real(basis)}() - cache = (; semi.mesh, alpha) - - return IndicatorRefineCoarsen{typeof(cache)}(cache) -end - -function (indicator::IndicatorRefineCoarsen)(u::AbstractArray{<:Any, 4}, - mesh, equations, dg, cache; - t, kwargs...) - alpha = indicator.cache.alpha - resize!(alpha, nelements(dg, cache)) - - if t >= 0.7 && t < 1.0 - # Refine to max level - alpha .= 1.0 - elseif t >= 1.0 - # Coarsen to base level - alpha .= -1.0 - else - alpha .= 0.0 + struct IndicatorRefineCoarsen{Cache <: NamedTuple} <: Trixi.AbstractIndicator + cache::Cache end - return alpha -end + function IndicatorRefineCoarsen(semi) + basis = semi.solver.basis + alpha = Vector{real(basis)}() + cache = (; semi.mesh, alpha) + + return IndicatorRefineCoarsen{typeof(cache)}(cache) + end + + function (indicator::IndicatorRefineCoarsen)( + u::AbstractArray{<:Any, 4}, + mesh, equations, dg, cache; + t, kwargs... + ) + alpha = indicator.cache.alpha + resize!(alpha, nelements(dg, cache)) + + if t >= 0.7 && t < 1.0 + # Refine to max level + alpha .= 1.0 + elseif t >= 1.0 + # Coarsen to base level + alpha .= -1.0 + else + alpha .= 0.0 + end + + return alpha + end end # module TrixiExtensionEulerAMR @@ -55,12 +57,16 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -75,32 +81,42 @@ 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) - -amr_controller = ControllerThreeLevel(semi, - TrixiExtensionEulerAMR.IndicatorRefineCoarsen(semi), - base_level = 3, max_level = 6, - med_threshold = 0.1, max_threshold = 0.6) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, + TrixiExtensionEulerAMR.IndicatorRefineCoarsen(semi), + base_level = 3, max_level = 6, + med_threshold = 0.1, max_threshold = 0.6 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_source_terms_nonperiodic.jl b/examples/tree_2d_dgsem/elixir_euler_source_terms_nonperiodic.jl index 05ed7ba78c..9dce5efe1f 100644 --- a/examples/tree_2d_dgsem/elixir_euler_source_terms_nonperiodic.jl +++ b/examples/tree_2d_dgsem/elixir_euler_source_terms_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,23 +11,29 @@ initial_condition = initial_condition_convergence_test # you can either use a single function to impose the BCs weakly in all # 2*ndims == 4 directions or you can pass a tuple containing BCs for each direction boundary_condition = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = (x_neg = boundary_condition, - x_pos = boundary_condition, - y_neg = boundary_condition, - y_pos = boundary_condition) +boundary_conditions = ( + x_neg = boundary_condition, + x_pos = boundary_condition, + y_neg = boundary_condition, + y_pos = boundary_condition, +) solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -43,24 +48,32 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_vortex.jl b/examples/tree_2d_dgsem/elixir_euler_vortex.jl index c87d6f49ba..738c7f0088 100644 --- a/examples/tree_2d_dgsem/elixir_euler_vortex.jl +++ b/examples/tree_2d_dgsem/elixir_euler_vortex.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -53,9 +52,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-10.0, -10.0) coordinates_max = (10.0, 10.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -68,31 +69,41 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_vortex_amr.jl b/examples/tree_2d_dgsem/elixir_euler_vortex_amr.jl index fd25defd41..7c272693e5 100644 --- a/examples/tree_2d_dgsem/elixir_euler_vortex_amr.jl +++ b/examples/tree_2d_dgsem/elixir_euler_vortex_amr.jl @@ -1,65 +1,68 @@ - using OrdinaryDiffEq using Trixi # define new structs inside a module to allow re-evaluating the file module TrixiExtension -using Trixi - -struct IndicatorVortex{Cache <: NamedTuple} <: Trixi.AbstractIndicator - cache::Cache -end - -function IndicatorVortex(semi) - basis = semi.solver.basis - alpha = Vector{real(basis)}() - A = Array{real(basis), 2} - indicator_threaded = [A(undef, nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] - cache = (; semi.mesh, alpha, indicator_threaded) - - return IndicatorVortex{typeof(cache)}(cache) -end + using Trixi -function (indicator_vortex::IndicatorVortex)(u::AbstractArray{<:Any, 4}, - mesh, equations, dg, cache; - t, kwargs...) - mesh = indicator_vortex.cache.mesh - alpha = indicator_vortex.cache.alpha - resize!(alpha, nelements(dg, cache)) - - # get analytical vortex center (based on assumption that center=[0.0,0.0] - # at t=0.0 and that we stop after one period) - domain_length = mesh.tree.length_level_0 - if t < 0.5 * domain_length - center = (t, t) - else - center = (t - domain_length, t - domain_length) + struct IndicatorVortex{Cache <: NamedTuple} <: Trixi.AbstractIndicator + cache::Cache end - Threads.@threads for element in eachelement(dg, cache) - cell_id = cache.elements.cell_ids[element] - coordinates = (mesh.tree.coordinates[1, cell_id], mesh.tree.coordinates[2, cell_id]) - # use the negative radius as indicator since the AMR controller increases - # the level with increasing value of the indicator and we want to use - # high levels near the vortex center - alpha[element] = -periodic_distance_2d(coordinates, center, domain_length) + function IndicatorVortex(semi) + basis = semi.solver.basis + alpha = Vector{real(basis)}() + A = Array{real(basis), 2} + indicator_threaded = [ + A(undef, nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] + cache = (; semi.mesh, alpha, indicator_threaded) + + return IndicatorVortex{typeof(cache)}(cache) end - return alpha -end + function (indicator_vortex::IndicatorVortex)( + u::AbstractArray{<:Any, 4}, + mesh, equations, dg, cache; + t, kwargs... + ) + mesh = indicator_vortex.cache.mesh + alpha = indicator_vortex.cache.alpha + resize!(alpha, nelements(dg, cache)) + + # get analytical vortex center (based on assumption that center=[0.0,0.0] + # at t=0.0 and that we stop after one period) + domain_length = mesh.tree.length_level_0 + if t < 0.5 * domain_length + center = (t, t) + else + center = (t - domain_length, t - domain_length) + end + + Threads.@threads for element in eachelement(dg, cache) + cell_id = cache.elements.cell_ids[element] + coordinates = (mesh.tree.coordinates[1, cell_id], mesh.tree.coordinates[2, cell_id]) + # use the negative radius as indicator since the AMR controller increases + # the level with increasing value of the indicator and we want to use + # high levels near the vortex center + alpha[element] = -periodic_distance_2d(coordinates, center, domain_length) + end + + return alpha + end -function periodic_distance_2d(coordinates, center, domain_length) - dx = @. abs(coordinates - center) - dx_periodic = @. min(dx, domain_length - dx) - return sqrt(sum(abs2, dx_periodic)) -end + function periodic_distance_2d(coordinates, center, domain_length) + dx = @. abs(coordinates - center) + dx_periodic = @. min(dx, domain_length - dx) + return sqrt(sum(abs2, dx_periodic)) + end -# Optional: Nicer display of the indicator -function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorVortex) - Trixi.summary_box(io, "IndicatorVortex") -end + # Optional: Nicer display of the indicator + function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorVortex) + Trixi.summary_box(io, "IndicatorVortex") + end end # module TrixiExtension @@ -118,9 +121,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-10.0, -10.0) coordinates_max = (10.0, 10.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -134,40 +139,54 @@ summary_callback = SummaryCallback() analysis_interval = 200 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 50, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, TrixiExtension.IndicatorVortex(semi), - base_level = 3, - med_level = 4, med_threshold = -3.0, - max_level = 5, max_threshold = -2.0) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 50, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, TrixiExtension.IndicatorVortex(semi), + base_level = 3, + med_level = 4, med_threshold = -3.0, + max_level = 5, max_threshold = -2.0 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_vortex_mortar.jl b/examples/tree_2d_dgsem/elixir_euler_vortex_mortar.jl index 858799d2d3..ec224b456c 100644 --- a/examples/tree_2d_dgsem/elixir_euler_vortex_mortar.jl +++ b/examples/tree_2d_dgsem/elixir_euler_vortex_mortar.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -53,12 +52,18 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-10.0, -10.0) coordinates_max = (10.0, 10.0) -refinement_patches = ((type = "box", coordinates_min = (0.0, -10.0), - coordinates_max = (10.0, 10.0)),) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - refinement_patches = refinement_patches, - n_cells_max = 10_000) +refinement_patches = ( + ( + type = "box", coordinates_min = (0.0, -10.0), + coordinates_max = (10.0, 10.0), + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + refinement_patches = refinement_patches, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -71,31 +76,41 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.4) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_vortex_mortar_shockcapturing.jl b/examples/tree_2d_dgsem/elixir_euler_vortex_mortar_shockcapturing.jl index 026f6d1462..624b0d7f9e 100644 --- a/examples/tree_2d_dgsem/elixir_euler_vortex_mortar_shockcapturing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_vortex_mortar_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -55,24 +54,34 @@ volume_flux = flux_shima_etal polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-10.0, -10.0) coordinates_max = (10.0, 10.0) -refinement_patches = ((type = "box", coordinates_min = (0.0, -10.0), - coordinates_max = (10.0, 10.0)),) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - refinement_patches = refinement_patches, - n_cells_max = 10_000) +refinement_patches = ( + ( + type = "box", coordinates_min = (0.0, -10.0), + coordinates_max = (10.0, 10.0), + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + refinement_patches = refinement_patches, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -85,31 +94,41 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_vortex_mortar_split.jl b/examples/tree_2d_dgsem/elixir_euler_vortex_mortar_split.jl index d719e01fd7..0def652bcd 100644 --- a/examples/tree_2d_dgsem/elixir_euler_vortex_mortar_split.jl +++ b/examples/tree_2d_dgsem/elixir_euler_vortex_mortar_split.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -51,17 +50,25 @@ end initial_condition = initial_condition_isentropic_vortex volume_flux = flux_shima_etal -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-10.0, -10.0) coordinates_max = (10.0, 10.0) -refinement_patches = ((type = "box", coordinates_min = (0.0, -10.0), - coordinates_max = (10.0, 10.0)),) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - refinement_patches = refinement_patches, - n_cells_max = 10_000) +refinement_patches = ( + ( + type = "box", coordinates_min = (0.0, -10.0), + coordinates_max = (10.0, 10.0), + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + refinement_patches = refinement_patches, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -74,31 +81,41 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.4) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_vortex_shockcapturing.jl b/examples/tree_2d_dgsem/elixir_euler_vortex_shockcapturing.jl index 99e4b09063..a5608ad2f8 100644 --- a/examples/tree_2d_dgsem/elixir_euler_vortex_shockcapturing.jl +++ b/examples/tree_2d_dgsem/elixir_euler_vortex_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -55,21 +54,27 @@ volume_flux = flux_shima_etal polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-10.0, -10.0) coordinates_max = (10.0, 10.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -82,34 +87,46 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_errors = (:conservation_error,), - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_errors = (:conservation_error,), + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_euler_warm_bubble.jl b/examples/tree_2d_dgsem/elixir_euler_warm_bubble.jl index f2e14273ae..3a052f76a6 100644 --- a/examples/tree_2d_dgsem/elixir_euler_warm_bubble.jl +++ b/examples/tree_2d_dgsem/elixir_euler_warm_bubble.jl @@ -3,7 +3,7 @@ using Trixi # Warm bubble test case from # - Wicker, L. J., and Skamarock, W. C. (1998) -# A time-splitting scheme for the elastic equations incorporating +# A time-splitting scheme for the elastic equations incorporating # second-order Runge–Kutta time differencing # [DOI: 10.1175/1520-0493(1998)126%3C1992:ATSSFT%3E2.0.CO;2](https://doi.org/10.1175/1520-0493(1998)126%3C1992:ATSSFT%3E2.0.CO;2) # See also @@ -11,7 +11,7 @@ using Trixi # A Benchmark Simulation for Moist Nonhydrostatic Numerical Models # [DOI: 10.1175/1520-0493(2002)130<2917:ABSFMN>2.0.CO;2](https://doi.org/10.1175/1520-0493(2002)130<2917:ABSFMN>2.0.CO;2) # - Carpenter, Droegemeier, Woodward, Hane (1990) -# Application of the Piecewise Parabolic Method (PPM) to +# Application of the Piecewise Parabolic Method (PPM) to # Meteorological Modeling # [DOI: 10.1175/1520-0493(1990)118<0586:AOTPPM>2.0.CO;2](https://doi.org/10.1175/1520-0493(1990)118<0586:AOTPPM>2.0.CO;2) struct WarmBubbleSetup @@ -79,10 +79,12 @@ warm_bubble_setup = WarmBubbleSetup() equations = CompressibleEulerEquations2D(warm_bubble_setup.gamma) -boundary_conditions = (x_neg = boundary_condition_periodic, - x_pos = boundary_condition_periodic, - y_neg = boundary_condition_slip_wall, - y_pos = boundary_condition_slip_wall) +boundary_conditions = ( + x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall, +) polydeg = 3 basis = LobattoLegendreBasis(polydeg) @@ -101,14 +103,18 @@ coordinates_max = (20_000.0, 10_000.0) # Same coordinates as in examples/structured_2d_dgsem/elixir_euler_warm_bubble.jl # However TreeMesh will generate a 20_000 x 20_000 square domain instead -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - n_cells_max = 10_000, - periodicity = (true, false)) - -semi = SemidiscretizationHyperbolic(mesh, equations, warm_bubble_setup, solver, - source_terms = warm_bubble_setup, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + n_cells_max = 10_000, + periodicity = (true, false) +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, warm_bubble_setup, solver, + source_terms = warm_bubble_setup, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -121,30 +127,38 @@ summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:entropy_conservation_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = (:entropy_conservation_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = analysis_interval, - save_initial_solution = true, - save_final_solution = true, - output_directory = "out", - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = analysis_interval, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - maxiters = 1.0e7, - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + maxiters = 1.0e7, + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_euleracoustics_co-rotating_vortex_pair.jl b/examples/tree_2d_dgsem/elixir_euleracoustics_co-rotating_vortex_pair.jl index ea81bd049e..ef2e6365e5 100644 --- a/examples/tree_2d_dgsem/elixir_euleracoustics_co-rotating_vortex_pair.jl +++ b/examples/tree_2d_dgsem/elixir_euleracoustics_co-rotating_vortex_pair.jl @@ -16,186 +16,208 @@ using Trixi # https://doi.org/10.18154/RWTH-2017-04082 module VortexPairSetup -using LinearAlgebra: norm -using Trixi - -# Parameters that describe the co-rotating vortex pair -struct VortexPair{RealT <: Real} - r0::RealT # Distance between origin and each vortex center - rc::RealT # Vortex core radius - c0::RealT # Speed of sound - circulation::RealT # Circulation of the vortices - rho0::RealT # Density -end - -# Analytical flow solution, used for the initial condition of the flow simulation -function velocity(x, t, vortex_pair::VortexPair) - @unpack r0, rc, circulation = vortex_pair - - omega = circulation / (4 * pi * r0^2) - si, co = sincos(omega * t) - b = SVector(r0 * co, r0 * si) # vortex centers are b and -b - z_plus = x - b - z_minus = x + b - - # Transform to polar coordinates - r_plus = norm(z_plus) - r_minus = norm(z_minus) - theta_plus = atan(z_plus[2], z_plus[1]) - theta_minus = atan(z_minus[2], z_minus[1]) - - si_plus, co_plus = sincos(theta_plus) - si_minus, co_minus = sincos(theta_minus) - - v1 = -circulation / (2 * pi) * (r_plus / (rc^2 + r_plus^2) * si_plus + - r_minus / (rc^2 + r_minus^2) * si_minus) - v2 = circulation / (2 * pi) * (r_plus / (rc^2 + r_plus^2) * co_plus + - r_minus / (rc^2 + r_minus^2) * co_minus) - - return SVector(v1, v2) -end - -# Initial condition of the flow simulation. Uses constant density rho0 and analytical velocities. -# The pressure is calculated using the given speed of sound c0 and Bernoulli's principle -struct InitialCondition{RealT <: Real} - vortex_pair::VortexPair{RealT} -end - -function (initial_condition::InitialCondition)(x, t, - equations::CompressibleEulerEquations2D) - @unpack vortex_pair = initial_condition - @unpack rho0, c0 = vortex_pair - gamma = equations.gamma - - v = velocity(x, t, vortex_pair) - p0 = rho0 * c0^2 / gamma - p = p0 - 0.5 * (gamma - 1) / gamma * sum(v .^ 2) # Bernoulli's principle - - prim = SVector(rho0, v[1], v[2], p) - return prim2cons(prim, equations) -end - -# For both the flow and acoustics solvers, a sponge layer is used to dampen the density -# and pressure towards the freestream values (for the flow solver) and the perturbed pressure -# to zero (for the acoustics solver). -struct SpongeLayer{RealT <: Real, uEltype <: Real, N, SourceTerms} - sponge_layer_min::NTuple{4, RealT} # (-x,+x,-y,+y) min coordinates of sponge layer per direction - sponge_layer_max::NTuple{4, RealT} # (-x,+x,-y,+y) max coordinates of sponge layer per direction - reference_values::NTuple{N, uEltype} # reference values for variables affected by sponge layer - source_terms::SourceTerms # source terms to be used outside the sponge zone -end - -function SpongeLayer(; sponge_layer_min, sponge_layer_max, reference_values, - source_terms = nothing) - return SpongeLayer(sponge_layer_min, sponge_layer_max, reference_values, source_terms) -end - -function (sponge_layer::SpongeLayer)(u, x, t, equations) - @unpack sponge_layer_min, sponge_layer_max, reference_values, source_terms = sponge_layer - - if lies_in_sponge_layer(x, sponge_layer_min, sponge_layer_max) - return source_term_sponge_layer(u, x, t, equations, sponge_layer_min, - sponge_layer_max, - reference_values) - elseif source_terms !== nothing - return source_terms(u, x, t, equations) - else - return SVector(ntuple(v -> zero(eltype(u)), Val(nvariables(equations)))) + using LinearAlgebra: norm + using Trixi + + # Parameters that describe the co-rotating vortex pair + struct VortexPair{RealT <: Real} + r0::RealT # Distance between origin and each vortex center + rc::RealT # Vortex core radius + c0::RealT # Speed of sound + circulation::RealT # Circulation of the vortices + rho0::RealT # Density end -end -function lies_in_sponge_layer(x, sponge_layer_min, sponge_layer_max) - return (sponge_layer_min[1] <= x[1] <= sponge_layer_max[1]) || # -x direction - (sponge_layer_min[2] <= x[1] <= sponge_layer_max[2]) || # +x direction - (sponge_layer_min[3] <= x[2] <= sponge_layer_max[3]) || # -y direction - (sponge_layer_min[4] <= x[2] <= sponge_layer_max[4]) # +y direction -end + # Analytical flow solution, used for the initial condition of the flow simulation + function velocity(x, t, vortex_pair::VortexPair) + @unpack r0, rc, circulation = vortex_pair + + omega = circulation / (4 * pi * r0^2) + si, co = sincos(omega * t) + b = SVector(r0 * co, r0 * si) # vortex centers are b and -b + z_plus = x - b + z_minus = x + b + + # Transform to polar coordinates + r_plus = norm(z_plus) + r_minus = norm(z_minus) + theta_plus = atan(z_plus[2], z_plus[1]) + theta_minus = atan(z_minus[2], z_minus[1]) + + si_plus, co_plus = sincos(theta_plus) + si_minus, co_minus = sincos(theta_minus) + + v1 = -circulation / (2 * pi) * ( + r_plus / (rc^2 + r_plus^2) * si_plus + + r_minus / (rc^2 + r_minus^2) * si_minus + ) + v2 = circulation / (2 * pi) * ( + r_plus / (rc^2 + r_plus^2) * co_plus + + r_minus / (rc^2 + r_minus^2) * co_minus + ) + + return SVector(v1, v2) + end -function source_term_sponge_layer(u, x, t, equations::AcousticPerturbationEquations2D, - sponge_layer_min::NTuple{4}, sponge_layer_max::NTuple{4}, - reference_values) - # Perturbed pressure source is -alpha^2 * (u - reference_value) where alpha in [0,1] is a damping - # factor depending on the position inside the sponge layer + # Initial condition of the flow simulation. Uses constant density rho0 and analytical velocities. + # The pressure is calculated using the given speed of sound c0 and Bernoulli's principle + struct InitialCondition{RealT <: Real} + vortex_pair::VortexPair{RealT} + end - # Calculate the damping factors for each direction (=0 if x is not in the corresponding sponge - # zone) and take the maximum. This ensures proper damping if x lies in a corner where the sponge - # zones for two directions overlap - alphas = ntuple(i -> calc_damping_factor(x, i, sponge_layer_min, sponge_layer_max), - Val(2 * ndims(equations))) - alpha_square = maximum(alphas)^2 + function (initial_condition::InitialCondition)( + x, t, + equations::CompressibleEulerEquations2D + ) + @unpack vortex_pair = initial_condition + @unpack rho0, c0 = vortex_pair + gamma = equations.gamma - return SVector(0, 0, -alpha_square * (u[3] - reference_values[1] / u[6]^2), 0, 0, 0, 0) -end + v = velocity(x, t, vortex_pair) + p0 = rho0 * c0^2 / gamma + p = p0 - 0.5 * (gamma - 1) / gamma * sum(v .^ 2) # Bernoulli's principle -function source_term_sponge_layer(u, x, t, equations::CompressibleEulerEquations2D, - sponge_layer_min::NTuple{4}, sponge_layer_max::NTuple{4}, - reference_values) - # Calculate the damping factors for each direction (=0 if x is not in the corresponding sponge - # zone) and take the maximum. This ensures proper damping if x lies in a corner where the sponge - # zones for two directions overlap - alphas = ntuple(i -> calc_damping_factor(x, i, sponge_layer_min, sponge_layer_max), - Val(2 * ndims(equations))) - alpha_square = maximum(alphas)^2 - - u_prim = cons2prim(u, equations) - s = SVector(-alpha_square * (u_prim[1] - reference_values[1]), 0, 0, - -alpha_square * (u_prim[4] - reference_values[2])) - - return prim2cons(s, equations) -end + prim = SVector(rho0, v[1], v[2], p) + return prim2cons(prim, equations) + end -function calc_damping_factor(x, direction, sponge_layer_min, sponge_layer_max) - # Damping factor alpha grows linearly from 0 to 1 depending on how deep x lies in the sponge layer - # If x does not lie in the sponge layer, this returns 0 + # For both the flow and acoustics solvers, a sponge layer is used to dampen the density + # and pressure towards the freestream values (for the flow solver) and the perturbed pressure + # to zero (for the acoustics solver). + struct SpongeLayer{RealT <: Real, uEltype <: Real, N, SourceTerms} + sponge_layer_min::NTuple{4, RealT} # (-x,+x,-y,+y) min coordinates of sponge layer per direction + sponge_layer_max::NTuple{4, RealT} # (-x,+x,-y,+y) max coordinates of sponge layer per direction + reference_values::NTuple{N, uEltype} # reference values for variables affected by sponge layer + source_terms::SourceTerms # source terms to be used outside the sponge zone + end - # Get the coordinate that determines how deep we are in the sponge zone - if direction in (1, 2) - pos = x[1] - else - pos = x[2] + function SpongeLayer(; + sponge_layer_min, sponge_layer_max, reference_values, + source_terms = nothing + ) + return SpongeLayer(sponge_layer_min, sponge_layer_max, reference_values, source_terms) end - # Determine where the sponge layer begins/ends to allow calculating the damping factor - if iseven(direction) - sponge_begin = sponge_layer_min[direction] - sponge_end = sponge_layer_max[direction] - else - sponge_begin = sponge_layer_max[direction] - sponge_end = sponge_layer_min[direction] + function (sponge_layer::SpongeLayer)(u, x, t, equations) + @unpack sponge_layer_min, sponge_layer_max, reference_values, source_terms = sponge_layer + + if lies_in_sponge_layer(x, sponge_layer_min, sponge_layer_max) + return source_term_sponge_layer( + u, x, t, equations, sponge_layer_min, + sponge_layer_max, + reference_values + ) + elseif source_terms !== nothing + return source_terms(u, x, t, equations) + else + return SVector(ntuple(v -> zero(eltype(u)), Val(nvariables(equations)))) + end end - alpha = (pos - sponge_begin) / (sponge_end - sponge_begin) + function lies_in_sponge_layer(x, sponge_layer_min, sponge_layer_max) + return (sponge_layer_min[1] <= x[1] <= sponge_layer_max[1]) || # -x direction + (sponge_layer_min[2] <= x[1] <= sponge_layer_max[2]) || # +x direction + (sponge_layer_min[3] <= x[2] <= sponge_layer_max[3]) || # -y direction + (sponge_layer_min[4] <= x[2] <= sponge_layer_max[4]) # +y direction + end - # alpha lies in [0, 1] if and only if x lies in the sponge zone - if 0 <= alpha <= 1 - return alpha - else - return zero(alpha) + function source_term_sponge_layer( + u, x, t, equations::AcousticPerturbationEquations2D, + sponge_layer_min::NTuple{4}, sponge_layer_max::NTuple{4}, + reference_values + ) + # Perturbed pressure source is -alpha^2 * (u - reference_value) where alpha in [0,1] is a damping + # factor depending on the position inside the sponge layer + + # Calculate the damping factors for each direction (=0 if x is not in the corresponding sponge + # zone) and take the maximum. This ensures proper damping if x lies in a corner where the sponge + # zones for two directions overlap + alphas = ntuple( + i -> calc_damping_factor(x, i, sponge_layer_min, sponge_layer_max), + Val(2 * ndims(equations)) + ) + alpha_square = maximum(alphas)^2 + + return SVector(0, 0, -alpha_square * (u[3] - reference_values[1] / u[6]^2), 0, 0, 0, 0) end -end -# Boundary condition for the flow problem: The sponge layer dampens density and pressure towards the -# freestream values. The freestream values (converted into conservative variables) are therefore -# used as a Dirichlet boundary -struct BoundaryCondition{uEltype} - rho::uEltype - rho_e::uEltype -end + function source_term_sponge_layer( + u, x, t, equations::CompressibleEulerEquations2D, + sponge_layer_min::NTuple{4}, sponge_layer_max::NTuple{4}, + reference_values + ) + # Calculate the damping factors for each direction (=0 if x is not in the corresponding sponge + # zone) and take the maximum. This ensures proper damping if x lies in a corner where the sponge + # zones for two directions overlap + alphas = ntuple( + i -> calc_damping_factor(x, i, sponge_layer_min, sponge_layer_max), + Val(2 * ndims(equations)) + ) + alpha_square = maximum(alphas)^2 + + u_prim = cons2prim(u, equations) + s = SVector( + -alpha_square * (u_prim[1] - reference_values[1]), 0, 0, + -alpha_square * (u_prim[4] - reference_values[2]) + ) + + return prim2cons(s, equations) + end -function (bc::BoundaryCondition)(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) - u_boundary = SVector(bc.rho, zero(bc.rho), zero(bc.rho), bc.rho_e) + function calc_damping_factor(x, direction, sponge_layer_min, sponge_layer_max) + # Damping factor alpha grows linearly from 0 to 1 depending on how deep x lies in the sponge layer + # If x does not lie in the sponge layer, this returns 0 + + # Get the coordinate that determines how deep we are in the sponge zone + if direction in (1, 2) + pos = x[1] + else + pos = x[2] + end + + # Determine where the sponge layer begins/ends to allow calculating the damping factor + if iseven(direction) + sponge_begin = sponge_layer_min[direction] + sponge_end = sponge_layer_max[direction] + else + sponge_begin = sponge_layer_max[direction] + sponge_end = sponge_layer_min[direction] + end + + alpha = (pos - sponge_begin) / (sponge_end - sponge_begin) + + # alpha lies in [0, 1] if and only if x lies in the sponge zone + if 0 <= alpha <= 1 + return alpha + else + return zero(alpha) + end + end - # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + # Boundary condition for the flow problem: The sponge layer dampens density and pressure towards the + # freestream values. The freestream values (converted into conservative variables) are therefore + # used as a Dirichlet boundary + struct BoundaryCondition{uEltype} + rho::uEltype + rho_e::uEltype end - return flux -end + function (bc::BoundaryCondition)( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) + u_boundary = SVector(bc.rho, zero(bc.rho), zero(bc.rho), bc.rho_e) + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end end # module @@ -221,17 +243,21 @@ vortex_pair = VortexPairSetup.VortexPair(r0, rc, c0, circulation, rho) # Shared mesh for both semidiscretizations coordinates_min = (-135 * r0, -135 * r0) # minimum coordinates (min(x), min(y)) coordinates_max = (135 * r0, 135 * r0) # maximum coordinates (max(x), max(y)) -refinement_patches = ((type = "sphere", center = (0.0, 0.0), radius = 85.0 * r0), - (type = "sphere", center = (0.0, 0.0), radius = 20.0 * r0), - (type = "sphere", center = (0.0, 0.0), radius = 10.0 * r0), - (type = "sphere", center = (0.0, 0.0), radius = 5.0 * r0)) +refinement_patches = ( + (type = "sphere", center = (0.0, 0.0), radius = 85.0 * r0), + (type = "sphere", center = (0.0, 0.0), radius = 20.0 * r0), + (type = "sphere", center = (0.0, 0.0), radius = 10.0 * r0), + (type = "sphere", center = (0.0, 0.0), radius = 5.0 * r0), +) initial_refinement_level = 7 n_cells_max = 500_000 -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = initial_refinement_level, - refinement_patches = refinement_patches, - n_cells_max = n_cells_max, # set maximum capacity of tree data structure - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = initial_refinement_level, + refinement_patches = refinement_patches, + n_cells_max = n_cells_max, # set maximum capacity of tree data structure + periodicity = false +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -244,37 +270,57 @@ equations_euler = CompressibleEulerEquations2D(gamma) initial_condition_euler = VortexPairSetup.InitialCondition(vortex_pair) -sponge_layer_euler = VortexPairSetup.SpongeLayer(sponge_layer_min = (-135 * r0, 115 * r0, - -135 * r0, 115 * r0), - sponge_layer_max = (-115 * r0, 135 * r0, - -115 * r0, 135 * r0), - reference_values = (rho, - rho * c0^2 / gamma)) # (rho0, p0) - -boundary_condition_euler = VortexPairSetup.BoundaryCondition(rho, - (rho * c0^2 / gamma) / - (gamma - 1)) - -semi_euler = SemidiscretizationHyperbolic(mesh, equations_euler, initial_condition_euler, - solver, - boundary_conditions = boundary_condition_euler, - source_terms = sponge_layer_euler) +sponge_layer_euler = VortexPairSetup.SpongeLayer( + sponge_layer_min = ( + -135 * r0, 115 * r0, + -135 * r0, 115 * r0, + ), + sponge_layer_max = ( + -115 * r0, 135 * r0, + -115 * r0, 135 * r0, + ), + reference_values = ( + rho, + rho * c0^2 / gamma, + ) +) # (rho0, p0) + +boundary_condition_euler = VortexPairSetup.BoundaryCondition( + rho, + (rho * c0^2 / gamma) / + (gamma - 1) +) + +semi_euler = SemidiscretizationHyperbolic( + mesh, equations_euler, initial_condition_euler, + solver, + boundary_conditions = boundary_condition_euler, + source_terms = sponge_layer_euler +) ############################################################################### # semidiscretization acoustic perturbation equations -equations_acoustics = AcousticPerturbationEquations2D(v_mean_global = (13.0, 26.0), - c_mean_global = 39.0, - rho_mean_global = 52.0) # global mean values will be overwritten - -sponge_layer_acoustics = VortexPairSetup.SpongeLayer(sponge_layer_min = (-135 * r0, - 100 * r0, - -135 * r0, - 100 * r0), - sponge_layer_max = (-100 * r0, - 135 * r0, - -100 * r0, - 135 * r0), - reference_values = (0.0,)) +equations_acoustics = AcousticPerturbationEquations2D( + v_mean_global = (13.0, 26.0), + c_mean_global = 39.0, + rho_mean_global = 52.0 +) # global mean values will be overwritten + +sponge_layer_acoustics = VortexPairSetup.SpongeLayer( + sponge_layer_min = ( + -135 * r0, + 100 * r0, + -135 * r0, + 100 * r0, + ), + sponge_layer_max = ( + -100 * r0, + 135 * r0, + -100 * r0, + 135 * r0, + ), + reference_values = (0.0,) +) """ boundary_condition_zero(u_inner, orientation, direction, x, t, surface_flux_function, @@ -283,9 +329,11 @@ sponge_layer_acoustics = VortexPairSetup.SpongeLayer(sponge_layer_min = (-135 * Boundary condition that uses a boundary state where the state variables are zero and the mean variables are the same as in `u_inner`. """ -function boundary_condition_zero(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::AcousticPerturbationEquations2D) +function boundary_condition_zero( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::AcousticPerturbationEquations2D + ) value = zero(eltype(u_inner)) u_boundary = SVector(value, value, value, cons2mean(u_inner, equations)...) @@ -299,11 +347,13 @@ function boundary_condition_zero(u_inner, orientation, direction, x, t, return flux end -semi_acoustics = SemidiscretizationHyperbolic(mesh, equations_acoustics, - initial_condition_constant, - solver, - boundary_conditions = boundary_condition_zero, - source_terms = sponge_layer_acoustics) +semi_acoustics = SemidiscretizationHyperbolic( + mesh, equations_acoustics, + initial_condition_constant, + solver, + boundary_conditions = boundary_condition_zero, + source_terms = sponge_layer_acoustics +) ############################################################################### # ODE solvers, callbacks etc. for averaging the flow field @@ -324,16 +374,20 @@ averaging_callback = AveragingCallback(semi_euler, tspan_averaging) stepsize_callback = StepsizeCallback(cfl = 0.8) -callbacks_averaging = CallbackSet(summary_callback, alive_callback, averaging_callback, - stepsize_callback) +callbacks_averaging = CallbackSet( + summary_callback, alive_callback, averaging_callback, + stepsize_callback +) ############################################################################### # run simulation for averaging the flow field # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol_averaging = solve(ode_averaging, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks_averaging); +sol_averaging = solve( + ode_averaging, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks_averaging +); # Print the timer summary summary_callback() @@ -345,8 +399,10 @@ source_region(x) = sum(abs2, x) < 6.0^2 # calculate sources within radius 6 arou # gradually reduce acoustic source term amplitudes to zero, starting at radius 5 weights(x) = sum(abs2, x) < 5.0^2 ? 1.0 : cospi(0.5 * (norm(x) - 5.0)) -semi = SemidiscretizationEulerAcoustics(semi_acoustics, semi_euler, - source_region = source_region, weights = weights) +semi = SemidiscretizationEulerAcoustics( + semi_acoustics, semi_euler, + source_region = source_region, weights = weights +) ############################################################################### # ODE solvers, callbacks etc. for the coupled simulation @@ -360,11 +416,15 @@ ode_euler = semidiscretize(semi.semi_euler, tspan) # Set up coupling callback cfl_acoustics = 0.8 cfl_euler = 0.8 -euler_acoustics_coupling = EulerAcousticsCouplingCallback(ode_euler, "out/averaging.h5", - CarpenterKennedy2N54(williamson_condition = false), - cfl_acoustics, cfl_euler, - callback = SaveRestartCallback(interval = 2300, - output_directory = "out/euler/")) +euler_acoustics_coupling = EulerAcousticsCouplingCallback( + ode_euler, "out/averaging.h5", + CarpenterKennedy2N54(williamson_condition = false), + cfl_acoustics, cfl_euler, + callback = SaveRestartCallback( + interval = 2300, + output_directory = "out/euler/" + ) +) # At the beginning of the main loop, the SummaryCallback prints a summary of the simulation setup # and resets the timers @@ -378,12 +438,16 @@ output_directory = "out/" save_solution = SaveSolutionCallback(interval = 2300, output_directory = output_directory) save_restart = SaveRestartCallback(interval = 2300, output_directory = output_directory) -callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback, save_solution, - save_restart, euler_acoustics_coupling) +callbacks = CallbackSet( + summary_callback, alive_callback, analysis_callback, save_solution, + save_restart, euler_acoustics_coupling +) -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_eulermulti_convergence_ec.jl b/examples/tree_2d_dgsem/elixir_eulermulti_convergence_ec.jl index bc4859e576..6d5105007c 100644 --- a/examples/tree_2d_dgsem/elixir_eulermulti_convergence_ec.jl +++ b/examples/tree_2d_dgsem/elixir_eulermulti_convergence_ec.jl @@ -1,26 +1,33 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler multicomponent equations -equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.4), - gas_constants = (0.4, 0.4)) +equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.4), + gas_constants = (0.4, 0.4) +) initial_condition = initial_condition_convergence_test volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -35,22 +42,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_eulermulti_convergence_es.jl b/examples/tree_2d_dgsem/elixir_eulermulti_convergence_es.jl index 771343937a..aa12019caf 100644 --- a/examples/tree_2d_dgsem/elixir_eulermulti_convergence_es.jl +++ b/examples/tree_2d_dgsem/elixir_eulermulti_convergence_es.jl @@ -1,26 +1,33 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler multicomponent equations -equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.4), - gas_constants = (0.4, 0.4)) +equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.4), + gas_constants = (0.4, 0.4) +) initial_condition = initial_condition_convergence_test volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -35,22 +42,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_eulermulti_ec.jl b/examples/tree_2d_dgsem/elixir_eulermulti_ec.jl index d912a280e4..3d63c7850b 100644 --- a/examples/tree_2d_dgsem/elixir_eulermulti_ec.jl +++ b/examples/tree_2d_dgsem/elixir_eulermulti_ec.jl @@ -1,23 +1,28 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler multicomponent equations -equations = CompressibleEulerMulticomponentEquations2D(gammas = 1.4, - gas_constants = 0.4) +equations = CompressibleEulerMulticomponentEquations2D( + gammas = 1.4, + gas_constants = 0.4 +) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -31,27 +36,35 @@ summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (Trixi.density,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (Trixi.density,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_eulermulti_es.jl b/examples/tree_2d_dgsem/elixir_eulermulti_es.jl index 470e533ab8..e344beef8b 100644 --- a/examples/tree_2d_dgsem/elixir_eulermulti_es.jl +++ b/examples/tree_2d_dgsem/elixir_eulermulti_es.jl @@ -1,23 +1,28 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible Euler multicomponent equations -equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.4, 1.4, 1.4), - gas_constants = (0.4, 0.4, 0.4, 0.4)) +equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.4, 1.4, 1.4), + gas_constants = (0.4, 0.4, 0.4, 0.4) +) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -34,25 +39,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble.jl b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble.jl index c6ed07dcda..5e8e283a72 100644 --- a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble.jl +++ b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble.jl @@ -5,8 +5,10 @@ using Trixi # semidiscretization of the compressible Euler multicomponent equations # 1) Dry Air 2) Helium + 28% Air -equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.648), - gas_constants = (0.287, 1.578)) +equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.648), + gas_constants = (0.287, 1.578) +) """ initial_condition_shock_bubble(x, t, equations::CompressibleEulerMulticomponentEquations2D{5, 2}) @@ -16,9 +18,13 @@ A shock-bubble testcase for multicomponent Euler equations Formulation of Entropy-Stable schemes for the multicomponent compressible Euler equations [arXiv: 1904.00972](https://arxiv.org/abs/1904.00972) """ -function initial_condition_shock_bubble(x, t, - equations::CompressibleEulerMulticomponentEquations2D{5, - 2}) +function initial_condition_shock_bubble( + x, t, + equations::CompressibleEulerMulticomponentEquations2D{ + 5, + 2, + } + ) # bubble test case, see Gouasmi et al. https://arxiv.org/pdf/1904.00972 # other reference: https://www.researchgate.net/profile/Pep_Mulet/publication/222675930_A_flux-split_algorithm_applied_to_conservative_models_for_multicomponent_compressible_flows/links/568da54508aeaa1481ae7af0.pdf # typical domain is rectangular, we change it to a square, as Trixi can only do squares @@ -54,7 +60,7 @@ function initial_condition_shock_bubble(x, t, y_norm = x[2] - inicenter[2] r = sqrt(x_norm^2 + y_norm^2) - if (x[1] > 0.50) + if (x[1] > 0.5) # Set up Region III rho1 = rho1_3 rho2 = rho2_3 @@ -84,21 +90,27 @@ initial_condition = initial_condition_shock_bubble surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.25, -2.225) -coordinates_max = (2.20, 2.225) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 1_000_000) +coordinates_max = (2.2, 2.225) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 1_000_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -111,29 +123,37 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 300 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (Trixi.density,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (Trixi.density,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 300, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 300, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.3) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, - callback = callbacks, - maxiters = 1e5); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, + callback = callbacks, + maxiters = 1.0e5 +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl index b2d49ecbd4..6add58d252 100644 --- a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl +++ b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl @@ -5,8 +5,10 @@ using Trixi # semidiscretization of the compressible Euler multicomponent equations # 1) Dry Air 2) Helium + 28% Air -equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.648), - gas_constants = (0.287, 1.578)) +equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.648), + gas_constants = (0.287, 1.578) +) """ initial_condition_shock_bubble(x, t, equations::CompressibleEulerMulticomponentEquations2D{5, 2}) @@ -16,9 +18,13 @@ A shock-bubble testcase for multicomponent Euler equations Formulation of Entropy-Stable schemes for the multicomponent compressible Euler equations [arXiv: 1904.00972](https://arxiv.org/abs/1904.00972) """ -function initial_condition_shock_bubble(x, t, - equations::CompressibleEulerMulticomponentEquations2D{5, - 2}) +function initial_condition_shock_bubble( + x, t, + equations::CompressibleEulerMulticomponentEquations2D{ + 5, + 2, + } + ) # bubble test case, see Gouasmi et al. https://arxiv.org/pdf/1904.00972 # other reference: https://www.researchgate.net/profile/Pep_Mulet/publication/222675930_A_flux-split_algorithm_applied_to_conservative_models_for_multicomponent_compressible_flows/links/568da54508aeaa1481ae7af0.pdf # typical domain is rectangular, we change it to a square, as Trixi can only do squares @@ -54,7 +60,7 @@ function initial_condition_shock_bubble(x, t, y_norm = x[2] - inicenter[2] r = sqrt(x_norm^2 + y_norm^2) - if (x[1] > 0.50) + if (x[1] > 0.5) # Set up Region III rho1 = rho1_3 rho2 = rho2_3 @@ -85,20 +91,28 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -limiter_idp = SubcellLimiterIDP(equations, basis; - local_twosided_variables_cons = ["rho" * string(i) - for i in eachcomponent(equations)]) -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +limiter_idp = SubcellLimiterIDP( + equations, basis; + local_twosided_variables_cons = [ + "rho" * string(i) + for i in eachcomponent(equations) + ] +) +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.25, -2.225) -coordinates_max = (2.20, 2.225) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 1_000_000) +coordinates_max = (2.2, 2.225) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 1_000_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -111,30 +125,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 300 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (Trixi.density,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (Trixi.density,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 600, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 600, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback(save_errors = false)) -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 - save_everystep = false, callback = callbacks); +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 + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl index be14c448e4..191776cd2f 100644 --- a/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl +++ b/examples/tree_2d_dgsem/elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl @@ -5,8 +5,10 @@ using Trixi # semidiscretization of the compressible Euler multicomponent equations # 1) Dry Air 2) Helium + 28% Air -equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.648), - gas_constants = (0.287, 1.578)) +equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.648), + gas_constants = (0.287, 1.578) +) """ initial_condition_shock_bubble(x, t, equations::CompressibleEulerMulticomponentEquations2D{5, 2}) @@ -16,9 +18,13 @@ A shock-bubble testcase for multicomponent Euler equations Formulation of Entropy-Stable schemes for the multicomponent compressible Euler equations [arXiv: 1904.00972](https://arxiv.org/abs/1904.00972) """ -function initial_condition_shock_bubble(x, t, - equations::CompressibleEulerMulticomponentEquations2D{5, - 2}) +function initial_condition_shock_bubble( + x, t, + equations::CompressibleEulerMulticomponentEquations2D{ + 5, + 2, + } + ) # bubble test case, see Gouasmi et al. https://arxiv.org/pdf/1904.00972 # other reference: https://www.researchgate.net/profile/Pep_Mulet/publication/222675930_A_flux-split_algorithm_applied_to_conservative_models_for_multicomponent_compressible_flows/links/568da54508aeaa1481ae7af0.pdf # typical domain is rectangular, we change it to a square, as Trixi can only do squares @@ -54,7 +60,7 @@ function initial_condition_shock_bubble(x, t, y_norm = x[2] - inicenter[2] r = sqrt(x_norm^2 + y_norm^2) - if (x[1] > 0.50) + if (x[1] > 0.5) # Set up Region III rho1 = rho1_3 rho2 = rho2_3 @@ -85,21 +91,29 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha basis = LobattoLegendreBasis(3) -limiter_idp = SubcellLimiterIDP(equations, basis; - positivity_variables_cons = ["rho" * string(i) - for i in eachcomponent(equations)]) +limiter_idp = SubcellLimiterIDP( + equations, basis; + positivity_variables_cons = [ + "rho" * string(i) + for i in eachcomponent(equations) + ] +) -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.25, -2.225) -coordinates_max = (2.20, 2.225) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 1_000_000) +coordinates_max = (2.2, 2.225) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 1_000_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -112,32 +126,42 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 300 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (Trixi.density,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (Trixi.density,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 300, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 300, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -stage_callbacks = (SubcellLimiterIDPCorrection(), - BoundsCheckCallback(save_errors = false, interval = 100)) +stage_callbacks = ( + SubcellLimiterIDPCorrection(), + BoundsCheckCallback(save_errors = false, interval = 100), +) # `interval` is used when calling this elixir in the tests with `save_errors=true`. -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 - save_everystep = false, callback = callbacks); +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 + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_eulerpolytropic_convergence.jl b/examples/tree_2d_dgsem/elixir_eulerpolytropic_convergence.jl index 95ef38eb70..1c644813c9 100644 --- a/examples/tree_2d_dgsem/elixir_eulerpolytropic_convergence.jl +++ b/examples/tree_2d_dgsem/elixir_eulerpolytropic_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,20 +11,26 @@ equations = PolytropicEulerEquations2D(gamma, kappa) initial_condition = initial_condition_convergence_test volume_flux = flux_winters_etal -solver = DGSEM(polydeg = 3, surface_flux = FluxHLL(min_max_speed_davis), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = FluxHLL(min_max_speed_davis), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - periodicity = true, - n_cells_max = 30_000) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + periodicity = true, + n_cells_max = 30_000 +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -36,28 +41,38 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_errors = (:l2_error_primitive, - :linf_error_primitive)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_errors = ( + :l2_error_primitive, + :linf_error_primitive, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_hypdiff_godunov.jl b/examples/tree_2d_dgsem/elixir_hypdiff_godunov.jl index 1700957d90..e367b6ec8e 100644 --- a/examples/tree_2d_dgsem/elixir_hypdiff_godunov.jl +++ b/examples/tree_2d_dgsem/elixir_hypdiff_godunov.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -23,8 +22,10 @@ function initial_condition_poisson_periodic(x, t, equations::HyperbolicDiffusion end initial_condition = initial_condition_poisson_periodic -@inline function source_terms_poisson_periodic(u, x, t, - equations::HyperbolicDiffusionEquations2D) +@inline function source_terms_poisson_periodic( + u, x, t, + equations::HyperbolicDiffusionEquations2D + ) # elliptic equation: -νΔϕ = f # analytical solution: phi = sin(2πx)*sin(2πy) and f = -8νπ^2 sin(2πx)*sin(2πy) @unpack inv_Tr = equations @@ -41,17 +42,23 @@ initial_condition = initial_condition_poisson_periodic end volume_flux = flux_central -solver = DGSEM(polydeg = 4, surface_flux = flux_godunov, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = flux_godunov, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_poisson_periodic) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_poisson_periodic +) ############################################################################### # ODE solvers, callbacks etc. @@ -69,22 +76,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl b/examples/tree_2d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl index e70b91906b..7bd728a87c 100644 --- a/examples/tree_2d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl +++ b/examples/tree_2d_dgsem/elixir_hypdiff_harmonic_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -7,8 +6,10 @@ using Trixi equations = HyperbolicDiffusionEquations2D() -@inline function initial_condition_harmonic_nonperiodic(x, t, - equations::HyperbolicDiffusionEquations2D) +@inline function initial_condition_harmonic_nonperiodic( + x, t, + equations::HyperbolicDiffusionEquations2D + ) # elliptic equation: -ν Δϕ = 0 in Ω, u = g on ∂Ω if t == 0.0 phi = 1.0 @@ -36,14 +37,18 @@ solver = DGSEM(polydeg = 4, surface_flux = flux_godunov) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions, - source_terms = source_terms_harmonic) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions, + source_terms = source_terms_harmonic +) ############################################################################### # ODE solvers, callbacks etc. @@ -61,22 +66,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_hypdiff_lax_friedrichs.jl b/examples/tree_2d_dgsem/elixir_hypdiff_lax_friedrichs.jl index a1a0397a46..e60209c8c5 100644 --- a/examples/tree_2d_dgsem/elixir_hypdiff_lax_friedrichs.jl +++ b/examples/tree_2d_dgsem/elixir_hypdiff_lax_friedrichs.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -23,8 +22,10 @@ function initial_condition_poisson_periodic(x, t, equations::HyperbolicDiffusion end initial_condition = initial_condition_poisson_periodic -@inline function source_terms_poisson_periodic(u, x, t, - equations::HyperbolicDiffusionEquations2D) +@inline function source_terms_poisson_periodic( + u, x, t, + equations::HyperbolicDiffusionEquations2D + ) # elliptic equation: -νΔϕ = f # analytical solution: phi = sin(2πx)*sin(2πy) and f = -8νπ^2 sin(2πx)*sin(2πy) @unpack inv_Tr = equations @@ -44,12 +45,16 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_poisson_periodic) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_poisson_periodic +) ############################################################################### # ODE solvers, callbacks etc. @@ -63,27 +68,35 @@ resid_tol = 5.0e-12 steady_state_callback = SteadyStateCallback(abstol = resid_tol, reltol = 0.0) analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = Trixi.solve(ode, Trixi.HypDiffN3Erk3Sstar52(), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = Trixi.solve( + ode, Trixi.HypDiffN3Erk3Sstar52(), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_hypdiff_nonperiodic.jl b/examples/tree_2d_dgsem/elixir_hypdiff_nonperiodic.jl index 1396481a3f..be600b53bb 100644 --- a/examples/tree_2d_dgsem/elixir_hypdiff_nonperiodic.jl +++ b/examples/tree_2d_dgsem/elixir_hypdiff_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,23 +8,29 @@ equations = HyperbolicDiffusionEquations2D() initial_condition = initial_condition_poisson_nonperiodic # 1 => -x, 2 => +x, 3 => -y, 4 => +y as usual for orientations -boundary_conditions = (x_neg = boundary_condition_poisson_nonperiodic, - x_pos = boundary_condition_poisson_nonperiodic, - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic) +boundary_conditions = ( + x_neg = boundary_condition_poisson_nonperiodic, + x_pos = boundary_condition_poisson_nonperiodic, + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, +) solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000, - periodicity = (false, true)) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions, - source_terms = source_terms_poisson_nonperiodic) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000, + periodicity = (false, true) +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions, + source_terms = source_terms_poisson_nonperiodic +) ############################################################################### # ODE solvers, callbacks etc. @@ -43,22 +48,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_kpp.jl b/examples/tree_2d_dgsem/elixir_kpp.jl index b48bde0d15..bb82a7c22a 100644 --- a/examples/tree_2d_dgsem/elixir_kpp.jl +++ b/examples/tree_2d_dgsem/elixir_kpp.jl @@ -24,9 +24,11 @@ end # Since the KPP problem is a scalar equation, the entropy-conservative flux is uniquely determined @inline function Trixi.flux_ec(u_ll, u_rr, orientation::Integer, ::KPPEquation2D) # The tolerance of 1e-12 is based on experience and somewhat arbitrarily chosen - if abs(u_ll[1] - u_rr[1]) < 1e-12 - return 0.5 * (flux(u_ll, orientation, KPPEquation2D()) + - flux(u_rr, orientation, KPPEquation2D())) + if abs(u_ll[1] - u_rr[1]) < 1.0e-12 + return 0.5 * ( + flux(u_ll, orientation, KPPEquation2D()) + + flux(u_rr, orientation, KPPEquation2D()) + ) else factor = 1.0 / (u_rr[1] - u_ll[1]) if orientation == 1 @@ -39,11 +41,13 @@ end # Wavespeeds @inline wavespeed(::KPPEquation2D) = 1.0 -@inline Trixi.max_abs_speeds(u, equation::KPPEquation2D) = (wavespeed(equation), - wavespeed(equation)) +@inline Trixi.max_abs_speeds(u, equation::KPPEquation2D) = ( + wavespeed(equation), + wavespeed(equation), +) @inline Trixi.max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equation::KPPEquation2D) = wavespeed(equation) @inline Trixi.max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, equation::KPPEquation2D) = wavespeed(equation) * - norm(normal_direction) + norm(normal_direction) # Compute entropy: we use the square entropy @inline Trixi.entropy(u::Real, ::KPPEquation2D) = 0.5 * u^2 @@ -76,26 +80,34 @@ volume_flux = flux_ec # volume_flux = flux_central polydeg = 3 basis = LobattoLegendreBasis(polydeg) -shock_indicator = IndicatorHennemannGassner(equation, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = first) -volume_integral = VolumeIntegralShockCapturingHG(shock_indicator; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +shock_indicator = IndicatorHennemannGassner( + equation, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = first +) +volume_integral = VolumeIntegralShockCapturingHG( + shock_indicator; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) ############################################################################### # Set up the tree mesh (initially a Cartesian grid of [-2,2]^2) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 6, - periodicity = true, - n_cells_max = 500_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 6, + periodicity = true, + n_cells_max = 500_000 +) ############################################################################### # Create the semi discretization object @@ -103,25 +115,31 @@ semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_kpp, solve ############################################################################### # Set up adaptive mesh refinement -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 1.0, - alpha_min = 0.0001, - alpha_smooth = false, - variable = first) +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 1.0, + alpha_min = 0.0001, + alpha_smooth = false, + variable = first +) max_refinement_level = 8 -amr_controller = ControllerThreeLevelCombined(semi, amr_indicator, shock_indicator, - base_level = 2, - med_level = 0, med_threshold = 0.0003, - max_level = max_refinement_level, - max_threshold = 0.003, - max_threshold_secondary = shock_indicator.alpha_max) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevelCombined( + semi, amr_indicator, shock_indicator, + base_level = 2, + med_level = 0, med_threshold = 0.0003, + max_level = max_refinement_level, + max_threshold = 0.003, + max_threshold_secondary = shock_indicator.alpha_max +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) ############################################################################### # ODE solvers, callbacks etc. @@ -134,14 +152,18 @@ analysis_callback = AnalysisCallback(semi, interval = 200) alive_callback = AliveCallback(analysis_interval = 200) -save_solution = SaveSolutionCallback(interval = 200, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2cons) - -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, amr_callback) +save_solution = SaveSolutionCallback( + interval = 200, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2cons +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, amr_callback +) ############################################################################### # run the simulation diff --git a/examples/tree_2d_dgsem/elixir_lbm_constant.jl b/examples/tree_2d_dgsem/elixir_lbm_constant.jl index 5a4f3074e4..2f07a06c9b 100644 --- a/examples/tree_2d_dgsem/elixir_lbm_constant.jl +++ b/examples/tree_2d_dgsem/elixir_lbm_constant.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,9 +12,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_godunov) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -32,28 +33,36 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2macroscopic) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2macroscopic +) stepsize_callback = StepsizeCallback(cfl = 1.0) collision_callback = LBMCollisionCallback() -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback, - collision_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback, + collision_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_lbm_couette.jl b/examples/tree_2d_dgsem/elixir_lbm_couette.jl index 1ba040405d..cb637504d4 100644 --- a/examples/tree_2d_dgsem/elixir_lbm_couette.jl +++ b/examples/tree_2d_dgsem/elixir_lbm_couette.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -23,7 +22,7 @@ function initial_condition_couette_unsteady(x, t, equations::LatticeBoltzmannEqu for m in 1:100 lambda_m = m * pi / L v1 += 2 * u0 * (-1)^m / (lambda_m * L) * exp(-nu * lambda_m^2 * t) * - sin(lambda_m * x2) + sin(lambda_m * x2) end rho = 1 @@ -42,17 +41,23 @@ Moving *upper* wall boundary condition for a Couette flow setup. To be used in c [`boundary_condition_noslip_wall`](@ref) for the lower wall and [`boundary_condition_periodic`](@ref) for the lateral boundaries. """ -function boundary_condition_couette(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::LatticeBoltzmannEquations2D) - return boundary_condition_moving_wall_ypos(u_inner, orientation, direction, x, t, - surface_flux_function, equations) +function boundary_condition_couette( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LatticeBoltzmannEquations2D + ) + return boundary_condition_moving_wall_ypos( + u_inner, orientation, direction, x, t, + surface_flux_function, equations + ) end -function boundary_condition_moving_wall_ypos(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::LatticeBoltzmannEquations2D) - @assert direction==4 "moving wall assumed in +y direction" +function boundary_condition_moving_wall_ypos( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LatticeBoltzmannEquations2D + ) + @assert direction == 4 "moving wall assumed in +y direction" @unpack rho0, u0, weights, c_s = equations cs_squared = c_s^2 @@ -72,22 +77,28 @@ function boundary_condition_moving_wall_ypos(u_inner, orientation, direction, x, # Calculate boundary flux (u_inner is "left" of boundary, u_boundary is "right" of boundary) return surface_flux_function(u_inner, u_boundary, orientation, equations) end -boundary_conditions = (x_neg = boundary_condition_periodic, - x_pos = boundary_condition_periodic, - y_neg = boundary_condition_noslip_wall, - y_pos = boundary_condition_couette) +boundary_conditions = ( + x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_noslip_wall, + y_pos = boundary_condition_couette, +) solver = DGSEM(polydeg = 3, surface_flux = flux_godunov) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - periodicity = (true, false), - n_cells_max = 10_000) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + periodicity = (true, false), + n_cells_max = 10_000 +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -110,30 +121,38 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) # Use `typeof(macroscopic)` to avoid having to explicitly add `using StaticArrays` convert(typeof(macroscopic), (rho, v1 / equations.u0, v2 / equations.u0, p)) end -function Trixi.varnames(::typeof(macroscopic_normalized), - equations::LatticeBoltzmannEquations2D) +function Trixi.varnames( + ::typeof(macroscopic_normalized), + equations::LatticeBoltzmannEquations2D + ) ("rho", "v1_normalized", "v2_normalized", "p") end -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = macroscopic_normalized) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = macroscopic_normalized +) stepsize_callback = StepsizeCallback(cfl = 1.0) collision_callback = LBMCollisionCallback() -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback, - collision_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + collision_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_lbm_lid_driven_cavity.jl b/examples/tree_2d_dgsem/elixir_lbm_lid_driven_cavity.jl index d00926cafa..1a5dbe5eb7 100644 --- a/examples/tree_2d_dgsem/elixir_lbm_lid_driven_cavity.jl +++ b/examples/tree_2d_dgsem/elixir_lbm_lid_driven_cavity.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -32,17 +31,23 @@ initial_condition = initial_condition_lid_driven_cavity Boundary condition for a lid-driven cavity flow setup, where the top lid (+y boundary) is a moving no-slip wall. To be used in combination with [`initial_condition_lid_driven_cavity`](@ref). """ -function boundary_condition_lid_driven_cavity(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::LatticeBoltzmannEquations2D) - return boundary_condition_moving_wall_ypos(u_inner, orientation, direction, x, t, - surface_flux_function, equations) +function boundary_condition_lid_driven_cavity( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LatticeBoltzmannEquations2D + ) + return boundary_condition_moving_wall_ypos( + u_inner, orientation, direction, x, t, + surface_flux_function, equations + ) end -function boundary_condition_moving_wall_ypos(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::LatticeBoltzmannEquations2D) - @assert direction==4 "moving wall assumed in +y direction" +function boundary_condition_moving_wall_ypos( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LatticeBoltzmannEquations2D + ) + @assert direction == 4 "moving wall assumed in +y direction" @unpack rho0, u0, weights, c_s = equations cs_squared = c_s^2 @@ -62,22 +67,28 @@ function boundary_condition_moving_wall_ypos(u_inner, orientation, direction, x, # Calculate boundary flux (u_inner is "left" of boundary, u_boundary is "right" of boundary) return surface_flux_function(u_inner, u_boundary, orientation, equations) end -boundary_conditions = (x_neg = boundary_condition_noslip_wall, - x_pos = boundary_condition_noslip_wall, - y_neg = boundary_condition_noslip_wall, - y_pos = boundary_condition_lid_driven_cavity) +boundary_conditions = ( + x_neg = boundary_condition_noslip_wall, + x_pos = boundary_condition_noslip_wall, + y_neg = boundary_condition_noslip_wall, + y_pos = boundary_condition_lid_driven_cavity, +) solver = DGSEM(polydeg = 5, surface_flux = flux_godunov) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - periodicity = false, - n_cells_max = 10_000) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + periodicity = false, + n_cells_max = 10_000 +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -92,25 +103,31 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2macroscopic) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2macroscopic +) stepsize_callback = StepsizeCallback(cfl = 1.0) collision_callback = LBMCollisionCallback() -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback, - collision_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + collision_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_linearizedeuler_convergence.jl b/examples/tree_2d_dgsem/elixir_linearizedeuler_convergence.jl index 93272833b7..e2a527087a 100644 --- a/examples/tree_2d_dgsem/elixir_linearizedeuler_convergence.jl +++ b/examples/tree_2d_dgsem/elixir_linearizedeuler_convergence.jl @@ -4,8 +4,10 @@ using Trixi ############################################################################### # semidiscretization of the linearized Euler equations -equations = LinearizedEulerEquations2D(v_mean_global = (0.0, 0.0), c_mean_global = 1.0, - rho_mean_global = 1.0) +equations = LinearizedEulerEquations2D( + v_mean_global = (0.0, 0.0), c_mean_global = 1.0, + rho_mean_global = 1.0 +) initial_condition = initial_condition_convergence_test @@ -16,9 +18,11 @@ coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) # A semidiscretization collects data structures and functions for the spatial discretization semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -40,8 +44,10 @@ analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = analysis_interval, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = analysis_interval, + solution_variables = cons2prim +) # The AliveCallback prints short status information in regular intervals alive_callback = AliveCallback(analysis_interval = analysis_interval) @@ -50,16 +56,20 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 0.8) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # print the timer summary summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_linearizedeuler_gauss_wall.jl b/examples/tree_2d_dgsem/elixir_linearizedeuler_gauss_wall.jl index fad03fab6e..392c403c7d 100644 --- a/examples/tree_2d_dgsem/elixir_linearizedeuler_gauss_wall.jl +++ b/examples/tree_2d_dgsem/elixir_linearizedeuler_gauss_wall.jl @@ -1,12 +1,13 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the linearized Euler equations -equations = LinearizedEulerEquations2D(v_mean_global = (0.5, 0.0), c_mean_global = 1.0, - rho_mean_global = 1.0) +equations = LinearizedEulerEquations2D( + v_mean_global = (0.5, 0.0), c_mean_global = 1.0, + rho_mean_global = 1.0 +) # Create DG solver with polynomial degree = 5 and upwind flux as surface flux solver = DGSEM(polydeg = 5, surface_flux = flux_godunov) @@ -15,10 +16,12 @@ coordinates_min = (-100.0, 0.0) # minimum coordinates (min(x), min(y)) coordinates_max = (100.0, 200.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 100_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 100_000, + periodicity = false +) function initial_condition_gauss_wall(x, t, equations::LinearizedEulerEquations2D) v1_prime = 0.0 @@ -29,8 +32,10 @@ end initial_condition = initial_condition_gauss_wall # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition_wall) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition_wall +) ############################################################################### # ODE solvers, callbacks etc. @@ -53,16 +58,20 @@ save_solution = SaveSolutionCallback(interval = 100) stepsize_callback = StepsizeCallback(cfl = 0.7) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks) +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/tree_2d_dgsem/elixir_mhd_alfven_wave.jl b/examples/tree_2d_dgsem/elixir_mhd_alfven_wave.jl index 377a07e947..f1f6c70367 100644 --- a/examples/tree_2d_dgsem/elixir_mhd_alfven_wave.jl +++ b/examples/tree_2d_dgsem/elixir_mhd_alfven_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,15 +9,19 @@ equations = IdealGlmMhdEquations2D(gamma) initial_condition = initial_condition_convergence_test volume_flux = (flux_central, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (sqrt(2.0), sqrt(2.0)) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -31,37 +34,47 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal, - energy_magnetic, - cross_helicity)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + energy_magnetic, + cross_helicity, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.5 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhd_alfven_wave_mortar.jl b/examples/tree_2d_dgsem/elixir_mhd_alfven_wave_mortar.jl index 0200b59184..477e20181d 100644 --- a/examples/tree_2d_dgsem/elixir_mhd_alfven_wave_mortar.jl +++ b/examples/tree_2d_dgsem/elixir_mhd_alfven_wave_mortar.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,19 +9,29 @@ equations = IdealGlmMhdEquations2D(gamma) initial_condition = initial_condition_convergence_test volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_hlle, - flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = ( + flux_hlle, + flux_nonconservative_powell, + ), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (sqrt(2.0), sqrt(2.0)) -refinement_patches = ((type = "box", coordinates_min = 0.25 .* coordinates_max, - coordinates_max = 0.75 .* coordinates_max),) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - refinement_patches = refinement_patches, - n_cells_max = 10_000) +refinement_patches = ( + ( + type = "box", coordinates_min = 0.25 .* coordinates_max, + coordinates_max = 0.75 .* coordinates_max, + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + refinement_patches = refinement_patches, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -35,37 +44,47 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal, - energy_magnetic, - cross_helicity)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + energy_magnetic, + cross_helicity, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhd_blast_wave.jl b/examples/tree_2d_dgsem/elixir_mhd_blast_wave.jl index a0909ca758..24a96e662d 100644 --- a/examples/tree_2d_dgsem/elixir_mhd_blast_wave.jl +++ b/examples/tree_2d_dgsem/elixir_mhd_blast_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -42,21 +41,27 @@ initial_condition = initial_condition_blast_wave surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_central, flux_nonconservative_powell) basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-0.5, -0.5) coordinates_max = (0.5, 0.5) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -73,41 +78,53 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = false, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 6, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 7, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = false, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 6, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 7, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) cfl = 0.8 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, - amr_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhd_ec.jl b/examples/tree_2d_dgsem/elixir_mhd_ec.jl index 173d3ed448..0786a73774 100644 --- a/examples/tree_2d_dgsem/elixir_mhd_ec.jl +++ b/examples/tree_2d_dgsem/elixir_mhd_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,15 +9,19 @@ equations = IdealGlmMhdEquations2D(1.4) initial_condition = initial_condition_weak_blast_wave volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -35,27 +38,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhd_orszag_tang.jl b/examples/tree_2d_dgsem/elixir_mhd_orszag_tang.jl index 7f26f270d6..a42ba9a2a2 100644 --- a/examples/tree_2d_dgsem/elixir_mhd_orszag_tang.jl +++ b/examples/tree_2d_dgsem/elixir_mhd_orszag_tang.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -34,21 +33,27 @@ initial_condition = initial_condition_orszag_tang surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_central, flux_nonconservative_powell) basis = LobattoLegendreBasis(3) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -65,41 +70,53 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = false, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 6, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 6, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = false, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 6, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 6, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) cfl = 1.25 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, - amr_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhd_rotor.jl b/examples/tree_2d_dgsem/elixir_mhd_rotor.jl index 3109b1ce30..28b5ef26ac 100644 --- a/examples/tree_2d_dgsem/elixir_mhd_rotor.jl +++ b/examples/tree_2d_dgsem/elixir_mhd_rotor.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -48,21 +47,27 @@ surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -79,41 +84,53 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = false, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 6, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 6, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = false, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 6, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 6, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) cfl = 0.35 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, - amr_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhd_shockcapturing_subcell.jl b/examples/tree_2d_dgsem/elixir_mhd_shockcapturing_subcell.jl index 74d0370647..b0acb05018 100644 --- a/examples/tree_2d_dgsem/elixir_mhd_shockcapturing_subcell.jl +++ b/examples/tree_2d_dgsem/elixir_mhd_shockcapturing_subcell.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -50,20 +49,26 @@ surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell_local_symmetric volume_flux = (flux_derigs_etal, flux_nonconservative_powell_local_symmetric) basis = LobattoLegendreBasis(3) -limiter_idp = SubcellLimiterIDP(equations, basis; - positivity_variables_cons = ["rho"], - positivity_variables_nonlinear = [pressure], - positivity_correction_factor = 0.1) -volume_integral = VolumeIntegralSubcellLimiting(limiter_idp; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +limiter_idp = SubcellLimiterIDP( + equations, basis; + positivity_variables_cons = ["rho"], + positivity_variables_nonlinear = [pressure], + positivity_correction_factor = 0.1 +) +volume_integral = VolumeIntegralSubcellLimiting( + limiter_idp; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-0.5, -0.5) coordinates_max = (0.5, 0.5) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -80,28 +85,34 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 0.4 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation stage_callbacks = (SubcellLimiterIDPCorrection(), BoundsCheckCallback()) -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 - save_everystep = false, callback = callbacks); +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 + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhdmulti_convergence.jl b/examples/tree_2d_dgsem/elixir_mhdmulti_convergence.jl index 4f1f8c5f2b..97dd81e2ad 100644 --- a/examples/tree_2d_dgsem/elixir_mhdmulti_convergence.jl +++ b/examples/tree_2d_dgsem/elixir_mhdmulti_convergence.jl @@ -1,25 +1,30 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible ideal GLM-MHD equations -equations = IdealGlmMhdMulticomponentEquations2D(gammas = (5 / 3, 5 / 3, 5 / 3), - gas_constants = (2.08, 2.08, 2.08)) +equations = IdealGlmMhdMulticomponentEquations2D( + gammas = (5 / 3, 5 / 3, 5 / 3), + gas_constants = (2.08, 2.08, 2.08) +) initial_condition = initial_condition_convergence_test volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (sqrt(2), sqrt(2)) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -32,32 +37,40 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhdmulti_ec.jl b/examples/tree_2d_dgsem/elixir_mhdmulti_ec.jl index a7db9eeee9..c105726fa9 100644 --- a/examples/tree_2d_dgsem/elixir_mhdmulti_ec.jl +++ b/examples/tree_2d_dgsem/elixir_mhdmulti_ec.jl @@ -1,25 +1,30 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible ideal GLM-MHD equations -equations = IdealGlmMhdMulticomponentEquations2D(gammas = (1.4, 1.4), - gas_constants = (1.0, 1.0)) +equations = IdealGlmMhdMulticomponentEquations2D( + gammas = (1.4, 1.4), + gas_constants = (1.0, 1.0) +) initial_condition = initial_condition_weak_blast_wave volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -36,27 +41,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhdmulti_es.jl b/examples/tree_2d_dgsem/elixir_mhdmulti_es.jl index fcaabdc7a5..45f27af0fb 100644 --- a/examples/tree_2d_dgsem/elixir_mhdmulti_es.jl +++ b/examples/tree_2d_dgsem/elixir_mhdmulti_es.jl @@ -1,25 +1,30 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible ideal GLM-MHD equations -equations = IdealGlmMhdMulticomponentEquations2D(gammas = (5 / 3, 5 / 3), - gas_constants = (0.2, 0.2)) +equations = IdealGlmMhdMulticomponentEquations2D( + gammas = (5 / 3, 5 / 3), + gas_constants = (0.2, 0.2) +) initial_condition = initial_condition_weak_blast_wave volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0) coordinates_max = (2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -36,27 +41,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_mhdmulti_rotor.jl b/examples/tree_2d_dgsem/elixir_mhdmulti_rotor.jl index 5ca21cc5e9..dcbf3265dc 100644 --- a/examples/tree_2d_dgsem/elixir_mhdmulti_rotor.jl +++ b/examples/tree_2d_dgsem/elixir_mhdmulti_rotor.jl @@ -1,11 +1,12 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the compressible ideal GLM-MHD equations -equations = IdealGlmMhdMulticomponentEquations2D(gammas = (1.4, 1.4), - gas_constants = (1.0, 1.0)) +equations = IdealGlmMhdMulticomponentEquations2D( + gammas = (1.4, 1.4), + gas_constants = (1.0, 1.0) +) """ initial_condition_rotor(x, t, equations::IdealGlmMhdMulticomponentEquations2D) @@ -49,21 +50,27 @@ surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.8, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.8, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -80,41 +87,53 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = false, - variable = density_pressure) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 4, - max_level = 6, max_threshold = 0.01) -amr_callback = AMRCallback(semi, amr_controller, - interval = 6, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = false, + variable = density_pressure +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 4, + max_level = 6, max_threshold = 0.01 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 6, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) cfl = 1.0 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, - amr_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_navierstokes_convergence.jl b/examples/tree_2d_dgsem/elixir_navierstokes_convergence.jl index b0c8678baa..637b087ae1 100644 --- a/examples/tree_2d_dgsem/elixir_navierstokes_convergence.jl +++ b/examples/tree_2d_dgsem/elixir_navierstokes_convergence.jl @@ -8,22 +8,28 @@ prandtl_number() = 0.72 mu() = 0.01 equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - periodicity = (true, false), - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + periodicity = (true, false), + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # Note: the initial condition cannot be specialized to `CompressibleNavierStokesDiffusion2D` # since it is called by both the parabolic solver (which passes in `CompressibleNavierStokesDiffusion2D`) @@ -79,16 +85,24 @@ end v1_t = -pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * sin(pi_t) v1_x = pi * cos(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_y = sin(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) v1_xx = -pi * pi * sin(pi_x) * log(y + 2.0) * (1.0 - exp(-A * (y - 1.0))) * cos(pi_t) v1_xy = pi * cos(pi_x) * - (A * log(y + 2.0) * exp(-A * (y - 1.0)) + - (1.0 - exp(-A * (y - 1.0))) / (y + 2.0)) * cos(pi_t) - v1_yy = (sin(pi_x) * - (2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - - A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - - (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0))) * cos(pi_t)) + ( + A * log(y + 2.0) * exp(-A * (y - 1.0)) + + (1.0 - exp(-A * (y - 1.0))) / (y + 2.0) + ) * cos(pi_t) + v1_yy = ( + sin(pi_x) * + ( + 2.0 * A * exp(-A * (y - 1.0)) / (y + 2.0) - + A * A * log(y + 2.0) * exp(-A * (y - 1.0)) - + (1.0 - exp(-A * (y - 1.0))) / ((y + 2.0) * (y + 2.0)) + ) * cos(pi_t) + ) v2 = v1 v2_t = v1_t v2_x = v1_x @@ -119,58 +133,68 @@ end du1 = rho_t + rho_x * v1 + rho * v1_x + rho_y * v2 + rho * v2_y # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 - + 2.0 * rho * v1 * v1_x - + rho_y * v1 * v2 - + rho * v1_y * v2 - + rho * v1 * v2_y - - # stress tensor from x-direction - 4.0 / 3.0 * v1_xx * mu_ + - 2.0 / 3.0 * v2_xy * mu_ - - v1_yy * mu_ - - v2_xy * mu_) + du2 = ( + rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y - + # stress tensor from x-direction + 4.0 / 3.0 * v1_xx * mu_ + + 2.0 / 3.0 * v2_xy * mu_ - + v1_yy * mu_ - + v2_xy * mu_ + ) # y-momentum equation - du3 = (rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 - + rho * v1_x * v2 - + rho * v1 * v2_x - + rho_y * v2^2 - + 2.0 * rho * v2 * v2_y - - # stress tensor from y-direction - v1_xy * mu_ - - v2_xx * mu_ - - 4.0 / 3.0 * v2_yy * mu_ + - 2.0 / 3.0 * v1_xy * mu_) + du3 = ( + rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y - + # stress tensor from y-direction + v1_xy * mu_ - + v2_xx * mu_ - + 4.0 / 3.0 * v2_yy * mu_ + + 2.0 / 3.0 * v1_xy * mu_ + ) # total energy equation - du4 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + v2_y * (E + p) + v2 * (E_y + p_y) - - # stress tensor and temperature gradient terms from x-direction - 4.0 / 3.0 * v1_xx * v1 * mu_ + - 2.0 / 3.0 * v2_xy * v1 * mu_ - - 4.0 / 3.0 * v1_x * v1_x * mu_ + - 2.0 / 3.0 * v2_y * v1_x * mu_ - - v1_xy * v2 * mu_ - - v2_xx * v2 * mu_ - - v1_y * v2_x * mu_ - - v2_x * v2_x * mu_ - - T_const * inv_rho_cubed * - (p_xx * rho * rho - - 2.0 * p_x * rho * rho_x + - 2.0 * p * rho_x * rho_x - - p * rho * rho_xx) * mu_ - - # stress tensor and temperature gradient terms from y-direction - v1_yy * v1 * mu_ - - v2_xy * v1 * mu_ - - v1_y * v1_y * mu_ - - v2_x * v1_y * mu_ - - 4.0 / 3.0 * v2_yy * v2 * mu_ + - 2.0 / 3.0 * v1_xy * v2 * mu_ - - 4.0 / 3.0 * v2_y * v2_y * mu_ + - 2.0 / 3.0 * v1_x * v2_y * mu_ - - T_const * inv_rho_cubed * - (p_yy * rho * rho - - 2.0 * p_y * rho * rho_y + - 2.0 * p * rho_y * rho_y - - p * rho * rho_yy) * mu_) + du4 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) - + # stress tensor and temperature gradient terms from x-direction + 4.0 / 3.0 * v1_xx * v1 * mu_ + + 2.0 / 3.0 * v2_xy * v1 * mu_ - + 4.0 / 3.0 * v1_x * v1_x * mu_ + + 2.0 / 3.0 * v2_y * v1_x * mu_ - + v1_xy * v2 * mu_ - + v2_xx * v2 * mu_ - + v1_y * v2_x * mu_ - + v2_x * v2_x * mu_ - + T_const * inv_rho_cubed * + ( + p_xx * rho * rho - + 2.0 * p_x * rho * rho_x + + 2.0 * p * rho_x * rho_x - + p * rho * rho_xx + ) * mu_ - + # stress tensor and temperature gradient terms from y-direction + v1_yy * v1 * mu_ - + v2_xy * v1 * mu_ - + v1_y * v1_y * mu_ - + v2_x * v1_y * mu_ - + 4.0 / 3.0 * v2_yy * v2 * mu_ + + 2.0 / 3.0 * v1_xy * v2 * mu_ - + 4.0 / 3.0 * v2_y * v2_y * mu_ + + 2.0 / 3.0 * v1_x * v2_y * mu_ - + T_const * inv_rho_cubed * + ( + p_yy * rho * rho - + 2.0 * p_y * rho * rho_y + + 2.0 * p * rho_y * rho_y - + p * rho * rho_yy + ) * mu_ + ) return SVector(du1, du2, du3, du4) end @@ -183,26 +207,36 @@ velocity_bc_top_bottom = NoSlip() do x, t, equations return SVector(u[2], u[3]) end heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, - heat_bc_top_bottom) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall( + velocity_bc_top_bottom, + heat_bc_top_bottom +) # define inviscid boundary conditions -boundary_conditions = (; x_neg = boundary_condition_periodic, - x_pos = boundary_condition_periodic, - y_neg = boundary_condition_slip_wall, - y_pos = boundary_condition_slip_wall) +boundary_conditions = (; + x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall, +) # define viscous boundary conditions -boundary_conditions_parabolic = (; x_neg = boundary_condition_periodic, - x_pos = boundary_condition_periodic, - y_neg = boundary_condition_top_bottom, - y_pos = boundary_condition_top_bottom) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +boundary_conditions_parabolic = (; + x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_top_bottom, + y_pos = boundary_condition_top_bottom, +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -220,7 +254,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1e-5, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1.0e-5, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_navierstokes_lid_driven_cavity.jl b/examples/tree_2d_dgsem/elixir_navierstokes_lid_driven_cavity.jl index b8e20e27f6..1c8cd3f509 100644 --- a/examples/tree_2d_dgsem/elixir_navierstokes_lid_driven_cavity.jl +++ b/examples/tree_2d_dgsem/elixir_navierstokes_lid_driven_cavity.jl @@ -8,8 +8,10 @@ prandtl_number() = 0.72 mu = 0.001 equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu, + Prandtl = prandtl_number() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) @@ -18,10 +20,12 @@ coordinates_min = (-1.0, -1.0) # minimum coordinates (min(x), min(y)) coordinates_max = (1.0, 1.0) # maximum coordinates (max(x), max(y)) # Create a uniformly refined mesh -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - periodicity = false, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + periodicity = false, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure function initial_condition_cavity(x, t, equations::CompressibleEulerEquations2D) Ma = 0.1 @@ -41,16 +45,22 @@ boundary_condition_cavity = BoundaryConditionNavierStokesWall(velocity_bc_cavity boundary_conditions = boundary_condition_slip_wall -boundary_conditions_parabolic = (; x_neg = boundary_condition_cavity, - y_neg = boundary_condition_cavity, - y_pos = boundary_condition_lid, - x_pos = boundary_condition_cavity) +boundary_conditions_parabolic = (; + x_neg = boundary_condition_cavity, + y_neg = boundary_condition_cavity, + y_pos = boundary_condition_lid, + x_pos = boundary_condition_cavity, +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -68,7 +78,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_navierstokes_shearlayer_amr.jl b/examples/tree_2d_dgsem/elixir_navierstokes_shearlayer_amr.jl index a7492bafb4..c91072a9c3 100644 --- a/examples/tree_2d_dgsem/elixir_navierstokes_shearlayer_amr.jl +++ b/examples/tree_2d_dgsem/elixir_navierstokes_shearlayer_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,8 +8,10 @@ prandtl_number() = 0.72 mu = 1.0 / 3.0 * 10^(-4) # equivalent to Re = 30,000 equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu, + Prandtl = prandtl_number() +) """ A compressible version of the double shear layer initial condition. Adapted from @@ -38,17 +39,23 @@ end initial_condition = initial_condition_shear_layer volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_hllc, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hllc, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (0.0, 0.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 100_000 +) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -69,27 +76,35 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) return rho_v1 / rho end amr_indicator = IndicatorLöhner(semi, variable = v1) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 3, - med_level = 5, med_threshold = 0.2, - max_level = 7, max_threshold = 0.5) -amr_callback = AMRCallback(semi, amr_controller, - interval = 50, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 3, + med_level = 5, med_threshold = 0.2, + max_level = 7, max_threshold = 0.5 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 50, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.3) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_navierstokes_taylor_green_vortex.jl b/examples/tree_2d_dgsem/elixir_navierstokes_taylor_green_vortex.jl index c6e5f0bc40..4cab6fac60 100644 --- a/examples/tree_2d_dgsem/elixir_navierstokes_taylor_green_vortex.jl +++ b/examples/tree_2d_dgsem/elixir_navierstokes_taylor_green_vortex.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,8 +8,10 @@ prandtl_number() = 0.72 mu = 6.25e-4 # equivalent to Re = 1600 equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu, + Prandtl = prandtl_number() +) """ initial_condition_taylor_green_vortex(x, t, equations::CompressibleEulerEquations2D) @@ -21,8 +22,10 @@ This forms the basis behind the 3D case found for instance in Simulation of the Compressible Taylor Green Vortex using High-Order Flux Reconstruction Schemes [DOI: 10.2514/6.2014-3210](https://doi.org/10.2514/6.2014-3210) """ -function initial_condition_taylor_green_vortex(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_taylor_green_vortex( + x, t, + equations::CompressibleEulerEquations2D + ) A = 1.0 # magnitude of speed Ms = 0.1 # maximum Mach number @@ -37,17 +40,23 @@ end initial_condition = initial_condition_taylor_green_vortex volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_hllc, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hllc, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0) .* pi coordinates_max = (1.0, 1.0) .* pi -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 100_000 +) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -58,21 +67,29 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -time_int_tol = 1e-9 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-9 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_navierstokes_taylor_green_vortex_sutherland.jl b/examples/tree_2d_dgsem/elixir_navierstokes_taylor_green_vortex_sutherland.jl index 9598ae184c..750bf30e8c 100644 --- a/examples/tree_2d_dgsem/elixir_navierstokes_taylor_green_vortex_sutherland.jl +++ b/examples/tree_2d_dgsem/elixir_navierstokes_taylor_green_vortex_sutherland.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -8,7 +7,7 @@ using Trixi prandtl_number() = 0.72 # Use Sutherland's law for a temperature-dependent viscosity. -# For details, see e.g. +# For details, see e.g. # Frank M. White: Viscous Fluid Flow, 2nd Edition. # 1991, McGraw-Hill, ISBN, 0-07-069712-4 # Pages 28 and 29. @@ -25,8 +24,10 @@ prandtl_number() = 0.72 end equations = CompressibleEulerEquations2D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu, + Prandtl = prandtl_number() +) """ initial_condition_taylor_green_vortex(x, t, equations::CompressibleEulerEquations2D) @@ -37,8 +38,10 @@ This forms the basis behind the 3D case found for instance in Simulation of the Compressible Taylor Green Vortex using High-Order Flux Reconstruction Schemes [DOI: 10.2514/6.2014-3210](https://doi.org/10.2514/6.2014-3210) """ -function initial_condition_taylor_green_vortex(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_taylor_green_vortex( + x, t, + equations::CompressibleEulerEquations2D + ) A = 1.0 # magnitude of speed Ms = 0.1 # maximum Mach number @@ -53,17 +56,23 @@ end initial_condition = initial_condition_taylor_green_vortex volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_hllc, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hllc, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0) .* pi coordinates_max = (1.0, 1.0) .* pi -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 100_000 +) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -74,21 +83,29 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -time_int_tol = 1e-9 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-9 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_ec.jl b/examples/tree_2d_dgsem/elixir_shallowwater_ec.jl index 8221dfebe3..26acd9ecb2 100644 --- a/examples/tree_2d_dgsem/elixir_shallowwater_ec.jl +++ b/examples/tree_2d_dgsem/elixir_shallowwater_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,18 +15,22 @@ initial_condition = initial_condition_weak_blast_wave # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 4, - surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, + surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) # Create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -47,8 +50,10 @@ ode = semidiscretize(semi, tspan) # In contrast to the usual signature of initial conditions, this one get passed the # `element_id` explicitly. In particular, this initial conditions works as intended # only for the TreeMesh2D with initial_refinement_level=2. -function initial_condition_ec_discontinuous_bottom(x, t, element_id, - equations::ShallowWaterEquations2D) +function initial_condition_ec_discontinuous_bottom( + x, t, element_id, + equations::ShallowWaterEquations2D + ) # Set up polar coordinates inicenter = SVector(0.7, 0.7) x_norm = x[1] - inicenter[1] @@ -83,10 +88,14 @@ u = Trixi.wrap_array(ode.u0, semi) # reset the initial condition for element in eachelement(semi.solver, semi.cache) for j in eachnode(semi.solver), i in eachnode(semi.solver) - x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, - semi.solver, i, j, element) - u_node = initial_condition_ec_discontinuous_bottom(x_node, first(tspan), element, - equations) + x_node = Trixi.get_node_coords( + semi.cache.elements.node_coordinates, equations, + semi.solver, i, j, element + ) + u_node = initial_condition_ec_discontinuous_bottom( + x_node, first(tspan), element, + equations + ) Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) end end @@ -101,19 +110,25 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(dt = 0.2, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + dt = 0.2, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_source_terms.jl b/examples/tree_2d_dgsem/elixir_shallowwater_source_terms.jl index c92e885c16..6f0bab79ab 100644 --- a/examples/tree_2d_dgsem/elixir_shallowwater_source_terms.jl +++ b/examples/tree_2d_dgsem/elixir_shallowwater_source_terms.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,23 +12,29 @@ initial_condition = initial_condition_convergence_test # MMS EOC test # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = (0.0, 0.0) coordinates_max = (sqrt(2.0), sqrt(2.0)) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000, + periodicity = true +) # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -44,9 +49,11 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 200, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 200, + save_initial_solution = true, + save_final_solution = true +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) @@ -54,6 +61,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, sav # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_source_terms_dirichlet.jl b/examples/tree_2d_dgsem/elixir_shallowwater_source_terms_dirichlet.jl index f7544b1e32..e5bd656fe5 100644 --- a/examples/tree_2d_dgsem/elixir_shallowwater_source_terms_dirichlet.jl +++ b/examples/tree_2d_dgsem/elixir_shallowwater_source_terms_dirichlet.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,24 +14,30 @@ boundary_condition = BoundaryConditionDirichlet(initial_condition) # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = (0.0, 0.0) coordinates_max = (sqrt(2.0), sqrt(2.0)) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000, + periodicity = false +) # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -47,9 +52,11 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 200, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 200, + save_initial_solution = true, + save_final_solution = true +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) @@ -57,6 +64,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, sav # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_wall.jl b/examples/tree_2d_dgsem/elixir_shallowwater_wall.jl index f8f601d412..5222aa42cf 100644 --- a/examples/tree_2d_dgsem/elixir_shallowwater_wall.jl +++ b/examples/tree_2d_dgsem/elixir_shallowwater_wall.jl @@ -6,7 +6,7 @@ using Trixi equations = ShallowWaterEquations2D(gravity_constant = 9.81, H0 = 3.25) -# An initial condition with a bottom topography and a perturbation in the waterheight to test +# An initial condition with a bottom topography and a perturbation in the waterheight to test # boundary_condition_slip_wall function initial_condition_perturbation(x, t, equations::ShallowWaterEquations2D) # Set the background values @@ -31,22 +31,28 @@ boundary_condition = boundary_condition_slip_wall volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) surface_flux = (flux_lax_friedrichs, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 3, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a non-periodic mesh coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000, + periodicity = false +) # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition +) ############################################################################### # ODE solvers, callbacks etc. @@ -64,19 +70,25 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced.jl b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced.jl index 22043392b2..062fb1af0c 100644 --- a/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced.jl +++ b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -18,8 +17,10 @@ function initial_condition_well_balancedness(x, t, equations::ShallowWaterEquati v2 = 0.0 # bottom topography taken from Pond.control in [HOHQMesh](https://github.com/trixi-framework/HOHQMesh) x1, x2 = x - b = (1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + - 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2))) + b = ( + 1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + + 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2)) + ) return prim2cons(SVector(H, v1, v2, b), equations) end @@ -30,17 +31,21 @@ initial_condition = initial_condition_well_balancedness volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal) -solver = DGSEM(polydeg = 4, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) # Create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -61,8 +66,10 @@ ode = semidiscretize(semi, tspan) # In contrast to the usual signature of initial conditions, this one get passed the # `element_id` explicitly. In particular, this initial conditions works as intended # only for the TreeMesh2D with initial_refinement_level=2. -function initial_condition_discontinuous_well_balancedness(x, t, element_id, - equations::ShallowWaterEquations2D) +function initial_condition_discontinuous_well_balancedness( + x, t, element_id, + equations::ShallowWaterEquations2D + ) # Set the background values H = equations.H0 v1 = 0.0 @@ -82,10 +89,14 @@ u = Trixi.wrap_array(ode.u0, semi) # reset the initial condition for element in eachelement(semi.solver, semi.cache) for j in eachnode(semi.solver), i in eachnode(semi.solver) - x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, - semi.solver, i, j, element) - u_node = initial_condition_discontinuous_well_balancedness(x_node, first(tspan), - element, equations) + x_node = Trixi.get_node_coords( + semi.cache.elements.node_coordinates, equations, + semi.solver, i, j, element + ) + u_node = initial_condition_discontinuous_well_balancedness( + x_node, first(tspan), + element, equations + ) Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) end end @@ -96,24 +107,32 @@ end summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (lake_at_rest_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (lake_at_rest_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wall.jl b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wall.jl index 19073b0504..38e7523003 100644 --- a/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wall.jl +++ b/examples/tree_2d_dgsem/elixir_shallowwater_well_balanced_wall.jl @@ -17,8 +17,10 @@ function initial_condition_well_balancedness(x, t, equations::ShallowWaterEquati v2 = 0.0 # bottom topography taken from Pond.control in [HOHQMesh](https://github.com/trixi-framework/HOHQMesh) x1, x2 = x - b = (1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + - 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2))) + b = ( + 1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + + 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2)) + ) return prim2cons(SVector(H, v1, v2, b), equations) end @@ -31,22 +33,28 @@ boundary_condition = boundary_condition_slip_wall volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal) -solver = DGSEM(polydeg = 4, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the TreeMesh and setup a periodic mesh coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000, + periodicity = false +) # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition +) ############################################################################### # ODE solvers, callbacks etc. @@ -64,8 +72,10 @@ ode = semidiscretize(semi, tspan) # In contrast to the usual signature of initial conditions, this one get passed the # `element_id` explicitly. In particular, this initial conditions works as intended # only for the TreeMesh2D with initial_refinement_level=2. -function initial_condition_discontinuous_well_balancedness(x, t, element_id, - equations::ShallowWaterEquations2D) +function initial_condition_discontinuous_well_balancedness( + x, t, element_id, + equations::ShallowWaterEquations2D + ) # Set the background values H = equations.H0 v1 = 0.0 @@ -85,10 +95,14 @@ u = Trixi.wrap_array(ode.u0, semi) # reset the initial condition for element in eachelement(semi.solver, semi.cache) for j in eachnode(semi.solver), i in eachnode(semi.solver) - x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, - semi.solver, i, j, element) - u_node = initial_condition_discontinuous_well_balancedness(x_node, first(tspan), - element, equations) + x_node = Trixi.get_node_coords( + semi.cache.elements.node_coordinates, equations, + semi.solver, i, j, element + ) + u_node = initial_condition_discontinuous_well_balancedness( + x_node, first(tspan), + element, equations + ) Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) end end @@ -99,24 +113,32 @@ end summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (lake_at_rest_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (lake_at_rest_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_2d_fdsbp/elixir_advection_extended.jl b/examples/tree_2d_fdsbp/elixir_advection_extended.jl index 8716a9a6b7..f521a66310 100644 --- a/examples/tree_2d_fdsbp/elixir_advection_extended.jl +++ b/examples/tree_2d_fdsbp/elixir_advection_extended.jl @@ -12,19 +12,25 @@ equations = LinearScalarAdvectionEquation2D(advection_velocity) initial_condition = initial_condition_convergence_test -D_SBP = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order = 1, accuracy_order = 4, - xmin = 0.0, xmax = 1.0, N = 100) -solver = FDSBP(D_SBP, - surface_integral = SurfaceIntegralStrongForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralStrongForm()) +D_SBP = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, accuracy_order = 4, + xmin = 0.0, xmax = 1.0, N = 100 +) +solver = FDSBP( + D_SBP, + surface_integral = SurfaceIntegralStrongForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralStrongForm() +) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 1, - n_cells_max = 30_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 1, + n_cells_max = 30_000, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -37,18 +43,24 @@ ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (energy_total,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (energy_total,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-9, reltol = 1.0e-9, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-9, reltol = 1.0e-9, + ode_default_options()..., callback = callbacks +) summary_callback() diff --git a/examples/tree_2d_fdsbp/elixir_euler_convergence.jl b/examples/tree_2d_fdsbp/elixir_euler_convergence.jl index 123be02bd1..e21729b84b 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_convergence.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_convergence.jl @@ -12,25 +12,33 @@ equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 16) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 16 +) flux_splitting = splitting_steger_warming -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000, - periodicity = true) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000, + periodicity = true +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -41,24 +49,32 @@ ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (energy_total,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (energy_total,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, - save_solution, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + save_solution, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(); abstol = 1.0e-9, reltol = 1.0e-9, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, SSPRK43(); abstol = 1.0e-9, reltol = 1.0e-9, + ode_default_options()..., callback = callbacks +) summary_callback() diff --git a/examples/tree_2d_fdsbp/elixir_euler_kelvin_helmholtz_instability.jl b/examples/tree_2d_fdsbp/elixir_euler_kelvin_helmholtz_instability.jl index 4c41b84af3..ffe519ef9e 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_kelvin_helmholtz_instability.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_kelvin_helmholtz_instability.jl @@ -9,8 +9,10 @@ using Trixi equations = CompressibleEulerEquations2D(1.4) -function initial_condition_kelvin_helmholtz_instability(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_kelvin_helmholtz_instability( + x, t, + equations::CompressibleEulerEquations2D + ) # change discontinuity to tanh # typical resolution 128^2, 256^2 # domain size is [-1,+1]^2 @@ -26,22 +28,28 @@ end initial_condition = initial_condition_kelvin_helmholtz_instability -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 16) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 16 +) flux_splitting = splitting_vanleer_haenel -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = (-1.0, -1.0) coordinates_max = (1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -54,26 +62,36 @@ ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (entropy, - energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + entropy, + energy_total, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, - save_solution, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + save_solution, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, dt = 1e-3, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, dt = 1.0e-3, + ode_default_options()..., callback = callbacks +) summary_callback() diff --git a/examples/tree_2d_fdsbp/elixir_euler_vortex.jl b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl index d0847cc801..4b08053180 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_vortex.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl @@ -55,22 +55,28 @@ end initial_condition = initial_condition_isentropic_vortex -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 16) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 16 +) flux_splitting = splitting_steger_warming -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = (-10.0, -10.0) coordinates_max = (10.0, 10.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -87,19 +93,25 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, - save_solution, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + save_solution, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, dt = 1e-3, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, dt = 1.0e-3, + ode_default_options()..., callback = callbacks +) summary_callback() diff --git a/examples/tree_3d_dgsem/elixir_advection_amr.jl b/examples/tree_3d_dgsem/elixir_advection_amr.jl index 19a9bd18a8..3bd7a33261 100644 --- a/examples/tree_3d_dgsem/elixir_advection_amr.jl +++ b/examples/tree_3d_dgsem/elixir_advection_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,9 +12,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0, -5.0) coordinates_max = (5.0, 5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 30_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -28,38 +29,50 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_advection_basic.jl b/examples/tree_3d_dgsem/elixir_advection_basic.jl index 3ea50423be..e808b60fd6 100644 --- a/examples/tree_3d_dgsem/elixir_advection_basic.jl +++ b/examples/tree_3d_dgsem/elixir_advection_basic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,13 +14,17 @@ coordinates_min = (-1.0, -1.0, -1.0) # minimum coordinates (min(x), min(y), min( coordinates_max = (1.0, 1.0, 1.0) # maximum coordinates (max(x), max(y), max(z)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -37,23 +40,29 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.2) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_3d_dgsem/elixir_advection_diffusion_amr.jl b/examples/tree_3d_dgsem/elixir_advection_diffusion_amr.jl index 6a8f8d0259..40d48a8aee 100644 --- a/examples/tree_3d_dgsem/elixir_advection_diffusion_amr.jl +++ b/examples/tree_3d_dgsem/elixir_advection_diffusion_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,13 +14,17 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-1.0, -1.0, -1.0) coordinates_max = (1.0, 1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 80_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 80_000 +) # Define initial condition -function initial_condition_diffusive_convergence_test(x, t, - equation::LinearScalarAdvectionEquation3D) +function initial_condition_diffusive_convergence_test( + x, t, + equation::LinearScalarAdvectionEquation3D + ) # Store translated coordinate for easy use of exact solution x_trans = x - equation.advection_velocity * t @@ -41,11 +44,15 @@ boundary_conditions = boundary_condition_periodic boundary_conditions_parabolic = boundary_condition_periodic # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -56,38 +63,50 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 3, - med_level = 4, med_threshold = 1.2, - max_level = 5, max_threshold = 1.45) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 3, + med_level = 4, med_threshold = 1.2, + max_level = 5, max_threshold = 1.45 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - amr_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + amr_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_advection_diffusion_nonperiodic.jl b/examples/tree_3d_dgsem/elixir_advection_diffusion_nonperiodic.jl index 4c30406680..954aa345e3 100644 --- a/examples/tree_3d_dgsem/elixir_advection_diffusion_nonperiodic.jl +++ b/examples/tree_3d_dgsem/elixir_advection_diffusion_nonperiodic.jl @@ -16,10 +16,12 @@ coordinates_min = (-1.0, -0.5, -0.25) # minimum coordinates (min(x), min(y), min coordinates_max = (0.0, 0.5, 0.25) # maximum coordinates (max(x), max(y), max(z)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - periodicity = false, - n_cells_max = 30_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + periodicity = false, + n_cells_max = 30_000 +) # set maximum capacity of tree data structure # Example setup taken from # - Truman Ellis, Jesse Chan, and Leszek Demkowicz (2016). @@ -40,21 +42,27 @@ function initial_condition_eriksson_johnson(x, t, equations) end initial_condition = initial_condition_eriksson_johnson -boundary_conditions = (; x_neg = BoundaryConditionDirichlet(initial_condition), - y_neg = BoundaryConditionDirichlet(initial_condition), - z_neg = boundary_condition_do_nothing, - y_pos = BoundaryConditionDirichlet(initial_condition), - x_pos = boundary_condition_do_nothing, - z_pos = boundary_condition_do_nothing) +boundary_conditions = (; + x_neg = BoundaryConditionDirichlet(initial_condition), + y_neg = BoundaryConditionDirichlet(initial_condition), + z_neg = boundary_condition_do_nothing, + y_pos = BoundaryConditionDirichlet(initial_condition), + x_pos = boundary_condition_do_nothing, + z_pos = boundary_condition_do_nothing, +) boundary_conditions_parabolic = BoundaryConditionDirichlet(initial_condition) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) +semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) +) ############################################################################### # ODE solvers, callbacks etc. @@ -82,8 +90,10 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback) # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks time_int_tol = 1.0e-11 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/tree_3d_dgsem/elixir_advection_extended.jl b/examples/tree_3d_dgsem/elixir_advection_extended.jl index efc20c64f6..cf3e465f75 100644 --- a/examples/tree_3d_dgsem/elixir_advection_extended.jl +++ b/examples/tree_3d_dgsem/elixir_advection_extended.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -24,14 +23,18 @@ coordinates_min = (-1.0, -1.0, -1.0) # minimum coordinates (min(x), min(y), min( coordinates_max = (1.0, 1.0, 1.0) # maximum coordinates (max(x), max(y), max(z)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000, # set maximum capacity of tree data structure - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000, # set maximum capacity of tree data structure + periodicity = true +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -46,38 +49,48 @@ summary_callback = SummaryCallback() # The AnalysisCallback allows to analyse the solution in regular intervals and prints the results analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) # The AliveCallback prints short status information in regular intervals alive_callback = AliveCallback(analysis_interval = analysis_interval) # The SaveRestartCallback allows to save a file from which a Trixi simulation can be restarted -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.2) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/tree_3d_dgsem/elixir_advection_mortar.jl b/examples/tree_3d_dgsem/elixir_advection_mortar.jl index d27a19c7dc..9815f5870b 100644 --- a/examples/tree_3d_dgsem/elixir_advection_mortar.jl +++ b/examples/tree_3d_dgsem/elixir_advection_mortar.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,14 +12,22 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-1.0, -1.0, -1.0) coordinates_max = (1.0, 1.0, 1.0) -refinement_patches = ((type = "box", coordinates_min = (0.0, -1.0, -1.0), - coordinates_max = (1.0, 1.0, 1.0)), - (type = "box", coordinates_min = (0.0, -0.5, -0.5), - coordinates_max = (0.5, 0.5, 0.5))) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - refinement_patches = refinement_patches, - n_cells_max = 10_000) +refinement_patches = ( + ( + type = "box", coordinates_min = (0.0, -1.0, -1.0), + coordinates_max = (1.0, 1.0, 1.0), + ), + ( + type = "box", coordinates_min = (0.0, -0.5, -0.5), + coordinates_max = (0.5, 0.5, 0.5), + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + refinement_patches = refinement_patches, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -33,28 +40,36 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.2) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_advection_restart.jl b/examples/tree_3d_dgsem/elixir_advection_restart.jl index 0a3e97ece0..33067100d8 100644 --- a/examples/tree_3d_dgsem/elixir_advection_restart.jl +++ b/examples/tree_3d_dgsem/elixir_advection_restart.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -25,9 +24,11 @@ ode = semidiscretize(semi, tspan, restart_filename); # Do not overwrite the initial snapshot written by elixir_advection_extended.jl. save_solution.condition.save_initial_solution = false -integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks, maxiters = 100_000); +integrator = init( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks, maxiters = 100_000 +); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/tree_3d_dgsem/elixir_euler_amr.jl b/examples/tree_3d_dgsem/elixir_euler_amr.jl index 9bd7f74c68..6c46b1cb96 100644 --- a/examples/tree_3d_dgsem/elixir_euler_amr.jl +++ b/examples/tree_3d_dgsem/elixir_euler_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -30,9 +29,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (-5.0, -5.0, -5.0) coordinates_max = (5.0, 5.0, 5.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -45,39 +46,53 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) - -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 1.05, - max_level = 6, max_threshold = 1.3) -amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) + +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 1.05, + max_level = 6, max_threshold = 1.3 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - amr_callback, stepsize_callback); +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + amr_callback, stepsize_callback +); ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_blob_amr.jl b/examples/tree_3d_dgsem/elixir_euler_blob_amr.jl index 0ce886620c..0b2bdaf2df 100644 --- a/examples/tree_3d_dgsem/elixir_euler_blob_amr.jl +++ b/examples/tree_3d_dgsem/elixir_euler_blob_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -46,7 +45,7 @@ function initial_condition_blob(x, t, equations::CompressibleEulerEquations3D) slope = 2 # density blob rho = rho + - (Chi - 1) * 0.5 * (1 + (tanh(slope * (r + R)) - (tanh(slope * (r - R)) + 1))) + (Chi - 1) * 0.5 * (1 + (tanh(slope * (r + R)) - (tanh(slope * (r - R)) + 1))) # velocity blob is zero v1 = v1 - v1 * 0.5 * (1 + (tanh(slope * (r + R)) - (tanh(slope * (r - R)) + 1))) return prim2cons(SVector(rho, v1, v2, v3, p), equations) @@ -54,24 +53,38 @@ end initial_condition = initial_condition_blob volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_hllc, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hllc, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-20.0, -20.0, -20.0) coordinates_max = (20.0, 20.0, 20.0) -refinement_patches = ((type = "box", coordinates_min = (-20.0, -10.0, -10.0), - coordinates_max = (-10.0, 10.0, 10.0)), - (type = "box", coordinates_min = (-20.0, -5.0, -5.0), - coordinates_max = (-10.0, 5.0, 5.0)), - (type = "box", coordinates_min = (-17.0, -2.0, -2.0), - coordinates_max = (-13.0, 2.0, 2.0)), - (type = "box", coordinates_min = (-17.0, -2.0, -2.0), - coordinates_max = (-13.0, 2.0, 2.0))) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - refinement_patches = refinement_patches, - n_cells_max = 100_000) +refinement_patches = ( + ( + type = "box", coordinates_min = (-20.0, -10.0, -10.0), + coordinates_max = (-10.0, 10.0, 10.0), + ), + ( + type = "box", coordinates_min = (-20.0, -5.0, -5.0), + coordinates_max = (-10.0, 5.0, 5.0), + ), + ( + type = "box", coordinates_min = (-17.0, -2.0, -2.0), + coordinates_max = (-13.0, 2.0, 2.0), + ), + ( + type = "box", coordinates_min = (-17.0, -2.0, -2.0), + coordinates_max = (-13.0, 2.0, 2.0), + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + refinement_patches = refinement_patches, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -88,36 +101,50 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 200, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) - -amr_indicator = IndicatorLöhner(semi, - variable = Trixi.density) -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 1, - med_level = 0, med_threshold = 0.1, # med_level = current level - max_level = 6, max_threshold = 0.3) -amr_callback = AMRCallback(semi, amr_controller, - interval = 3, - adapt_initial_condition = false, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 200, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorLöhner( + semi, + variable = Trixi.density +) +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 1, + med_level = 0, med_threshold = 0.1, # med_level = current level + max_level = 6, max_threshold = 0.3 +) +amr_callback = AMRCallback( + semi, amr_controller, + interval = 3, + adapt_initial_condition = false, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.7) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) -stage_limiter! = PositivityPreservingLimiterZhangShu(thresholds = (1.0e-4, 1.0e-4), - variables = (Trixi.density, pressure)) +stage_limiter! = PositivityPreservingLimiterZhangShu( + thresholds = (1.0e-4, 1.0e-4), + variables = (Trixi.density, pressure) +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(stage_limiter!, williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_convergence.jl b/examples/tree_3d_dgsem/elixir_euler_convergence.jl index 170b292a42..b1501bab27 100644 --- a/examples/tree_3d_dgsem/elixir_euler_convergence.jl +++ b/examples/tree_3d_dgsem/elixir_euler_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,17 +8,23 @@ equations = CompressibleEulerEquations3D(2.0) initial_condition = initial_condition_eoc_test_coupled_euler_gravity -solver = DGSEM(polydeg = 3, surface_flux = flux_hll, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hll, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_eoc_test_euler) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_eoc_test_euler +) ############################################################################### # ODE solvers, callbacks etc. @@ -34,22 +39,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_convergence_pure_fv.jl b/examples/tree_3d_dgsem/elixir_euler_convergence_pure_fv.jl index 4789b46dac..ef81c39dcc 100644 --- a/examples/tree_3d_dgsem/elixir_euler_convergence_pure_fv.jl +++ b/examples/tree_3d_dgsem/elixir_euler_convergence_pure_fv.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,17 +8,23 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test -solver = DGSEM(polydeg = 3, surface_flux = flux_hllc, - volume_integral = VolumeIntegralPureLGLFiniteVolume(flux_hllc)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hllc, + volume_integral = VolumeIntegralPureLGLFiniteVolume(flux_hllc) +) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -34,22 +39,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_density_pulse.jl b/examples/tree_3d_dgsem/elixir_euler_density_pulse.jl index cad8fc578c..06670ccd0e 100644 --- a/examples/tree_3d_dgsem/elixir_euler_density_pulse.jl +++ b/examples/tree_3d_dgsem/elixir_euler_density_pulse.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -28,14 +27,18 @@ end initial_condition = initial_condition_density_pulse volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0, -2.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -52,25 +55,33 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_ec.jl b/examples/tree_3d_dgsem/elixir_euler_ec.jl index 88d7cbc7ba..3eb1df3225 100644 --- a/examples/tree_3d_dgsem/elixir_euler_ec.jl +++ b/examples/tree_3d_dgsem/elixir_euler_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,14 +9,18 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_weak_blast_wave volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0, -2.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -34,22 +37,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.3) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_mortar.jl b/examples/tree_3d_dgsem/elixir_euler_mortar.jl index cf103dc26c..02f4aa3ee4 100644 --- a/examples/tree_3d_dgsem/elixir_euler_mortar.jl +++ b/examples/tree_3d_dgsem/elixir_euler_mortar.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,15 +11,23 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (2.0, 2.0, 2.0) -refinement_patches = ((type = "box", coordinates_min = (0.5, 0.5, 0.5), - coordinates_max = (1.5, 1.5, 1.5)),) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - refinement_patches = refinement_patches, - n_cells_max = 10_000) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +refinement_patches = ( + ( + type = "box", coordinates_min = (0.5, 0.5, 0.5), + coordinates_max = (1.5, 1.5, 1.5), + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + refinement_patches = refinement_patches, + n_cells_max = 10_000 +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -35,22 +42,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.6) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_sedov_blast_wave.jl b/examples/tree_3d_dgsem/elixir_euler_sedov_blast_wave.jl index 87774ada26..3cb50af616 100644 --- a/examples/tree_3d_dgsem/elixir_euler_sedov_blast_wave.jl +++ b/examples/tree_3d_dgsem/elixir_euler_sedov_blast_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -26,7 +25,7 @@ function initial_condition_sedov_self_gravity(x, t, equations::CompressibleEuler r0 = 0.25 # = 4.0 * smallest dx (for domain length=8 and max-ref=7) E = 1.0 p_inner = (equations.gamma - 1) * E / (4 / 3 * pi * r0^3) - p_ambient = 1e-5 # = true Sedov setup + p_ambient = 1.0e-5 # = true Sedov setup # Calculate primitive variables # use a logistic function to transfer density value smoothly @@ -34,7 +33,7 @@ function initial_condition_sedov_self_gravity(x, t, equations::CompressibleEuler x0 = 1.0 # center point of function k = -50.0 # sharpness of transfer logistic_function_rho = L / (1.0 + exp(-k * (r - x0))) - rho_ambient = 1e-5 + rho_ambient = 1.0e-5 rho = max(logistic_function_rho, rho_ambient) # clip background density to not be so tiny # velocities are zero @@ -63,16 +62,18 @@ based on - https://flash.rochester.edu/site/flashcode/user_support/flash_ug_devel/node187.html#SECTION010114000000000000000 Should be used together with [`initial_condition_sedov_self_gravity`](@ref). """ -function boundary_condition_sedov_self_gravity(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::CompressibleEulerEquations3D) +function boundary_condition_sedov_self_gravity( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::CompressibleEulerEquations3D + ) # velocities are zero, density/pressure are ambient values according to # initial_condition_sedov_self_gravity - rho = 1e-5 + rho = 1.0e-5 v1 = 0.0 v2 = 0.0 v3 = 0.0 - p = 1e-5 + p = 1.0e-5 u_boundary = prim2cons(SVector(rho, v1, v2, v3, p), equations) @@ -91,25 +92,33 @@ surface_flux = flux_hll volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.7, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.7, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-4.0, -4.0, -4.0) coordinates_max = (4.0, 4.0, 4.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 1_000_000, - periodicity = false) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 1_000_000, + periodicity = false +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -124,37 +133,49 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_max = 1.0, - alpha_min = 0.0, - alpha_smooth = false, - variable = density_pressure) - -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 2, - max_level = 7, max_threshold = 0.0003) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_max = 1.0, + alpha_min = 0.0, + alpha_smooth = false, + variable = density_pressure +) + +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 2, + max_level = 7, max_threshold = 0.0003 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 0.35) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_shockcapturing.jl b/examples/tree_3d_dgsem/elixir_euler_shockcapturing.jl index 0a90615016..c55dc947b8 100644 --- a/examples/tree_3d_dgsem/elixir_euler_shockcapturing.jl +++ b/examples/tree_3d_dgsem/elixir_euler_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,21 +13,27 @@ surface_flux = flux_ranocha # OBS! Using a non-dissipative flux is only sensible volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0, -2.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -45,22 +50,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.4) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_shockcapturing_amr.jl b/examples/tree_3d_dgsem/elixir_euler_shockcapturing_amr.jl index be31fbbc42..8738308ba3 100644 --- a/examples/tree_3d_dgsem/elixir_euler_shockcapturing_amr.jl +++ b/examples/tree_3d_dgsem/elixir_euler_shockcapturing_amr.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,21 +13,27 @@ surface_flux = flux_ranocha # OBS! Using a non-dissipative flux is only sensible volume_flux = flux_ranocha polydeg = 3 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) coordinates_min = (-2.0, -2.0, -2.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -45,35 +50,47 @@ 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) - -amr_indicator = IndicatorHennemannGassner(semi, - alpha_smooth = false, - variable = density_pressure) - -amr_controller = ControllerThreeLevel(semi, amr_indicator, - base_level = 2, - max_level = 4, max_threshold = 0.0003) - -amr_callback = AMRCallback(semi, amr_controller, - interval = 1, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) + +amr_indicator = IndicatorHennemannGassner( + semi, + alpha_smooth = false, + variable = density_pressure +) + +amr_controller = ControllerThreeLevel( + semi, amr_indicator, + base_level = 2, + max_level = 4, max_threshold = 0.0003 +) + +amr_callback = AMRCallback( + semi, amr_controller, + interval = 1, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true +) stepsize_callback = StepsizeCallback(cfl = 1.3) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - amr_callback, stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + amr_callback, stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_source_terms.jl b/examples/tree_3d_dgsem/elixir_euler_source_terms.jl index 021fd09f31..b1967747aa 100644 --- a/examples/tree_3d_dgsem/elixir_euler_source_terms.jl +++ b/examples/tree_3d_dgsem/elixir_euler_source_terms.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,17 +8,23 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -34,27 +39,35 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 0.6) -time_series = TimeSeriesCallback(semi, - [(0.0, 0.0, 0.0), (0.33, 0.33, 0.33), (1.0, 1.0, 1.0)], - interval = 10) +time_series = TimeSeriesCallback( + semi, + [(0.0, 0.0, 0.0), (0.33, 0.33, 0.33), (1.0, 1.0, 1.0)], + interval = 10 +) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - time_series, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + time_series, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_euler_taylor_green_vortex.jl b/examples/tree_3d_dgsem/elixir_euler_taylor_green_vortex.jl index 135ee673e4..6c6477320c 100644 --- a/examples/tree_3d_dgsem/elixir_euler_taylor_green_vortex.jl +++ b/examples/tree_3d_dgsem/elixir_euler_taylor_green_vortex.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,8 +11,10 @@ equations = CompressibleEulerEquations3D(1.4) The classical inviscid Taylor-Green vortex. """ -function initial_condition_taylor_green_vortex(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_taylor_green_vortex( + x, t, + equations::CompressibleEulerEquations3D + ) A = 1.0 # magnitude of speed Ms = 0.1 # maximum Mach number @@ -24,22 +25,28 @@ function initial_condition_taylor_green_vortex(x, t, p = (A / Ms)^2 * rho / equations.gamma # scaling to get Ms p = p + 1.0 / 16.0 * A^2 * rho * - (cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + - cos(2 * x[2]) * cos(2 * x[3])) + ( + cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + + cos(2 * x[2]) * cos(2 * x[3]) + ) return prim2cons(SVector(rho, v1, v2, v3, p), equations) end initial_condition = initial_condition_taylor_green_vortex volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0, -1.0) .* pi coordinates_max = (1.0, 1.0, 1.0) .* pi -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -56,22 +63,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.4) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_eulergravity_convergence.jl b/examples/tree_3d_dgsem/elixir_eulergravity_convergence.jl index 0a8c427bf8..999cd6c1a8 100644 --- a/examples/tree_3d_dgsem/elixir_eulergravity_convergence.jl +++ b/examples/tree_3d_dgsem/elixir_eulergravity_convergence.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -14,13 +13,17 @@ solver_euler = DGSEM(polydeg, FluxHLL(min_max_speed_naive)) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) - -semi_euler = SemidiscretizationHyperbolic(mesh, equations_euler, initial_condition, - solver_euler, - source_terms = source_terms_eoc_test_coupled_euler_gravity) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) + +semi_euler = SemidiscretizationHyperbolic( + mesh, equations_euler, initial_condition, + solver_euler, + source_terms = source_terms_eoc_test_coupled_euler_gravity +) ############################################################################### # semidiscretization of the hyperbolic diffusion equations @@ -28,18 +31,22 @@ equations_gravity = HyperbolicDiffusionEquations3D() solver_gravity = DGSEM(polydeg, flux_lax_friedrichs) -semi_gravity = SemidiscretizationHyperbolic(mesh, equations_gravity, initial_condition, - solver_gravity, - source_terms = source_terms_harmonic) +semi_gravity = SemidiscretizationHyperbolic( + mesh, equations_gravity, initial_condition, + solver_gravity, + source_terms = source_terms_harmonic +) ############################################################################### # combining both semidiscretizations for Euler + self-gravity -parameters = ParametersEulerGravity(background_density = 2.0, # aka rho0 - gravitational_constant = 1.0, # aka G - cfl = 1.5, - resid_tol = 1.0e-10, - n_iterations_max = 1000, - timestep_gravity = timestep_gravity_erk52_3Sstar!) +parameters = ParametersEulerGravity( + background_density = 2.0, # aka rho0 + gravitational_constant = 1.0, # aka G + cfl = 1.5, + resid_tol = 1.0e-10, + n_iterations_max = 1000, + timestep_gravity = timestep_gravity_erk52_3Sstar! +) semi = SemidiscretizationEulerGravity(semi_euler, semi_gravity, parameters) @@ -51,32 +58,42 @@ ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi_euler, interval = analysis_interval, - save_analysis = true) +analysis_callback = AnalysisCallback( + semi_euler, interval = analysis_interval, + save_analysis = true +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_restart, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_restart, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary println("Number of gravity subcycles: ", semi.gravity_counter.ncalls_since_readout) diff --git a/examples/tree_3d_dgsem/elixir_hypdiff_lax_friedrichs.jl b/examples/tree_3d_dgsem/elixir_hypdiff_lax_friedrichs.jl index 7bba154a92..a48b8588ac 100644 --- a/examples/tree_3d_dgsem/elixir_hypdiff_lax_friedrichs.jl +++ b/examples/tree_3d_dgsem/elixir_hypdiff_lax_friedrichs.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -25,8 +24,10 @@ function initial_condition_poisson_periodic(x, t, equations::HyperbolicDiffusion end initial_condition = initial_condition_poisson_periodic -@inline function source_terms_poisson_periodic(u, x, t, - equations::HyperbolicDiffusionEquations3D) +@inline function source_terms_poisson_periodic( + u, x, t, + equations::HyperbolicDiffusionEquations3D + ) # elliptic equation: -νΔϕ = f # analytical solution: phi = sin(2πx)*sin(2πy) and f = -8νπ^2 sin(2πx)*sin(2πy) @unpack inv_Tr = equations @@ -48,12 +49,16 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (1.0, 1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 30_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 30_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_poisson_periodic) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_poisson_periodic +) ############################################################################### # ODE solvers, callbacks etc. @@ -67,27 +72,35 @@ resid_tol = 5.0e-12 steady_state_callback = SteadyStateCallback(abstol = resid_tol, reltol = 0.0) analysis_interval = 200 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 2.4) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = Trixi.solve(ode, Trixi.HypDiffN3Erk3Sstar52(), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = Trixi.solve( + ode, Trixi.HypDiffN3Erk3Sstar52(), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_hypdiff_nonperiodic.jl b/examples/tree_3d_dgsem/elixir_hypdiff_nonperiodic.jl index 831e01519b..4df8aaad23 100644 --- a/examples/tree_3d_dgsem/elixir_hypdiff_nonperiodic.jl +++ b/examples/tree_3d_dgsem/elixir_hypdiff_nonperiodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -8,25 +7,31 @@ using Trixi equations = HyperbolicDiffusionEquations3D() initial_condition = initial_condition_poisson_nonperiodic -boundary_conditions = (x_neg = boundary_condition_poisson_nonperiodic, - x_pos = boundary_condition_poisson_nonperiodic, - y_neg = boundary_condition_periodic, - y_pos = boundary_condition_periodic, - z_neg = boundary_condition_periodic, - z_pos = boundary_condition_periodic) +boundary_conditions = ( + x_neg = boundary_condition_poisson_nonperiodic, + x_pos = boundary_condition_poisson_nonperiodic, + y_neg = boundary_condition_periodic, + y_pos = boundary_condition_periodic, + z_neg = boundary_condition_periodic, + z_pos = boundary_condition_periodic, +) solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (1.0, 1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 30_000, - periodicity = (false, true, true)) - -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_poisson_nonperiodic, - boundary_conditions = boundary_conditions) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 30_000, + periodicity = (false, true, true) +) + +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_poisson_nonperiodic, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -40,27 +45,35 @@ resid_tol = 1.0e-5 steady_state_callback = SteadyStateCallback(abstol = resid_tol, reltol = 0.0) analysis_interval = 200 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (entropy, energy_total)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (entropy, energy_total) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.8) -callbacks = CallbackSet(summary_callback, steady_state_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, steady_state_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = Trixi.solve(ode, Trixi.HypDiffN3Erk3Sstar52(), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = Trixi.solve( + ode, Trixi.HypDiffN3Erk3Sstar52(), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_lbm_constant.jl b/examples/tree_3d_dgsem/elixir_lbm_constant.jl index ee38f62887..ae881dae77 100644 --- a/examples/tree_3d_dgsem/elixir_lbm_constant.jl +++ b/examples/tree_3d_dgsem/elixir_lbm_constant.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,9 +12,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_godunov) coordinates_min = (-1.0, -1.0, -1.0) coordinates_max = (1.0, 1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -32,28 +33,36 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 100, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 100, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2macroscopic) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2macroscopic +) stepsize_callback = StepsizeCallback(cfl = 1.0) collision_callback = LBMCollisionCallback() -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_restart, save_solution, - stepsize_callback, - collision_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_restart, save_solution, + stepsize_callback, + collision_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_lbm_taylor_green_vortex.jl b/examples/tree_3d_dgsem/elixir_lbm_taylor_green_vortex.jl index 0980ee56be..e116014d14 100644 --- a/examples/tree_3d_dgsem/elixir_lbm_taylor_green_vortex.jl +++ b/examples/tree_3d_dgsem/elixir_lbm_taylor_green_vortex.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -19,9 +18,11 @@ function initial_condition_taylor_green_vortex(x, t, equations::LatticeBoltzmann v1 = u0 * sin(x[1] / L) * cos(x[2] / L) * cos(x[3] / L) v2 = -u0 * cos(x[1] / L) * sin(x[2] / L) * cos(x[3] / L) v3 = 0 - p0 = (pressure(rho0, equations) + - rho0 * u0^2 / 16 * (cos(2 * x[1] / L) + cos(2 * x[2] / L)) * - (cos(2 * x[3] / L) + 2)) + p0 = ( + pressure(rho0, equations) + + rho0 * u0^2 / 16 * (cos(2 * x[1] / L) + cos(2 * x[2] / L)) * + (cos(2 * x[3] / L) + 2) + ) rho = density(p0, equations) return equilibrium_distribution(rho, v1, v2, v3, equations) @@ -32,9 +33,11 @@ solver = DGSEM(polydeg = 3, surface_flux = flux_godunov) coordinates_min = (-pi * L, -pi * L, -pi * L) coordinates_max = (pi * L, pi * L, pi * L) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 5, - n_cells_max = 300_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 5, + n_cells_max = 300_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -47,32 +50,40 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 20 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (Trixi.energy_kinetic_nondimensional,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = (Trixi.energy_kinetic_nondimensional,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2macroscopic) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2macroscopic +) stepsize_callback = StepsizeCallback(cfl = 0.3) collision_callback = LBMCollisionCallback() -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback, - collision_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + collision_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks, - save_start = false, alias_u0 = true); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks, + save_start = false, alias_u0 = true +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_linearizedeuler_gauss_wall.jl b/examples/tree_3d_dgsem/elixir_linearizedeuler_gauss_wall.jl index 2eb8ef822a..0bb8c6fd0f 100644 --- a/examples/tree_3d_dgsem/elixir_linearizedeuler_gauss_wall.jl +++ b/examples/tree_3d_dgsem/elixir_linearizedeuler_gauss_wall.jl @@ -4,18 +4,22 @@ using Trixi ############################################################################### # semidiscretization of the linearized Euler equations -equations = LinearizedEulerEquations3D(v_mean_global = (0.5, 0.5, 0.5), c_mean_global = 1.0, - rho_mean_global = 1.0) +equations = LinearizedEulerEquations3D( + v_mean_global = (0.5, 0.5, 0.5), c_mean_global = 1.0, + rho_mean_global = 1.0 +) solver = DGSEM(polydeg = 5, surface_flux = flux_lax_friedrichs) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (90.0, 90.0, 90.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 4, - n_cells_max = 100_000, - periodicity = false) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 4, + n_cells_max = 100_000, + periodicity = false +) # Initialize density and pressure perturbation with a Gaussian bump # that splits into radial waves which are advected with v - c and v + c. @@ -29,8 +33,10 @@ end initial_condition = initial_condition_gauss_wall # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition_wall) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition_wall +) ############################################################################### # ODE solvers, callbacks etc. @@ -50,16 +56,20 @@ analysis_callback = AnalysisCallback(semi, interval = 100) stepsize_callback = StepsizeCallback(cfl = 0.9) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, + stepsize_callback +) ############################################################################### # run the simulation # OrdinaryDiffEq's `solve` method evolves the solution in time and executes the passed callbacks -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks) +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +) # Print the timer summary summary_callback() diff --git a/examples/tree_3d_dgsem/elixir_mhd_alfven_wave.jl b/examples/tree_3d_dgsem/elixir_mhd_alfven_wave.jl index 9aab5e5878..c2a5a0db06 100644 --- a/examples/tree_3d_dgsem/elixir_mhd_alfven_wave.jl +++ b/examples/tree_3d_dgsem/elixir_mhd_alfven_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,15 +9,19 @@ equations = IdealGlmMhdEquations3D(5 / 3) initial_condition = initial_condition_convergence_test volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0, -1.0) coordinates_max = (1.0, 1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -34,26 +37,32 @@ analysis_interval = 100 analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.5 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_mhd_alfven_wave_mortar.jl b/examples/tree_3d_dgsem/elixir_mhd_alfven_wave_mortar.jl index 2fa22d535d..a9b40ad00a 100644 --- a/examples/tree_3d_dgsem/elixir_mhd_alfven_wave_mortar.jl +++ b/examples/tree_3d_dgsem/elixir_mhd_alfven_wave_mortar.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,19 +9,29 @@ equations = IdealGlmMhdEquations3D(5 / 3) initial_condition = initial_condition_convergence_test volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_hlle, - flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = ( + flux_hlle, + flux_nonconservative_powell, + ), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0, -1.0) coordinates_max = (1.0, 1.0, 1.0) -refinement_patches = ((type = "box", coordinates_min = (-0.5, -0.5, -0.5), - coordinates_max = (0.5, 0.5, 0.5)),) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - refinement_patches = refinement_patches, - n_cells_max = 10_000) +refinement_patches = ( + ( + type = "box", coordinates_min = (-0.5, -0.5, -0.5), + coordinates_max = (0.5, 0.5, 0.5), + ), +) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + refinement_patches = refinement_patches, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -38,26 +47,32 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_mhd_ec.jl b/examples/tree_3d_dgsem/elixir_mhd_ec.jl index 1f0088e82c..dcb05a9273 100644 --- a/examples/tree_3d_dgsem/elixir_mhd_ec.jl +++ b/examples/tree_3d_dgsem/elixir_mhd_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,15 +9,19 @@ equations = IdealGlmMhdEquations3D(1.4) initial_condition = initial_condition_weak_blast_wave volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 3, - surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, + surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-2.0, -2.0, -2.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -35,26 +38,32 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.4 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_mhd_ec_shockcapturing.jl b/examples/tree_3d_dgsem/elixir_mhd_ec_shockcapturing.jl index f8a72b6f45..aae029f800 100644 --- a/examples/tree_3d_dgsem/elixir_mhd_ec_shockcapturing.jl +++ b/examples/tree_3d_dgsem/elixir_mhd_ec_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,23 +12,31 @@ surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) polydeg = 4 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) coordinates_min = (-2.0, -2.0, -2.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 10_000 +) # create the semi discretization object semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -52,15 +59,19 @@ stepsize_callback = StepsizeCallback(cfl = cfl) glm_speed_callback = GlmSpeedCallback(glm_scale = 0.5, cfl = cfl) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback, - glm_speed_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_navierstokes_convergence.jl b/examples/tree_3d_dgsem/elixir_navierstokes_convergence.jl index 3ada9503c6..12b380c9e5 100644 --- a/examples/tree_3d_dgsem/elixir_navierstokes_convergence.jl +++ b/examples/tree_3d_dgsem/elixir_navierstokes_convergence.jl @@ -8,22 +8,28 @@ prandtl_number() = 0.72 mu() = 0.01 equations = CompressibleEulerEquations3D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesPrimitive()) +equations_parabolic = CompressibleNavierStokesDiffusion3D( + equations, mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesPrimitive() +) # Create DG solver with polynomial degree = 3 and (local) Lax-Friedrichs/Rusanov flux as surface flux -solver = DGSEM(polydeg = 3, surface_flux = flux_lax_friedrichs, - volume_integral = VolumeIntegralWeakForm()) +solver = DGSEM( + polydeg = 3, surface_flux = flux_lax_friedrichs, + volume_integral = VolumeIntegralWeakForm() +) coordinates_min = (-1.0, -1.0, -1.0) # minimum coordinates (min(x), min(y), min(z)) coordinates_max = (1.0, 1.0, 1.0) # maximum coordinates (max(x), max(y), max(z)) # Create a uniformly refined mesh with periodic boundaries -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - periodicity = (true, false, true), - n_cells_max = 50_000) # set maximum capacity of tree data structure +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + periodicity = (true, false, true), + n_cells_max = 50_000 +) # set maximum capacity of tree data structure # Note: the initial condition cannot be specialized to `CompressibleNavierStokesDiffusion3D` # since it is called by both the parabolic solver (which passes in `CompressibleNavierStokesDiffusion3D`) @@ -44,7 +50,7 @@ function initial_condition_navier_stokes_convergence_test(x, t, equations) rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) v1 = A2 * sin(pi_x) * log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) * sin(pi_z) * - cos(pi_t) + cos(pi_t) v2 = v1 v3 = v1 p = rho^2 @@ -75,11 +81,15 @@ end # Define auxiliary functions for the strange function of the y variable # to make expressions easier to read g = log(x[2] + 2.0) * (1.0 - exp(-A3 * (x[2] - 1.0))) - g_y = (A3 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + - (1.0 - exp(-A3 * (x[2] - 1.0))) / (x[2] + 2.0)) - g_yy = (2.0 * A3 * exp(-A3 * (x[2] - 1.0)) / (x[2] + 2.0) - + g_y = ( + A3 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + + (1.0 - exp(-A3 * (x[2] - 1.0))) / (x[2] + 2.0) + ) + g_yy = ( + 2.0 * A3 * exp(-A3 * (x[2] - 1.0)) / (x[2] + 2.0) - (1.0 - exp(-A3 * (x[2] - 1.0))) / ((x[2] + 2.0)^2) - - A3^2 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0))) + A3^2 * log(x[2] + 2.0) * exp(-A3 * (x[2] - 1.0)) + ) # Density and its derivatives rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) @@ -169,52 +179,68 @@ end # Compute the source terms # Density equation - du1 = (rho_t + rho_x * v1 + rho * v1_x - + rho_y * v2 + rho * v2_y - + rho_z * v3 + rho * v3_z) + du1 = ( + rho_t + rho_x * v1 + rho * v1_x + + rho_y * v2 + rho * v2_y + + rho_z * v3 + rho * v3_z + ) # x-momentum equation - du2 = (rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 - + 2.0 * rho * v1 * v1_x - + rho_y * v1 * v2 - + rho * v1_y * v2 - + rho * v1 * v2_y - + rho_z * v1 * v3 - + rho * v1_z * v3 - + rho * v1 * v3_z - - mu_ * (tau11_x + tau12_y + tau13_z)) + du2 = ( + rho_t * v1 + rho * v1_t + p_x + rho_x * v1^2 + + 2.0 * rho * v1 * v1_x + + rho_y * v1 * v2 + + rho * v1_y * v2 + + rho * v1 * v2_y + + rho_z * v1 * v3 + + rho * v1_z * v3 + + rho * v1 * v3_z - + mu_ * (tau11_x + tau12_y + tau13_z) + ) # y-momentum equation - du3 = (rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 - + rho * v1_x * v2 - + rho * v1 * v2_x - + rho_y * v2^2 - + 2.0 * rho * v2 * v2_y - + rho_z * v2 * v3 - + rho * v2_z * v3 - + rho * v2 * v3_z - - mu_ * (tau12_x + tau22_y + tau23_z)) + du3 = ( + rho_t * v2 + rho * v2_t + p_y + rho_x * v1 * v2 + + rho * v1_x * v2 + + rho * v1 * v2_x + + rho_y * v2^2 + + 2.0 * rho * v2 * v2_y + + rho_z * v2 * v3 + + rho * v2_z * v3 + + rho * v2 * v3_z - + mu_ * (tau12_x + tau22_y + tau23_z) + ) # z-momentum equation - du4 = (rho_t * v3 + rho * v3_t + p_z + rho_x * v1 * v3 - + rho * v1_x * v3 - + rho * v1 * v3_x - + rho_y * v2 * v3 - + rho * v2_y * v3 - + rho * v2 * v3_y - + rho_z * v3^2 - + 2.0 * rho * v3 * v3_z - - mu_ * (tau13_x + tau23_y + tau33_z)) + du4 = ( + rho_t * v3 + rho * v3_t + p_z + rho_x * v1 * v3 + + rho * v1_x * v3 + + rho * v1 * v3_x + + rho_y * v2 * v3 + + rho * v2_y * v3 + + rho * v2 * v3_y + + rho_z * v3^2 + + 2.0 * rho * v3 * v3_z - + mu_ * (tau13_x + tau23_y + tau33_z) + ) # Total energy equation - du5 = (E_t + v1_x * (E + p) + v1 * (E_x + p_x) - + v2_y * (E + p) + v2 * (E_y + p_y) - + v3_z * (E + p) + v3 * (E_z + p_z) - - # stress tensor and temperature gradient from x-direction - mu_ * (q_xx + v1_x * tau11 + v2_x * tau12 + v3_x * tau13 - + v1 * tau11_x + v2 * tau12_x + v3 * tau13_x) - - # stress tensor and temperature gradient terms from y-direction - mu_ * (q_yy + v1_y * tau12 + v2_y * tau22 + v3_y * tau23 - + v1 * tau12_y + v2 * tau22_y + v3 * tau23_y) - - # stress tensor and temperature gradient terms from z-direction - mu_ * (q_zz + v1_z * tau13 + v2_z * tau23 + v3_z * tau33 - + v1 * tau13_z + v2 * tau23_z + v3 * tau33_z)) + du5 = ( + E_t + v1_x * (E + p) + v1 * (E_x + p_x) + + v2_y * (E + p) + v2 * (E_y + p_y) + + v3_z * (E + p) + v3 * (E_z + p_z) - + # stress tensor and temperature gradient from x-direction + mu_ * ( + q_xx + v1_x * tau11 + v2_x * tau12 + v3_x * tau13 + + v1 * tau11_x + v2 * tau12_x + v3 * tau13_x + ) - + # stress tensor and temperature gradient terms from y-direction + mu_ * ( + q_yy + v1_y * tau12 + v2_y * tau22 + v3_y * tau23 + + v1 * tau12_y + v2 * tau22_y + v3 * tau23_y + ) - + # stress tensor and temperature gradient terms from z-direction + mu_ * ( + q_zz + v1_z * tau13 + v2_z * tau23 + v3_z * tau33 + + v1 * tau13_z + v2 * tau23_z + v3 * tau33_z + ) + ) return SVector(du1, du2, du3, du4, du5) end @@ -227,30 +253,40 @@ velocity_bc_top_bottom = NoSlip() do x, t, equations return SVector(u[2], u[3], u[4]) end heat_bc_top_bottom = Adiabatic((x, t, equations) -> 0.0) -boundary_condition_top_bottom = BoundaryConditionNavierStokesWall(velocity_bc_top_bottom, - heat_bc_top_bottom) +boundary_condition_top_bottom = BoundaryConditionNavierStokesWall( + velocity_bc_top_bottom, + heat_bc_top_bottom +) # define inviscid boundary conditions -boundary_conditions = (; x_neg = boundary_condition_periodic, - x_pos = boundary_condition_periodic, - y_neg = boundary_condition_slip_wall, - y_pos = boundary_condition_slip_wall, - z_neg = boundary_condition_periodic, - z_pos = boundary_condition_periodic) +boundary_conditions = (; + x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_slip_wall, + y_pos = boundary_condition_slip_wall, + z_neg = boundary_condition_periodic, + z_pos = boundary_condition_periodic, +) # define viscous boundary conditions -boundary_conditions_parabolic = (; x_neg = boundary_condition_periodic, - x_pos = boundary_condition_periodic, - y_neg = boundary_condition_top_bottom, - y_pos = boundary_condition_top_bottom, - z_neg = boundary_condition_periodic, - z_pos = boundary_condition_periodic) - -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) +boundary_conditions_parabolic = (; + x_neg = boundary_condition_periodic, + x_pos = boundary_condition_periodic, + y_neg = boundary_condition_top_bottom, + y_pos = boundary_condition_top_bottom, + z_neg = boundary_condition_periodic, + z_pos = boundary_condition_periodic, +) + +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -268,7 +304,9 @@ callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1e-5, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, dt = 1.0e-5, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl b/examples/tree_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl index 3e54c791ec..984f8bf086 100644 --- a/examples/tree_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl +++ b/examples/tree_3d_dgsem/elixir_navierstokes_taylor_green_vortex.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -9,8 +8,10 @@ prandtl_number() = 0.72 mu = 6.25e-4 # equivalent to Re = 1600 equations = CompressibleEulerEquations3D(1.4) -equations_parabolic = CompressibleNavierStokesDiffusion3D(equations, mu = mu, - Prandtl = prandtl_number()) +equations_parabolic = CompressibleNavierStokesDiffusion3D( + equations, mu = mu, + Prandtl = prandtl_number() +) """ initial_condition_taylor_green_vortex(x, t, equations::CompressibleEulerEquations3D) @@ -21,8 +22,10 @@ The classical viscous Taylor-Green vortex, as found for instance in Simulation of the Compressible Taylor Green Vortex using High-Order Flux Reconstruction Schemes [DOI: 10.2514/6.2014-3210](https://doi.org/10.2514/6.2014-3210) """ -function initial_condition_taylor_green_vortex(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_taylor_green_vortex( + x, t, + equations::CompressibleEulerEquations3D + ) A = 1.0 # magnitude of speed Ms = 0.1 # maximum Mach number @@ -33,25 +36,33 @@ function initial_condition_taylor_green_vortex(x, t, p = (A / Ms)^2 * rho / equations.gamma # scaling to get Ms p = p + 1.0 / 16.0 * A^2 * rho * - (cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + - cos(2 * x[2]) * cos(2 * x[3])) + ( + cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + + cos(2 * x[2]) * cos(2 * x[3]) + ) return prim2cons(SVector(rho, v1, v2, v3, p), equations) end initial_condition = initial_condition_taylor_green_vortex volume_flux = flux_ranocha -solver = DGSEM(polydeg = 3, surface_flux = flux_hllc, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 3, surface_flux = flux_hllc, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) coordinates_min = (-1.0, -1.0, -1.0) .* pi coordinates_max = (1.0, 1.0, 1.0) .* pi -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 3, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 3, + n_cells_max = 100_000 +) -semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver) +semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -62,22 +73,30 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 50 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (energy_kinetic, - energy_internal, - enstrophy)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + enstrophy, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -time_int_tol = 1e-8 -sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) +time_int_tol = 1.0e-8 +sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/tree_3d_fdsbp/elixir_advection_extended.jl b/examples/tree_3d_fdsbp/elixir_advection_extended.jl index 22085a6351..324eaa39df 100644 --- a/examples/tree_3d_fdsbp/elixir_advection_extended.jl +++ b/examples/tree_3d_fdsbp/elixir_advection_extended.jl @@ -12,19 +12,25 @@ equations = LinearScalarAdvectionEquation3D(advection_velocity) initial_condition = initial_condition_convergence_test -D_SBP = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order = 1, accuracy_order = 4, - xmin = 0.0, xmax = 1.0, N = 10) -solver = FDSBP(D_SBP, - surface_integral = SurfaceIntegralStrongForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralStrongForm()) +D_SBP = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, accuracy_order = 4, + xmin = 0.0, xmax = 1.0, N = 10 +) +solver = FDSBP( + D_SBP, + surface_integral = SurfaceIntegralStrongForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralStrongForm() +) coordinates_min = (-1.0, -1.0, -1.0) coordinates_max = (1.0, 1.0, 1.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 1, - n_cells_max = 30_000, - periodicity = true) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 1, + n_cells_max = 30_000, + periodicity = true +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -37,18 +43,24 @@ ode = semidiscretize(semi, tspan); summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (energy_total,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (energy_total,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-9, reltol = 1.0e-9, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-9, reltol = 1.0e-9, + ode_default_options()..., callback = callbacks +) summary_callback() diff --git a/examples/tree_3d_fdsbp/elixir_euler_convergence.jl b/examples/tree_3d_fdsbp/elixir_euler_convergence.jl index aa5b231ee1..12a41c31eb 100644 --- a/examples/tree_3d_fdsbp/elixir_euler_convergence.jl +++ b/examples/tree_3d_fdsbp/elixir_euler_convergence.jl @@ -10,24 +10,32 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 16) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 16 +) flux_splitting = splitting_steger_warming -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = (0.0, 0.0, 0.0) coordinates_max = (2.0, 2.0, 2.0) -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 10_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 10_000 +) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -42,22 +50,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) stepsize_callback = StepsizeCallback(cfl = 1.1) -callbacks = CallbackSet(summary_callback, - analysis_callback, alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl b/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl index 0354200ae4..de90435bdc 100644 --- a/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl +++ b/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl @@ -14,8 +14,10 @@ equations = CompressibleEulerEquations3D(1.4) The classical inviscid Taylor-Green vortex. """ -function initial_condition_taylor_green_vortex(x, t, - equations::CompressibleEulerEquations3D) +function initial_condition_taylor_green_vortex( + x, t, + equations::CompressibleEulerEquations3D + ) A = 1.0 # magnitude of speed Ms = 0.1 # maximum Mach number @@ -26,28 +28,36 @@ function initial_condition_taylor_green_vortex(x, t, p = (A / Ms)^2 * rho / equations.gamma # scaling to get Ms p = p + 1.0 / 16.0 * A^2 * rho * - (cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + - cos(2 * x[2]) * cos(2 * x[3])) + ( + cos(2 * x[1]) * cos(2 * x[3]) + 2 * cos(2 * x[2]) + 2 * cos(2 * x[1]) + + cos(2 * x[2]) * cos(2 * x[3]) + ) return prim2cons(SVector(rho, v1, v2, v3, p), equations) end initial_condition = initial_condition_taylor_green_vortex -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 16) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 16 +) flux_splitting = splitting_steger_warming -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralUpwind(flux_splitting), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralUpwind(flux_splitting), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) coordinates_min = (-1.0, -1.0, -1.0) .* pi coordinates_max = (1.0, 1.0, 1.0) .* pi -mesh = TreeMesh(coordinates_min, coordinates_max, - initial_refinement_level = 2, - n_cells_max = 100_000) +mesh = TreeMesh( + coordinates_min, coordinates_max, + initial_refinement_level = 2, + n_cells_max = 100_000 +) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) @@ -60,27 +70,37 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (energy_total, - energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + energy_total, + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks) +sol = solve( + ode, SSPRK43(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_acoustics_gauss_wall.jl b/examples/unstructured_2d_dgsem/elixir_acoustics_gauss_wall.jl index 9741430d11..bbf4deab3e 100644 --- a/examples/unstructured_2d_dgsem/elixir_acoustics_gauss_wall.jl +++ b/examples/unstructured_2d_dgsem/elixir_acoustics_gauss_wall.jl @@ -1,20 +1,23 @@ - using OrdinaryDiffEq using Trixi ############################################################################### # semidiscretization of the acoustic perturbation equations -equations = AcousticPerturbationEquations2D(v_mean_global = (0.0, -0.5), - c_mean_global = 1.0, - rho_mean_global = 1.0) +equations = AcousticPerturbationEquations2D( + v_mean_global = (0.0, -0.5), + c_mean_global = 1.0, + rho_mean_global = 1.0 +) # Create DG solver with polynomial degree = 4 and (local) Lax-Friedrichs/Rusanov flux solver = DGSEM(polydeg = 4, surface_flux = flux_lax_friedrichs) # Create unstructured quadrilateral mesh from a file -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/3c79baad6b4d73bb26ec6420b5d16f45/raw/22aefc4ec2107cf0bffc40e81dfbc52240c625b1/mesh_five_circles_in_circle.mesh", - joinpath(@__DIR__, "mesh_five_circles_in_circle.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/3c79baad6b4d73bb26ec6420b5d16f45/raw/22aefc4ec2107cf0bffc40e81dfbc52240c625b1/mesh_five_circles_in_circle.mesh", + joinpath(@__DIR__, "mesh_five_circles_in_circle.mesh") +) mesh = UnstructuredMesh2D(mesh_file) @@ -35,16 +38,20 @@ function initial_condition_gauss_wall(x, t, equations::AcousticPerturbationEquat end initial_condition = initial_condition_gauss_wall -boundary_conditions = Dict(:OuterCircle => boundary_condition_slip_wall, - :InnerCircle1 => boundary_condition_slip_wall, - :InnerCircle2 => boundary_condition_slip_wall, - :InnerCircle3 => boundary_condition_slip_wall, - :InnerCircle4 => boundary_condition_slip_wall, - :InnerCircle5 => boundary_condition_slip_wall) +boundary_conditions = Dict( + :OuterCircle => boundary_condition_slip_wall, + :InnerCircle1 => boundary_condition_slip_wall, + :InnerCircle2 => boundary_condition_slip_wall, + :InnerCircle3 => boundary_condition_slip_wall, + :InnerCircle4 => boundary_condition_slip_wall, + :InnerCircle5 => boundary_condition_slip_wall +) # A semidiscretization collects data structures and functions for the spatial discretization -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -70,7 +77,9 @@ callbacks = CallbackSet(summary_callback, analysis_callback, save_solution) # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); # Print the timer summary summary_callback() diff --git a/examples/unstructured_2d_dgsem/elixir_advection_basic.jl b/examples/unstructured_2d_dgsem/elixir_advection_basic.jl index c0ee453344..07d05b3f5b 100644 --- a/examples/unstructured_2d_dgsem/elixir_advection_basic.jl +++ b/examples/unstructured_2d_dgsem/elixir_advection_basic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,16 +14,20 @@ solver = DGSEM(polydeg = 6, surface_flux = flux_lax_friedrichs) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", - joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", + joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -40,20 +43,26 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_euler_basic.jl b/examples/unstructured_2d_dgsem/elixir_euler_basic.jl index f8976120d5..0c2d034ba0 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_basic.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_basic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -11,11 +10,13 @@ initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test boundary_condition_convergence_test = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:Slant => boundary_condition_convergence_test, - :Bezier => boundary_condition_convergence_test, - :Right => boundary_condition_convergence_test, - :Bottom => boundary_condition_convergence_test, - :Top => boundary_condition_convergence_test) +boundary_conditions = Dict( + :Slant => boundary_condition_convergence_test, + :Bezier => boundary_condition_convergence_test, + :Right => boundary_condition_convergence_test, + :Bottom => boundary_condition_convergence_test, + :Top => boundary_condition_convergence_test +) ############################################################################### # Get the DG approximation space @@ -24,17 +25,21 @@ solver = DGSEM(polydeg = 8, surface_flux = flux_lax_friedrichs) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/52056f1487853fab63b7f4ed7f171c80/raw/9d573387dfdbb8bce2a55db7246f4207663ac07f/mesh_trixi_unstructured_mesh_docs.mesh", - joinpath(@__DIR__, "mesh_trixi_unstructured_mesh_docs.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/52056f1487853fab63b7f4ed7f171c80/raw/9d573387dfdbb8bce2a55db7246f4207663ac07f/mesh_trixi_unstructured_mesh_docs.mesh", + joinpath(@__DIR__, "mesh_trixi_unstructured_mesh_docs.mesh") +) mesh = UnstructuredMesh2D(mesh_file) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -49,26 +54,34 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_restart = SaveRestartCallback(interval = 50, - save_final_restart = true) +save_restart = SaveRestartCallback( + interval = 50, + save_final_restart = true +) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 0.9) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_restart, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_restart, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_euler_ec.jl b/examples/unstructured_2d_dgsem/elixir_euler_ec.jl index 58b4d9a1dd..de7f47f6f9 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_ec.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -13,13 +12,17 @@ initial_condition = initial_condition_weak_blast_wave # Get the DG approximation space volume_flux = flux_ranocha -solver = DGSEM(polydeg = 6, surface_flux = flux_ranocha, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 6, surface_flux = flux_ranocha, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # Get the curved quad mesh from a file -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", - joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", + joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) @@ -41,22 +44,28 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_euler_free_stream.jl b/examples/unstructured_2d_dgsem/elixir_euler_free_stream.jl index f266a3de0b..fb7a764873 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_free_stream.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_free_stream.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,13 +9,15 @@ equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_constant boundary_condition_free_stream = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:Body => boundary_condition_free_stream, - :Button1 => boundary_condition_free_stream, - :Button2 => boundary_condition_free_stream, - :Eye1 => boundary_condition_free_stream, - :Eye2 => boundary_condition_free_stream, - :Smile => boundary_condition_free_stream, - :Bowtie => boundary_condition_free_stream) +boundary_conditions = Dict( + :Body => boundary_condition_free_stream, + :Button1 => boundary_condition_free_stream, + :Button2 => boundary_condition_free_stream, + :Eye1 => boundary_condition_free_stream, + :Eye2 => boundary_condition_free_stream, + :Smile => boundary_condition_free_stream, + :Bowtie => boundary_condition_free_stream +) ############################################################################### # Get the DG approximation space @@ -25,16 +26,20 @@ solver = DGSEM(polydeg = 6, surface_flux = flux_hll) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/2c6440b5f8a57db131061ad7aa78ee2b/raw/1f89fdf2c874ff678c78afb6fe8dc784bdfd421f/mesh_gingerbread_man.mesh", - joinpath(@__DIR__, "mesh_gingerbread_man.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/2c6440b5f8a57db131061ad7aa78ee2b/raw/1f89fdf2c874ff678c78afb6fe8dc784bdfd421f/mesh_gingerbread_man.mesh", + joinpath(@__DIR__, "mesh_gingerbread_man.mesh") +) mesh = UnstructuredMesh2D(mesh_file) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -49,19 +54,25 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_euler_periodic.jl b/examples/unstructured_2d_dgsem/elixir_euler_periodic.jl index e640001ad7..bbaf2196ec 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_periodic.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_periodic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -18,17 +17,21 @@ solver = DGSEM(polydeg = 6, surface_flux = FluxRotated(flux_hll)) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", - joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", + joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. diff --git a/examples/unstructured_2d_dgsem/elixir_euler_restart.jl b/examples/unstructured_2d_dgsem/elixir_euler_restart.jl index 4676718f72..5582d553df 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_restart.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_restart.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,9 +15,11 @@ trixi_include(@__MODULE__, joinpath(@__DIR__, "elixir_euler_basic.jl")) restart_filename = joinpath("out", "restart_000000050.h5") mesh = load_mesh(restart_filename) -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms, + boundary_conditions = boundary_conditions +) tspan = (load_time(restart_filename), 1.0) dt = load_dt(restart_filename) @@ -27,9 +28,11 @@ ode = semidiscretize(semi, tspan, restart_filename); # Do not overwrite the initial snapshot written by elixir_advection_extended.jl. save_solution.condition.save_initial_solution = false -integrator = init(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks, maxiters = 100_000); +integrator = init( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = dt, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks, maxiters = 100_000 +); # Get the last time index and work with that. load_timestep!(integrator, restart_filename) diff --git a/examples/unstructured_2d_dgsem/elixir_euler_sedov.jl b/examples/unstructured_2d_dgsem/elixir_euler_sedov.jl index 06053273b7..04f2090670 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_sedov.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_sedov.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -42,21 +41,29 @@ surface_flux = flux_lax_friedrichs volume_flux = flux_ranocha polydeg = 6 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 1.0, - alpha_min = 0.001, - alpha_smooth = true, - variable = density_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 1.0, + alpha_min = 0.001, + alpha_smooth = true, + variable = density_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) # Get the curved quad mesh from a file -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", - joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", + joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) @@ -76,22 +83,28 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 300, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 300, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 0.5) -callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_euler_time_series.jl b/examples/unstructured_2d_dgsem/elixir_euler_time_series.jl index 13233cdadb..758fd608e6 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_time_series.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_time_series.jl @@ -14,8 +14,10 @@ equations = CompressibleEulerEquations2D(1.4) # Modify the manufactured solution test to use `L = sqrt(2)` # in the initial condition and source terms -function initial_condition_convergence_shifted(x, t, - equations::CompressibleEulerEquations2D) +function initial_condition_convergence_shifted( + x, t, + equations::CompressibleEulerEquations2D + ) c = 2 A = 0.1 L = sqrt(2) @@ -31,8 +33,10 @@ function initial_condition_convergence_shifted(x, t, return SVector(rho, rho_v1, rho_v2, rho_e) end -@inline function source_terms_convergence_shifted(u, x, t, - equations::CompressibleEulerEquations2D) +@inline function source_terms_convergence_shifted( + u, x, t, + equations::CompressibleEulerEquations2D + ) # Same settings as in `initial_condition` c = 2 A = 0.1 @@ -69,16 +73,20 @@ solver = DGSEM(polydeg = 6, surface_flux = flux_lax_friedrichs) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/b434e724e3972a9c4ee48d58c80cdcdb/raw/55c916cd8c0294a2d4a836e960dac7247b7c8ccf/mesh_multiple_flips.mesh", - joinpath(@__DIR__, "mesh_multiple_flips.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/b434e724e3972a9c4ee48d58c80cdcdb/raw/55c916cd8c0294a2d4a836e960dac7247b7c8ccf/mesh_multiple_flips.mesh", + joinpath(@__DIR__, "mesh_multiple_flips.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_term) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_term +) ############################################################################### # ODE solvers, callbacks etc. @@ -93,23 +101,31 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -time_series = TimeSeriesCallback(semi, - [(0.75, 0.7), (1.23, 0.302), (0.8, 1.0), - (0.353553390593274, 0.353553390593274), - (0.505, 1.125), (1.37, 0.89), (0.349, 0.7153), - (0.883883476483184, 0.406586401289607), - (sqrt(2), sqrt(2))]; - interval = 10) - -callbacks = CallbackSet(summary_callback, - analysis_callback, - time_series, - alive_callback) +time_series = TimeSeriesCallback( + semi, + [ + (0.75, 0.7), (1.23, 0.302), (0.8, 1.0), + (0.353553390593274, 0.353553390593274), + (0.505, 1.125), (1.37, 0.89), (0.349, 0.7153), + (0.883883476483184, 0.406586401289607), + (sqrt(2), sqrt(2)), + ]; + interval = 10 +) + +callbacks = CallbackSet( + summary_callback, + analysis_callback, + time_series, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-6, reltol = 1.0e-6, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_euler_wall_bc.jl b/examples/unstructured_2d_dgsem/elixir_euler_wall_bc.jl index 65e5eb51ce..6eecae071f 100644 --- a/examples/unstructured_2d_dgsem/elixir_euler_wall_bc.jl +++ b/examples/unstructured_2d_dgsem/elixir_euler_wall_bc.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -26,11 +25,13 @@ end initial_condition = uniform_flow_state boundary_condition_uniform_flow = BoundaryConditionDirichlet(uniform_flow_state) -boundary_conditions = Dict(:Bottom => boundary_condition_uniform_flow, - :Top => boundary_condition_uniform_flow, - :Right => boundary_condition_uniform_flow, - :Left => boundary_condition_uniform_flow, - :Circle => boundary_condition_slip_wall) +boundary_conditions = Dict( + :Bottom => boundary_condition_uniform_flow, + :Top => boundary_condition_uniform_flow, + :Right => boundary_condition_uniform_flow, + :Left => boundary_condition_uniform_flow, + :Circle => boundary_condition_slip_wall +) ############################################################################### # Get the DG approximation space @@ -39,16 +40,20 @@ solver = DGSEM(polydeg = 4, surface_flux = flux_hll) ############################################################################### # Get the curved quad mesh from a file -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/8b9b11a1eedfa54b215c122c3d17b271/raw/0d2b5d98c87e67a6f384693a8b8e54b4c9fcbf3d/mesh_box_around_circle.mesh", - joinpath(@__DIR__, "mesh_box_around_circle.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/8b9b11a1eedfa54b215c122c3d17b271/raw/0d2b5d98c87e67a6f384693a8b8e54b4c9fcbf3d/mesh_box_around_circle.mesh", + joinpath(@__DIR__, "mesh_box_around_circle.mesh") +) mesh = UnstructuredMesh2D(mesh_file) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -63,19 +68,25 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 10, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 10, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_mhd_alfven_wave.jl b/examples/unstructured_2d_dgsem/elixir_mhd_alfven_wave.jl index 0c7152a6ea..cd571a7324 100644 --- a/examples/unstructured_2d_dgsem/elixir_mhd_alfven_wave.jl +++ b/examples/unstructured_2d_dgsem/elixir_mhd_alfven_wave.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -10,14 +9,20 @@ equations = IdealGlmMhdEquations2D(gamma) initial_condition = initial_condition_convergence_test volume_flux = (flux_central, flux_nonconservative_powell) -solver = DGSEM(polydeg = 7, - surface_flux = (flux_hlle, - flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 7, + surface_flux = ( + flux_hlle, + flux_nonconservative_powell, + ), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", - joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", + joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) @@ -32,36 +37,46 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = false, - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal, - energy_magnetic, - cross_helicity)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = false, + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + energy_magnetic, + cross_helicity, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_mhd_ec.jl b/examples/unstructured_2d_dgsem/elixir_mhd_ec.jl index 805934e305..3b8387a8c7 100644 --- a/examples/unstructured_2d_dgsem/elixir_mhd_ec.jl +++ b/examples/unstructured_2d_dgsem/elixir_mhd_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -29,13 +28,17 @@ initial_condition = initial_condition_shifted_weak_blast_wave # Get the DG approximation space volume_flux = (flux_hindenlang_gassner, flux_nonconservative_powell) -solver = DGSEM(polydeg = 6, - surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 6, + surface_flux = (flux_hindenlang_gassner, flux_nonconservative_powell), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", - joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", + joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) @@ -51,36 +54,46 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = false, - extra_analysis_integrals = (entropy, energy_total, - energy_kinetic, - energy_internal, - energy_magnetic, - cross_helicity)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = false, + extra_analysis_integrals = ( + entropy, energy_total, + energy_kinetic, + energy_internal, + energy_magnetic, + cross_helicity, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true, + solution_variables = cons2prim +) cfl = 1.0 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) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + save_solution, + stepsize_callback, + glm_speed_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_dirichlet.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_dirichlet.jl index 38e1279e22..8a8f7bbe71 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_dirichlet.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_dirichlet.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,8 +15,10 @@ function initial_condition_well_balancedness(x, t, equations::ShallowWaterEquati v2 = 0.0 # bottom topography taken from Pond.control in [HOHQMesh](https://github.com/trixi-framework/HOHQMesh) x1, x2 = x - b = (1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + - 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2))) + b = ( + 1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + + 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2)) + ) return prim2cons(SVector(H, v1, v2, b), equations) end @@ -30,23 +31,31 @@ boundary_condition = Dict(:OuterCircle => boundary_condition_constant) # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 4, - surface_flux = (flux_hll, - flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 4, + surface_flux = ( + flux_hll, + flux_nonconservative_fjordholm_etal, + ), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # This setup is for the curved, split form well-balancedness testing # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/9beddd9cd00e2a0a15865129eeb24928/raw/be71e67fa48bc4e1e97f5f6cd77c3ed34c6ba9be/mesh_outer_circle.mesh", - joinpath(@__DIR__, "mesh_outer_circle.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/9beddd9cd00e2a0a15865129eeb24928/raw/be71e67fa48bc4e1e97f5f6cd77c3ed34c6ba9be/mesh_outer_circle.mesh", + joinpath(@__DIR__, "mesh_outer_circle.mesh") +) mesh = UnstructuredMesh2D(mesh_file) # Create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition +) ############################################################################### # ODE solvers, callbacks, etc. @@ -57,15 +66,19 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (lake_at_rest_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = (lake_at_rest_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) @@ -73,6 +86,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, sav # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-11, reltol = 1.0e-11, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-11, reltol = 1.0e-11, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_ec.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec.jl index 1f4aa41490..b626a6c019 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_ec.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -16,16 +15,20 @@ initial_condition = initial_condition_weak_blast_wave # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 6, - surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 6, + surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # This setup is for the curved, split form entropy conservation testing (needs periodic BCs) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", - joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", + joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) @@ -47,8 +50,10 @@ ode = semidiscretize(semi, tspan) # In contrast to the usual signature of initial conditions, this one get passed the # `element_id` explicitly. In particular, this initial conditions works as intended # only for the specific mesh loaded above! -function initial_condition_ec_discontinuous_bottom(x, t, element_id, - equations::ShallowWaterEquations2D) +function initial_condition_ec_discontinuous_bottom( + x, t, element_id, + equations::ShallowWaterEquations2D + ) # Set up polar coordinates inicenter = SVector(0.7, 0.7) x_norm = x[1] - inicenter[1] @@ -83,10 +88,14 @@ u = Trixi.wrap_array(ode.u0, semi) # reset the initial condition for element in eachelement(semi.solver, semi.cache) for j in eachnode(semi.solver), i in eachnode(semi.solver) - x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, - semi.solver, i, j, element) - u_node = initial_condition_ec_discontinuous_bottom(x_node, first(tspan), element, - equations) + x_node = Trixi.get_node_coords( + semi.cache.elements.node_coordinates, equations, + semi.solver, i, j, element + ) + u_node = initial_condition_ec_discontinuous_bottom( + x_node, first(tspan), element, + equations + ) Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) end end @@ -101,19 +110,25 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_float32.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_float32.jl index cc527fc00e..8e0255714e 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_float32.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_float32.jl @@ -18,17 +18,21 @@ initial_condition = initial_condition_weak_blast_wave # Get the DG approximation space volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) -solver = DGSEM(polydeg = 6, - surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux), - RealT = Float32) +solver = DGSEM( + polydeg = 6, + surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux), + RealT = Float32 +) ############################################################################### # This setup is for the curved, split form entropy conservation testing (needs periodic BCs) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", - joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", + joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true, RealT = Float32) @@ -50,8 +54,10 @@ ode = semidiscretize(semi, tspan) # In contrast to the usual signature of initial conditions, this one get passed the # `element_id` explicitly. In particular, this initial conditions works as intended # only for the specific mesh loaded above! -function initial_condition_ec_discontinuous_bottom(x, t, element_id, - equations::ShallowWaterEquations2D) +function initial_condition_ec_discontinuous_bottom( + x, t, element_id, + equations::ShallowWaterEquations2D + ) RealT = eltype(x) # Set up polar coordinates @@ -88,10 +94,14 @@ u = Trixi.wrap_array(ode.u0, semi) # reset the initial condition for element in eachelement(semi.solver, semi.cache) for j in eachnode(semi.solver), i in eachnode(semi.solver) - x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, - semi.solver, i, j, element) - u_node = initial_condition_ec_discontinuous_bottom(x_node, first(tspan), element, - equations) + x_node = Trixi.get_node_coords( + semi.cache.elements.node_coordinates, equations, + semi.solver, i, j, element + ) + u_node = initial_condition_ec_discontinuous_bottom( + x_node, first(tspan), element, + equations + ) Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) end end @@ -106,19 +116,25 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0f0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0f0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0f0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_shockcapturing.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_shockcapturing.jl index c4736e8b9a..ef004d860c 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_shockcapturing.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_ec_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -19,22 +18,28 @@ surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal) volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) polydeg = 6 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = waterheight_pressure) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = waterheight_pressure +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) solver = DGSEM(basis, surface_flux, volume_integral) ############################################################################### # This setup is for the curved, split form entropy conservation testing (needs periodic BCs) # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", - joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", + joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) @@ -56,8 +61,10 @@ ode = semidiscretize(semi, tspan) # In contrast to the usual signature of initial conditions, this one get passed the # `element_id` explicitly. In particular, this initial conditions works as intended # only for the specific mesh loaded above! -function initial_condition_ec_discontinuous_bottom(x, t, element_id, - equations::ShallowWaterEquations2D) +function initial_condition_ec_discontinuous_bottom( + x, t, element_id, + equations::ShallowWaterEquations2D + ) # Set up polar coordinates inicenter = SVector(0.7, 0.7) x_norm = x[1] - inicenter[1] @@ -92,10 +99,14 @@ u = Trixi.wrap_array(ode.u0, semi) # reset the initial condition for element in eachelement(semi.solver, semi.cache) for j in eachnode(semi.solver), i in eachnode(semi.solver) - x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, - semi.solver, i, j, element) - u_node = initial_condition_ec_discontinuous_bottom(x_node, first(tspan), element, - equations) + x_node = Trixi.get_node_coords( + semi.cache.elements.node_coordinates, equations, + semi.solver, i, j, element + ) + u_node = initial_condition_ec_discontinuous_bottom( + x_node, first(tspan), element, + equations + ) Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) end end @@ -112,13 +123,17 @@ alive_callback = AliveCallback(analysis_interval = analysis_interval) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_source_terms.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_source_terms.jl index a7aa580895..9d2c7a2851 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_source_terms.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_source_terms.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -15,21 +14,27 @@ initial_condition = initial_condition_convergence_test volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal) -solver = DGSEM(polydeg = 6, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 6, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # This setup is for the curved, split form convergence test on a periodic domain # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", - joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", + joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) # Create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -44,19 +49,25 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_wall_bc_shockcapturing.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_wall_bc_shockcapturing.jl index f115113ed2..c8d07d2505 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_wall_bc_shockcapturing.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_wall_bc_shockcapturing.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -21,8 +20,10 @@ function initial_condition_stone_throw(x, t, equations::ShallowWaterEquations2D) v2 = r < 0.6 ? -2.0 : 0.0 # bottom topography taken from Pond.control in [HOHQMesh](https://github.com/trixi-framework/HOHQMesh) x1, x2 = x - b = (1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + - 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2))) + b = ( + 1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + + 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2)) + ) return prim2cons(SVector(H, v1, v2, b), equations) end @@ -34,34 +35,48 @@ boundary_condition = Dict(:OuterCircle => boundary_condition_slip_wall) ############################################################################### # Get the DG approximation space -surface_flux = (FluxHydrostaticReconstruction(flux_hll, - hydrostatic_reconstruction_audusse_etal), - flux_nonconservative_audusse_etal) +surface_flux = ( + FluxHydrostaticReconstruction( + flux_hll, + hydrostatic_reconstruction_audusse_etal + ), + flux_nonconservative_audusse_etal, +) volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) polydeg = 6 basis = LobattoLegendreBasis(polydeg) -indicator_sc = IndicatorHennemannGassner(equations, basis, - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable = Trixi.waterheight) -volume_integral = VolumeIntegralShockCapturingHG(indicator_sc; - volume_flux_dg = volume_flux, - volume_flux_fv = surface_flux) - -solver = DGSEM(polydeg = polydeg, surface_flux = surface_flux, - volume_integral = volume_integral) +indicator_sc = IndicatorHennemannGassner( + equations, basis, + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable = Trixi.waterheight +) +volume_integral = VolumeIntegralShockCapturingHG( + indicator_sc; + volume_flux_dg = volume_flux, + volume_flux_fv = surface_flux +) + +solver = DGSEM( + polydeg = polydeg, surface_flux = surface_flux, + volume_integral = volume_integral +) ############################################################################### # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/9beddd9cd00e2a0a15865129eeb24928/raw/be71e67fa48bc4e1e97f5f6cd77c3ed34c6ba9be/mesh_outer_circle.mesh", - joinpath(@__DIR__, "mesh_outer_circle.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/9beddd9cd00e2a0a15865129eeb24928/raw/be71e67fa48bc4e1e97f5f6cd77c3ed34c6ba9be/mesh_outer_circle.mesh", + joinpath(@__DIR__, "mesh_outer_circle.mesh") +) mesh = UnstructuredMesh2D(mesh_file) # Create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_condition) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_condition +) ############################################################################### # ODE solvers, callbacks, etc. @@ -72,16 +87,22 @@ ode = semidiscretize(semi, tspan) summary_callback = SummaryCallback() analysis_interval = 100 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = false, - extra_analysis_integrals = (energy_kinetic, - energy_internal)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = false, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + ) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 100, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) @@ -89,6 +110,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, sav # run the simulation # use a Runge-Kutta method with automatic (error based) time step size control -sol = solve(ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, - ode_default_options()..., callback = callbacks); +sol = solve( + ode, RDPK3SpFSAL49(); abstol = 1.0e-8, reltol = 1.0e-8, + ode_default_options()..., callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_dgsem/elixir_shallowwater_well_balanced.jl b/examples/unstructured_2d_dgsem/elixir_shallowwater_well_balanced.jl index 6cefca853c..69fa56699a 100644 --- a/examples/unstructured_2d_dgsem/elixir_shallowwater_well_balanced.jl +++ b/examples/unstructured_2d_dgsem/elixir_shallowwater_well_balanced.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -18,8 +17,10 @@ function initial_condition_well_balancedness(x, t, equations::ShallowWaterEquati v2 = 0.0 # bottom topography taken from Pond.control in [HOHQMesh](https://github.com/trixi-framework/HOHQMesh) x1, x2 = x - b = (1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + - 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2))) + b = ( + 1.5 / exp(0.5 * ((x1 - 1.0)^2 + (x2 - 1.0)^2)) + + 0.75 / exp(0.5 * ((x1 + 1.0)^2 + (x2 + 1.0)^2)) + ) return prim2cons(SVector(H, v1, v2, b), equations) end @@ -30,14 +31,18 @@ initial_condition = initial_condition_well_balancedness volume_flux = (flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal) surface_flux = (flux_fjordholm_etal, flux_nonconservative_fjordholm_etal) -solver = DGSEM(polydeg = 6, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) +solver = DGSEM( + polydeg = 6, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) +) ############################################################################### # This setup is for the curved, split form well-balancedness testing # Get the unstructured quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", - joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/8f8cd23df27fcd494553f2a89f3c1ba4/raw/85e3c8d976bbe57ca3d559d653087b0889535295/mesh_alfven_wave_with_twist_and_flip.mesh", + joinpath(@__DIR__, "mesh_alfven_wave_with_twist_and_flip.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) @@ -60,8 +65,10 @@ ode = semidiscretize(semi, tspan) # In contrast to the usual signature of initial conditions, this one get passed the # `element_id` explicitly. In particular, this initial conditions works as intended # only for the specific mesh loaded above! -function initial_condition_discontinuous_well_balancedness(x, t, element_id, - equations::ShallowWaterEquations2D) +function initial_condition_discontinuous_well_balancedness( + x, t, element_id, + equations::ShallowWaterEquations2D + ) # Set the background values H = equations.H0 v1 = 0.0 @@ -81,10 +88,14 @@ u = Trixi.wrap_array(ode.u0, semi) # reset the initial condition for element in eachelement(semi.solver, semi.cache) for j in eachnode(semi.solver), i in eachnode(semi.solver) - x_node = Trixi.get_node_coords(semi.cache.elements.node_coordinates, equations, - semi.solver, i, j, element) - u_node = initial_condition_discontinuous_well_balancedness(x_node, first(tspan), - element, equations) + x_node = Trixi.get_node_coords( + semi.cache.elements.node_coordinates, equations, + semi.solver, i, j, element + ) + u_node = initial_condition_discontinuous_well_balancedness( + x_node, first(tspan), + element, equations + ) Trixi.set_node_vars!(u, u_node, equations, semi.solver, i, j, element) end end @@ -95,24 +106,32 @@ end summary_callback = SummaryCallback() analysis_interval = 1000 -analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - extra_analysis_integrals = (lake_at_rest_error,)) +analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + extra_analysis_integrals = (lake_at_rest_error,) +) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) stepsize_callback = StepsizeCallback(cfl = 1.0) -callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_fdsbp/elixir_advection_basic.jl b/examples/unstructured_2d_fdsbp/elixir_advection_basic.jl index fe7e708f3b..44c1af5953 100644 --- a/examples/unstructured_2d_fdsbp/elixir_advection_basic.jl +++ b/examples/unstructured_2d_fdsbp/elixir_advection_basic.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -11,25 +10,33 @@ equations = LinearScalarAdvectionEquation2D(advection_velocity) ############################################################################### # Get the FDSBP approximation operator -D_SBP = derivative_operator(SummationByPartsOperators.MattssonAlmquistVanDerWeide2018Accurate(), - derivative_order = 1, accuracy_order = 4, - xmin = -1.0, xmax = 1.0, N = 15) -solver = FDSBP(D_SBP, - surface_integral = SurfaceIntegralStrongForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralStrongForm()) +D_SBP = derivative_operator( + SummationByPartsOperators.MattssonAlmquistVanDerWeide2018Accurate(), + derivative_order = 1, accuracy_order = 4, + xmin = -1.0, xmax = 1.0, N = 15 +) +solver = FDSBP( + D_SBP, + surface_integral = SurfaceIntegralStrongForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralStrongForm() +) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", - joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", + joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) ############################################################################### # create the semidiscretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_convergence_test, - solver) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition_convergence_test, + solver +) ############################################################################### # ODE solvers, callbacks etc. @@ -45,20 +52,26 @@ summary_callback = SummaryCallback() analysis_callback = AnalysisCallback(semi, interval = 100) # The SaveSolutionCallback allows to save the solution to a file in regular intervals -save_solution = SaveSolutionCallback(interval = 100, - solution_variables = cons2prim) +save_solution = SaveSolutionCallback( + interval = 100, + solution_variables = cons2prim +) # The StepsizeCallback handles the re-calculation of the maximum Δt after each time step stepsize_callback = StepsizeCallback(cfl = 1.6) # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver -callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, - stepsize_callback) +callbacks = CallbackSet( + summary_callback, analysis_callback, save_solution, + stepsize_callback +) ############################################################################### # run the simulation -sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback - save_everystep = false, callback = callbacks); +sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 1.0, # solve needs some value here but it will be overwritten by the stepsize_callback + save_everystep = false, callback = callbacks +); summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_fdsbp/elixir_euler_free_stream.jl b/examples/unstructured_2d_fdsbp/elixir_euler_free_stream.jl index 25a81c16bf..acf456d466 100644 --- a/examples/unstructured_2d_fdsbp/elixir_euler_free_stream.jl +++ b/examples/unstructured_2d_fdsbp/elixir_euler_free_stream.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,36 +11,46 @@ initial_condition = initial_condition_constant # Boundary conditions for free-stream testing boundary_condition_free_stream = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:Body => boundary_condition_free_stream, - :Button1 => boundary_condition_free_stream, - :Button2 => boundary_condition_free_stream, - :Eye1 => boundary_condition_free_stream, - :Eye2 => boundary_condition_free_stream, - :Smile => boundary_condition_free_stream, - :Bowtie => boundary_condition_free_stream) +boundary_conditions = Dict( + :Body => boundary_condition_free_stream, + :Button1 => boundary_condition_free_stream, + :Button2 => boundary_condition_free_stream, + :Eye1 => boundary_condition_free_stream, + :Eye2 => boundary_condition_free_stream, + :Smile => boundary_condition_free_stream, + :Bowtie => boundary_condition_free_stream +) ############################################################################### # Get the FDSBP approximation space -D_SBP = derivative_operator(SummationByPartsOperators.MattssonAlmquistVanDerWeide2018Accurate(), - derivative_order = 1, accuracy_order = 4, - xmin = -1.0, xmax = 1.0, N = 12) -solver = FDSBP(D_SBP, - surface_integral = SurfaceIntegralStrongForm(flux_hll), - volume_integral = VolumeIntegralStrongForm()) +D_SBP = derivative_operator( + SummationByPartsOperators.MattssonAlmquistVanDerWeide2018Accurate(), + derivative_order = 1, accuracy_order = 4, + xmin = -1.0, xmax = 1.0, N = 12 +) +solver = FDSBP( + D_SBP, + surface_integral = SurfaceIntegralStrongForm(flux_hll), + volume_integral = VolumeIntegralStrongForm() +) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/2c6440b5f8a57db131061ad7aa78ee2b/raw/1f89fdf2c874ff678c78afb6fe8dc784bdfd421f/mesh_gingerbread_man.mesh", - joinpath(@__DIR__, "mesh_gingerbread_man.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/2c6440b5f8a57db131061ad7aa78ee2b/raw/1f89fdf2c874ff678c78afb6fe8dc784bdfd421f/mesh_gingerbread_man.mesh", + joinpath(@__DIR__, "mesh_gingerbread_man.mesh") +) mesh = UnstructuredMesh2D(mesh_file) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -56,17 +65,23 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) -callbacks = CallbackSet(summary_callback, analysis_callback, - alive_callback, save_solution) +callbacks = CallbackSet( + summary_callback, analysis_callback, + alive_callback, save_solution +) ############################################################################### # run the simulation # set small tolerances for the free-stream preservation test -sol = solve(ode, SSPRK43(), abstol = 1.0e-12, reltol = 1.0e-12, - save_everystep = false, callback = callbacks) +sol = solve( + ode, SSPRK43(), abstol = 1.0e-12, reltol = 1.0e-12, + save_everystep = false, callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_fdsbp/elixir_euler_free_stream_upwind.jl b/examples/unstructured_2d_fdsbp/elixir_euler_free_stream_upwind.jl index 2a1956f9d1..41d078f1b7 100644 --- a/examples/unstructured_2d_fdsbp/elixir_euler_free_stream_upwind.jl +++ b/examples/unstructured_2d_fdsbp/elixir_euler_free_stream_upwind.jl @@ -14,10 +14,12 @@ initial_condition = initial_condition_constant # Boundary conditions for free-stream preservation test boundary_condition_free_stream = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:outerCircle => boundary_condition_free_stream, - :cone1 => boundary_condition_free_stream, - :cone2 => boundary_condition_free_stream, - :iceCream => boundary_condition_free_stream) +boundary_conditions = Dict( + :outerCircle => boundary_condition_free_stream, + :cone1 => boundary_condition_free_stream, + :cone2 => boundary_condition_free_stream, + :iceCream => boundary_condition_free_stream +) ############################################################################### # Get the Upwind FDSBP approximation space @@ -26,16 +28,20 @@ boundary_conditions = Dict(:outerCircle => boundary_condition_free_stream, # Note, one must set `xmin=-1` and `xmax=1` due to the reuse # of interpolation routines from `calc_node_coordinates!` to create # the physical coordinates in the mappings. -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 8, - xmin = -1.0, xmax = 1.0, - N = 17) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 8, + xmin = -1.0, xmax = 1.0, + N = 17 +) flux_splitting = splitting_vanleer_haenel -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralStrongForm(FluxUpwind(flux_splitting)), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralStrongForm(FluxUpwind(flux_splitting)), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) @@ -43,16 +49,20 @@ solver = FDSBP(D_upw, # Mesh with second-order boundary polynomials requires an upwind SBP operator # with (at least) 4th order boundary closure to guarantee the approximation is # free-stream preserving -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/ec9a345f09199ebe471d35d5c1e4e08f/raw/15975943d8642e42f8292235314b6f1b30aa860d/mesh_inner_outer_boundaries.mesh", - joinpath(@__DIR__, "mesh_inner_outer_boundaries.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/ec9a345f09199ebe471d35d5c1e4e08f/raw/15975943d8642e42f8292235314b6f1b30aa860d/mesh_inner_outer_boundaries.mesh", + joinpath(@__DIR__, "mesh_inner_outer_boundaries.mesh") +) mesh = UnstructuredMesh2D(mesh_file) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -67,20 +77,26 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) -callbacks = CallbackSet(summary_callback, - analysis_callback, - save_solution, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + save_solution, + alive_callback +) ############################################################################### # run the simulation # set small tolerances for the free-stream preservation test -sol = solve(ode, SSPRK43(), abstol = 1.0e-12, reltol = 1.0e-12, - save_everystep = false, callback = callbacks) +sol = solve( + ode, SSPRK43(), abstol = 1.0e-12, reltol = 1.0e-12, + save_everystep = false, callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_fdsbp/elixir_euler_free_stream_upwind_float32.jl b/examples/unstructured_2d_fdsbp/elixir_euler_free_stream_upwind_float32.jl index 9cfd45a4d3..a19a52e5b2 100644 --- a/examples/unstructured_2d_fdsbp/elixir_euler_free_stream_upwind_float32.jl +++ b/examples/unstructured_2d_fdsbp/elixir_euler_free_stream_upwind_float32.jl @@ -16,10 +16,12 @@ initial_condition = initial_condition_constant # Boundary conditions for free-stream preservation test boundary_condition_free_stream = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:outerCircle => boundary_condition_free_stream, - :cone1 => boundary_condition_free_stream, - :cone2 => boundary_condition_free_stream, - :iceCream => boundary_condition_free_stream) +boundary_conditions = Dict( + :outerCircle => boundary_condition_free_stream, + :cone1 => boundary_condition_free_stream, + :cone2 => boundary_condition_free_stream, + :iceCream => boundary_condition_free_stream +) ############################################################################### # Get the Upwind FDSBP approximation space @@ -28,16 +30,20 @@ boundary_conditions = Dict(:outerCircle => boundary_condition_free_stream, # Note, one must set `xmin=-1` and `xmax=1` due to the reuse # of interpolation routines from `calc_node_coordinates!` to create # the physical coordinates in the mappings. -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 8, - xmin = -1.0f0, xmax = 1.0f0, - N = 17) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 8, + xmin = -1.0f0, xmax = 1.0f0, + N = 17 +) flux_splitting = splitting_vanleer_haenel -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralStrongForm(FluxUpwind(flux_splitting)), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralStrongForm(FluxUpwind(flux_splitting)), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) @@ -45,16 +51,20 @@ solver = FDSBP(D_upw, # Mesh with second-order boundary polynomials requires an upwind SBP operator # with (at least) 4th order boundary closure to guarantee the approximation is # free-stream preserving -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/ec9a345f09199ebe471d35d5c1e4e08f/raw/15975943d8642e42f8292235314b6f1b30aa860d/mesh_inner_outer_boundaries.mesh", - joinpath(@__DIR__, "mesh_inner_outer_boundaries.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/ec9a345f09199ebe471d35d5c1e4e08f/raw/15975943d8642e42f8292235314b6f1b30aa860d/mesh_inner_outer_boundaries.mesh", + joinpath(@__DIR__, "mesh_inner_outer_boundaries.mesh") +) mesh = UnstructuredMesh2D(mesh_file, RealT = Float32) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -69,20 +79,26 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) -callbacks = CallbackSet(summary_callback, - analysis_callback, - save_solution, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + save_solution, + alive_callback +) ############################################################################### # run the simulation # set small tolerances for the free-stream preservation test -sol = solve(ode, SSPRK43(), abstol = 1.0f-6, reltol = 1.0f-6, - save_everystep = false, callback = callbacks) +sol = solve( + ode, SSPRK43(), abstol = 1.0f-6, reltol = 1.0f-6, + save_everystep = false, callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_fdsbp/elixir_euler_source_terms.jl b/examples/unstructured_2d_fdsbp/elixir_euler_source_terms.jl index 5f11d41ad5..3c85f41db3 100644 --- a/examples/unstructured_2d_fdsbp/elixir_euler_source_terms.jl +++ b/examples/unstructured_2d_fdsbp/elixir_euler_source_terms.jl @@ -1,4 +1,3 @@ - using OrdinaryDiffEq using Trixi @@ -12,25 +11,33 @@ initial_condition = initial_condition_convergence_test ############################################################################### # Get the FDSBP approximation operator -D_SBP = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order = 1, accuracy_order = 4, - xmin = -1.0, xmax = 1.0, N = 10) -solver = FDSBP(D_SBP, - surface_integral = SurfaceIntegralStrongForm(flux_lax_friedrichs), - volume_integral = VolumeIntegralStrongForm()) +D_SBP = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, accuracy_order = 4, + xmin = -1.0, xmax = 1.0, N = 10 +) +solver = FDSBP( + D_SBP, + surface_integral = SurfaceIntegralStrongForm(flux_lax_friedrichs), + volume_integral = VolumeIntegralStrongForm() +) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", - joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/12ce661d7c354c3d94c74b964b0f1c96/raw/8275b9a60c6e7ebbdea5fc4b4f091c47af3d5273/mesh_periodic_square_with_twist.mesh", + joinpath(@__DIR__, "mesh_periodic_square_with_twist.mesh") +) mesh = UnstructuredMesh2D(mesh_file, periodicity = true) ############################################################################### # create the semi discretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_terms_convergence_test) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_terms_convergence_test +) ############################################################################### # ODE solvers, callbacks etc. @@ -45,16 +52,22 @@ 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) +save_solution = SaveSolutionCallback( + interval = 100, + save_initial_solution = true, + save_final_solution = true +) -callbacks = CallbackSet(summary_callback, analysis_callback, - alive_callback, save_solution) +callbacks = CallbackSet( + summary_callback, analysis_callback, + alive_callback, save_solution +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(), abstol = 1.0e-9, reltol = 1.0e-9, - save_everystep = false, callback = callbacks) +sol = solve( + ode, SSPRK43(), abstol = 1.0e-9, reltol = 1.0e-9, + save_everystep = false, callback = callbacks +) summary_callback() # print the timer summary diff --git a/examples/unstructured_2d_fdsbp/elixir_euler_source_terms_upwind.jl b/examples/unstructured_2d_fdsbp/elixir_euler_source_terms_upwind.jl index 9bd2afa574..642a124129 100644 --- a/examples/unstructured_2d_fdsbp/elixir_euler_source_terms_upwind.jl +++ b/examples/unstructured_2d_fdsbp/elixir_euler_source_terms_upwind.jl @@ -15,10 +15,12 @@ source_term = source_terms_convergence_test boundary_condition_eoc = BoundaryConditionDirichlet(initial_condition) -boundary_conditions = Dict(:Top => boundary_condition_eoc, - :Bottom => boundary_condition_eoc, - :Right => boundary_condition_eoc, - :Left => boundary_condition_eoc) +boundary_conditions = Dict( + :Top => boundary_condition_eoc, + :Bottom => boundary_condition_eoc, + :Right => boundary_condition_eoc, + :Left => boundary_condition_eoc +) ############################################################################### # Get the Upwind FDSBP approximation space @@ -27,16 +29,20 @@ boundary_conditions = Dict(:Top => boundary_condition_eoc, # Note, one must set `xmin=-1` and `xmax=1` due to the reuse # of interpolation routines from `calc_node_coordinates!` to create # the physical coordinates in the mappings. -D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, - derivative_order = 1, - accuracy_order = 4, - xmin = -1.0, xmax = 1.0, - N = 9) +D_upw = upwind_operators( + SummationByPartsOperators.Mattsson2017, + derivative_order = 1, + accuracy_order = 4, + xmin = -1.0, xmax = 1.0, + N = 9 +) flux_splitting = splitting_drikakis_tsangaris -solver = FDSBP(D_upw, - surface_integral = SurfaceIntegralStrongForm(FluxUpwind(flux_splitting)), - volume_integral = VolumeIntegralUpwind(flux_splitting)) +solver = FDSBP( + D_upw, + surface_integral = SurfaceIntegralStrongForm(FluxUpwind(flux_splitting)), + volume_integral = VolumeIntegralUpwind(flux_splitting) +) ############################################################################### # Get the curved quad mesh from a file (downloads the file if not available locally) @@ -44,17 +50,21 @@ solver = FDSBP(D_upw, # Mesh with first-order boundary polynomials requires an upwind SBP operator # with (at least) 2nd order boundary closure to guarantee the approximation is # free-stream preserving -mesh_file = Trixi.download("https://gist.githubusercontent.com/andrewwinters5000/a4f4743008bf3233957a9ea6ac7a62e0/raw/8b36cc6649153fe0a5723b200368a210a1d74eaf/mesh_refined_box.mesh", - joinpath(@__DIR__, "mesh_refined_box.mesh")) +mesh_file = Trixi.download( + "https://gist.githubusercontent.com/andrewwinters5000/a4f4743008bf3233957a9ea6ac7a62e0/raw/8b36cc6649153fe0a5723b200368a210a1d74eaf/mesh_refined_box.mesh", + joinpath(@__DIR__, "mesh_refined_box.mesh") +) mesh = UnstructuredMesh2D(mesh_file) ############################################################################### # create the semidiscretization object -semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, - source_terms = source_term, - boundary_conditions = boundary_conditions) +semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver, + source_terms = source_term, + boundary_conditions = boundary_conditions +) ############################################################################### # ODE solvers, callbacks etc. @@ -69,19 +79,25 @@ analysis_callback = AnalysisCallback(semi, interval = analysis_interval) alive_callback = AliveCallback(analysis_interval = analysis_interval) -save_solution = SaveSolutionCallback(interval = 1000, - save_initial_solution = true, - save_final_solution = true) +save_solution = SaveSolutionCallback( + interval = 1000, + save_initial_solution = true, + save_final_solution = true +) -callbacks = CallbackSet(summary_callback, - analysis_callback, - save_solution, - alive_callback) +callbacks = CallbackSet( + summary_callback, + analysis_callback, + save_solution, + alive_callback +) ############################################################################### # run the simulation -sol = solve(ode, SSPRK43(), abstol = 1.0e-6, reltol = 1.0e-6, - save_everystep = false, callback = callbacks) +sol = solve( + ode, SSPRK43(), abstol = 1.0e-6, reltol = 1.0e-6, + save_everystep = false, callback = callbacks +) summary_callback() # print the timer summary diff --git a/ext/TrixiConvexECOSExt.jl b/ext/TrixiConvexECOSExt.jl index 8251fe3eed..a0aed959c2 100644 --- a/ext/TrixiConvexECOSExt.jl +++ b/ext/TrixiConvexECOSExt.jl @@ -22,48 +22,50 @@ using Trixi: Trixi, undo_normalization!, bisect_stability_polynomial, @muladd # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Undo normalization of stability polynomial coefficients by index factorial -# relative to consistency order. -function Trixi.undo_normalization!(gamma_opt, consistency_order, num_stage_evals) - for k in (consistency_order + 1):num_stage_evals - gamma_opt[k - consistency_order] = gamma_opt[k - consistency_order] / - factorial(k) - end - return gamma_opt -end - -# Compute stability polynomials for paired explicit Runge-Kutta up to specified consistency -# order, including contributions from free coefficients for higher orders, and -# return the maximum absolute value -function stability_polynomials!(pnoms, consistency_order, num_stage_evals, - normalized_powered_eigvals_scaled, - gamma) - num_eig_vals = length(pnoms) - - # Initialize with zero'th order (z^0) coefficient - for i in 1:num_eig_vals - pnoms[i] = 1.0 + #! format: noindent + + # Undo normalization of stability polynomial coefficients by index factorial + # relative to consistency order. + function Trixi.undo_normalization!(gamma_opt, consistency_order, num_stage_evals) + for k in (consistency_order + 1):num_stage_evals + gamma_opt[k - consistency_order] = gamma_opt[k - consistency_order] / + factorial(k) + end + return gamma_opt end - # First `consistency_order` terms of the exponential - for k in 1:consistency_order + # Compute stability polynomials for paired explicit Runge-Kutta up to specified consistency + # order, including contributions from free coefficients for higher orders, and + # return the maximum absolute value + function stability_polynomials!( + pnoms, consistency_order, num_stage_evals, + normalized_powered_eigvals_scaled, + gamma + ) + num_eig_vals = length(pnoms) + + # Initialize with zero'th order (z^0) coefficient for i in 1:num_eig_vals - pnoms[i] += normalized_powered_eigvals_scaled[i, k] + pnoms[i] = 1.0 end - end - # Contribution from free coefficients - for k in (consistency_order + 1):num_stage_evals - pnoms += gamma[k - consistency_order] * normalized_powered_eigvals_scaled[:, k] - end + # First `consistency_order` terms of the exponential + for k in 1:consistency_order + for i in 1:num_eig_vals + pnoms[i] += normalized_powered_eigvals_scaled[i, k] + end + end - # For optimization only the maximum is relevant - return maximum(abs(pnoms)) -end + # Contribution from free coefficients + for k in (consistency_order + 1):num_stage_evals + pnoms += gamma[k - consistency_order] * normalized_powered_eigvals_scaled[:, k] + end + + # For optimization only the maximum is relevant + return maximum(abs(pnoms)) + end -#= + #= The following structures and methods provide a simplified implementation to discover optimal stability polynomial for a given set of `eig_vals` These are designed for the one-step (i.e., Runge-Kutta methods) integration of initial value ordinary @@ -74,92 +76,104 @@ Optimal stability polynomials for numerical integration of initial value problem [DOI: 10.2140/camcos.2012.7.247](https://doi.org/10.2140/camcos.2012.7.247) =# -# Perform bisection to optimize timestep for stability of the polynomial -function Trixi.bisect_stability_polynomial(consistency_order, num_eig_vals, - num_stage_evals, - dtmax, dteps, eig_vals; - verbose = false) - dtmin = 0.0 - dt = -1.0 - abs_p = -1.0 + # Perform bisection to optimize timestep for stability of the polynomial + function Trixi.bisect_stability_polynomial( + consistency_order, num_eig_vals, + num_stage_evals, + dtmax, dteps, eig_vals; + verbose = false + ) + dtmin = 0.0 + dt = -1.0 + abs_p = -1.0 - # Construct stability polynomial for each eigenvalue - pnoms = ones(Complex{Float64}, num_eig_vals, 1) + # Construct stability polynomial for each eigenvalue + pnoms = ones(Complex{Float64}, num_eig_vals, 1) - # Init datastructure for monomial coefficients - gamma = Variable(num_stage_evals - consistency_order) + # Init datastructure for monomial coefficients + gamma = Variable(num_stage_evals - consistency_order) - normalized_powered_eigvals = zeros(Complex{Float64}, num_eig_vals, num_stage_evals) + normalized_powered_eigvals = zeros(Complex{Float64}, num_eig_vals, num_stage_evals) - for j in 1:num_stage_evals - fac_j = factorial(j) - for i in 1:num_eig_vals - normalized_powered_eigvals[i, j] = eig_vals[i]^j / fac_j + for j in 1:num_stage_evals + fac_j = factorial(j) + for i in 1:num_eig_vals + normalized_powered_eigvals[i, j] = eig_vals[i]^j / fac_j + end end - end - normalized_powered_eigvals_scaled = similar(normalized_powered_eigvals) + normalized_powered_eigvals_scaled = similar(normalized_powered_eigvals) - if verbose - println("Start optimization of stability polynomial \n") - end + if verbose + println("Start optimization of stability polynomial \n") + end - # Bisection on timestep - while dtmax - dtmin > dteps - dt = 0.5 * (dtmax + dtmin) + # Bisection on timestep + while dtmax - dtmin > dteps + dt = 0.5 * (dtmax + dtmin) + + # Compute stability polynomial for current timestep + for k in 1:num_stage_evals + dt_k = dt^k + for i in 1:num_eig_vals + normalized_powered_eigvals_scaled[i, k] = dt_k * + normalized_powered_eigvals[ + i, + k, + ] + end + end - # Compute stability polynomial for current timestep - for k in 1:num_stage_evals - dt_k = dt^k - for i in 1:num_eig_vals - normalized_powered_eigvals_scaled[i, k] = dt_k * - normalized_powered_eigvals[i, - k] + # Use last optimal values for gamma in (potentially) next iteration + problem = minimize( + stability_polynomials!( + pnoms, consistency_order, + num_stage_evals, + normalized_powered_eigvals_scaled, + gamma + ) + ) + + solve!( + problem, + # Parameters taken from default values for EiCOS + MOI.OptimizerWithAttributes( + Optimizer, "gamma" => 0.99, + "delta" => 2.0e-7, + "feastol" => 1.0e-9, + "abstol" => 1.0e-9, + "reltol" => 1.0e-9, + "feastol_inacc" => 1.0e-4, + "abstol_inacc" => 5.0e-5, + "reltol_inacc" => 5.0e-5, + "nitref" => 9, + "maxit" => 100, + "verbose" => 3 + ); silent = true + ) + + abs_p = problem.optval + + if abs_p < 1 + dtmin = dt + else + dtmax = dt end end - # Use last optimal values for gamma in (potentially) next iteration - problem = minimize(stability_polynomials!(pnoms, consistency_order, - num_stage_evals, - normalized_powered_eigvals_scaled, - gamma)) - - solve!(problem, - # Parameters taken from default values for EiCOS - MOI.OptimizerWithAttributes(Optimizer, "gamma" => 0.99, - "delta" => 2e-7, - "feastol" => 1e-9, - "abstol" => 1e-9, - "reltol" => 1e-9, - "feastol_inacc" => 1e-4, - "abstol_inacc" => 5e-5, - "reltol_inacc" => 5e-5, - "nitref" => 9, - "maxit" => 100, - "verbose" => 3); silent = true) - - abs_p = problem.optval - - if abs_p < 1 - dtmin = dt - else - dtmax = dt + if verbose + println("Concluded stability polynomial optimization \n") end - end - if verbose - println("Concluded stability polynomial optimization \n") - end + gamma_opt = evaluate(gamma) - gamma_opt = evaluate(gamma) + # Catch case S = 3 (only one opt. variable) + if isa(gamma_opt, Number) + gamma_opt = [gamma_opt] + end - # Catch case S = 3 (only one opt. variable) - if isa(gamma_opt, Number) - gamma_opt = [gamma_opt] + return gamma_opt, dt end - - return gamma_opt, dt -end end # @muladd end # module TrixiConvexECOSExt diff --git a/ext/TrixiMakieExt.jl b/ext/TrixiMakieExt.jl index 301a7656da..a2e923f126 100644 --- a/ext/TrixiMakieExt.jl +++ b/ext/TrixiMakieExt.jl @@ -14,7 +14,7 @@ using Trixi # Use additional symbols that are not exported using Trixi: PlotData2DTriangulated, TrixiODESolution, PlotDataSeries, ScalarData, @muladd, - wrap_array_native, mesh_equations_solver_cache + wrap_array_native, mesh_equations_solver_cache # Import functions such that they can be extended with new methods import Trixi: iplot, iplot! @@ -24,399 +24,473 @@ import Trixi: iplot, iplot! # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# First some utilities -# Given a reference plotting triangulation, this function generates a plotting triangulation for -# the entire global mesh. The output can be plotted using `Makie.mesh`. -function global_plotting_triangulation_makie(pds::PlotDataSeries{<:PlotData2DTriangulated}; - set_z_coordinate_zero = false) - @unpack variable_id = pds - pd = pds.plot_data - @unpack x, y, data, t = pd - - makie_triangles = Makie.to_triangles(t) - - # trimesh[i] holds GeometryBasics.Mesh containing plotting information on the ith element. - # Note: Float32 is required by GeometryBasics - num_plotting_nodes, num_elements = size(x) - trimesh = Vector{GeometryBasics.Mesh{3, Float32}}(undef, num_elements) - coordinates = zeros(Float32, num_plotting_nodes, 3) - for element in Base.OneTo(num_elements) - for i in Base.OneTo(num_plotting_nodes) - coordinates[i, 1] = x[i, element] - coordinates[i, 2] = y[i, element] - if set_z_coordinate_zero == false - coordinates[i, 3] = data[i, element][variable_id] + #! format: noindent + + # First some utilities + # Given a reference plotting triangulation, this function generates a plotting triangulation for + # the entire global mesh. The output can be plotted using `Makie.mesh`. + function global_plotting_triangulation_makie( + pds::PlotDataSeries{<:PlotData2DTriangulated}; + set_z_coordinate_zero = false + ) + @unpack variable_id = pds + pd = pds.plot_data + @unpack x, y, data, t = pd + + makie_triangles = Makie.to_triangles(t) + + # trimesh[i] holds GeometryBasics.Mesh containing plotting information on the ith element. + # Note: Float32 is required by GeometryBasics + num_plotting_nodes, num_elements = size(x) + trimesh = Vector{GeometryBasics.Mesh{3, Float32}}(undef, num_elements) + coordinates = zeros(Float32, num_plotting_nodes, 3) + for element in Base.OneTo(num_elements) + for i in Base.OneTo(num_plotting_nodes) + coordinates[i, 1] = x[i, element] + coordinates[i, 2] = y[i, element] + if set_z_coordinate_zero == false + coordinates[i, 3] = data[i, element][variable_id] + end end + trimesh[element] = GeometryBasics.normal_mesh( + Makie.to_vertices(coordinates), + makie_triangles + ) end - trimesh[element] = GeometryBasics.normal_mesh(Makie.to_vertices(coordinates), - makie_triangles) - end - plotting_mesh = merge([trimesh...]) # merge meshes on each element into one large mesh - return plotting_mesh -end - -# Returns a list of `Makie.Point`s which can be used to plot the mesh, or a solution "wireframe" -# (e.g., a plot of the mesh lines but with the z-coordinate equal to the value of the solution). -function convert_PlotData2D_to_mesh_Points(pds::PlotDataSeries{<:PlotData2DTriangulated}; - set_z_coordinate_zero = false) - @unpack variable_id = pds - pd = pds.plot_data - @unpack x_face, y_face, face_data = pd - - if set_z_coordinate_zero - # plot 2d surface by setting z coordinate to zero. - # Uses `x_face` since `face_data` may be `::Nothing`, as it's not used for 2D plots. - sol_f = zeros(eltype(first(x_face)), size(x_face)) - else - sol_f = StructArrays.component(face_data, variable_id) + plotting_mesh = merge([trimesh...]) # merge meshes on each element into one large mesh + return plotting_mesh end - # This line separates solution lines on each edge by NaNs to ensure that they are rendered - # separately. The coordinates `xf`, `yf` and the solution `sol_f`` are assumed to be a matrix - # whose columns correspond to different elements. We add NaN separators by appending a row of - # NaNs to this matrix. We also flatten (e.g., apply `vec` to) the result, as this speeds up - # plotting. - xyz_wireframe = GeometryBasics.Point.(map(x -> vec(vcat(x, - fill(NaN, 1, size(x, 2)))), - (x_face, y_face, sol_f))...) + # Returns a list of `Makie.Point`s which can be used to plot the mesh, or a solution "wireframe" + # (e.g., a plot of the mesh lines but with the z-coordinate equal to the value of the solution). + function convert_PlotData2D_to_mesh_Points( + pds::PlotDataSeries{<:PlotData2DTriangulated}; + set_z_coordinate_zero = false + ) + @unpack variable_id = pds + pd = pds.plot_data + @unpack x_face, y_face, face_data = pd + + if set_z_coordinate_zero + # plot 2d surface by setting z coordinate to zero. + # Uses `x_face` since `face_data` may be `::Nothing`, as it's not used for 2D plots. + sol_f = zeros(eltype(first(x_face)), size(x_face)) + else + sol_f = StructArrays.component(face_data, variable_id) + end - return xyz_wireframe -end + # This line separates solution lines on each edge by NaNs to ensure that they are rendered + # separately. The coordinates `xf`, `yf` and the solution `sol_f`` are assumed to be a matrix + # whose columns correspond to different elements. We add NaN separators by appending a row of + # NaNs to this matrix. We also flatten (e.g., apply `vec` to) the result, as this speeds up + # plotting. + xyz_wireframe = GeometryBasics.Point.( + map( + x -> vec( + vcat( + x, + fill(NaN, 1, size(x, 2)) + ) + ), + (x_face, y_face, sol_f) + )... + ) + + return xyz_wireframe + end -# Creates a GeometryBasics triangulation for the visualization of a ScalarData2D plot object. -function global_plotting_triangulation_makie(pd::PlotData2DTriangulated{<:ScalarData}; - set_z_coordinate_zero = false) - @unpack x, y, data, t = pd - - makie_triangles = Makie.to_triangles(t) - - # trimesh[i] holds GeometryBasics.Mesh containing plotting information on the ith element. - # Note: Float32 is required by GeometryBasics - num_plotting_nodes, num_elements = size(x) - trimesh = Vector{GeometryBasics.Mesh{3, Float32}}(undef, num_elements) - coordinates = zeros(Float32, num_plotting_nodes, 3) - for element in Base.OneTo(num_elements) - for i in Base.OneTo(num_plotting_nodes) - coordinates[i, 1] = x[i, element] - coordinates[i, 2] = y[i, element] - if set_z_coordinate_zero == false - coordinates[i, 3] = data.data[i, element] + # Creates a GeometryBasics triangulation for the visualization of a ScalarData2D plot object. + function global_plotting_triangulation_makie( + pd::PlotData2DTriangulated{<:ScalarData}; + set_z_coordinate_zero = false + ) + @unpack x, y, data, t = pd + + makie_triangles = Makie.to_triangles(t) + + # trimesh[i] holds GeometryBasics.Mesh containing plotting information on the ith element. + # Note: Float32 is required by GeometryBasics + num_plotting_nodes, num_elements = size(x) + trimesh = Vector{GeometryBasics.Mesh{3, Float32}}(undef, num_elements) + coordinates = zeros(Float32, num_plotting_nodes, 3) + for element in Base.OneTo(num_elements) + for i in Base.OneTo(num_plotting_nodes) + coordinates[i, 1] = x[i, element] + coordinates[i, 2] = y[i, element] + if set_z_coordinate_zero == false + coordinates[i, 3] = data.data[i, element] + end end + trimesh[element] = GeometryBasics.normal_mesh( + Makie.to_vertices(coordinates), + makie_triangles + ) end - trimesh[element] = GeometryBasics.normal_mesh(Makie.to_vertices(coordinates), - makie_triangles) - end - plotting_mesh = merge([trimesh...]) # merge meshes on each element into one large mesh - return plotting_mesh -end - -# Returns a list of `GeometryBasics.Point`s which can be used to plot the mesh, or a solution "wireframe" -# (e.g., a plot of the mesh lines but with the z-coordinate equal to the value of the solution). -function convert_PlotData2D_to_mesh_Points(pd::PlotData2DTriangulated{<:ScalarData}; - set_z_coordinate_zero = false) - @unpack x_face, y_face, face_data = pd - - if set_z_coordinate_zero - # plot 2d surface by setting z coordinate to zero. - # Uses `x_face` since `face_data` may be `::Nothing`, as it's not used for 2D plots. - sol_f = zeros(eltype(first(x_face)), size(x_face)) - else - sol_f = face_data + plotting_mesh = merge([trimesh...]) # merge meshes on each element into one large mesh + return plotting_mesh end - # This line separates solution lines on each edge by NaNs to ensure that they are rendered - # separately. The coordinates `xf`, `yf` and the solution `sol_f`` are assumed to be a matrix - # whose columns correspond to different elements. We add NaN separators by appending a row of - # NaNs to this matrix. We also flatten (e.g., apply `vec` to) the result, as this speeds up - # plotting. - xyz_wireframe = GeometryBasics.Point.(map(x -> vec(vcat(x, - fill(NaN, 1, size(x, 2)))), - (x_face, y_face, sol_f))...) - - return xyz_wireframe -end + # Returns a list of `GeometryBasics.Point`s which can be used to plot the mesh, or a solution "wireframe" + # (e.g., a plot of the mesh lines but with the z-coordinate equal to the value of the solution). + function convert_PlotData2D_to_mesh_Points( + pd::PlotData2DTriangulated{<:ScalarData}; + set_z_coordinate_zero = false + ) + @unpack x_face, y_face, face_data = pd + + if set_z_coordinate_zero + # plot 2d surface by setting z coordinate to zero. + # Uses `x_face` since `face_data` may be `::Nothing`, as it's not used for 2D plots. + sol_f = zeros(eltype(first(x_face)), size(x_face)) + else + sol_f = face_data + end -# We set the Makie default colormap to match Plots.jl, which uses `:inferno` by default. -default_Makie_colormap() = :inferno + # This line separates solution lines on each edge by NaNs to ensure that they are rendered + # separately. The coordinates `xf`, `yf` and the solution `sol_f`` are assumed to be a matrix + # whose columns correspond to different elements. We add NaN separators by appending a row of + # NaNs to this matrix. We also flatten (e.g., apply `vec` to) the result, as this speeds up + # plotting. + xyz_wireframe = GeometryBasics.Point.( + map( + x -> vec( + vcat( + x, + fill(NaN, 1, size(x, 2)) + ) + ), + (x_face, y_face, sol_f) + )... + ) + + return xyz_wireframe + end -# convenience struct for editing Makie plots after they're created. -struct FigureAndAxes{Axes} - fig::Makie.Figure - axes::Axes -end + # We set the Makie default colormap to match Plots.jl, which uses `:inferno` by default. + default_Makie_colormap() = :inferno -# for "quiet" return arguments to Makie.plot(::TrixiODESolution) and -# Makie.plot(::PlotData2DTriangulated) -Base.show(io::IO, fa::FigureAndAxes) = nothing - -# allows for returning fig, axes = Makie.plot(...) -function Base.iterate(fa::FigureAndAxes, state = 1) - if state == 1 - return (fa.fig, 2) - elseif state == 2 - return (fa.axes, 3) - else - return nothing + # convenience struct for editing Makie plots after they're created. + struct FigureAndAxes{Axes} + fig::Makie.Figure + axes::Axes end -end -""" - iplot(u, mesh::UnstructuredMesh2D, equations, solver, cache; - plot_mesh=true, show_axis=false, colormap=default_Makie_colormap(), - variable_to_plot_in=1) - -Creates an interactive surface plot of the solution and mesh for an `UnstructuredMesh2D` type. - -Keywords: -- variable_to_plot_in: variable to show by default - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -function iplot end - -# Enables `iplot(PlotData2D(sol))`. -function iplot(pd::PlotData2DTriangulated; - plot_mesh = true, show_axis = false, colormap = default_Makie_colormap(), - variable_to_plot_in = 1) - @unpack variable_names = pd - - # Initialize a Makie figure that we'll add the solution and toggle switches to. - fig = Makie.Figure() - - # Set up options for the drop-down menu - menu_options = [zip(variable_names, 1:length(variable_names))...] - menu = Makie.Menu(fig, options = menu_options) - - # Initialize toggle switches for viewing the mesh - toggle_solution_mesh = Makie.Toggle(fig, active = plot_mesh) - toggle_mesh = Makie.Toggle(fig, active = plot_mesh) - - # Add dropdown menu and toggle switches to the left side of the figure. - fig[1, 1] = Makie.vgrid!(Makie.Label(fig, "Solution field", width = nothing), menu, - Makie.Label(fig, "Solution mesh visible"), - toggle_solution_mesh, - Makie.Label(fig, "Mesh visible"), toggle_mesh; - tellheight = false, width = 200) - - # Create a zoomable interactive axis object on top of which to plot the solution. - ax = Makie.LScene(fig[1, 2], scenekw = (show_axis = show_axis,)) - - # Initialize the dropdown menu to `variable_to_plot_in` - # Since menu.selection is an Observable type, we need to dereference it using `[]` to set. - menu.selection[] = variable_to_plot_in - menu.i_selected[] = variable_to_plot_in - - # Since `variable_to_plot` is an Observable, these lines are re-run whenever `variable_to_plot[]` - # is updated from the drop-down menu. - plotting_mesh = Makie.@lift(global_plotting_triangulation_makie(getindex(pd, - variable_names[$(menu.selection)]))) - solution_z = Makie.@lift(getindex.($plotting_mesh.position, 3)) - - # Plot the actual solution. - Makie.mesh!(ax, plotting_mesh; color = solution_z, colormap) - - # Create a mesh overlay by plotting a mesh both on top of and below the solution contours. - wire_points = Makie.@lift(convert_PlotData2D_to_mesh_Points(getindex(pd, - variable_names[$(menu.selection)]))) - wire_mesh_top = Makie.lines!(ax, wire_points, color = :white) - wire_mesh_bottom = Makie.lines!(ax, wire_points, color = :white) - Makie.translate!(wire_mesh_top, 0, 0, 1e-3) - Makie.translate!(wire_mesh_bottom, 0, 0, -1e-3) - - # This draws flat mesh lines below the solution. - function compute_z_offset(solution_z) - zmin = minimum(solution_z) - zrange = (x -> x[2] - x[1])(extrema(solution_z)) - return zmin - 0.25 * zrange - end - z_offset = Makie.@lift(compute_z_offset($solution_z)) - function get_flat_points(wire_points, z_offset) - [Makie.Point(point.data[1:2]..., z_offset) for point in wire_points] - end - flat_wire_points = Makie.@lift get_flat_points($wire_points, $z_offset) - wire_mesh_flat = Makie.lines!(ax, flat_wire_points, color = :black) - - # create a small variation in the extrema to avoid the Makie `range_step` cannot be zero error. - # see https://github.com/MakieOrg/Makie.jl/issues/931 for more details. - # the colorbar range is perturbed by 1e-5 * the magnitude of the solution. - function scaled_extrema(x) - ex = extrema(x) - if ex[2] ≈ ex[1] # if solution is close to constant, perturb colorbar - return ex .+ 1e-5 .* maximum(abs.(ex)) .* (-1, 1) + # for "quiet" return arguments to Makie.plot(::TrixiODESolution) and + # Makie.plot(::PlotData2DTriangulated) + Base.show(io::IO, fa::FigureAndAxes) = nothing + + # allows for returning fig, axes = Makie.plot(...) + function Base.iterate(fa::FigureAndAxes, state = 1) + if state == 1 + return (fa.fig, 2) + elseif state == 2 + return (fa.axes, 3) else - return ex + return nothing end end - # Resets the colorbar each time the solution changes. - Makie.Colorbar(fig[1, 3], limits = Makie.@lift(scaled_extrema($solution_z)), - colormap = colormap) - - # This syncs the toggle buttons to the mesh plots. - Makie.connect!(wire_mesh_top.visible, toggle_solution_mesh.active) - Makie.connect!(wire_mesh_bottom.visible, toggle_solution_mesh.active) - Makie.connect!(wire_mesh_flat.visible, toggle_mesh.active) - - # On OSX, shift-command-4 for screenshots triggers a constant "up-zoom". - # To avoid this, we remap up-zoom to the right shift button instead. - Makie.cameracontrols(ax.scene).attributes[:up_key][] = Makie.Keyboard.right_shift - - # typing this pulls up the figure (similar to display(plot!()) in Plots.jl) - fig -end - -function iplot(u, mesh, equations, solver, cache; - solution_variables = nothing, nvisnodes = 2 * nnodes(solver), kwargs...) - @assert ndims(mesh) == 2 - - pd = PlotData2DTriangulated(u, mesh, equations, solver, cache; - solution_variables = solution_variables, - nvisnodes = nvisnodes) - - iplot(pd; kwargs...) -end + """ + iplot(u, mesh::UnstructuredMesh2D, equations, solver, cache; + plot_mesh=true, show_axis=false, colormap=default_Makie_colormap(), + variable_to_plot_in=1) + + Creates an interactive surface plot of the solution and mesh for an `UnstructuredMesh2D` type. + + Keywords: + - variable_to_plot_in: variable to show by default + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + function iplot end + + # Enables `iplot(PlotData2D(sol))`. + function iplot( + pd::PlotData2DTriangulated; + plot_mesh = true, show_axis = false, colormap = default_Makie_colormap(), + variable_to_plot_in = 1 + ) + @unpack variable_names = pd + + # Initialize a Makie figure that we'll add the solution and toggle switches to. + fig = Makie.Figure() + + # Set up options for the drop-down menu + menu_options = [zip(variable_names, 1:length(variable_names))...] + menu = Makie.Menu(fig, options = menu_options) + + # Initialize toggle switches for viewing the mesh + toggle_solution_mesh = Makie.Toggle(fig, active = plot_mesh) + toggle_mesh = Makie.Toggle(fig, active = plot_mesh) + + # Add dropdown menu and toggle switches to the left side of the figure. + fig[1, 1] = Makie.vgrid!( + Makie.Label(fig, "Solution field", width = nothing), menu, + Makie.Label(fig, "Solution mesh visible"), + toggle_solution_mesh, + Makie.Label(fig, "Mesh visible"), toggle_mesh; + tellheight = false, width = 200 + ) + + # Create a zoomable interactive axis object on top of which to plot the solution. + ax = Makie.LScene(fig[1, 2], scenekw = (show_axis = show_axis,)) + + # Initialize the dropdown menu to `variable_to_plot_in` + # Since menu.selection is an Observable type, we need to dereference it using `[]` to set. + menu.selection[] = variable_to_plot_in + menu.i_selected[] = variable_to_plot_in + + # Since `variable_to_plot` is an Observable, these lines are re-run whenever `variable_to_plot[]` + # is updated from the drop-down menu. + plotting_mesh = Makie.@lift( + global_plotting_triangulation_makie( + getindex( + pd, + variable_names[$(menu.selection)] + ) + ) + ) + solution_z = Makie.@lift(getindex.($plotting_mesh.position, 3)) + + # Plot the actual solution. + Makie.mesh!(ax, plotting_mesh; color = solution_z, colormap) + + # Create a mesh overlay by plotting a mesh both on top of and below the solution contours. + wire_points = Makie.@lift( + convert_PlotData2D_to_mesh_Points( + getindex( + pd, + variable_names[$(menu.selection)] + ) + ) + ) + wire_mesh_top = Makie.lines!(ax, wire_points, color = :white) + wire_mesh_bottom = Makie.lines!(ax, wire_points, color = :white) + Makie.translate!(wire_mesh_top, 0, 0, 1.0e-3) + Makie.translate!(wire_mesh_bottom, 0, 0, -1.0e-3) + + # This draws flat mesh lines below the solution. + function compute_z_offset(solution_z) + zmin = minimum(solution_z) + zrange = (x -> x[2] - x[1])(extrema(solution_z)) + return zmin - 0.25 * zrange + end + z_offset = Makie.@lift(compute_z_offset($solution_z)) + function get_flat_points(wire_points, z_offset) + [Makie.Point(point.data[1:2]..., z_offset) for point in wire_points] + end + flat_wire_points = Makie.@lift get_flat_points($wire_points, $z_offset) + wire_mesh_flat = Makie.lines!(ax, flat_wire_points, color = :black) + + # create a small variation in the extrema to avoid the Makie `range_step` cannot be zero error. + # see https://github.com/MakieOrg/Makie.jl/issues/931 for more details. + # the colorbar range is perturbed by 1e-5 * the magnitude of the solution. + function scaled_extrema(x) + ex = extrema(x) + if ex[2] ≈ ex[1] # if solution is close to constant, perturb colorbar + return ex .+ 1.0e-5 .* maximum(abs.(ex)) .* (-1, 1) + else + return ex + end + end -# redirect `iplot(sol)` to dispatchable `iplot` signature. -iplot(sol::TrixiODESolution; kwargs...) = iplot(sol.u[end], sol.prob.p; kwargs...) -function iplot(u, semi; kwargs...) - iplot(wrap_array_native(u, semi), mesh_equations_solver_cache(semi)...; kwargs...) -end + # Resets the colorbar each time the solution changes. + Makie.Colorbar( + fig[1, 3], limits = Makie.@lift(scaled_extrema($solution_z)), + colormap = colormap + ) -# Interactive visualization of user-defined ScalarData. -function iplot(pd::PlotData2DTriangulated{<:ScalarData}; - show_axis = false, colormap = default_Makie_colormap(), - plot_mesh = false) - fig = Makie.Figure() + # This syncs the toggle buttons to the mesh plots. + Makie.connect!(wire_mesh_top.visible, toggle_solution_mesh.active) + Makie.connect!(wire_mesh_bottom.visible, toggle_solution_mesh.active) + Makie.connect!(wire_mesh_flat.visible, toggle_mesh.active) - # Create a zoomable interactive axis object on top of which to plot the solution. - ax = Makie.LScene(fig[1, 1], scenekw = (show_axis = show_axis,)) + # On OSX, shift-command-4 for screenshots triggers a constant "up-zoom". + # To avoid this, we remap up-zoom to the right shift button instead. + Makie.cameracontrols(ax.scene).attributes[:up_key][] = Makie.Keyboard.right_shift - # plot the user-defined ScalarData - fig_axis_plt = iplot!(FigureAndAxes(fig, ax), pd; colormap = colormap, - plot_mesh = plot_mesh) + # typing this pulls up the figure (similar to display(plot!()) in Plots.jl) + fig + end - fig - return fig_axis_plt -end + function iplot( + u, mesh, equations, solver, cache; + solution_variables = nothing, nvisnodes = 2 * nnodes(solver), kwargs... + ) + @assert ndims(mesh) == 2 -function iplot!(fig_axis::Union{FigureAndAxes, Makie.FigureAxisPlot}, - pd::PlotData2DTriangulated{<:ScalarData}; - colormap = default_Makie_colormap(), plot_mesh = false) + pd = PlotData2DTriangulated( + u, mesh, equations, solver, cache; + solution_variables = solution_variables, + nvisnodes = nvisnodes + ) - # destructure first two fields of either FigureAndAxes or Makie.FigureAxisPlot - fig, ax = fig_axis + iplot(pd; kwargs...) + end - # create triangulation of the scalar data to plot - plotting_mesh = global_plotting_triangulation_makie(pd) - solution_z = getindex.(plotting_mesh.position, 3) - plt = Makie.mesh!(ax, plotting_mesh; color = solution_z, colormap) + # redirect `iplot(sol)` to dispatchable `iplot` signature. + iplot(sol::TrixiODESolution; kwargs...) = iplot(sol.u[end], sol.prob.p; kwargs...) + function iplot(u, semi; kwargs...) + iplot(wrap_array_native(u, semi), mesh_equations_solver_cache(semi)...; kwargs...) + end - if plot_mesh - wire_points = convert_PlotData2D_to_mesh_Points(pd) - wire_mesh_top = Makie.lines!(ax, wire_points, color = :white) - wire_mesh_bottom = Makie.lines!(ax, wire_points, color = :white) - Makie.translate!(wire_mesh_top, 0, 0, 1e-3) - Makie.translate!(wire_mesh_bottom, 0, 0, -1e-3) + # Interactive visualization of user-defined ScalarData. + function iplot( + pd::PlotData2DTriangulated{<:ScalarData}; + show_axis = false, colormap = default_Makie_colormap(), + plot_mesh = false + ) + fig = Makie.Figure() + + # Create a zoomable interactive axis object on top of which to plot the solution. + ax = Makie.LScene(fig[1, 1], scenekw = (show_axis = show_axis,)) + + # plot the user-defined ScalarData + fig_axis_plt = iplot!( + FigureAndAxes(fig, ax), pd; colormap = colormap, + plot_mesh = plot_mesh + ) + + fig + return fig_axis_plt end - # Add a colorbar to the rightmost part of the layout - Makie.Colorbar(fig[1, end + 1], plt) + function iplot!( + fig_axis::Union{FigureAndAxes, Makie.FigureAxisPlot}, + pd::PlotData2DTriangulated{<:ScalarData}; + colormap = default_Makie_colormap(), plot_mesh = false + ) + + # destructure first two fields of either FigureAndAxes or Makie.FigureAxisPlot + fig, ax = fig_axis + + # create triangulation of the scalar data to plot + plotting_mesh = global_plotting_triangulation_makie(pd) + solution_z = getindex.(plotting_mesh.position, 3) + plt = Makie.mesh!(ax, plotting_mesh; color = solution_z, colormap) + + if plot_mesh + wire_points = convert_PlotData2D_to_mesh_Points(pd) + wire_mesh_top = Makie.lines!(ax, wire_points, color = :white) + wire_mesh_bottom = Makie.lines!(ax, wire_points, color = :white) + Makie.translate!(wire_mesh_top, 0, 0, 1.0e-3) + Makie.translate!(wire_mesh_bottom, 0, 0, -1.0e-3) + end - fig - return Makie.FigureAxisPlot(fig, ax, plt) -end + # Add a colorbar to the rightmost part of the layout + Makie.Colorbar(fig[1, end + 1], plt) -# ================== new Makie plot recipes ==================== + fig + return Makie.FigureAxisPlot(fig, ax, plt) + end -# This initializes a Makie recipe, which creates a new type definition which Makie uses to create -# custom `trixiheatmap` plots. See also https://docs.makie.org/stable/documentation/recipes/ -Makie.@recipe(TrixiHeatmap, plot_data_series) do scene - Makie.Theme(colormap = default_Makie_colormap()) -end + # ================== new Makie plot recipes ==================== -function Makie.plot!(myplot::TrixiHeatmap) - pds = myplot[:plot_data_series][] + # This initializes a Makie recipe, which creates a new type definition which Makie uses to create + # custom `trixiheatmap` plots. See also https://docs.makie.org/stable/documentation/recipes/ + Makie.@recipe(TrixiHeatmap, plot_data_series) do scene + Makie.Theme(colormap = default_Makie_colormap()) + end - plotting_mesh = global_plotting_triangulation_makie(pds; - set_z_coordinate_zero = true) + function Makie.plot!(myplot::TrixiHeatmap) + pds = myplot[:plot_data_series][] + + plotting_mesh = global_plotting_triangulation_makie( + pds; + set_z_coordinate_zero = true + ) + + pd = pds.plot_data + solution_z = vec(StructArrays.component(pd.data, pds.variable_id)) + Makie.mesh!( + myplot, plotting_mesh, color = solution_z, shading = false, + colormap = myplot[:colormap] + ) + myplot.colorrange = extrema(solution_z) + + # Makie hides keyword arguments within `myplot`; see also + # https://github.com/JuliaPlots/Makie.jl/issues/837#issuecomment-845985070 + plot_mesh = if haskey(myplot, :plot_mesh) + myplot.plot_mesh[] + else + true # default to plotting the mesh + end - pd = pds.plot_data - solution_z = vec(StructArrays.component(pd.data, pds.variable_id)) - Makie.mesh!(myplot, plotting_mesh, color = solution_z, shading = false, - colormap = myplot[:colormap]) - myplot.colorrange = extrema(solution_z) + if plot_mesh + xyz_wireframe = convert_PlotData2D_to_mesh_Points( + pds; + set_z_coordinate_zero = true + ) + Makie.lines!(myplot, xyz_wireframe, color = :lightgrey) + end - # Makie hides keyword arguments within `myplot`; see also - # https://github.com/JuliaPlots/Makie.jl/issues/837#issuecomment-845985070 - plot_mesh = if haskey(myplot, :plot_mesh) - myplot.plot_mesh[] - else - true # default to plotting the mesh + myplot end - if plot_mesh - xyz_wireframe = convert_PlotData2D_to_mesh_Points(pds; - set_z_coordinate_zero = true) - Makie.lines!(myplot, xyz_wireframe, color = :lightgrey) + # redirects Makie.plot(pd::PlotDataSeries) to custom recipe TrixiHeatmap(pd) + Makie.plottype(::Trixi.PlotDataSeries{<:Trixi.PlotData2DTriangulated}) = TrixiHeatmap + + # Makie does not yet support layouts in its plot recipes, so we overload `Makie.plot` directly. + function Makie.plot( + sol::TrixiODESolution; + plot_mesh = false, solution_variables = nothing, + colormap = default_Makie_colormap() + ) + return Makie.plot( + PlotData2DTriangulated(sol; solution_variables); plot_mesh, + colormap + ) end - myplot -end - -# redirects Makie.plot(pd::PlotDataSeries) to custom recipe TrixiHeatmap(pd) -Makie.plottype(::Trixi.PlotDataSeries{<:Trixi.PlotData2DTriangulated}) = TrixiHeatmap - -# Makie does not yet support layouts in its plot recipes, so we overload `Makie.plot` directly. -function Makie.plot(sol::TrixiODESolution; - plot_mesh = false, solution_variables = nothing, - colormap = default_Makie_colormap()) - return Makie.plot(PlotData2DTriangulated(sol; solution_variables); plot_mesh, - colormap) -end - -function Makie.plot(pd::PlotData2DTriangulated, fig = Makie.Figure(); - plot_mesh = false, colormap = default_Makie_colormap()) - figAxes = Makie.plot!(fig, pd; plot_mesh, colormap) - display(figAxes.fig) - return figAxes -end - -function Makie.plot!(fig, pd::PlotData2DTriangulated; - plot_mesh = false, colormap = default_Makie_colormap()) - # Create layout that is as square as possible, when there are more than 3 subplots. - # This is done with a preference for more columns than rows if not. - if length(pd) <= 3 - cols = length(pd) - rows = 1 - else - cols = ceil(Int, sqrt(length(pd))) - rows = cld(length(pd), cols) + function Makie.plot( + pd::PlotData2DTriangulated, fig = Makie.Figure(); + plot_mesh = false, colormap = default_Makie_colormap() + ) + figAxes = Makie.plot!(fig, pd; plot_mesh, colormap) + display(figAxes.fig) + return figAxes end - axes = [Makie.Axis(fig[i, j], xlabel = "x", ylabel = "y") - for j in 1:rows, i in 1:cols] - row_list, col_list = ([i for j in 1:rows, i in 1:cols], - [j for j in 1:rows, i in 1:cols]) - - for (variable_to_plot, (variable_name, pds)) in enumerate(pd) - ax = axes[variable_to_plot] - plt = trixiheatmap!(ax, pds; plot_mesh, colormap) + function Makie.plot!( + fig, pd::PlotData2DTriangulated; + plot_mesh = false, colormap = default_Makie_colormap() + ) + # Create layout that is as square as possible, when there are more than 3 subplots. + # This is done with a preference for more columns than rows if not. + if length(pd) <= 3 + cols = length(pd) + rows = 1 + else + cols = ceil(Int, sqrt(length(pd))) + rows = cld(length(pd), cols) + end - row = row_list[variable_to_plot] - col = col_list[variable_to_plot] - Makie.Colorbar(fig[row, col][1, 2], colormap = colormap) + axes = [ + Makie.Axis(fig[i, j], xlabel = "x", ylabel = "y") + for j in 1:rows, i in 1:cols + ] + row_list, col_list = ( + [i for j in 1:rows, i in 1:cols], + [j for j in 1:rows, i in 1:cols], + ) + + for (variable_to_plot, (variable_name, pds)) in enumerate(pd) + ax = axes[variable_to_plot] + plt = trixiheatmap!(ax, pds; plot_mesh, colormap) + + row = row_list[variable_to_plot] + col = col_list[variable_to_plot] + Makie.Colorbar(fig[row, col][1, 2], colormap = colormap) + + ax.aspect = Makie.DataAspect() # equal aspect ratio + ax.title = variable_name + Makie.xlims!(ax, extrema(pd.x)) + Makie.ylims!(ax, extrema(pd.y)) + end - ax.aspect = Makie.DataAspect() # equal aspect ratio - ax.title = variable_name - Makie.xlims!(ax, extrema(pd.x)) - Makie.ylims!(ax, extrema(pd.y)) + return FigureAndAxes(fig, axes) end - - return FigureAndAxes(fig, axes) -end end # @muladd end diff --git a/src/Trixi.jl b/src/Trixi.jl index 90fa590c50..fe5a1e2e0c 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -19,10 +19,10 @@ module Trixi # (standard library packages first, other packages next, all of them sorted alphabetically) using LinearAlgebra: LinearAlgebra, Diagonal, diag, dot, mul!, norm, cross, normalize, I, - UniformScaling, det + UniformScaling, det using Printf: @printf, @sprintf, println using SparseArrays: AbstractSparseMatrix, AbstractSparseMatrixCSC, sparse, droptol!, - rowvals, nzrange, nonzeros + rowvals, nzrange, nonzeros # import @reexport now to make it available for further imports/exports using Reexport: @reexport @@ -32,12 +32,12 @@ using Reexport: @reexport using MPI: MPI using SciMLBase: CallbackSet, DiscreteCallback, - ODEProblem, ODESolution, - SplitODEProblem + ODEProblem, ODESolution, + SplitODEProblem import SciMLBase: get_du, get_tmp_cache, u_modified!, - init, step!, check_error, - get_proposed_dt, set_proposed_dt!, - terminate!, remake, add_tstop!, has_tstop, first_tstop + init, step!, check_error, + get_proposed_dt, set_proposed_dt!, + terminate!, remake, add_tstop!, has_tstop, first_tstop using Downloads: Downloads using CodeTracking: CodeTracking @@ -84,19 +84,19 @@ const _PREFERENCE_POLYESTER = @load_preference("polyester", true) # finite difference SBP operators using SummationByPartsOperators: AbstractDerivativeOperator, - AbstractNonperiodicDerivativeOperator, - AbstractPeriodicDerivativeOperator, - grid + AbstractNonperiodicDerivativeOperator, + AbstractPeriodicDerivativeOperator, + grid import SummationByPartsOperators: integrate, semidiscretize, - compute_coefficients, compute_coefficients!, - left_boundary_weight, right_boundary_weight + compute_coefficients, compute_coefficients!, + left_boundary_weight, right_boundary_weight @reexport using SummationByPartsOperators: SummationByPartsOperators, derivative_operator, - periodic_derivative_operator, - upwind_operators + periodic_derivative_operator, + upwind_operators # DGMulti solvers @reexport using StartUpDG: StartUpDG, Polynomial, Gauss, TensorProductWedge, SBP, Line, Tri, - Quad, Hex, Tet, Wedge + Quad, Hex, Tet, Wedge using StartUpDG: RefElemData, MeshData, AbstractElemShape # TODO: include_optimized @@ -146,106 +146,106 @@ include("visualization/visualization.jl") # export types/functions that define the public API of Trixi.jl export AcousticPerturbationEquations2D, - CompressibleEulerEquations1D, CompressibleEulerEquations2D, - CompressibleEulerEquations3D, - CompressibleEulerMulticomponentEquations1D, - CompressibleEulerMulticomponentEquations2D, - CompressibleEulerEquationsQuasi1D, - IdealGlmMhdEquations1D, IdealGlmMhdEquations2D, IdealGlmMhdEquations3D, - IdealGlmMhdMulticomponentEquations1D, IdealGlmMhdMulticomponentEquations2D, - HyperbolicDiffusionEquations1D, HyperbolicDiffusionEquations2D, - HyperbolicDiffusionEquations3D, - LinearScalarAdvectionEquation1D, LinearScalarAdvectionEquation2D, - LinearScalarAdvectionEquation3D, - InviscidBurgersEquation1D, - LatticeBoltzmannEquations2D, LatticeBoltzmannEquations3D, - ShallowWaterEquations1D, ShallowWaterEquations2D, - ShallowWaterEquationsQuasi1D, - LinearizedEulerEquations1D, LinearizedEulerEquations2D, LinearizedEulerEquations3D, - PolytropicEulerEquations2D, - TrafficFlowLWREquations1D, - MaxwellEquations1D + CompressibleEulerEquations1D, CompressibleEulerEquations2D, + CompressibleEulerEquations3D, + CompressibleEulerMulticomponentEquations1D, + CompressibleEulerMulticomponentEquations2D, + CompressibleEulerEquationsQuasi1D, + IdealGlmMhdEquations1D, IdealGlmMhdEquations2D, IdealGlmMhdEquations3D, + IdealGlmMhdMulticomponentEquations1D, IdealGlmMhdMulticomponentEquations2D, + HyperbolicDiffusionEquations1D, HyperbolicDiffusionEquations2D, + HyperbolicDiffusionEquations3D, + LinearScalarAdvectionEquation1D, LinearScalarAdvectionEquation2D, + LinearScalarAdvectionEquation3D, + InviscidBurgersEquation1D, + LatticeBoltzmannEquations2D, LatticeBoltzmannEquations3D, + ShallowWaterEquations1D, ShallowWaterEquations2D, + ShallowWaterEquationsQuasi1D, + LinearizedEulerEquations1D, LinearizedEulerEquations2D, LinearizedEulerEquations3D, + PolytropicEulerEquations2D, + TrafficFlowLWREquations1D, + MaxwellEquations1D export LaplaceDiffusion1D, LaplaceDiffusion2D, LaplaceDiffusion3D, - CompressibleNavierStokesDiffusion1D, CompressibleNavierStokesDiffusion2D, - CompressibleNavierStokesDiffusion3D + CompressibleNavierStokesDiffusion1D, CompressibleNavierStokesDiffusion2D, + CompressibleNavierStokesDiffusion3D export GradientVariablesConservative, GradientVariablesPrimitive, GradientVariablesEntropy export flux, flux_central, flux_lax_friedrichs, flux_hll, flux_hllc, flux_hlle, - flux_godunov, - flux_chandrashekar, flux_ranocha, flux_derigs_etal, flux_hindenlang_gassner, - flux_nonconservative_powell, flux_nonconservative_powell_local_symmetric, - flux_kennedy_gruber, flux_shima_etal, flux_ec, - flux_fjordholm_etal, flux_nonconservative_fjordholm_etal, - flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal, - flux_chan_etal, flux_nonconservative_chan_etal, flux_winters_etal, - hydrostatic_reconstruction_audusse_etal, flux_nonconservative_audusse_etal, - FluxPlusDissipation, DissipationGlobalLaxFriedrichs, DissipationLocalLaxFriedrichs, - FluxLaxFriedrichs, max_abs_speed_naive, - FluxHLL, min_max_speed_naive, min_max_speed_davis, min_max_speed_einfeldt, - FluxLMARS, - FluxRotated, - flux_shima_etal_turbo, flux_ranocha_turbo, - FluxHydrostaticReconstruction, - FluxUpwind + flux_godunov, + flux_chandrashekar, flux_ranocha, flux_derigs_etal, flux_hindenlang_gassner, + flux_nonconservative_powell, flux_nonconservative_powell_local_symmetric, + flux_kennedy_gruber, flux_shima_etal, flux_ec, + flux_fjordholm_etal, flux_nonconservative_fjordholm_etal, + flux_wintermeyer_etal, flux_nonconservative_wintermeyer_etal, + flux_chan_etal, flux_nonconservative_chan_etal, flux_winters_etal, + hydrostatic_reconstruction_audusse_etal, flux_nonconservative_audusse_etal, + FluxPlusDissipation, DissipationGlobalLaxFriedrichs, DissipationLocalLaxFriedrichs, + FluxLaxFriedrichs, max_abs_speed_naive, + FluxHLL, min_max_speed_naive, min_max_speed_davis, min_max_speed_einfeldt, + FluxLMARS, + FluxRotated, + flux_shima_etal_turbo, flux_ranocha_turbo, + FluxHydrostaticReconstruction, + FluxUpwind export splitting_steger_warming, splitting_vanleer_haenel, - splitting_coirier_vanleer, splitting_lax_friedrichs, - splitting_drikakis_tsangaris + splitting_coirier_vanleer, splitting_lax_friedrichs, + splitting_drikakis_tsangaris export initial_condition_constant, - initial_condition_gauss, - initial_condition_density_wave, - initial_condition_weak_blast_wave + initial_condition_gauss, + initial_condition_density_wave, + initial_condition_weak_blast_wave export boundary_condition_do_nothing, - boundary_condition_periodic, - BoundaryConditionDirichlet, - BoundaryConditionNeumann, - boundary_condition_noslip_wall, - boundary_condition_slip_wall, - boundary_condition_wall, - BoundaryConditionNavierStokesWall, NoSlip, Adiabatic, Isothermal, - BoundaryConditionCoupled + boundary_condition_periodic, + BoundaryConditionDirichlet, + BoundaryConditionNeumann, + boundary_condition_noslip_wall, + boundary_condition_slip_wall, + boundary_condition_wall, + BoundaryConditionNavierStokesWall, NoSlip, Adiabatic, Isothermal, + BoundaryConditionCoupled export initial_condition_convergence_test, source_terms_convergence_test export source_terms_harmonic export initial_condition_poisson_nonperiodic, source_terms_poisson_nonperiodic, - boundary_condition_poisson_nonperiodic + boundary_condition_poisson_nonperiodic export initial_condition_eoc_test_coupled_euler_gravity, - source_terms_eoc_test_coupled_euler_gravity, source_terms_eoc_test_euler + source_terms_eoc_test_coupled_euler_gravity, source_terms_eoc_test_euler export cons2cons, cons2prim, prim2cons, cons2macroscopic, cons2state, cons2mean, - cons2entropy, entropy2cons + cons2entropy, entropy2cons export density, pressure, density_pressure, velocity, global_mean_vars, - equilibrium_distribution, waterheight_pressure + equilibrium_distribution, waterheight_pressure export entropy, energy_total, energy_kinetic, energy_internal, energy_magnetic, - cross_helicity, - enstrophy + cross_helicity, + enstrophy export lake_at_rest_error export ncomponents, eachcomponent export TreeMesh, StructuredMesh, StructuredMeshView, UnstructuredMesh2D, P4estMesh, - T8codeMesh + T8codeMesh export DG, - DGSEM, LobattoLegendreBasis, - FDSBP, - VolumeIntegralWeakForm, VolumeIntegralStrongForm, - VolumeIntegralFluxDifferencing, - VolumeIntegralPureLGLFiniteVolume, - VolumeIntegralShockCapturingHG, IndicatorHennemannGassner, - VolumeIntegralUpwind, - SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, - SurfaceIntegralUpwind, - MortarL2 + DGSEM, LobattoLegendreBasis, + FDSBP, + VolumeIntegralWeakForm, VolumeIntegralStrongForm, + VolumeIntegralFluxDifferencing, + VolumeIntegralPureLGLFiniteVolume, + VolumeIntegralShockCapturingHG, IndicatorHennemannGassner, + VolumeIntegralUpwind, + SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, + SurfaceIntegralUpwind, + MortarL2 export VolumeIntegralSubcellLimiting, BoundsCheckCallback, - SubcellLimiterIDP, SubcellLimiterIDPCorrection + SubcellLimiterIDP, SubcellLimiterIDPCorrection export nelements, nnodes, nvariables, - eachelement, eachnode, eachvariable + eachelement, eachnode, eachvariable export SemidiscretizationHyperbolic, semidiscretize, compute_coefficients, integrate @@ -254,29 +254,29 @@ export SemidiscretizationHyperbolicParabolic export SemidiscretizationEulerAcoustics export SemidiscretizationEulerGravity, ParametersEulerGravity, - timestep_gravity_erk52_3Sstar!, timestep_gravity_carpenter_kennedy_erk54_2N! + timestep_gravity_erk52_3Sstar!, timestep_gravity_carpenter_kennedy_erk54_2N! export SemidiscretizationCoupled export SummaryCallback, SteadyStateCallback, AnalysisCallback, AliveCallback, - SaveRestartCallback, SaveSolutionCallback, TimeSeriesCallback, VisualizationCallback, - AveragingCallback, - AMRCallback, StepsizeCallback, - GlmSpeedCallback, LBMCollisionCallback, EulerAcousticsCouplingCallback, - TrivialCallback, AnalysisCallbackCoupled, - AnalysisSurfaceIntegral, DragCoefficientPressure, LiftCoefficientPressure, - DragCoefficientShearStress, LiftCoefficientShearStress + SaveRestartCallback, SaveSolutionCallback, TimeSeriesCallback, VisualizationCallback, + AveragingCallback, + AMRCallback, StepsizeCallback, + GlmSpeedCallback, LBMCollisionCallback, EulerAcousticsCouplingCallback, + TrivialCallback, AnalysisCallbackCoupled, + AnalysisSurfaceIntegral, DragCoefficientPressure, LiftCoefficientPressure, + DragCoefficientShearStress, LiftCoefficientShearStress export load_mesh, load_time, load_timestep, load_timestep!, load_dt, - load_adaptive_time_integrator! + load_adaptive_time_integrator! export ControllerThreeLevel, ControllerThreeLevelCombined, - IndicatorLöhner, IndicatorLoehner, IndicatorMax + IndicatorLöhner, IndicatorLoehner, IndicatorMax export PositivityPreservingLimiterZhangShu export trixi_include, examples_dir, get_examples, default_example, - default_example_unstructured, ode_default_options + default_example_unstructured, ode_default_options export ode_norm, ode_unstable_check @@ -288,8 +288,8 @@ export ViscousFormulationBassiRebay1, ViscousFormulationLocalDG # Visualization-related exports export PlotData1D, PlotData2D, ScalarPlotData2D, getmesh, adapt_to_mesh_level!, - adapt_to_mesh_level, - iplot, iplot! + adapt_to_mesh_level, + iplot, iplot! function __init__() init_mpi() @@ -300,20 +300,20 @@ function __init__() register_error_hints() # Enable features that depend on the availability of the Plots package - @require Plots="91a5bcdd-55d7-5caf-9e0b-520d859cae80" begin + @require Plots = "91a5bcdd-55d7-5caf-9e0b-520d859cae80" begin using .Plots: Plots end # Until Julia v1.9 is the minimum required version for Trixi.jl, we still support Requires.jl @static if !isdefined(Base, :get_extension) - @require Makie="ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" begin + @require Makie = "ee78f7c6-11fb-53f2-987a-cfe4a2b5a57a" begin include("../ext/TrixiMakieExt.jl") end end @static if !isdefined(Base, :get_extension) - @require Convex="f65535da-76fb-5f13-bab9-19810c17039a" begin - @require ECOS="e2685f51-7e38-5353-a97d-a921fd2c8199" begin + @require Convex = "f65535da-76fb-5f13-bab9-19810c17039a" begin + @require ECOS = "e2685f51-7e38-5353-a97d-a921fd2c8199" begin include("../ext/TrixiConvexECOSExt.jl") end end diff --git a/src/auxiliary/auxiliary.jl b/src/auxiliary/auxiliary.jl index 8ab798dd33..0530c95014 100644 --- a/src/auxiliary/auxiliary.jl +++ b/src/auxiliary/auxiliary.jl @@ -9,320 +9,328 @@ include("math.jl") # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - PerformanceCounter() - -A `PerformanceCounter` can be used to track the runtime performance of some calls. -Add a new runtime measurement via `put!(counter, runtime)` and get the averaged -runtime of all measurements added so far via `take!(counter)`, resetting the -`counter`. -""" -mutable struct PerformanceCounter - ncalls_since_readout::Int - runtime::Float64 -end - -PerformanceCounter() = PerformanceCounter(0, 0.0) - -@inline function Base.take!(counter::PerformanceCounter) - time_per_call = counter.runtime / counter.ncalls_since_readout - counter.ncalls_since_readout = 0 - counter.runtime = 0.0 - return time_per_call -end - -@inline function Base.put!(counter::PerformanceCounter, runtime::Real) - counter.ncalls_since_readout += 1 - counter.runtime += runtime -end - -@inline ncalls(counter::PerformanceCounter) = counter.ncalls_since_readout - -""" - PerformanceCounterList{N}() - -A `PerformanceCounterList{N}` can be used to track the runtime performance of -calls to multiple functions, adding them up. -Add a new runtime measurement via `put!(counter.counters[i], runtime)` and get -the averaged runtime of all measurements added so far via `take!(counter)`, -resetting the `counter`. -""" -struct PerformanceCounterList{N} - counters::NTuple{N, PerformanceCounter} - check_ncalls_consistency::Bool -end - -function PerformanceCounterList{N}(check_ncalls_consistency) where {N} - counters = ntuple(_ -> PerformanceCounter(), Val{N}()) - return PerformanceCounterList{N}(counters, check_ncalls_consistency) -end -PerformanceCounterList{N}() where {N} = PerformanceCounterList{N}(true) - -@inline function Base.take!(counter_list::PerformanceCounterList) - time_per_call = 0.0 - for c in counter_list.counters - time_per_call += take!(c) + #! format: noindent + + """ + PerformanceCounter() + + A `PerformanceCounter` can be used to track the runtime performance of some calls. + Add a new runtime measurement via `put!(counter, runtime)` and get the averaged + runtime of all measurements added so far via `take!(counter)`, resetting the + `counter`. + """ + mutable struct PerformanceCounter + ncalls_since_readout::Int + runtime::Float64 end - return time_per_call -end -@inline function ncalls(counter_list::PerformanceCounterList) - ncalls_first = ncalls(first(counter_list.counters)) + PerformanceCounter() = PerformanceCounter(0, 0.0) - if counter_list.check_ncalls_consistency + @inline function Base.take!(counter::PerformanceCounter) + time_per_call = counter.runtime / counter.ncalls_since_readout + counter.ncalls_since_readout = 0 + counter.runtime = 0.0 + return time_per_call + end + + @inline function Base.put!(counter::PerformanceCounter, runtime::Real) + counter.ncalls_since_readout += 1 + counter.runtime += runtime + end + + @inline ncalls(counter::PerformanceCounter) = counter.ncalls_since_readout + + """ + PerformanceCounterList{N}() + + A `PerformanceCounterList{N}` can be used to track the runtime performance of + calls to multiple functions, adding them up. + Add a new runtime measurement via `put!(counter.counters[i], runtime)` and get + the averaged runtime of all measurements added so far via `take!(counter)`, + resetting the `counter`. + """ + struct PerformanceCounterList{N} + counters::NTuple{N, PerformanceCounter} + check_ncalls_consistency::Bool + end + + function PerformanceCounterList{N}(check_ncalls_consistency) where {N} + counters = ntuple(_ -> PerformanceCounter(), Val{N}()) + return PerformanceCounterList{N}(counters, check_ncalls_consistency) + end + PerformanceCounterList{N}() where {N} = PerformanceCounterList{N}(true) + + @inline function Base.take!(counter_list::PerformanceCounterList) + time_per_call = 0.0 for c in counter_list.counters - if ncalls_first != ncalls(c) - error("Some counters have a different number of calls. Using `ncalls` on the counter list is undefined behavior.") + time_per_call += take!(c) + end + return time_per_call + end + + @inline function ncalls(counter_list::PerformanceCounterList) + ncalls_first = ncalls(first(counter_list.counters)) + + if counter_list.check_ncalls_consistency + for c in counter_list.counters + if ncalls_first != ncalls(c) + error("Some counters have a different number of calls. Using `ncalls` on the counter list is undefined behavior.") + end end end + + return ncalls_first end - return ncalls_first -end - -""" - examples_dir() - -Return the directory where the example files provided with Trixi.jl are located. If Trixi.jl is -installed as a regular package (with `]add Trixi`), these files are read-only and should *not* be -modified. To find out which files are available, use, e.g., `readdir`: - -# Examples -```@example -readdir(examples_dir()) -``` -""" -examples_dir() = pkgdir(Trixi, "examples") - -""" - get_examples() - -Return a list of all example elixirs that are provided by Trixi.jl. See also -[`examples_dir`](@ref) and [`default_example`](@ref). -""" -function get_examples() - examples = String[] - for (root, dirs, files) in walkdir(examples_dir()) - for f in files - if startswith(f, "elixir_") && endswith(f, ".jl") - push!(examples, joinpath(root, f)) + """ + examples_dir() + + Return the directory where the example files provided with Trixi.jl are located. If Trixi.jl is + installed as a regular package (with `]add Trixi`), these files are read-only and should *not* be + modified. To find out which files are available, use, e.g., `readdir`: + + # Examples + ```@example + readdir(examples_dir()) + ``` + """ + examples_dir() = pkgdir(Trixi, "examples") + + """ + get_examples() + + Return a list of all example elixirs that are provided by Trixi.jl. See also + [`examples_dir`](@ref) and [`default_example`](@ref). + """ + function get_examples() + examples = String[] + for (root, dirs, files) in walkdir(examples_dir()) + for f in files + if startswith(f, "elixir_") && endswith(f, ".jl") + push!(examples, joinpath(root, f)) + end end end + + return examples end - return examples -end - -""" - default_example() - -Return the path to an example elixir that can be used to quickly see Trixi.jl in action on a -[`TreeMesh`](@ref). See also [`examples_dir`](@ref) and [`get_examples`](@ref). -""" -function default_example() - joinpath(examples_dir(), "tree_2d_dgsem", "elixir_advection_basic.jl") -end - -""" - default_example_unstructured() - -Return the path to an example elixir that can be used to quickly see Trixi.jl in action on an -[`UnstructuredMesh2D`](@ref). This simulation is run on the example curved, unstructured mesh -given in the Trixi.jl documentation regarding unstructured meshes. -""" -function default_example_unstructured() - joinpath(examples_dir(), "unstructured_2d_dgsem", "elixir_euler_basic.jl") -end - -""" - ode_default_options() - -Return the default options for OrdinaryDiffEq's `solve`. Pass `ode_default_options()...` to `solve` -to only return the solution at the final time and enable **MPI aware** error-based step size control, -whenever MPI is used. -For example, use `solve(ode, alg; ode_default_options()...)`. -""" -function ode_default_options() - if mpi_isparallel() - return (; save_everystep = false, internalnorm = ode_norm, - unstable_check = ode_unstable_check) - else - return (; save_everystep = false) + """ + default_example() + + Return the path to an example elixir that can be used to quickly see Trixi.jl in action on a + [`TreeMesh`](@ref). See also [`examples_dir`](@ref) and [`get_examples`](@ref). + """ + function default_example() + joinpath(examples_dir(), "tree_2d_dgsem", "elixir_advection_basic.jl") end -end - -# Print informative message at startup -function print_startup_message() - s = """ - - ████████╗██████╗ ██╗██╗ ██╗██╗ - ╚══██╔══╝██╔══██╗██║╚██╗██╔╝██║ - ██║ ██████╔╝██║ ╚███╔╝ ██║ - ██║ ██╔══██╗██║ ██╔██╗ ██║ - ██║ ██║ ██║██║██╔╝ ██╗██║ - ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚═╝ - """ - mpi_println(s) -end - -""" - get_name(x) - -Returns a name of `x` ready for pretty printing. -By default, return `string(y)` if `x isa Val{y}` and return `string(x)` otherwise. - -# Examples - -```jldoctest -julia> Trixi.get_name("test") -"test" - -julia> Trixi.get_name(Val(:test)) -"test" -``` -""" -get_name(x) = string(x) -get_name(::Val{x}) where {x} = string(x) - -""" - @threaded for ... end - -Semantically the same as `Threads.@threads` when iterating over a `AbstractUnitRange` -but without guarantee that the underlying implementation uses `Threads.@threads` -or works for more general `for` loops. -In particular, there may be an additional check whether only one thread is used -to reduce the overhead of serial execution or the underlying threading capabilities -might be provided by other packages such as [Polyester.jl](https://github.com/JuliaSIMD/Polyester.jl). - -!!! warn - This macro does not necessarily work for general `for` loops. For example, - it does not necessarily support general iterables such as `eachline(filename)`. - -Some discussion can be found at [https://discourse.julialang.org/t/overhead-of-threads-threads/53964](https://discourse.julialang.org/t/overhead-of-threads-threads/53964) -and [https://discourse.julialang.org/t/threads-threads-with-one-thread-how-to-remove-the-overhead/58435](https://discourse.julialang.org/t/threads-threads-with-one-thread-how-to-remove-the-overhead/58435). -""" -macro threaded(expr) - # !!! danger "Heisenbug" - # Look at the comments for `wrap_array` when considering to change this macro. - expr = if _PREFERENCE_POLYESTER - # Currently using `@batch` from Polyester.jl is more efficient, - # bypasses the Julia task scheduler and provides parallelization with less overhead. - quote - $Trixi.@batch $(expr) + + """ + default_example_unstructured() + + Return the path to an example elixir that can be used to quickly see Trixi.jl in action on an + [`UnstructuredMesh2D`](@ref). This simulation is run on the example curved, unstructured mesh + given in the Trixi.jl documentation regarding unstructured meshes. + """ + function default_example_unstructured() + joinpath(examples_dir(), "unstructured_2d_dgsem", "elixir_euler_basic.jl") + end + + """ + ode_default_options() + + Return the default options for OrdinaryDiffEq's `solve`. Pass `ode_default_options()...` to `solve` + to only return the solution at the final time and enable **MPI aware** error-based step size control, + whenever MPI is used. + For example, use `solve(ode, alg; ode_default_options()...)`. + """ + function ode_default_options() + if mpi_isparallel() + return (; + save_everystep = false, internalnorm = ode_norm, + unstable_check = ode_unstable_check, + ) + else + return (; save_everystep = false) end - else - # The following code is a simple version using only `Threads.@threads` from the - # standard library with an additional check whether only a single thread is used - # to reduce some overhead (and allocations) for serial execution. - quote - let - if $Threads.nthreads() == 1 - $(expr) - else - $Threads.@threads :static $(expr) + end + + # Print informative message at startup + function print_startup_message() + s = """ + + ████████╗██████╗ ██╗██╗ ██╗██╗ + ╚══██╔══╝██╔══██╗██║╚██╗██╔╝██║ + ██║ ██████╔╝██║ ╚███╔╝ ██║ + ██║ ██╔══██╗██║ ██╔██╗ ██║ + ██║ ██║ ██║██║██╔╝ ██╗██║ + ╚═╝ ╚═╝ ╚═╝╚═╝╚═╝ ╚═╝╚═╝ + """ + mpi_println(s) + end + + """ + get_name(x) + + Returns a name of `x` ready for pretty printing. + By default, return `string(y)` if `x isa Val{y}` and return `string(x)` otherwise. + + # Examples + + ```jldoctest + julia> Trixi.get_name("test") + "test" + + julia> Trixi.get_name(Val(:test)) + "test" + ``` + """ + get_name(x) = string(x) + get_name(::Val{x}) where {x} = string(x) + + """ + @threaded for ... end + + Semantically the same as `Threads.@threads` when iterating over a `AbstractUnitRange` + but without guarantee that the underlying implementation uses `Threads.@threads` + or works for more general `for` loops. + In particular, there may be an additional check whether only one thread is used + to reduce the overhead of serial execution or the underlying threading capabilities + might be provided by other packages such as [Polyester.jl](https://github.com/JuliaSIMD/Polyester.jl). + + !!! warn + This macro does not necessarily work for general `for` loops. For example, + it does not necessarily support general iterables such as `eachline(filename)`. + + Some discussion can be found at [https://discourse.julialang.org/t/overhead-of-threads-threads/53964](https://discourse.julialang.org/t/overhead-of-threads-threads/53964) + and [https://discourse.julialang.org/t/threads-threads-with-one-thread-how-to-remove-the-overhead/58435](https://discourse.julialang.org/t/threads-threads-with-one-thread-how-to-remove-the-overhead/58435). + """ + macro threaded(expr) + # !!! danger "Heisenbug" + # Look at the comments for `wrap_array` when considering to change this macro. + expr = if _PREFERENCE_POLYESTER + # Currently using `@batch` from Polyester.jl is more efficient, + # bypasses the Julia task scheduler and provides parallelization with less overhead. + quote + $Trixi.@batch $(expr) + end + else + # The following code is a simple version using only `Threads.@threads` from the + # standard library with an additional check whether only a single thread is used + # to reduce some overhead (and allocations) for serial execution. + quote + let + if $Threads.nthreads() == 1 + $(expr) + else + $Threads.@threads :static $(expr) + end end end end + # Use `esc(quote ... end)` for nested macro calls as suggested in + # https://github.com/JuliaLang/julia/issues/23221 + return esc(expr) end - # Use `esc(quote ... end)` for nested macro calls as suggested in - # https://github.com/JuliaLang/julia/issues/23221 - return esc(expr) -end - -""" - @autoinfiltrate - @autoinfiltrate condition::Bool - -Invoke the `@infiltrate` macro of the package Infiltrator.jl to create a breakpoint for ad-hoc -interactive debugging in the REPL. If the optional argument `condition` is given, the breakpoint is -only enabled if `condition` evaluates to `true`. - -As opposed to using `Infiltrator.@infiltrate` directly, this macro does not require Infiltrator.jl -to be added as a dependency to Trixi.jl. As a bonus, the macro will also attempt to load the -Infiltrator module if it has not yet been loaded manually. - -Note: For this macro to work, the Infiltrator.jl package needs to be installed in your current Julia -environment stack. - -See also: [Infiltrator.jl](https://github.com/JuliaDebug/Infiltrator.jl) - -!!! warning "Internal use only" - Please note that this macro is intended for internal use only. It is *not* part of the public - API of Trixi.jl, and it thus can altered (or be removed) at any time without it being considered - a breaking change. -""" -macro autoinfiltrate(condition = true) - pkgid = Base.PkgId(Base.UUID("5903a43b-9cc3-4c30-8d17-598619ec4e9b"), "Infiltrator") - if !haskey(Base.loaded_modules, pkgid) - try - Base.eval(Main, :(using Infiltrator)) - catch err - @error "Cannot load Infiltrator.jl. Make sure it is included in your environment stack." + + """ + @autoinfiltrate + @autoinfiltrate condition::Bool + + Invoke the `@infiltrate` macro of the package Infiltrator.jl to create a breakpoint for ad-hoc + interactive debugging in the REPL. If the optional argument `condition` is given, the breakpoint is + only enabled if `condition` evaluates to `true`. + + As opposed to using `Infiltrator.@infiltrate` directly, this macro does not require Infiltrator.jl + to be added as a dependency to Trixi.jl. As a bonus, the macro will also attempt to load the + Infiltrator module if it has not yet been loaded manually. + + Note: For this macro to work, the Infiltrator.jl package needs to be installed in your current Julia + environment stack. + + See also: [Infiltrator.jl](https://github.com/JuliaDebug/Infiltrator.jl) + + !!! warning "Internal use only" + Please note that this macro is intended for internal use only. It is *not* part of the public + API of Trixi.jl, and it thus can altered (or be removed) at any time without it being considered + a breaking change. + """ + macro autoinfiltrate(condition = true) + pkgid = Base.PkgId(Base.UUID("5903a43b-9cc3-4c30-8d17-598619ec4e9b"), "Infiltrator") + if !haskey(Base.loaded_modules, pkgid) + try + Base.eval(Main, :(using Infiltrator)) + catch err + @error "Cannot load Infiltrator.jl. Make sure it is included in your environment stack." + end end - end - i = get(Base.loaded_modules, pkgid, nothing) - lnn = LineNumberNode(__source__.line, __source__.file) - - if i === nothing - return Expr(:macrocall, - Symbol("@warn"), - lnn, - "Could not load Infiltrator.") - end + i = get(Base.loaded_modules, pkgid, nothing) + lnn = LineNumberNode(__source__.line, __source__.file) - return Expr(:macrocall, - Expr(:., i, QuoteNode(Symbol("@infiltrate"))), + if i === nothing + return Expr( + :macrocall, + Symbol("@warn"), lnn, - esc(condition)) -end - -# Use the *experimental* feature in `Base` to add error hints for specific errors. We use it to -# warn users in case they try to execute functions that are extended in package extensions which -# have not yet been loaded. -# -# Reference: https://docs.julialang.org/en/v1/base/base/#Base.Experimental.register_error_hint -function register_error_hints() - # We follow the advice in the docs and gracefully exit without doing anything if the experimental - # features gets silently removed. - if !isdefined(Base.Experimental, :register_error_hint) - return nothing + "Could not load Infiltrator." + ) + end + + return Expr( + :macrocall, + Expr(:., i, QuoteNode(Symbol("@infiltrate"))), + lnn, + esc(condition) + ) end - Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs - if exc.f in [iplot, iplot!] && isempty(methods(exc.f)) - print(io, - "\n$(exc.f) has no methods yet. It is part of a plotting extension of Trixi.jl " * - "that relies on Makie being loaded.\n" * - "To activate the extension, execute `using Makie`, `using CairoMakie`, " * - "`using GLMakie`, or load any other package that also uses Makie.") + # Use the *experimental* feature in `Base` to add error hints for specific errors. We use it to + # warn users in case they try to execute functions that are extended in package extensions which + # have not yet been loaded. + # + # Reference: https://docs.julialang.org/en/v1/base/base/#Base.Experimental.register_error_hint + function register_error_hints() + # We follow the advice in the docs and gracefully exit without doing anything if the experimental + # features gets silently removed. + if !isdefined(Base.Experimental, :register_error_hint) + return nothing end - end - return nothing -end - -""" - Trixi.download(src_url, file_path) - -Download a file from given `src_url` to given `file_path` if -`file_path` is not already a file. This function just returns -`file_path`. -This is a small wrapper of `Downloads.download(src_url, file_path)` -that avoids race conditions when multiple MPI ranks are used. -""" -function download(src_url, file_path) - # Note that `mpi_isroot()` is also `true` if running - # in serial (without MPI). - if mpi_isroot() - isfile(file_path) || Downloads.download(src_url, file_path) - end + Base.Experimental.register_error_hint(MethodError) do io, exc, argtypes, kwargs + if exc.f in [iplot, iplot!] && isempty(methods(exc.f)) + print( + io, + "\n$(exc.f) has no methods yet. It is part of a plotting extension of Trixi.jl " * + "that relies on Makie being loaded.\n" * + "To activate the extension, execute `using Makie`, `using CairoMakie`, " * + "`using GLMakie`, or load any other package that also uses Makie." + ) + end + end - if mpi_isparallel() - MPI.Barrier(mpi_comm()) + return nothing end - return file_path -end + """ + Trixi.download(src_url, file_path) + + Download a file from given `src_url` to given `file_path` if + `file_path` is not already a file. This function just returns + `file_path`. + This is a small wrapper of `Downloads.download(src_url, file_path)` + that avoids race conditions when multiple MPI ranks are used. + """ + function download(src_url, file_path) + # Note that `mpi_isroot()` is also `true` if running + # in serial (without MPI). + if mpi_isroot() + isfile(file_path) || Downloads.download(src_url, file_path) + end + + if mpi_isparallel() + MPI.Barrier(mpi_comm()) + end + + return file_path + end end # @muladd diff --git a/src/auxiliary/containers.jl b/src/auxiliary/containers.jl index 90650f6abc..2eac882636 100644 --- a/src/auxiliary/containers.jl +++ b/src/auxiliary/containers.jl @@ -3,315 +3,323 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Abstract base type - all containers that want to use these features must inherit from it -abstract type AbstractContainer end - -# Generic functions for which concrete containers must add implementations -function invalidate! end -function raw_copy! end -function move_connectivity! end -function delete_connectivity! end -function reset_data_structures! end - -# Auxiliary copy function to copy data between containers -function copy_data!(target::AbstractArray, source::AbstractArray, - first::Int, last::Int, destination::Int, block_size::Int = 1) - count = last - first + 1 - if destination <= first || destination > last - # In this case it is safe to copy forward (left-to-right) without overwriting data - for i in 0:(count - 1), j in 1:block_size - target[block_size * (destination + i - 1) + j] = source[block_size * (first + i - 1) + j] - end - else - # In this case we need to copy backward (right-to-left) to prevent overwriting data - for i in (count - 1):-1:0, j in 1:block_size - target[block_size * (destination + i - 1) + j] = source[block_size * (first + i - 1) + j] + #! format: noindent + + # Abstract base type - all containers that want to use these features must inherit from it + abstract type AbstractContainer end + + # Generic functions for which concrete containers must add implementations + function invalidate! end + function raw_copy! end + function move_connectivity! end + function delete_connectivity! end + function reset_data_structures! end + + # Auxiliary copy function to copy data between containers + function copy_data!( + target::AbstractArray, source::AbstractArray, + first::Int, last::Int, destination::Int, block_size::Int = 1 + ) + count = last - first + 1 + if destination <= first || destination > last + # In this case it is safe to copy forward (left-to-right) without overwriting data + for i in 0:(count - 1), j in 1:block_size + target[block_size * (destination + i - 1) + j] = source[block_size * (first + i - 1) + j] + end + else + # In this case we need to copy backward (right-to-left) to prevent overwriting data + for i in (count - 1):-1:0, j in 1:block_size + target[block_size * (destination + i - 1) + j] = source[block_size * (first + i - 1) + j] + end end + + return target end - return target -end - -# Inquire about capacity and size -capacity(c::AbstractContainer) = c.capacity -Base.length(c::AbstractContainer) = c.length -Base.size(c::AbstractContainer) = (length(c),) - -""" - resize!(c::AbstractContainer, new_length) -> AbstractContainer - -Resize `c` to contain `new_length` elements. If `new_length` is smaller than the current container -length, the first `new_length` elements will be retained. If `new_length` is -larger, the new elements are invalidated. -""" -function Base.resize!(c::AbstractContainer, new_length) - @assert new_length>=zero(new_length) "New length must be >= 0" - @assert new_length<=capacity(c) "New length would exceed capacity" - - # If new length is greater than current length, append to container. - # If new length is less than current length, shrink container. - # If new length is equal to current length, do nothing. - if new_length > length(c) - # First, invalidate range (to be sure that no sensible values are accidentally left there) - invalidate!(c, length(c) + 1, new_length) - - # Then, set new container length - c.length = new_length - elseif new_length < length(c) - # Rely on remove&shift to do The Right Thing (`remove_shift!` also updates the length) - remove_shift!(c, new_length + 1, length(c)) + # Inquire about capacity and size + capacity(c::AbstractContainer) = c.capacity + Base.length(c::AbstractContainer) = c.length + Base.size(c::AbstractContainer) = (length(c),) + + """ + resize!(c::AbstractContainer, new_length) -> AbstractContainer + + Resize `c` to contain `new_length` elements. If `new_length` is smaller than the current container + length, the first `new_length` elements will be retained. If `new_length` is + larger, the new elements are invalidated. + """ + function Base.resize!(c::AbstractContainer, new_length) + @assert new_length >= zero(new_length) "New length must be >= 0" + @assert new_length <= capacity(c) "New length would exceed capacity" + + # If new length is greater than current length, append to container. + # If new length is less than current length, shrink container. + # If new length is equal to current length, do nothing. + if new_length > length(c) + # First, invalidate range (to be sure that no sensible values are accidentally left there) + invalidate!(c, length(c) + 1, new_length) + + # Then, set new container length + c.length = new_length + elseif new_length < length(c) + # Rely on remove&shift to do The Right Thing (`remove_shift!` also updates the length) + remove_shift!(c, new_length + 1, length(c)) + end + + return c end - return c -end - -# Copy data range from source to target container. -# -# Calls `raw_copy` internally, which must be implemented for each concrete type -# inheriting from AbstractContainer. -# TODO: Shall we extend Base.copyto! ? -function Trixi.copy!(target::AbstractContainer, source::AbstractContainer, - first::Int, last::Int, destination::Int) - @assert 1<=first<=length(source) "First cell out of range" - @assert 1<=last<=length(source) "Last cell out of range" - @assert 1<=destination<=length(target) "Destination out of range" - @assert destination + (last - first)<=length(target) "Target range out of bounds" - - # Return if copy would be a no-op - if last < first || (source === target && first == destination) + # Copy data range from source to target container. + # + # Calls `raw_copy` internally, which must be implemented for each concrete type + # inheriting from AbstractContainer. + # TODO: Shall we extend Base.copyto! ? + function Trixi.copy!( + target::AbstractContainer, source::AbstractContainer, + first::Int, last::Int, destination::Int + ) + @assert 1 <= first <= length(source) "First cell out of range" + @assert 1 <= last <= length(source) "Last cell out of range" + @assert 1 <= destination <= length(target) "Destination out of range" + @assert destination + (last - first) <= length(target) "Target range out of bounds" + + # Return if copy would be a no-op + if last < first || (source === target && first == destination) + return target + end + + raw_copy!(target, source, first, last, destination) + return target end - raw_copy!(target, source, first, last, destination) + # Convenience method to copy a single element + function Trixi.copy!( + target::AbstractContainer, source::AbstractContainer, from::Int, + destination::Int + ) + Trixi.copy!(target, source, from, from, destination) + end + + # Convenience method for copies within a single container + function Trixi.copy!(c::AbstractContainer, first::Int, last::Int, destination::Int) + Trixi.copy!(c, c, first, last, destination) + end + + # Convenience method for copying a single element within a single container + function Trixi.copy!(c::AbstractContainer, from::Int, destination::Int) + Trixi.copy!(c, c, from, from, destination) + end - return target -end + # Move elements in a way that preserves connectivity. + function move!(c::AbstractContainer, first::Int, last::Int, destination::Int) + @assert 1 <= first <= length(c) "First cell $first out of range" + @assert 1 <= last <= length(c) "Last cell $last out of range" + @assert 1 <= destination <= length(c) "Destination $destination out of range" + @assert destination + (last - first) <= length(c) "Target range out of bounds" -# Convenience method to copy a single element -function Trixi.copy!(target::AbstractContainer, source::AbstractContainer, from::Int, - destination::Int) - Trixi.copy!(target, source, from, from, destination) -end + # Return if move would be a no-op + if last < first || first == destination + return c + end -# Convenience method for copies within a single container -function Trixi.copy!(c::AbstractContainer, first::Int, last::Int, destination::Int) - Trixi.copy!(c, c, first, last, destination) -end + # Copy cells to new location + raw_copy!(c, first, last, destination) -# Convenience method for copying a single element within a single container -function Trixi.copy!(c::AbstractContainer, from::Int, destination::Int) - Trixi.copy!(c, c, from, from, destination) -end + # Move connectivity + move_connectivity!(c, first, last, destination) -# Move elements in a way that preserves connectivity. -function move!(c::AbstractContainer, first::Int, last::Int, destination::Int) - @assert 1<=first<=length(c) "First cell $first out of range" - @assert 1<=last<=length(c) "Last cell $last out of range" - @assert 1<=destination<=length(c) "Destination $destination out of range" - @assert destination + (last - first)<=length(c) "Target range out of bounds" + # Invalidate original cell locations (unless they already contain new data due to overlap) + # 1) If end of destination range is within original range, shift first_invalid to the right + count = last - first + 1 + first_invalid = (first <= destination + count - 1 <= last) ? destination + count : + first + # 2) If beginning of destination range is within original range, shift last_invalid to the left + last_invalid = (first <= destination <= last) ? destination - 1 : last + # 3) Invalidate range + invalidate!(c, first_invalid, last_invalid) - # Return if move would be a no-op - if last < first || first == destination return c end + function move!(c::AbstractContainer, from::Int, destination::Int) + move!(c, from, from, destination) + end - # Copy cells to new location - raw_copy!(c, first, last, destination) - - # Move connectivity - move_connectivity!(c, first, last, destination) - - # Invalidate original cell locations (unless they already contain new data due to overlap) - # 1) If end of destination range is within original range, shift first_invalid to the right - count = last - first + 1 - first_invalid = (first <= destination + count - 1 <= last) ? destination + count : - first - # 2) If beginning of destination range is within original range, shift last_invalid to the left - last_invalid = (first <= destination <= last) ? destination - 1 : last - # 3) Invalidate range - invalidate!(c, first_invalid, last_invalid) - - return c -end -function move!(c::AbstractContainer, from::Int, destination::Int) - move!(c, from, from, destination) -end - -# Default implementation for moving a single element -function move_connectivity!(c::AbstractContainer, from::Int, destination::Int) - return move_connectivity!(c, from, from, destination) -end - -# Default implementation for invalidating a single element -function invalidate!(c::AbstractContainer, id::Int) - return invalidate!(c, id, id) -end - -# Swap two elements in a container while preserving element connectivity. -function swap!(c::AbstractContainer, a::Int, b::Int) - @assert 1<=a<=length(c) "a out of range" - @assert 1<=b<=length(c) "b out of range" - - # Return if swap would be a no-op - if a == b - return c + # Default implementation for moving a single element + function move_connectivity!(c::AbstractContainer, from::Int, destination::Int) + return move_connectivity!(c, from, from, destination) + end + + # Default implementation for invalidating a single element + function invalidate!(c::AbstractContainer, id::Int) + return invalidate!(c, id, id) end - # Move a to dummy location - raw_copy!(c, a, c.dummy) - move_connectivity!(c, a, c.dummy) + # Swap two elements in a container while preserving element connectivity. + function swap!(c::AbstractContainer, a::Int, b::Int) + @assert 1 <= a <= length(c) "a out of range" + @assert 1 <= b <= length(c) "b out of range" - # Move b to a - raw_copy!(c, b, a) - move_connectivity!(c, b, a) + # Return if swap would be a no-op + if a == b + return c + end - # Move from dummy location to b - raw_copy!(c, c.dummy, b) - move_connectivity!(c, c.dummy, b) + # Move a to dummy location + raw_copy!(c, a, c.dummy) + move_connectivity!(c, a, c.dummy) - # Invalidate dummy to be sure - invalidate!(c, c.dummy) + # Move b to a + raw_copy!(c, b, a) + move_connectivity!(c, b, a) - return c -end + # Move from dummy location to b + raw_copy!(c, c.dummy, b) + move_connectivity!(c, c.dummy, b) -# Insert blank elements in container, shifting the following elements back. -# -# After a call to insert!, the range `position:position + count - 1` will be available for use. -# TODO: Shall we extend Base.insert! ? -function insert!(c::AbstractContainer, position::Int, count::Int) - @assert 1<=position<=length(c)+1 "Insert position out of range" - @assert count>=0 "Count must be non-negative" - @assert count + length(c)<=capacity(c) "New length would exceed capacity" + # Invalidate dummy to be sure + invalidate!(c, c.dummy) - # Return if insertation would be a no-op - if count == 0 return c end - # Append and return if insertion is beyond last current element - if position == length(c) + 1 - resize!(c, length(c) + count) - return c - end + # Insert blank elements in container, shifting the following elements back. + # + # After a call to insert!, the range `position:position + count - 1` will be available for use. + # TODO: Shall we extend Base.insert! ? + function insert!(c::AbstractContainer, position::Int, count::Int) + @assert 1 <= position <= length(c) + 1 "Insert position out of range" + @assert count >= 0 "Count must be non-negative" + @assert count + length(c) <= capacity(c) "New length would exceed capacity" + + # Return if insertation would be a no-op + if count == 0 + return c + end - # Increase length - c.length += count + # Append and return if insertion is beyond last current element + if position == length(c) + 1 + resize!(c, length(c) + count) + return c + end - # Move original cells that currently occupy the insertion region, unless - # insert position is one beyond previous length - if position <= length(c) - count - move!(c, position, length(c) - count, position + count) + # Increase length + c.length += count + + # Move original cells that currently occupy the insertion region, unless + # insert position is one beyond previous length + if position <= length(c) - count + move!(c, position, length(c) - count, position + count) + end + + return c end - return c -end + # Erase elements from container, deleting their connectivity and then invalidating their data. + # TODO: Shall we extend Base.deleteat! or Base.delete! ? + function erase!(c::AbstractContainer, first::Int, last::Int) + @assert 1 <= first <= length(c) "First cell out of range" + @assert 1 <= last <= length(c) "Last cell out of range" + + # Return if eraseure would be a no-op + if last < first + return c + end -# Erase elements from container, deleting their connectivity and then invalidating their data. -# TODO: Shall we extend Base.deleteat! or Base.delete! ? -function erase!(c::AbstractContainer, first::Int, last::Int) - @assert 1<=first<=length(c) "First cell out of range" - @assert 1<=last<=length(c) "Last cell out of range" + # Delete connectivity and invalidate cells + delete_connectivity!(c, first, last) + invalidate!(c, first, last) - # Return if eraseure would be a no-op - if last < first return c end + erase!(c::AbstractContainer, id::Int) = erase!(c, id, id) + + # Remove cells and shift existing cells forward to close the gap + function remove_shift!(c::AbstractContainer, first::Int, last::Int) + @assert 1 <= first <= length(c) "First cell out of range" + @assert 1 <= last <= length(c) "Last cell out of range" + + # Return if removal would be a no-op + if last < first + return c + end - # Delete connectivity and invalidate cells - delete_connectivity!(c, first, last) - invalidate!(c, first, last) + # Delete connectivity of cells to be removed + delete_connectivity!(c, first, last) - return c -end -erase!(c::AbstractContainer, id::Int) = erase!(c, id, id) + if last == length(c) + # If everything up to the last cell is removed, no shifting is required + invalidate!(c, first, last) + else + # Otherwise, the corresponding cells are moved forward + move!(c, last + 1, length(c), first) + end -# Remove cells and shift existing cells forward to close the gap -function remove_shift!(c::AbstractContainer, first::Int, last::Int) - @assert 1<=first<=length(c) "First cell out of range" - @assert 1<=last<=length(c) "Last cell out of range" + # Reduce length + count = last - first + 1 + c.length -= count - # Return if removal would be a no-op - if last < first return c end + remove_shift!(c::AbstractContainer, id::Int) = remove_shift!(c, id, id) - # Delete connectivity of cells to be removed - delete_connectivity!(c, first, last) + # Remove cells and fill gap with cells from the end of the container (to reduce copy operations) + function remove_fill!(c::AbstractContainer, first::Int, last::Int) + @assert 1 <= first <= length(c) "First cell out of range" + @assert 1 <= last <= length(c) "Last cell out of range" - if last == length(c) - # If everything up to the last cell is removed, no shifting is required + # Return if removal would be a no-op + if last < first + return c + end + + # Delete connectivity of cells to be removed and then invalidate them + delete_connectivity!(c, first, last) invalidate!(c, first, last) - else - # Otherwise, the corresponding cells are moved forward - move!(c, last + 1, length(c), first) - end - # Reduce length - count = last - first + 1 - c.length -= count + # Copy cells from end (unless last is already the last cell) + count = last - first + 1 + if last < length(c) + move!(c, max(length(c) - count + 1, last + 1), length(c), first) + end + + # Reduce length + c.length -= count - return c -end -remove_shift!(c::AbstractContainer, id::Int) = remove_shift!(c, id, id) + return c + end -# Remove cells and fill gap with cells from the end of the container (to reduce copy operations) -function remove_fill!(c::AbstractContainer, first::Int, last::Int) - @assert 1<=first<=length(c) "First cell out of range" - @assert 1<=last<=length(c) "Last cell out of range" + # Reset container to zero-length and with a new capacity + function reset!(c::AbstractContainer, capacity::Int) + @assert capacity >= 0 + + c.capacity = capacity + c.length = 0 + c.dummy = capacity + 1 + reset_data_structures!(c) - # Return if removal would be a no-op - if last < first return c end - # Delete connectivity of cells to be removed and then invalidate them - delete_connectivity!(c, first, last) - invalidate!(c, first, last) + # Invalidate all elements and set length to zero. + function clear!(c::AbstractContainer) + invalidate!(c) + c.length = 0 - # Copy cells from end (unless last is already the last cell) - count = last - first + 1 - if last < length(c) - move!(c, max(length(c) - count + 1, last + 1), length(c), first) + return c end - # Reduce length - c.length -= count - - return c -end - -# Reset container to zero-length and with a new capacity -function reset!(c::AbstractContainer, capacity::Int) - @assert capacity >= 0 - - c.capacity = capacity - c.length = 0 - c.dummy = capacity + 1 - reset_data_structures!(c) - - return c -end - -# Invalidate all elements and set length to zero. -function clear!(c::AbstractContainer) - invalidate!(c) - c.length = 0 - - return c -end - -# Helpful overloads for `raw_copy` -function raw_copy!(c::AbstractContainer, first::Int, last::Int, destination::Int) - raw_copy!(c, c, first, last, destination) -end -function raw_copy!(target::AbstractContainer, source::AbstractContainer, from::Int, - destination::Int) - raw_copy!(target, source, from, from, destination) -end -function raw_copy!(c::AbstractContainer, from::Int, destination::Int) - raw_copy!(c, c, from, from, destination) -end + # Helpful overloads for `raw_copy` + function raw_copy!(c::AbstractContainer, first::Int, last::Int, destination::Int) + raw_copy!(c, c, first, last, destination) + end + function raw_copy!( + target::AbstractContainer, source::AbstractContainer, from::Int, + destination::Int + ) + raw_copy!(target, source, from, from, destination) + end + function raw_copy!(c::AbstractContainer, from::Int, destination::Int) + raw_copy!(c, c, from, from, destination) + end end # @muladd diff --git a/src/auxiliary/math.jl b/src/auxiliary/math.jl index fa816da9a1..d85789ad31 100644 --- a/src/auxiliary/math.jl +++ b/src/auxiliary/math.jl @@ -3,412 +3,420 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -const TRIXI_UUID = UUID("a7f1ee26-1774-49b1-8366-f1abc58fbfcb") - -""" - Trixi.set_polyester!(toggle::Bool; force = true) - -Toggle the usage of [Polyester.jl](https://github.com/JuliaSIMD/Polyester.jl) for multithreading. -By default, Polyester.jl is enabled, but it can -be useful for performance comparisons to switch to the Julia core backend. - -This does not fully disable Polyester.jl, -buy only its use as part of Trixi.jl's `@threaded` macro. -""" -function set_polyester!(toggle::Bool; force = true) - set_preferences!(TRIXI_UUID, "polyester" => toggle, force = force) - @info "Please restart Julia and reload Trixi.jl for the `polyester` change to take effect" -end - -""" - Trixi.set_sqrt_type!(type; force = true) - -Set the `type` of the square root function to be used in Trixi.jl. -The default is `"sqrt_Trixi_NaN"` which returns `NaN` for negative arguments -instead of throwing an error. -Alternatively, you can set `type` to `"sqrt_Base"` to use the Julia built-in `sqrt` function -which provides a stack-trace of the error which might come in handy when debugging code. -""" -function set_sqrt_type!(type; force = true) - @assert type == "sqrt_Trixi_NaN"||type == "sqrt_Base" "Only allowed `sqrt` function types are `\"sqrt_Trixi_NaN\"` and `\"sqrt_Base\"`" - set_preferences!(TRIXI_UUID, "sqrt" => type, force = force) - @info "Please restart Julia and reload Trixi.jl for the `sqrt` computation change to take effect" -end - -# TODO: deprecation introduced in v0.8 -@deprecate set_sqrt_type(type; force = true) set_sqrt_type!(type; force = true) false - -@static if _PREFERENCE_SQRT == "sqrt_Trixi_NaN" + #! format: noindent + + const TRIXI_UUID = UUID("a7f1ee26-1774-49b1-8366-f1abc58fbfcb") + """ - Trixi.sqrt(x::Real) - - Custom square root function which returns `NaN` for negative arguments instead of throwing an error. - This is required to ensure [correct results for multithreaded computations](https://github.com/trixi-framework/Trixi.jl/issues/1766) - when using the [`Polyester` package](https://github.com/JuliaSIMD/Polyester.jl), - i.e., using the `@batch` macro instead of the Julia built-in `@threads` macro, see [`@threaded`](@ref). - - We dispatch this function for `Float64, Float32, Float16` to the LLVM intrinsics - `llvm.sqrt.f64`, `llvm.sqrt.f32`, `llvm.sqrt.f16` as for these the LLVM functions can be used out-of the box, - i.e., they return `NaN` for negative arguments. - In principle, one could also use the `sqrt_llvm` call, but for transparency and consistency with [`log`](@ref) we - spell out the datatype-dependent functions here. - For other types, such as integers or dual numbers required for algorithmic differentiation, we - fall back to the Julia built-in `sqrt` function after a check for negative arguments. - Since these cases are not performance critical, the check for negativity does not hurt here - and can (as of now) even be optimized away by the compiler due to the implementation of `sqrt` in Julia. - - When debugging code, it might be useful to change the implementation of this function to redirect to - the Julia built-in `sqrt` function, as this reports the exact place in code where the domain is violated - in the stacktrace. - - See also [`Trixi.set_sqrt_type!`](@ref). + Trixi.set_polyester!(toggle::Bool; force = true) + + Toggle the usage of [Polyester.jl](https://github.com/JuliaSIMD/Polyester.jl) for multithreading. + By default, Polyester.jl is enabled, but it can + be useful for performance comparisons to switch to the Julia core backend. + + This does not fully disable Polyester.jl, + buy only its use as part of Trixi.jl's `@threaded` macro. """ - @inline sqrt(x::Real) = x < zero(x) ? oftype(x, NaN) : Base.sqrt(x) - - # For `sqrt` we could use the `sqrt_llvm` call, ... - #@inline sqrt(x::Union{Float64, Float32, Float16}) = Base.sqrt_llvm(x) - - # ... but for transparency and consistency we use the direct LLVM calls here. - @inline sqrt(x::Float64) = ccall("llvm.sqrt.f64", llvmcall, Float64, (Float64,), x) - @inline sqrt(x::Float32) = ccall("llvm.sqrt.f32", llvmcall, Float32, (Float32,), x) - @inline sqrt(x::Float16) = ccall("llvm.sqrt.f16", llvmcall, Float16, (Float16,), x) -end - -""" - Trixi.set_log_type!(type; force = true) - -Set the `type` of the (natural) `log` function to be used in Trixi.jl. -The default is `"sqrt_Trixi_NaN"` which returns `NaN` for negative arguments -instead of throwing an error. -Alternatively, you can set `type` to `"sqrt_Base"` to use the Julia built-in `sqrt` function -which provides a stack-trace of the error which might come in handy when debugging code. -""" -function set_log_type!(type; force = true) - @assert type == "log_Trixi_NaN"||type == "log_Base" "Only allowed log function types are `\"log_Trixi_NaN\"` and `\"log_Base\"`." - set_preferences!(TRIXI_UUID, "log" => type, force = force) - @info "Please restart Julia and reload Trixi.jl for the `log` computation change to take effect" -end - -# TODO: deprecation introduced in v0.8 -@deprecate set_log_type(type; force = true) set_log_type!(type; force = true) false - -@static if _PREFERENCE_LOG == "log_Trixi_NaN" + function set_polyester!(toggle::Bool; force = true) + set_preferences!(TRIXI_UUID, "polyester" => toggle, force = force) + @info "Please restart Julia and reload Trixi.jl for the `polyester` change to take effect" + end + """ - Trixi.log(x::Real) + Trixi.set_sqrt_type!(type; force = true) - Custom natural logarithm function which returns `NaN` for negative arguments instead of throwing an error. - This is required to ensure [correct results for multithreaded computations](https://github.com/trixi-framework/Trixi.jl/issues/1766) - when using the [`Polyester` package](https://github.com/JuliaSIMD/Polyester.jl), - i.e., using the `@batch` macro instead of the Julia built-in `@threads` macro, see [`@threaded`](@ref). + Set the `type` of the square root function to be used in Trixi.jl. + The default is `"sqrt_Trixi_NaN"` which returns `NaN` for negative arguments + instead of throwing an error. + Alternatively, you can set `type` to `"sqrt_Base"` to use the Julia built-in `sqrt` function + which provides a stack-trace of the error which might come in handy when debugging code. + """ + function set_sqrt_type!(type; force = true) + @assert type == "sqrt_Trixi_NaN"||type == "sqrt_Base" "Only allowed `sqrt` function types are `\"sqrt_Trixi_NaN\"` and `\"sqrt_Base\"`" + set_preferences!(TRIXI_UUID, "sqrt" => type, force = force) + @info "Please restart Julia and reload Trixi.jl for the `sqrt` computation change to take effect" + end - We dispatch this function for `Float64, Float32, Float16` to the respective LLVM intrinsics - `llvm.log.f64`, `llvm.log.f32`, `llvm.log.f16` as for this the LLVM functions can be used out-of the box, i.e., - they return `NaN` for negative arguments. - For other types, such as integers or dual numbers required for algorithmic differentiation, we - fall back to the Julia built-in `log` function after a check for negative arguments. - Since these cases are not performance critical, the check for negativity does not hurt here. + # TODO: deprecation introduced in v0.8 + @deprecate set_sqrt_type(type; force = true) set_sqrt_type!(type; force = true) false + + @static if _PREFERENCE_SQRT == "sqrt_Trixi_NaN" + """ + Trixi.sqrt(x::Real) + + Custom square root function which returns `NaN` for negative arguments instead of throwing an error. + This is required to ensure [correct results for multithreaded computations](https://github.com/trixi-framework/Trixi.jl/issues/1766) + when using the [`Polyester` package](https://github.com/JuliaSIMD/Polyester.jl), + i.e., using the `@batch` macro instead of the Julia built-in `@threads` macro, see [`@threaded`](@ref). + + We dispatch this function for `Float64, Float32, Float16` to the LLVM intrinsics + `llvm.sqrt.f64`, `llvm.sqrt.f32`, `llvm.sqrt.f16` as for these the LLVM functions can be used out-of the box, + i.e., they return `NaN` for negative arguments. + In principle, one could also use the `sqrt_llvm` call, but for transparency and consistency with [`log`](@ref) we + spell out the datatype-dependent functions here. + For other types, such as integers or dual numbers required for algorithmic differentiation, we + fall back to the Julia built-in `sqrt` function after a check for negative arguments. + Since these cases are not performance critical, the check for negativity does not hurt here + and can (as of now) even be optimized away by the compiler due to the implementation of `sqrt` in Julia. + + When debugging code, it might be useful to change the implementation of this function to redirect to + the Julia built-in `sqrt` function, as this reports the exact place in code where the domain is violated + in the stacktrace. + + See also [`Trixi.set_sqrt_type!`](@ref). + """ + @inline sqrt(x::Real) = x < zero(x) ? oftype(x, NaN) : Base.sqrt(x) + + # For `sqrt` we could use the `sqrt_llvm` call, ... + #@inline sqrt(x::Union{Float64, Float32, Float16}) = Base.sqrt_llvm(x) + + # ... but for transparency and consistency we use the direct LLVM calls here. + @inline sqrt(x::Float64) = ccall("llvm.sqrt.f64", llvmcall, Float64, (Float64,), x) + @inline sqrt(x::Float32) = ccall("llvm.sqrt.f32", llvmcall, Float32, (Float32,), x) + @inline sqrt(x::Float16) = ccall("llvm.sqrt.f16", llvmcall, Float16, (Float16,), x) + end - When debugging code, it might be useful to change the implementation of this function to redirect to - the Julia built-in `log` function, as this reports the exact place in code where the domain is violated - in the stacktrace. + """ + Trixi.set_log_type!(type; force = true) - See also [`Trixi.set_log_type!`](@ref). + Set the `type` of the (natural) `log` function to be used in Trixi.jl. + The default is `"sqrt_Trixi_NaN"` which returns `NaN` for negative arguments + instead of throwing an error. + Alternatively, you can set `type` to `"sqrt_Base"` to use the Julia built-in `sqrt` function + which provides a stack-trace of the error which might come in handy when debugging code. """ - @inline log(x::Real) = x < zero(x) ? oftype(x, NaN) : Base.log(x) + function set_log_type!(type; force = true) + @assert type == "log_Trixi_NaN"||type == "log_Base" "Only allowed log function types are `\"log_Trixi_NaN\"` and `\"log_Base\"`." + set_preferences!(TRIXI_UUID, "log" => type, force = force) + @info "Please restart Julia and reload Trixi.jl for the `log` computation change to take effect" + end + + # TODO: deprecation introduced in v0.8 + @deprecate set_log_type(type; force = true) set_log_type!(type; force = true) false - @inline log(x::Float64) = ccall("llvm.log.f64", llvmcall, Float64, (Float64,), x) - @inline log(x::Float32) = ccall("llvm.log.f32", llvmcall, Float32, (Float32,), x) - @inline log(x::Float16) = ccall("llvm.log.f16", llvmcall, Float16, (Float16,), x) -end + @static if _PREFERENCE_LOG == "log_Trixi_NaN" + """ + Trixi.log(x::Real) -""" - Trixi.ln_mean(x::Real, y::Real) + Custom natural logarithm function which returns `NaN` for negative arguments instead of throwing an error. + This is required to ensure [correct results for multithreaded computations](https://github.com/trixi-framework/Trixi.jl/issues/1766) + when using the [`Polyester` package](https://github.com/JuliaSIMD/Polyester.jl), + i.e., using the `@batch` macro instead of the Julia built-in `@threads` macro, see [`@threaded`](@ref). -Compute the logarithmic mean + We dispatch this function for `Float64, Float32, Float16` to the respective LLVM intrinsics + `llvm.log.f64`, `llvm.log.f32`, `llvm.log.f16` as for this the LLVM functions can be used out-of the box, i.e., + they return `NaN` for negative arguments. + For other types, such as integers or dual numbers required for algorithmic differentiation, we + fall back to the Julia built-in `log` function after a check for negative arguments. + Since these cases are not performance critical, the check for negativity does not hurt here. - ln_mean(x, y) = (y - x) / (log(y) - log(x)) = (y - x) / log(y / x) + When debugging code, it might be useful to change the implementation of this function to redirect to + the Julia built-in `log` function, as this reports the exact place in code where the domain is violated + in the stacktrace. -Problem: The formula above has a removable singularity at `x == y`. Thus, -some care must be taken to implement it correctly without problems or loss -of accuracy when `x ≈ y`. Here, we use the approach proposed by -Ismail and Roe (2009). -Set ξ = y / x. Then, we have + See also [`Trixi.set_log_type!`](@ref). + """ + @inline log(x::Real) = x < zero(x) ? oftype(x, NaN) : Base.log(x) - (y - x) / log(y / x) = (x + y) / log(ξ) * (ξ - 1) / (ξ + 1) + @inline log(x::Float64) = ccall("llvm.log.f64", llvmcall, Float64, (Float64,), x) + @inline log(x::Float32) = ccall("llvm.log.f32", llvmcall, Float32, (Float32,), x) + @inline log(x::Float16) = ccall("llvm.log.f16", llvmcall, Float16, (Float16,), x) + end + + """ + Trixi.ln_mean(x::Real, y::Real) -Set f = (ξ - 1) / (ξ + 1) = (y - x) / (x + y). Then, we use the expansion + Compute the logarithmic mean - log(ξ) = 2 * f * (1 + f^2 / 3 + f^4 / 5 + f^6 / 7) + O(ξ^9) + ln_mean(x, y) = (y - x) / (log(y) - log(x)) = (y - x) / log(y / x) -Inserting the first few terms of this expansion yields + Problem: The formula above has a removable singularity at `x == y`. Thus, + some care must be taken to implement it correctly without problems or loss + of accuracy when `x ≈ y`. Here, we use the approach proposed by + Ismail and Roe (2009). + Set ξ = y / x. Then, we have - (y - x) / log(ξ) ≈ (x + y) * f / (2 * f * (1 + f^2 / 3 + f^4 / 5 + f^6 / 7)) - = (x + y) / (2 + 2/3 * f^2 + 2/5 * f^4 + 2/7 * f^6) + (y - x) / log(y / x) = (x + y) / log(ξ) * (ξ - 1) / (ξ + 1) -Since divisions are usually more expensive on modern hardware than -multiplications (Agner Fog), we try to avoid computing two divisions. Thus, -we use + Set f = (ξ - 1) / (ξ + 1) = (y - x) / (x + y). Then, we use the expansion - f^2 = (y - x)^2 / (x + y)^2 - = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) + log(ξ) = 2 * f * (1 + f^2 / 3 + f^4 / 5 + f^6 / 7) + O(ξ^9) -Given ε = 1.0e-4, we use the following algorithm. + Inserting the first few terms of this expansion yields - if f^2 < ε - # use the expansion above - else - # use the direct formula (y - x) / log(y / x) - end + (y - x) / log(ξ) ≈ (x + y) * f / (2 * f * (1 + f^2 / 3 + f^4 / 5 + f^6 / 7)) + = (x + y) / (2 + 2/3 * f^2 + 2/5 * f^4 + 2/7 * f^6) -# References -- Ismail, Roe (2009). - Affordable, entropy-consistent Euler flux functions II: Entropy production at shocks. - [DOI: 10.1016/j.jcp.2009.04.021](https://doi.org/10.1016/j.jcp.2009.04.021) -- Agner Fog. - Lists of instruction latencies, throughputs and micro-operation breakdowns - for Intel, AMD, and VIA CPUs. - [https://www.agner.org/optimize/instruction_tables.pdf](https://www.agner.org/optimize/instruction_tables.pdf) -""" -@inline ln_mean(x::Real, y::Real) = ln_mean(promote(x, y)...) - -@inline function ln_mean(x::RealT, y::RealT) where {RealT <: Real} - epsilon_f2 = convert(RealT, 1.0e-4) - f2 = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) # f2 = f^2 - if f2 < epsilon_f2 - return (x + y) / @evalpoly(f2, - 2, - convert(RealT, 2 / 3), - convert(RealT, 2 / 5), - convert(RealT, 2 / 7)) - else - return (y - x) / log(y / x) + Since divisions are usually more expensive on modern hardware than + multiplications (Agner Fog), we try to avoid computing two divisions. Thus, + we use + + f^2 = (y - x)^2 / (x + y)^2 + = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) + + Given ε = 1.0e-4, we use the following algorithm. + + if f^2 < ε + # use the expansion above + else + # use the direct formula (y - x) / log(y / x) + end + + # References + - Ismail, Roe (2009). + Affordable, entropy-consistent Euler flux functions II: Entropy production at shocks. + [DOI: 10.1016/j.jcp.2009.04.021](https://doi.org/10.1016/j.jcp.2009.04.021) + - Agner Fog. + Lists of instruction latencies, throughputs and micro-operation breakdowns + for Intel, AMD, and VIA CPUs. + [https://www.agner.org/optimize/instruction_tables.pdf](https://www.agner.org/optimize/instruction_tables.pdf) + """ + @inline ln_mean(x::Real, y::Real) = ln_mean(promote(x, y)...) + + @inline function ln_mean(x::RealT, y::RealT) where {RealT <: Real} + epsilon_f2 = convert(RealT, 1.0e-4) + f2 = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) # f2 = f^2 + if f2 < epsilon_f2 + return (x + y) / @evalpoly( + f2, + 2, + convert(RealT, 2 / 3), + convert(RealT, 2 / 5), + convert(RealT, 2 / 7) + ) + else + return (y - x) / log(y / x) + end end -end - -""" - Trixi.inv_ln_mean(x::Real, y::Real) - -Compute the inverse `1 / ln_mean(x, y)` of the logarithmic mean -[`ln_mean`](@ref). - -This function may be used to increase performance where the inverse of the -logarithmic mean is needed, by replacing a (slow) division by a (fast) -multiplication. -""" -@inline inv_ln_mean(x::Real, y::Real) = inv_ln_mean(promote(x, y)...) - -@inline function inv_ln_mean(x::RealT, y::RealT) where {RealT <: Real} - epsilon_f2 = convert(RealT, 1.0e-4) - f2 = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) # f2 = f^2 - if f2 < epsilon_f2 - return @evalpoly(f2, - 2, - convert(RealT, 2 / 3), - convert(RealT, 2 / 5), - convert(RealT, 2 / 7)) / (x + y) - else - return log(y / x) / (y - x) + + """ + Trixi.inv_ln_mean(x::Real, y::Real) + + Compute the inverse `1 / ln_mean(x, y)` of the logarithmic mean + [`ln_mean`](@ref). + + This function may be used to increase performance where the inverse of the + logarithmic mean is needed, by replacing a (slow) division by a (fast) + multiplication. + """ + @inline inv_ln_mean(x::Real, y::Real) = inv_ln_mean(promote(x, y)...) + + @inline function inv_ln_mean(x::RealT, y::RealT) where {RealT <: Real} + epsilon_f2 = convert(RealT, 1.0e-4) + f2 = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) # f2 = f^2 + if f2 < epsilon_f2 + return @evalpoly( + f2, + 2, + convert(RealT, 2 / 3), + convert(RealT, 2 / 5), + convert(RealT, 2 / 7) + ) / (x + y) + else + return log(y / x) / (y - x) + end end -end - -# `Base.max` and `Base.min` perform additional checks for signed zeros and `NaN`s -# which are not present in comparable functions in Fortran/C++. For example, -# ```julia -# julia> @code_native debuginfo=:none syntax=:intel max(1.0, 2.0) -# .text -# vmovq rcx, xmm1 -# vmovq rax, xmm0 -# vcmpordsd xmm2, xmm0, xmm0 -# vblendvpd xmm2, xmm0, xmm1, xmm2 -# vcmpordsd xmm3, xmm1, xmm1 -# vblendvpd xmm3, xmm1, xmm0, xmm3 -# vmovapd xmm4, xmm2 -# test rcx, rcx -# jns L45 -# vmovapd xmm4, xmm3 -# L45: -# test rax, rax -# js L54 -# vmovapd xmm4, xmm3 -# L54: -# vcmpltsd xmm0, xmm0, xmm1 -# vblendvpd xmm0, xmm4, xmm2, xmm0 -# ret -# nop word ptr cs:[rax + rax] -# -# julia> @code_native debuginfo=:none syntax=:intel min(1.0, 2.0) -# .text -# vmovq rcx, xmm1 -# vmovq rax, xmm0 -# vcmpordsd xmm2, xmm0, xmm0 -# vblendvpd xmm2, xmm0, xmm1, xmm2 -# vcmpordsd xmm3, xmm1, xmm1 -# vblendvpd xmm3, xmm1, xmm0, xmm3 -# vmovapd xmm4, xmm2 -# test rcx, rcx -# jns L58 -# test rax, rax -# js L67 -# L46: -# vcmpltsd xmm0, xmm1, xmm0 -# vblendvpd xmm0, xmm4, xmm2, xmm0 -# ret -# L58: -# vmovapd xmm4, xmm3 -# test rax, rax -# jns L46 -# L67: -# vmovapd xmm4, xmm3 -# vcmpltsd xmm0, xmm1, xmm0 -# vblendvpd xmm0, xmm4, xmm2, xmm0 -# ret -# nop word ptr cs:[rax + rax] -# nop dword ptr [rax] -# ``` -# In contrast, we get the much simpler and faster version -# ```julia -# julia> @code_native debuginfo=:none syntax=:intel Base.FastMath.max_fast(1.0, 2.0) -# .text -# vmaxsd xmm0, xmm1, xmm0 -# ret -# nop word ptr cs:[rax + rax] -# -# julia> @code_native debuginfo=:none syntax=:intel Base.FastMath.min_fast(1.0, 2.0) -# .text -# vminsd xmm0, xmm0, xmm1 -# ret -# nop word ptr cs:[rax + rax] -# ``` -# when using `@fastmath`, which we also get from -# [Fortran](https://godbolt.org/z/Yrsa1js7P) -# or [C++](https://godbolt.org/z/674G7Pccv). -""" - Trixi.max(x, y, ...) - -Return the maximum of the arguments. See also the `maximum` function to take -the maximum element from a collection. - -This version in Trixi.jl is semantically equivalent to `Base.max` but may -be implemented differently. In particular, it may avoid potentially expensive -checks necessary in the presence of `NaN`s (or signed zeros). - -# Examples - -```jldoctest -julia> max(2, 5, 1) -5 -``` -""" -@inline max(args...) = @fastmath max(args...) - -""" - Trixi.min(x, y, ...) - -Return the minimum of the arguments. See also the `minimum` function to take -the minimum element from a collection. - -This version in Trixi.jl is semantically equivalent to `Base.min` but may -be implemented differently. In particular, it may avoid potentially expensive -checks necessary in the presence of `NaN`s (or signed zeros). - -# Examples - -```jldoctest -julia> min(2, 5, 1) -1 -``` -""" -@inline min(args...) = @fastmath min(args...) - -""" - Trixi.positive_part(x) - -Return `x` if `x` is positive, else zero. In other words, return -`(x + abs(x)) / 2` for real numbers `x`. -""" -@inline function positive_part(x) - return max(x, zero(x)) -end - -""" - Trixi.negative_part(x) - -Return `x` if `x` is negative, else zero. In other words, return -`(x - abs(x)) / 2` for real numbers `x`. -""" -@inline function negative_part(x) - return min(x, zero(x)) -end - -""" - Trixi.stolarsky_mean(x::Real, y:Real, gamma::Real) - -Compute an instance of a weighted Stolarsky mean of the form - - stolarsky_mean(x, y, gamma) = (gamma - 1)/gamma * (y^gamma - x^gamma) / (y^(gamma-1) - x^(gamma-1)) - -where `gamma > 1`. - -Problem: The formula above has a removable singularity at `x == y`. Thus, -some care must be taken to implement it correctly without problems or loss -of accuracy when `x ≈ y`. Here, we use the approach proposed by -Winters et al. (2020). -Set f = (y - x) / (y + x) and g = gamma (for compact notation). -Then, we use the expansions - - ((1+f)^g - (1-f)^g) / g = 2*f + (g-1)(g-2)/3 * f^3 + (g-1)(g-2)(g-3)(g-4)/60 * f^5 + O(f^7) - -and - - ((1+f)^(g-1) - (1-f)^(g-1)) / (g-1) = 2*f + (g-2)(g-3)/3 * f^3 + (g-2)(g-3)(g-4)(g-5)/60 * f^5 + O(f^7) - -Inserting the first few terms of these expansions and performing polynomial long division -we find that - - stolarsky_mean(x, y, gamma) ≈ (y + x) / 2 * (1 + (g-2)/3 * f^2 - (g+1)(g-2)(g-3)/45 * f^4 + (g+1)(g-2)(g-3)(2g(g-2)-9)/945 * f^6) - -Since divisions are usually more expensive on modern hardware than -multiplications (Agner Fog), we try to avoid computing two divisions. Thus, -we use - f^2 = (y - x)^2 / (x + y)^2 - = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) + # `Base.max` and `Base.min` perform additional checks for signed zeros and `NaN`s + # which are not present in comparable functions in Fortran/C++. For example, + # ```julia + # julia> @code_native debuginfo=:none syntax=:intel max(1.0, 2.0) + # .text + # vmovq rcx, xmm1 + # vmovq rax, xmm0 + # vcmpordsd xmm2, xmm0, xmm0 + # vblendvpd xmm2, xmm0, xmm1, xmm2 + # vcmpordsd xmm3, xmm1, xmm1 + # vblendvpd xmm3, xmm1, xmm0, xmm3 + # vmovapd xmm4, xmm2 + # test rcx, rcx + # jns L45 + # vmovapd xmm4, xmm3 + # L45: + # test rax, rax + # js L54 + # vmovapd xmm4, xmm3 + # L54: + # vcmpltsd xmm0, xmm0, xmm1 + # vblendvpd xmm0, xmm4, xmm2, xmm0 + # ret + # nop word ptr cs:[rax + rax] + # + # julia> @code_native debuginfo=:none syntax=:intel min(1.0, 2.0) + # .text + # vmovq rcx, xmm1 + # vmovq rax, xmm0 + # vcmpordsd xmm2, xmm0, xmm0 + # vblendvpd xmm2, xmm0, xmm1, xmm2 + # vcmpordsd xmm3, xmm1, xmm1 + # vblendvpd xmm3, xmm1, xmm0, xmm3 + # vmovapd xmm4, xmm2 + # test rcx, rcx + # jns L58 + # test rax, rax + # js L67 + # L46: + # vcmpltsd xmm0, xmm1, xmm0 + # vblendvpd xmm0, xmm4, xmm2, xmm0 + # ret + # L58: + # vmovapd xmm4, xmm3 + # test rax, rax + # jns L46 + # L67: + # vmovapd xmm4, xmm3 + # vcmpltsd xmm0, xmm1, xmm0 + # vblendvpd xmm0, xmm4, xmm2, xmm0 + # ret + # nop word ptr cs:[rax + rax] + # nop dword ptr [rax] + # ``` + # In contrast, we get the much simpler and faster version + # ```julia + # julia> @code_native debuginfo=:none syntax=:intel Base.FastMath.max_fast(1.0, 2.0) + # .text + # vmaxsd xmm0, xmm1, xmm0 + # ret + # nop word ptr cs:[rax + rax] + # + # julia> @code_native debuginfo=:none syntax=:intel Base.FastMath.min_fast(1.0, 2.0) + # .text + # vminsd xmm0, xmm0, xmm1 + # ret + # nop word ptr cs:[rax + rax] + # ``` + # when using `@fastmath`, which we also get from + # [Fortran](https://godbolt.org/z/Yrsa1js7P) + # or [C++](https://godbolt.org/z/674G7Pccv). + """ + Trixi.max(x, y, ...) + + Return the maximum of the arguments. See also the `maximum` function to take + the maximum element from a collection. + + This version in Trixi.jl is semantically equivalent to `Base.max` but may + be implemented differently. In particular, it may avoid potentially expensive + checks necessary in the presence of `NaN`s (or signed zeros). + + # Examples + + ```jldoctest + julia> max(2, 5, 1) + 5 + ``` + """ + @inline max(args...) = @fastmath max(args...) + + """ + Trixi.min(x, y, ...) + + Return the minimum of the arguments. See also the `minimum` function to take + the minimum element from a collection. + + This version in Trixi.jl is semantically equivalent to `Base.min` but may + be implemented differently. In particular, it may avoid potentially expensive + checks necessary in the presence of `NaN`s (or signed zeros). + + # Examples + + ```jldoctest + julia> min(2, 5, 1) + 1 + ``` + """ + @inline min(args...) = @fastmath min(args...) -Given ε = 1.0e-4, we use the following algorithm. + """ + Trixi.positive_part(x) + + Return `x` if `x` is positive, else zero. In other words, return + `(x + abs(x)) / 2` for real numbers `x`. + """ + @inline function positive_part(x) + return max(x, zero(x)) + end - if f^2 < ε - # use the expansion above - else - # use the direct formula (gamma - 1)/gamma * (y^gamma - x^gamma) / (y^(gamma-1) - x^(gamma-1)) + """ + Trixi.negative_part(x) + + Return `x` if `x` is negative, else zero. In other words, return + `(x - abs(x)) / 2` for real numbers `x`. + """ + @inline function negative_part(x) + return min(x, zero(x)) end -# References -- Andrew R. Winters, Christof Czernik, Moritz B. Schily & Gregor J. Gassner (2020) - Entropy stable numerical approximations for the isothermal and polytropic - Euler equations - [DOI: 10.1007/s10543-019-00789-w](https://doi.org/10.1007/s10543-019-00789-w) -- Agner Fog. - Lists of instruction latencies, throughputs and micro-operation breakdowns - for Intel, AMD, and VIA CPUs. - [https://www.agner.org/optimize/instruction_tables.pdf](https://www.agner.org/optimize/instruction_tables.pdf) -""" -@inline stolarsky_mean(x::Real, y::Real, gamma::Real) = stolarsky_mean(promote(x, y, - gamma)...) - -@inline function stolarsky_mean(x::RealT, y::RealT, gamma::RealT) where {RealT <: Real} - epsilon_f2 = convert(RealT, 1.0e-4) - f2 = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) # f2 = f^2 - if f2 < epsilon_f2 - # convenience coefficients - c1 = convert(RealT, 1 / 3) * (gamma - 2) - c2 = convert(RealT, -1 / 15) * (gamma + 1) * (gamma - 3) * c1 - c3 = convert(RealT, -1 / 21) * (2 * gamma * (gamma - 2) - 9) * c2 - return 0.5f0 * (x + y) * @evalpoly(f2, 1, c1, c2, c3) - else - return (gamma - 1) / gamma * (y^gamma - x^gamma) / - (y^(gamma - 1) - x^(gamma - 1)) + """ + Trixi.stolarsky_mean(x::Real, y:Real, gamma::Real) + + Compute an instance of a weighted Stolarsky mean of the form + + stolarsky_mean(x, y, gamma) = (gamma - 1)/gamma * (y^gamma - x^gamma) / (y^(gamma-1) - x^(gamma-1)) + + where `gamma > 1`. + + Problem: The formula above has a removable singularity at `x == y`. Thus, + some care must be taken to implement it correctly without problems or loss + of accuracy when `x ≈ y`. Here, we use the approach proposed by + Winters et al. (2020). + Set f = (y - x) / (y + x) and g = gamma (for compact notation). + Then, we use the expansions + + ((1+f)^g - (1-f)^g) / g = 2*f + (g-1)(g-2)/3 * f^3 + (g-1)(g-2)(g-3)(g-4)/60 * f^5 + O(f^7) + + and + + ((1+f)^(g-1) - (1-f)^(g-1)) / (g-1) = 2*f + (g-2)(g-3)/3 * f^3 + (g-2)(g-3)(g-4)(g-5)/60 * f^5 + O(f^7) + + Inserting the first few terms of these expansions and performing polynomial long division + we find that + + stolarsky_mean(x, y, gamma) ≈ (y + x) / 2 * (1 + (g-2)/3 * f^2 - (g+1)(g-2)(g-3)/45 * f^4 + (g+1)(g-2)(g-3)(2g(g-2)-9)/945 * f^6) + + Since divisions are usually more expensive on modern hardware than + multiplications (Agner Fog), we try to avoid computing two divisions. Thus, + we use + + f^2 = (y - x)^2 / (x + y)^2 + = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) + + Given ε = 1.0e-4, we use the following algorithm. + + if f^2 < ε + # use the expansion above + else + # use the direct formula (gamma - 1)/gamma * (y^gamma - x^gamma) / (y^(gamma-1) - x^(gamma-1)) + end + + # References + - Andrew R. Winters, Christof Czernik, Moritz B. Schily & Gregor J. Gassner (2020) + Entropy stable numerical approximations for the isothermal and polytropic + Euler equations + [DOI: 10.1007/s10543-019-00789-w](https://doi.org/10.1007/s10543-019-00789-w) + - Agner Fog. + Lists of instruction latencies, throughputs and micro-operation breakdowns + for Intel, AMD, and VIA CPUs. + [https://www.agner.org/optimize/instruction_tables.pdf](https://www.agner.org/optimize/instruction_tables.pdf) + """ + @inline stolarsky_mean(x::Real, y::Real, gamma::Real) = stolarsky_mean( + promote( + x, y, + gamma + )... + ) + + @inline function stolarsky_mean(x::RealT, y::RealT, gamma::RealT) where {RealT <: Real} + epsilon_f2 = convert(RealT, 1.0e-4) + f2 = (x * (x - 2 * y) + y * y) / (x * (x + 2 * y) + y * y) # f2 = f^2 + if f2 < epsilon_f2 + # convenience coefficients + c1 = convert(RealT, 1 / 3) * (gamma - 2) + c2 = convert(RealT, -1 / 15) * (gamma + 1) * (gamma - 3) * c1 + c3 = convert(RealT, -1 / 21) * (2 * gamma * (gamma - 2) - 9) * c2 + return 0.5f0 * (x + y) * @evalpoly(f2, 1, c1, c2, c3) + else + return (gamma - 1) / gamma * (y^gamma - x^gamma) / + (y^(gamma - 1) - x^(gamma - 1)) + end end -end end # @muladd diff --git a/src/auxiliary/mpi.jl b/src/auxiliary/mpi.jl index c85c23670b..65bd12770f 100644 --- a/src/auxiliary/mpi.jl +++ b/src/auxiliary/mpi.jl @@ -1,4 +1,3 @@ - """ init_mpi() @@ -15,7 +14,7 @@ function init_mpi() # threadlevel=MPI.THREAD_FUNNELED: Only main thread makes MPI calls # finalize_atexit=true : MPI.jl will call call MPI.Finalize as `atexit` hook provided = MPI.Init(threadlevel = MPI.THREAD_FUNNELED, finalize_atexit = true) - @assert provided>=MPI.THREAD_FUNNELED "MPI library with insufficient threading support" + @assert provided >= MPI.THREAD_FUNNELED "MPI library with insufficient threading support" # Initialize global MPI state MPI_RANK[] = MPI.Comm_rank(MPI.COMM_WORLD) @@ -79,8 +78,10 @@ function ode_norm(u::AbstractArray, t) local_sumabs2 = recursive_sum_abs2(u) # sum(abs2, u) local_length = recursive_length(u) # length(u) if mpi_isparallel() - global_sumabs2, global_length = MPI.Allreduce([local_sumabs2, local_length], +, - mpi_comm()) + global_sumabs2, global_length = MPI.Allreduce( + [local_sumabs2, local_length], +, + mpi_comm() + ) return sqrt(global_sumabs2 / global_length) else return sqrt(local_sumabs2 / local_length) @@ -96,9 +97,9 @@ recursive_sum_abs2(u::Number) = abs2(u) # Use `mapreduce` instead of `sum` since `sum` from StaticArrays.jl does not # support the kwarg `init` # We need `init=zero(eltype(eltype(u))` below to deal with arrays of `SVector`s etc. -# A better solution would be `recursive_unitless_bottom_eltype` from +# A better solution would be `recursive_unitless_bottom_eltype` from # https://github.com/SciML/RecursiveArrayTools.jl -# However, what you have is good enough for us for now, so we don't need this +# However, what you have is good enough for us for now, so we don't need this # additional dependency at the moment. function recursive_sum_abs2(u::AbstractArray) mapreduce(recursive_sum_abs2, +, u; init = zero(eltype(eltype(u)))) @@ -107,8 +108,14 @@ end recursive_length(u::Number) = length(u) recursive_length(u::AbstractArray{<:Number}) = length(u) recursive_length(u::AbstractArray{<:AbstractArray}) = sum(recursive_length, u) -function recursive_length(u::AbstractArray{<:StaticArrays.StaticArray{S, - <:Number}}) where {S} +function recursive_length( + u::AbstractArray{ + <:StaticArrays.StaticArray{ + S, + <:Number, + }, + } + ) where {S} prod(StaticArrays.Size(eltype(u))) * length(u) end diff --git a/src/auxiliary/p4est.jl b/src/auxiliary/p4est.jl index 2478dd37b6..f03abd1ca0 100644 --- a/src/auxiliary/p4est.jl +++ b/src/auxiliary/p4est.jl @@ -3,280 +3,318 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - init_p4est() - -Initialize `p4est` by calling `p4est_init` and setting the log level to `SC_LP_ERROR`. -This function will check if `p4est` is already initialized -and if yes, do nothing, thus it is safe to call it multiple times. -""" -function init_p4est() - # Only initialize p4est if P4est.jl can be used - if P4est.preferences_set_correctly() - p4est_package_id = P4est.package_id() - if p4est_package_id >= 0 - return nothing + #! format: noindent + + """ + init_p4est() + + Initialize `p4est` by calling `p4est_init` and setting the log level to `SC_LP_ERROR`. + This function will check if `p4est` is already initialized + and if yes, do nothing, thus it is safe to call it multiple times. + """ + function init_p4est() + # Only initialize p4est if P4est.jl can be used + if P4est.preferences_set_correctly() + p4est_package_id = P4est.package_id() + if p4est_package_id >= 0 + return nothing + end + + # Initialize `p4est` with log level ERROR to prevent a lot of output in AMR simulations + p4est_init(C_NULL, SC_LP_ERROR) + else + @warn "Preferences for P4est.jl are not set correctly. Until fixed, using `P4estMesh` will result in a crash. " * + "See also https://trixi-framework.github.io/Trixi.jl/stable/parallelization/#parallel_system_MPI" end - # Initialize `p4est` with log level ERROR to prevent a lot of output in AMR simulations - p4est_init(C_NULL, SC_LP_ERROR) - else - @warn "Preferences for P4est.jl are not set correctly. Until fixed, using `P4estMesh` will result in a crash. " * - "See also https://trixi-framework.github.io/Trixi.jl/stable/parallelization/#parallel_system_MPI" + return nothing end - return nothing -end - -# for convenience to either pass a Ptr or a PointerWrapper -const PointerOrWrapper = Union{Ptr{T}, PointerWrapper{T}} where {T} - -# Convert sc_array of type T to Julia array -function unsafe_wrap_sc(::Type{T}, sc_array_ptr::Ptr{sc_array}) where {T} - sc_array_obj = unsafe_load(sc_array_ptr) - return unsafe_wrap_sc(T, sc_array_obj) -end - -function unsafe_wrap_sc(::Type{T}, sc_array_obj::sc_array) where {T} - elem_count = sc_array_obj.elem_count - array = sc_array_obj.array - return unsafe_wrap(Array, Ptr{T}(array), elem_count) -end - -function unsafe_wrap_sc(::Type{T}, sc_array_pw::PointerWrapper{sc_array}) where {T} - elem_count = sc_array_pw.elem_count[] - array = sc_array_pw.array - - return unsafe_wrap(Array, Ptr{T}(pointer(array)), elem_count) -end - -# Load the ith element (1-indexed) of an sc array of type T as PointerWrapper -function load_pointerwrapper_sc(::Type{T}, sc_array::PointerWrapper{sc_array}, - i::Integer = 1) where {T} - return PointerWrapper(T, pointer(sc_array.array) + (i - 1) * sizeof(T)) -end - -# Create new `p4est` from a p4est_connectivity -# 2D -function new_p4est(connectivity::PointerOrWrapper{p4est_connectivity_t}, - initial_refinement_level) - comm = P4est.uses_mpi() ? mpi_comm() : 0 # Use Trixi.jl's MPI communicator if p4est supports MPI - p4est_new_ext(comm, - connectivity, - 0, # No minimum initial qudrants per processor - initial_refinement_level, - true, # Refine uniformly - 2 * sizeof(Int), # Use Int-Vector of size 2 as quadrant user data - C_NULL, # No init function - C_NULL) # No user pointer -end - -# 3D -function new_p4est(connectivity::PointerOrWrapper{p8est_connectivity_t}, - initial_refinement_level) - comm = P4est.uses_mpi() ? mpi_comm() : 0 # Use Trixi.jl's MPI communicator if p4est supports MPI - p8est_new_ext(comm, connectivity, 0, initial_refinement_level, true, - 2 * sizeof(Int), C_NULL, C_NULL) -end - -# Save `p4est` data to file -# 2D -function save_p4est!(file, p4est::PointerOrWrapper{p4est_t}) - # Don't save user data of the quads - p4est_save(file, p4est, false) -end - -# 3D -function save_p4est!(file, p8est::PointerOrWrapper{p8est_t}) - # Don't save user data of the quads - p8est_save(file, p8est, false) -end - -# Load `p4est` from file -# 2D -function load_p4est(file, ::Val{2}) - conn_vec = Vector{Ptr{p4est_connectivity_t}}(undef, 1) - comm = P4est.uses_mpi() ? mpi_comm() : C_NULL # Use Trixi.jl's MPI communicator if p4est supports MPI - p4est = p4est_load_ext(file, - comm, - 0, # Size of user data - 0, # Flag to load user data - 1, # Autopartition: ignore saved partition - 0, # Have only rank 0 read headers and bcast them - C_NULL, # No pointer to user data - pointer(conn_vec)) - # p4est_load_ext only allocates memory when also data is read - # use p4est_reset_data to allocate uninitialized memory - p4est_reset_data(p4est, - 2 * sizeof(Int), # Use Int-Vector of size 2 as quadrant user data - C_NULL, # No init function - C_NULL) # No pointer to user data - return p4est -end - -# 3D -function load_p4est(file, ::Val{3}) - conn_vec = Vector{Ptr{p8est_connectivity_t}}(undef, 1) - comm = P4est.uses_mpi() ? mpi_comm() : C_NULL # Use Trixi.jl's MPI communicator if p4est supports MPI - p4est = p8est_load_ext(file, comm, 0, 0, 1, 0, C_NULL, pointer(conn_vec)) - p8est_reset_data(p4est, 2 * sizeof(Int), C_NULL, C_NULL) - return p4est -end - -# Read `p4est` connectivity from Abaqus mesh file (.inp) -# 2D -read_inp_p4est(meshfile, ::Val{2}) = p4est_connectivity_read_inp(meshfile) -# 3D -read_inp_p4est(meshfile, ::Val{3}) = p8est_connectivity_read_inp(meshfile) - -# Refine `p4est` if refine_fn_c returns 1 -# 2D -function refine_p4est!(p4est::PointerOrWrapper{p4est_t}, recursive, refine_fn_c, - init_fn_c) - p4est_refine(p4est, recursive, refine_fn_c, init_fn_c) -end -# 3D -function refine_p4est!(p8est::PointerOrWrapper{p8est_t}, recursive, refine_fn_c, - init_fn_c) - p8est_refine(p8est, recursive, refine_fn_c, init_fn_c) -end - -# Refine `p4est` if coarsen_fn_c returns 1 -# 2D -function coarsen_p4est!(p4est::PointerOrWrapper{p4est_t}, recursive, coarsen_fn_c, - init_fn_c) - p4est_coarsen(p4est, recursive, coarsen_fn_c, init_fn_c) -end -# 3D -function coarsen_p4est!(p8est::PointerOrWrapper{p8est_t}, recursive, coarsen_fn_c, - init_fn_c) - p8est_coarsen(p8est, recursive, coarsen_fn_c, init_fn_c) -end - -# Create new ghost layer from p4est, only connections via faces are relevant -# 2D -function ghost_new_p4est(p4est::PointerOrWrapper{p4est_t}) - p4est_ghost_new(p4est, P4est.P4EST_CONNECT_FACE) -end -# 3D -# In 3D it is not sufficient to use `P8EST_CONNECT_FACE`. Consider the neighbor elements of a mortar -# in 3D. We have to determine which MPI ranks are involved in this mortar. -# ┌─────────────┬─────────────┐ ┌───────────────────────────┐ -# │ │ │ │ │ -# │ small │ small │ │ │ -# │ 3 │ 4 │ │ │ -# │ │ │ │ large │ -# ├─────────────┼─────────────┤ │ 5 │ -# │ │ │ │ │ -# │ small │ small │ │ │ -# │ 1 │ 2 │ │ │ -# │ │ │ │ │ -# └─────────────┴─────────────┘ └───────────────────────────┘ -# Suppose one process only owns element 1. Since element 4 is not connected to element 1 via a face, -# there is no guarantee that element 4 will be in the ghost layer, if it is constructed with -# `P8EST_CONNECT_FACE`. But if it is not in the ghost layer, it will not be available in -# `iterate_p4est` and thus we cannot determine its MPI rank -# (see https://github.com/cburstedde/p4est/blob/439bc9aae849555256ddfe4b03d1f9fe8d18ff0e/src/p8est_iterate.h#L66-L72). -function ghost_new_p4est(p8est::PointerOrWrapper{p8est_t}) - p8est_ghost_new(p8est, P4est.P8EST_CONNECT_FULL) -end - -# Check if ghost layer is valid -# 2D -function ghost_is_valid_p4est(p4est::PointerOrWrapper{p4est_t}, - ghost_layer::Ptr{p4est_ghost_t}) - return p4est_ghost_is_valid(p4est, ghost_layer) -end -# 3D -function ghost_is_valid_p4est(p4est::PointerOrWrapper{p8est_t}, - ghost_layer::Ptr{p8est_ghost_t}) - return p8est_ghost_is_valid(p4est, ghost_layer) -end - -# Destroy ghost layer -# 2D -function ghost_destroy_p4est(ghost_layer::PointerOrWrapper{p4est_ghost_t}) - p4est_ghost_destroy(ghost_layer) -end -# 3D -function ghost_destroy_p4est(ghost_layer::PointerOrWrapper{p8est_ghost_t}) - p8est_ghost_destroy(ghost_layer) -end - -# Let `p4est` iterate over each cell volume and cell face. -# Call iter_volume_c for each cell and iter_face_c for each face. -# 2D -function iterate_p4est(p4est::PointerOrWrapper{p4est_t}, user_data; - ghost_layer = C_NULL, - iter_volume_c = C_NULL, iter_face_c = C_NULL) - if user_data === C_NULL - user_data_ptr = user_data - elseif user_data isa AbstractArray - user_data_ptr = pointer(user_data) - else - user_data_ptr = pointer_from_objref(user_data) + # for convenience to either pass a Ptr or a PointerWrapper + const PointerOrWrapper = Union{Ptr{T}, PointerWrapper{T}} where {T} + + # Convert sc_array of type T to Julia array + function unsafe_wrap_sc(::Type{T}, sc_array_ptr::Ptr{sc_array}) where {T} + sc_array_obj = unsafe_load(sc_array_ptr) + return unsafe_wrap_sc(T, sc_array_obj) + end + + function unsafe_wrap_sc(::Type{T}, sc_array_obj::sc_array) where {T} + elem_count = sc_array_obj.elem_count + array = sc_array_obj.array + return unsafe_wrap(Array, Ptr{T}(array), elem_count) + end + + function unsafe_wrap_sc(::Type{T}, sc_array_pw::PointerWrapper{sc_array}) where {T} + elem_count = sc_array_pw.elem_count[] + array = sc_array_pw.array + + return unsafe_wrap(Array, Ptr{T}(pointer(array)), elem_count) + end + + # Load the ith element (1-indexed) of an sc array of type T as PointerWrapper + function load_pointerwrapper_sc( + ::Type{T}, sc_array::PointerWrapper{sc_array}, + i::Integer = 1 + ) where {T} + return PointerWrapper(T, pointer(sc_array.array) + (i - 1) * sizeof(T)) + end + + # Create new `p4est` from a p4est_connectivity + # 2D + function new_p4est( + connectivity::PointerOrWrapper{p4est_connectivity_t}, + initial_refinement_level + ) + comm = P4est.uses_mpi() ? mpi_comm() : 0 # Use Trixi.jl's MPI communicator if p4est supports MPI + p4est_new_ext( + comm, + connectivity, + 0, # No minimum initial qudrants per processor + initial_refinement_level, + true, # Refine uniformly + 2 * sizeof(Int), # Use Int-Vector of size 2 as quadrant user data + C_NULL, # No init function + C_NULL + ) # No user pointer + end + + # 3D + function new_p4est( + connectivity::PointerOrWrapper{p8est_connectivity_t}, + initial_refinement_level + ) + comm = P4est.uses_mpi() ? mpi_comm() : 0 # Use Trixi.jl's MPI communicator if p4est supports MPI + p8est_new_ext( + comm, connectivity, 0, initial_refinement_level, true, + 2 * sizeof(Int), C_NULL, C_NULL + ) + end + + # Save `p4est` data to file + # 2D + function save_p4est!(file, p4est::PointerOrWrapper{p4est_t}) + # Don't save user data of the quads + p4est_save(file, p4est, false) + end + + # 3D + function save_p4est!(file, p8est::PointerOrWrapper{p8est_t}) + # Don't save user data of the quads + p8est_save(file, p8est, false) + end + + # Load `p4est` from file + # 2D + function load_p4est(file, ::Val{2}) + conn_vec = Vector{Ptr{p4est_connectivity_t}}(undef, 1) + comm = P4est.uses_mpi() ? mpi_comm() : C_NULL # Use Trixi.jl's MPI communicator if p4est supports MPI + p4est = p4est_load_ext( + file, + comm, + 0, # Size of user data + 0, # Flag to load user data + 1, # Autopartition: ignore saved partition + 0, # Have only rank 0 read headers and bcast them + C_NULL, # No pointer to user data + pointer(conn_vec) + ) + # p4est_load_ext only allocates memory when also data is read + # use p4est_reset_data to allocate uninitialized memory + p4est_reset_data( + p4est, + 2 * sizeof(Int), # Use Int-Vector of size 2 as quadrant user data + C_NULL, # No init function + C_NULL + ) # No pointer to user data + return p4est + end + + # 3D + function load_p4est(file, ::Val{3}) + conn_vec = Vector{Ptr{p8est_connectivity_t}}(undef, 1) + comm = P4est.uses_mpi() ? mpi_comm() : C_NULL # Use Trixi.jl's MPI communicator if p4est supports MPI + p4est = p8est_load_ext(file, comm, 0, 0, 1, 0, C_NULL, pointer(conn_vec)) + p8est_reset_data(p4est, 2 * sizeof(Int), C_NULL, C_NULL) + return p4est + end + + # Read `p4est` connectivity from Abaqus mesh file (.inp) + # 2D + read_inp_p4est(meshfile, ::Val{2}) = p4est_connectivity_read_inp(meshfile) + # 3D + read_inp_p4est(meshfile, ::Val{3}) = p8est_connectivity_read_inp(meshfile) + + # Refine `p4est` if refine_fn_c returns 1 + # 2D + function refine_p4est!( + p4est::PointerOrWrapper{p4est_t}, recursive, refine_fn_c, + init_fn_c + ) + p4est_refine(p4est, recursive, refine_fn_c, init_fn_c) + end + # 3D + function refine_p4est!( + p8est::PointerOrWrapper{p8est_t}, recursive, refine_fn_c, + init_fn_c + ) + p8est_refine(p8est, recursive, refine_fn_c, init_fn_c) + end + + # Refine `p4est` if coarsen_fn_c returns 1 + # 2D + function coarsen_p4est!( + p4est::PointerOrWrapper{p4est_t}, recursive, coarsen_fn_c, + init_fn_c + ) + p4est_coarsen(p4est, recursive, coarsen_fn_c, init_fn_c) + end + # 3D + function coarsen_p4est!( + p8est::PointerOrWrapper{p8est_t}, recursive, coarsen_fn_c, + init_fn_c + ) + p8est_coarsen(p8est, recursive, coarsen_fn_c, init_fn_c) + end + + # Create new ghost layer from p4est, only connections via faces are relevant + # 2D + function ghost_new_p4est(p4est::PointerOrWrapper{p4est_t}) + p4est_ghost_new(p4est, P4est.P4EST_CONNECT_FACE) + end + # 3D + # In 3D it is not sufficient to use `P8EST_CONNECT_FACE`. Consider the neighbor elements of a mortar + # in 3D. We have to determine which MPI ranks are involved in this mortar. + # ┌─────────────┬─────────────┐ ┌───────────────────────────┐ + # │ │ │ │ │ + # │ small │ small │ │ │ + # │ 3 │ 4 │ │ │ + # │ │ │ │ large │ + # ├─────────────┼─────────────┤ │ 5 │ + # │ │ │ │ │ + # │ small │ small │ │ │ + # │ 1 │ 2 │ │ │ + # │ │ │ │ │ + # └─────────────┴─────────────┘ └───────────────────────────┘ + # Suppose one process only owns element 1. Since element 4 is not connected to element 1 via a face, + # there is no guarantee that element 4 will be in the ghost layer, if it is constructed with + # `P8EST_CONNECT_FACE`. But if it is not in the ghost layer, it will not be available in + # `iterate_p4est` and thus we cannot determine its MPI rank + # (see https://github.com/cburstedde/p4est/blob/439bc9aae849555256ddfe4b03d1f9fe8d18ff0e/src/p8est_iterate.h#L66-L72). + function ghost_new_p4est(p8est::PointerOrWrapper{p8est_t}) + p8est_ghost_new(p8est, P4est.P8EST_CONNECT_FULL) + end + + # Check if ghost layer is valid + # 2D + function ghost_is_valid_p4est( + p4est::PointerOrWrapper{p4est_t}, + ghost_layer::Ptr{p4est_ghost_t} + ) + return p4est_ghost_is_valid(p4est, ghost_layer) + end + # 3D + function ghost_is_valid_p4est( + p4est::PointerOrWrapper{p8est_t}, + ghost_layer::Ptr{p8est_ghost_t} + ) + return p8est_ghost_is_valid(p4est, ghost_layer) + end + + # Destroy ghost layer + # 2D + function ghost_destroy_p4est(ghost_layer::PointerOrWrapper{p4est_ghost_t}) + p4est_ghost_destroy(ghost_layer) + end + # 3D + function ghost_destroy_p4est(ghost_layer::PointerOrWrapper{p8est_ghost_t}) + p8est_ghost_destroy(ghost_layer) + end + + # Let `p4est` iterate over each cell volume and cell face. + # Call iter_volume_c for each cell and iter_face_c for each face. + # 2D + function iterate_p4est( + p4est::PointerOrWrapper{p4est_t}, user_data; + ghost_layer = C_NULL, + iter_volume_c = C_NULL, iter_face_c = C_NULL + ) + if user_data === C_NULL + user_data_ptr = user_data + elseif user_data isa AbstractArray + user_data_ptr = pointer(user_data) + else + user_data_ptr = pointer_from_objref(user_data) + end + + GC.@preserve user_data begin + p4est_iterate( + p4est, + ghost_layer, + user_data_ptr, + iter_volume_c, # iter_volume + iter_face_c, # iter_face + C_NULL + ) # iter_corner + end + + return nothing + end + + # 3D + function iterate_p4est( + p8est::PointerOrWrapper{p8est_t}, user_data; + ghost_layer = C_NULL, + iter_volume_c = C_NULL, iter_face_c = C_NULL + ) + if user_data === C_NULL + user_data_ptr = user_data + elseif user_data isa AbstractArray + user_data_ptr = pointer(user_data) + else + user_data_ptr = pointer_from_objref(user_data) + end + + GC.@preserve user_data begin + p8est_iterate( + p8est, + ghost_layer, + user_data_ptr, + iter_volume_c, # iter_volume + iter_face_c, # iter_face + C_NULL, # iter_edge + C_NULL + ) # iter_corner + end + + return nothing end - GC.@preserve user_data begin - p4est_iterate(p4est, - ghost_layer, - user_data_ptr, - iter_volume_c, # iter_volume - iter_face_c, # iter_face - C_NULL) # iter_corner + # Load i-th element of the sc_array info.sides of the type p[48]est_iter_face_side_t + # 2D version + function load_pointerwrapper_side( + info::PointerWrapper{p4est_iter_face_info_t}, + i::Integer = 1 + ) + return load_pointerwrapper_sc(p4est_iter_face_side_t, info.sides, i) end - return nothing -end - -# 3D -function iterate_p4est(p8est::PointerOrWrapper{p8est_t}, user_data; - ghost_layer = C_NULL, - iter_volume_c = C_NULL, iter_face_c = C_NULL) - if user_data === C_NULL - user_data_ptr = user_data - elseif user_data isa AbstractArray - user_data_ptr = pointer(user_data) - else - user_data_ptr = pointer_from_objref(user_data) + # 3D version + function load_pointerwrapper_side( + info::PointerWrapper{p8est_iter_face_info_t}, + i::Integer = 1 + ) + return load_pointerwrapper_sc(p8est_iter_face_side_t, info.sides, i) end - GC.@preserve user_data begin - p8est_iterate(p8est, - ghost_layer, - user_data_ptr, - iter_volume_c, # iter_volume - iter_face_c, # iter_face - C_NULL, # iter_edge - C_NULL) # iter_corner + # Load i-th element of the sc_array p4est.trees of the type p[48]est_tree_t + # 2D version + function load_pointerwrapper_tree(p4est::PointerWrapper{p4est_t}, i::Integer = 1) + return load_pointerwrapper_sc(p4est_tree_t, p4est.trees, i) end - return nothing -end - -# Load i-th element of the sc_array info.sides of the type p[48]est_iter_face_side_t -# 2D version -function load_pointerwrapper_side(info::PointerWrapper{p4est_iter_face_info_t}, - i::Integer = 1) - return load_pointerwrapper_sc(p4est_iter_face_side_t, info.sides, i) -end - -# 3D version -function load_pointerwrapper_side(info::PointerWrapper{p8est_iter_face_info_t}, - i::Integer = 1) - return load_pointerwrapper_sc(p8est_iter_face_side_t, info.sides, i) -end - -# Load i-th element of the sc_array p4est.trees of the type p[48]est_tree_t -# 2D version -function load_pointerwrapper_tree(p4est::PointerWrapper{p4est_t}, i::Integer = 1) - return load_pointerwrapper_sc(p4est_tree_t, p4est.trees, i) -end - -# 3D version -function load_pointerwrapper_tree(p8est::PointerWrapper{p8est_t}, i::Integer = 1) - return load_pointerwrapper_sc(p8est_tree_t, p8est.trees, i) -end + # 3D version + function load_pointerwrapper_tree(p8est::PointerWrapper{p8est_t}, i::Integer = 1) + return load_pointerwrapper_sc(p8est_tree_t, p8est.trees, i) + end end # @muladd diff --git a/src/auxiliary/precompile.jl b/src/auxiliary/precompile.jl index 4d5399b5ba..e493d75285 100644 --- a/src/auxiliary/precompile.jl +++ b/src/auxiliary/precompile.jl @@ -139,127 +139,190 @@ function _precompile_manual_() ccall(:jl_generating_output, Cint, ()) == 1 || return nothing function equations_types_1d(RealT) - (LinearScalarAdvectionEquation1D{RealT}, - HyperbolicDiffusionEquations1D{RealT}, - CompressibleEulerEquations1D{RealT}, - IdealGlmMhdEquations1D{RealT}) + ( + LinearScalarAdvectionEquation1D{RealT}, + HyperbolicDiffusionEquations1D{RealT}, + CompressibleEulerEquations1D{RealT}, + IdealGlmMhdEquations1D{RealT}, + ) end function equations_types_2d(RealT) - (LinearScalarAdvectionEquation2D{RealT}, - HyperbolicDiffusionEquations2D{RealT}, - CompressibleEulerEquations2D{RealT}, - IdealGlmMhdEquations2D{RealT}, - LatticeBoltzmannEquations2D{RealT, typeof(Trixi.collision_bgk)}) + ( + LinearScalarAdvectionEquation2D{RealT}, + HyperbolicDiffusionEquations2D{RealT}, + CompressibleEulerEquations2D{RealT}, + IdealGlmMhdEquations2D{RealT}, + LatticeBoltzmannEquations2D{RealT, typeof(Trixi.collision_bgk)}, + ) end function equations_types_3d(RealT) - (LinearScalarAdvectionEquation3D{RealT}, - HyperbolicDiffusionEquations3D{RealT}, - CompressibleEulerEquations3D{RealT}, - IdealGlmMhdEquations3D{RealT}, - LatticeBoltzmannEquations3D{RealT, typeof(Trixi.collision_bgk)}) + ( + LinearScalarAdvectionEquation3D{RealT}, + HyperbolicDiffusionEquations3D{RealT}, + CompressibleEulerEquations3D{RealT}, + IdealGlmMhdEquations3D{RealT}, + LatticeBoltzmannEquations3D{RealT, typeof(Trixi.collision_bgk)}, + ) end function equations_types(RealT) - (LinearScalarAdvectionEquation1D{RealT}, - LinearScalarAdvectionEquation2D{RealT}, - LinearScalarAdvectionEquation3D{RealT}, - HyperbolicDiffusionEquations1D{RealT}, - HyperbolicDiffusionEquations2D{RealT}, - HyperbolicDiffusionEquations3D{RealT}, - CompressibleEulerEquations1D{RealT}, - CompressibleEulerEquations2D{RealT}, - CompressibleEulerEquations3D{RealT}, - IdealGlmMhdEquations1D{RealT}, - IdealGlmMhdEquations2D{RealT}, - IdealGlmMhdEquations3D{RealT}, - LatticeBoltzmannEquations2D{RealT, typeof(Trixi.collision_bgk)}, - LatticeBoltzmannEquations3D{RealT, typeof(Trixi.collision_bgk)}) + ( + LinearScalarAdvectionEquation1D{RealT}, + LinearScalarAdvectionEquation2D{RealT}, + LinearScalarAdvectionEquation3D{RealT}, + HyperbolicDiffusionEquations1D{RealT}, + HyperbolicDiffusionEquations2D{RealT}, + HyperbolicDiffusionEquations3D{RealT}, + CompressibleEulerEquations1D{RealT}, + CompressibleEulerEquations2D{RealT}, + CompressibleEulerEquations3D{RealT}, + IdealGlmMhdEquations1D{RealT}, + IdealGlmMhdEquations2D{RealT}, + IdealGlmMhdEquations3D{RealT}, + LatticeBoltzmannEquations2D{RealT, typeof(Trixi.collision_bgk)}, + LatticeBoltzmannEquations3D{RealT, typeof(Trixi.collision_bgk)}, + ) end function basis_type_dgsem(RealT, nnodes_) - LobattoLegendreBasis{RealT, nnodes_, - # VectorT - StaticArrays.SVector{nnodes_, RealT}, - # InverseVandermondeLegendre - Matrix{RealT}, - # BoundaryMatrix - #StaticArrays.SArray{Tuple{nnodes_,2},RealT,2,2*nnodes_}, - Matrix{RealT}, - # DerivativeMatrix - #StaticArrays.SArray{Tuple{nnodes_,nnodes_},RealT,2,nnodes_^2}, - Matrix{RealT}} + LobattoLegendreBasis{ + RealT, nnodes_, + # VectorT + StaticArrays.SVector{nnodes_, RealT}, + # InverseVandermondeLegendre + Matrix{RealT}, + # BoundaryMatrix + #StaticArrays.SArray{Tuple{nnodes_,2},RealT,2,2*nnodes_}, + Matrix{RealT}, + # DerivativeMatrix + #StaticArrays.SArray{Tuple{nnodes_,nnodes_},RealT,2,nnodes_^2}, + Matrix{RealT}, + } end function mortar_type_dgsem(RealT, nnodes_) - LobattoLegendreMortarL2{RealT, nnodes_, - # ForwardMatrix - #StaticArrays.SArray{Tuple{nnodes_,nnodes_},RealT,2,nnodes_^2}, - Matrix{RealT}, - # ReverseMatrix - # StaticArrays.SArray{Tuple{nnodes_,nnodes_},RealT,2,nnodes_^2}, - Matrix{RealT}} + LobattoLegendreMortarL2{ + RealT, nnodes_, + # ForwardMatrix + #StaticArrays.SArray{Tuple{nnodes_,nnodes_},RealT,2,nnodes_^2}, + Matrix{RealT}, + # ReverseMatrix + # StaticArrays.SArray{Tuple{nnodes_,nnodes_},RealT,2,nnodes_^2}, + Matrix{RealT}, + } end function analyzer_type_dgsem(RealT, nnodes_) polydeg = nnodes_ - 1 nnodes_analysis = 2 * polydeg + 1 - LobattoLegendreAnalyzer{RealT, nnodes_analysis, - # VectorT - StaticArrays.SVector{nnodes_analysis, RealT}, - # Vandermonde - Array{RealT, 2}} + LobattoLegendreAnalyzer{ + RealT, nnodes_analysis, + # VectorT + StaticArrays.SVector{nnodes_analysis, RealT}, + # Vandermonde + Array{RealT, 2}, + } end function adaptor_type_dgsem(RealT, nnodes_) - LobattoLegendreAdaptorL2{RealT, nnodes_, - # ForwardMatrix - StaticArrays.SArray{Tuple{nnodes_, nnodes_}, RealT, 2, - nnodes_^2}, - # Matrix{RealT}, - # ReverseMatrix - StaticArrays.SArray{Tuple{nnodes_, nnodes_}, RealT, 2, - nnodes_^2} - # Matrix{RealT}, - } + LobattoLegendreAdaptorL2{ + RealT, nnodes_, + # ForwardMatrix + StaticArrays.SArray{ + Tuple{nnodes_, nnodes_}, RealT, 2, + nnodes_^2, + }, + # Matrix{RealT}, + # ReverseMatrix + StaticArrays.SArray{ + Tuple{nnodes_, nnodes_}, RealT, 2, + nnodes_^2, + }, + # Matrix{RealT}, + } end # Constructors: mesh for RealT in (Int, Float64) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:initial_refinement_level, :n_cells_max), - Tuple{Int, Int}}, Type{TreeMesh}, RealT, - RealT}) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:initial_refinement_level, :n_cells_max), - Tuple{Int, Int}}, Type{TreeMesh}, - Tuple{RealT}, Tuple{RealT}}) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:initial_refinement_level, :n_cells_max), - Tuple{Int, Int}}, Type{TreeMesh}, - Tuple{RealT, RealT}, Tuple{RealT, RealT}}) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:initial_refinement_level, :n_cells_max), - Tuple{Int, Int}}, Type{TreeMesh}, - Tuple{RealT, RealT, RealT}, - Tuple{RealT, RealT, RealT}}) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{ + (:initial_refinement_level, :n_cells_max), + Tuple{Int, Int}, + }, Type{TreeMesh}, RealT, + RealT, + } + ) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{ + (:initial_refinement_level, :n_cells_max), + Tuple{Int, Int}, + }, Type{TreeMesh}, + Tuple{RealT}, Tuple{RealT}, + } + ) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{ + (:initial_refinement_level, :n_cells_max), + Tuple{Int, Int}, + }, Type{TreeMesh}, + Tuple{RealT, RealT}, Tuple{RealT, RealT}, + } + ) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{ + (:initial_refinement_level, :n_cells_max), + Tuple{Int, Int}, + }, Type{TreeMesh}, + Tuple{RealT, RealT, RealT}, + Tuple{RealT, RealT, RealT}, + } + ) end for TreeType in (SerialTree, ParallelTree), NDIMS in 1:3 - @assert Base.precompile(Tuple{typeof(Trixi.initialize!), - TreeMesh{NDIMS, TreeType{NDIMS}}, Int, Tuple{}, - Tuple{}}) - @assert Base.precompile(Tuple{typeof(Trixi.save_mesh_file), - TreeMesh{NDIMS, TreeType{NDIMS}}, String, Int}) + @assert Base.precompile( + Tuple{ + typeof(Trixi.initialize!), + TreeMesh{NDIMS, TreeType{NDIMS}}, Int, Tuple{}, + Tuple{}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.save_mesh_file), + TreeMesh{NDIMS, TreeType{NDIMS}}, String, Int, + } + ) end # Constructors: linear advection for RealT in (Float64,) @assert Base.precompile(Tuple{Type{LinearScalarAdvectionEquation1D}, RealT}) @assert Base.precompile(Tuple{Type{LinearScalarAdvectionEquation2D}, RealT, RealT}) - @assert Base.precompile(Tuple{Type{LinearScalarAdvectionEquation2D}, - Tuple{RealT, RealT}}) - @assert Base.precompile(Tuple{Type{LinearScalarAdvectionEquation3D}, RealT, RealT, - RealT}) - @assert Base.precompile(Tuple{Type{LinearScalarAdvectionEquation3D}, - Tuple{RealT, RealT, RealT}}) + @assert Base.precompile( + Tuple{ + Type{LinearScalarAdvectionEquation2D}, + Tuple{RealT, RealT}, + } + ) + @assert Base.precompile( + Tuple{ + Type{LinearScalarAdvectionEquation3D}, RealT, RealT, + RealT, + } + ) + @assert Base.precompile( + Tuple{ + Type{LinearScalarAdvectionEquation3D}, + Tuple{RealT, RealT, RealT}, + } + ) end # Constructors: hyperbolic diffusion @@ -285,18 +348,34 @@ function _precompile_manual_() # Constructors: LBM for RealT in (Float64,) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:Ma, :Re), Tuple{RealT, RealT}}, - Type{LatticeBoltzmannEquations2D}}) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:Ma, :Re), Tuple{RealT, Int}}, - Type{LatticeBoltzmannEquations2D}}) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:Ma, :Re), Tuple{RealT, RealT}}, - Type{LatticeBoltzmannEquations3D}}) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:Ma, :Re), Tuple{RealT, Int}}, - Type{LatticeBoltzmannEquations3D}}) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{(:Ma, :Re), Tuple{RealT, RealT}}, + Type{LatticeBoltzmannEquations2D}, + } + ) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{(:Ma, :Re), Tuple{RealT, Int}}, + Type{LatticeBoltzmannEquations2D}, + } + ) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{(:Ma, :Re), Tuple{RealT, RealT}}, + Type{LatticeBoltzmannEquations3D}, + } + ) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{(:Ma, :Re), Tuple{RealT, Int}}, + Type{LatticeBoltzmannEquations3D}, + } + ) end # Constructors of the basis are inherently type-unstable since we pass integers @@ -305,22 +384,50 @@ function _precompile_manual_() Base.precompile(Tuple{Type{LobattoLegendreBasis}, Int}) for RealT in (Float64,) Base.precompile(Tuple{Type{LobattoLegendreBasis}, RealT, Int}) - @assert Base.precompile(Tuple{typeof(Trixi.calc_dhat), Vector{RealT}, - Vector{RealT}}) - @assert Base.precompile(Tuple{typeof(Trixi.calc_dsplit), Vector{RealT}, - Vector{RealT}}) - @assert Base.precompile(Tuple{typeof(Trixi.polynomial_derivative_matrix), - Vector{RealT}}) - @assert Base.precompile(Tuple{typeof(Trixi.polynomial_interpolation_matrix), - Vector{RealT}, Vector{RealT}}) + @assert Base.precompile( + Tuple{ + typeof(Trixi.calc_dhat), Vector{RealT}, + Vector{RealT}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.calc_dsplit), Vector{RealT}, + Vector{RealT}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.polynomial_derivative_matrix), + Vector{RealT}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.polynomial_interpolation_matrix), + Vector{RealT}, Vector{RealT}, + } + ) @assert Base.precompile(Tuple{typeof(Trixi.barycentric_weights), Vector{RealT}}) - @assert Base.precompile(Tuple{typeof(Trixi.calc_lhat), RealT, Vector{RealT}, - Vector{RealT}}) - @assert Base.precompile(Tuple{typeof(Trixi.lagrange_interpolating_polynomials), - RealT, Vector{RealT}, Vector{RealT}}) + @assert Base.precompile( + Tuple{ + typeof(Trixi.calc_lhat), RealT, Vector{RealT}, + Vector{RealT}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.lagrange_interpolating_polynomials), + RealT, Vector{RealT}, Vector{RealT}, + } + ) @assert Base.precompile(Tuple{typeof(Trixi.calc_q_and_l), Int, RealT}) - @assert Base.precompile(Tuple{typeof(Trixi.legendre_polynomial_and_derivative), Int, - RealT}) + @assert Base.precompile( + Tuple{ + typeof(Trixi.legendre_polynomial_and_derivative), Int, + RealT, + } + ) @assert Base.precompile(Tuple{typeof(Trixi.vandermonde_legendre), Vector{RealT}}) end @assert Base.precompile(Tuple{typeof(Trixi.gauss_lobatto_nodes_weights), Int}) @@ -329,10 +436,18 @@ function _precompile_manual_() @assert Base.precompile(Tuple{typeof(Trixi.calc_forward_lower), Int}) @assert Base.precompile(Tuple{typeof(Trixi.calc_reverse_upper), Int, Val{:gauss}}) @assert Base.precompile(Tuple{typeof(Trixi.calc_reverse_lower), Int, Val{:gauss}}) - @assert Base.precompile(Tuple{typeof(Trixi.calc_reverse_upper), Int, - Val{:gauss_lobatto}}) - @assert Base.precompile(Tuple{typeof(Trixi.calc_reverse_lower), Int, - Val{:gauss_lobatto}}) + @assert Base.precompile( + Tuple{ + typeof(Trixi.calc_reverse_upper), Int, + Val{:gauss_lobatto}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.calc_reverse_lower), Int, + Val{:gauss_lobatto}, + } + ) # Constructors: mortars, analyzers, adaptors for RealT in (Float64,), polydeg in 1:7 @@ -344,30 +459,64 @@ function _precompile_manual_() end # Constructors: callbacks - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:analysis_interval,), Tuple{Int}}, - Type{AliveCallback}}) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{(:analysis_interval,), Tuple{Int}}, + Type{AliveCallback}, + } + ) for RealT in (Float64,) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:cfl,), Tuple{RealT}}, - Type{StepsizeCallback}}) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:glm_scale, :cfl), Tuple{RealT, RealT}}, - Type{GlmSpeedCallback}}) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{(:cfl,), Tuple{RealT}}, + Type{StepsizeCallback}, + } + ) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{(:glm_scale, :cfl), Tuple{RealT, RealT}}, + Type{GlmSpeedCallback}, + } + ) end - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:interval, :save_final_restart), - Tuple{Int, Bool}}, Type{SaveRestartCallback}}) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:interval, :save_initial_solution, - :save_final_solution, :solution_variables), - Tuple{Int, Bool, Bool, typeof(cons2cons)}}, - Type{SaveSolutionCallback}}) - @assert Base.precompile(Tuple{Core.kwftype(typeof(Trixi.Type)), - NamedTuple{(:interval, :save_initial_solution, - :save_final_solution, :solution_variables), - Tuple{Int, Bool, Bool, typeof(cons2prim)}}, - Type{SaveSolutionCallback}}) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{ + (:interval, :save_final_restart), + Tuple{Int, Bool}, + }, Type{SaveRestartCallback}, + } + ) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{ + ( + :interval, :save_initial_solution, + :save_final_solution, :solution_variables, + ), + Tuple{Int, Bool, Bool, typeof(cons2cons)}, + }, + Type{SaveSolutionCallback}, + } + ) + @assert Base.precompile( + Tuple{ + Core.kwftype(typeof(Trixi.Type)), + NamedTuple{ + ( + :interval, :save_initial_solution, + :save_final_solution, :solution_variables, + ), + Tuple{Int, Bool, Bool, typeof(cons2prim)}, + }, + Type{SaveSolutionCallback}, + } + ) # TODO: AnalysisCallback? # for RealT in (Float64,), polydeg in 1:7 # nnodes_ = polydeg + 1 @@ -380,12 +529,22 @@ function _precompile_manual_() # end # end @assert Base.precompile(Tuple{typeof(SummaryCallback)}) - @assert Base.precompile(Tuple{DiscreteCallback{typeof(Trixi.summary_callback), - typeof(Trixi.summary_callback), - typeof(Trixi.initialize_summary_callback), - typeof(SciMLBase.FINALIZE_DEFAULT)}}) - @assert Base.precompile(Tuple{typeof(summary_box), Base.TTY, String, - Vector{Pair{String, Any}}}) + @assert Base.precompile( + Tuple{ + DiscreteCallback{ + typeof(Trixi.summary_callback), + typeof(Trixi.summary_callback), + typeof(Trixi.initialize_summary_callback), + typeof(SciMLBase.FINALIZE_DEFAULT), + }, + } + ) + @assert Base.precompile( + Tuple{ + typeof(summary_box), Base.TTY, String, + Vector{Pair{String, Any}}, + } + ) # TODO: AMRCallback, ControllerThreeLevel, indicators # init_elements, interfaces, etc. @@ -395,59 +554,123 @@ function _precompile_manual_() mortar_type = mortar_type_dgsem(RealT, nnodes_) # 1D, serial - @assert Base.precompile(Tuple{typeof(Trixi.init_boundaries), Array{Int, 1}, - TreeMesh{1, Trixi.SerialTree{1}}, - Trixi.ElementContainer1D{RealT, uEltype}}) - @assert Base.precompile(Tuple{typeof(Trixi.init_interfaces), Array{Int, 1}, - TreeMesh{1, Trixi.SerialTree{1}}, - Trixi.ElementContainer1D{RealT, uEltype}}) - @assert Base.precompile(Tuple{typeof(Trixi.save_mesh_file), - TreeMesh{1, Trixi.SerialTree{1}}, String}) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_boundaries), Array{Int, 1}, + TreeMesh{1, Trixi.SerialTree{1}}, + Trixi.ElementContainer1D{RealT, uEltype}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_interfaces), Array{Int, 1}, + TreeMesh{1, Trixi.SerialTree{1}}, + Trixi.ElementContainer1D{RealT, uEltype}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.save_mesh_file), + TreeMesh{1, Trixi.SerialTree{1}}, String, + } + ) # 2D, serial - @assert Base.precompile(Tuple{typeof(Trixi.init_boundaries), Array{Int, 1}, - TreeMesh{2, Trixi.SerialTree{2}}, - Trixi.ElementContainer2D{RealT, uEltype}}) - @assert Base.precompile(Tuple{typeof(Trixi.init_interfaces), Array{Int, 1}, - TreeMesh{2, Trixi.SerialTree{2}}, - Trixi.ElementContainer2D{RealT, uEltype}}) - @assert Base.precompile(Tuple{typeof(Trixi.init_mortars), Array{Int, 1}, - TreeMesh{2, Trixi.SerialTree{2}}, - Trixi.ElementContainer2D{RealT, uEltype}, - mortar_type}) - @assert Base.precompile(Tuple{typeof(Trixi.save_mesh_file), - TreeMesh{2, Trixi.SerialTree{2}}, String}) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_boundaries), Array{Int, 1}, + TreeMesh{2, Trixi.SerialTree{2}}, + Trixi.ElementContainer2D{RealT, uEltype}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_interfaces), Array{Int, 1}, + TreeMesh{2, Trixi.SerialTree{2}}, + Trixi.ElementContainer2D{RealT, uEltype}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_mortars), Array{Int, 1}, + TreeMesh{2, Trixi.SerialTree{2}}, + Trixi.ElementContainer2D{RealT, uEltype}, + mortar_type, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.save_mesh_file), + TreeMesh{2, Trixi.SerialTree{2}}, String, + } + ) # 2D, parallel - @assert Base.precompile(Tuple{typeof(Trixi.init_boundaries), Array{Int, 1}, - TreeMesh{2, Trixi.ParallelTree{2}}, - Trixi.ElementContainer2D{RealT, uEltype}}) - @assert Base.precompile(Tuple{typeof(Trixi.init_interfaces), Array{Int, 1}, - TreeMesh{2, Trixi.ParallelTree{2}}, - Trixi.ElementContainer2D{RealT, uEltype}}) - @assert Base.precompile(Tuple{typeof(Trixi.init_mortars), Array{Int, 1}, - TreeMesh{2, Trixi.ParallelTree{2}}, - Trixi.ElementContainer2D{RealT, uEltype}, - mortar_type}) - @assert Base.precompile(Tuple{typeof(Trixi.init_mpi_interfaces), Array{Int, 1}, - TreeMesh{2, Trixi.ParallelTree{2}}, - Trixi.ElementContainer2D{RealT, uEltype}}) - @assert Base.precompile(Tuple{typeof(Trixi.save_mesh_file), - TreeMesh{2, Trixi.ParallelTree{2}}, String}) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_boundaries), Array{Int, 1}, + TreeMesh{2, Trixi.ParallelTree{2}}, + Trixi.ElementContainer2D{RealT, uEltype}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_interfaces), Array{Int, 1}, + TreeMesh{2, Trixi.ParallelTree{2}}, + Trixi.ElementContainer2D{RealT, uEltype}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_mortars), Array{Int, 1}, + TreeMesh{2, Trixi.ParallelTree{2}}, + Trixi.ElementContainer2D{RealT, uEltype}, + mortar_type, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_mpi_interfaces), Array{Int, 1}, + TreeMesh{2, Trixi.ParallelTree{2}}, + Trixi.ElementContainer2D{RealT, uEltype}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.save_mesh_file), + TreeMesh{2, Trixi.ParallelTree{2}}, String, + } + ) # 3D, serial - @assert Base.precompile(Tuple{typeof(Trixi.init_boundaries), Array{Int, 1}, - TreeMesh{3, Trixi.SerialTree{3}}, - Trixi.ElementContainer3D{RealT, uEltype}}) - @assert Base.precompile(Tuple{typeof(Trixi.init_interfaces), Array{Int, 1}, - TreeMesh{3, Trixi.SerialTree{3}}, - Trixi.ElementContainer3D{RealT, uEltype}}) - @assert Base.precompile(Tuple{typeof(Trixi.init_mortars), Array{Int, 1}, - TreeMesh{3, Trixi.SerialTree{3}}, - Trixi.ElementContainer3D{RealT, uEltype}, - mortar_type}) - @assert Base.precompile(Tuple{typeof(Trixi.save_mesh_file), - TreeMesh{3, Trixi.SerialTree{3}}, String}) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_boundaries), Array{Int, 1}, + TreeMesh{3, Trixi.SerialTree{3}}, + Trixi.ElementContainer3D{RealT, uEltype}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_interfaces), Array{Int, 1}, + TreeMesh{3, Trixi.SerialTree{3}}, + Trixi.ElementContainer3D{RealT, uEltype}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.init_mortars), Array{Int, 1}, + TreeMesh{3, Trixi.SerialTree{3}}, + Trixi.ElementContainer3D{RealT, uEltype}, + mortar_type, + } + ) + @assert Base.precompile( + Tuple{ + typeof(Trixi.save_mesh_file), + TreeMesh{3, Trixi.SerialTree{3}}, String, + } + ) end # various `show` methods @@ -455,24 +678,44 @@ function _precompile_manual_() # meshes for NDIMS in 1:3 # serial - @assert Base.precompile(Tuple{typeof(show), Base.TTY, - TreeMesh{NDIMS, Trixi.SerialTree{NDIMS}}}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, - MIME"text/plain", - TreeMesh{NDIMS, Trixi.SerialTree{NDIMS}}}) + @assert Base.precompile( + Tuple{ + typeof(show), Base.TTY, + TreeMesh{NDIMS, Trixi.SerialTree{NDIMS}}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, + MIME"text/plain", + TreeMesh{NDIMS, Trixi.SerialTree{NDIMS}}, + } + ) # parallel - @assert Base.precompile(Tuple{typeof(show), Base.TTY, - TreeMesh{NDIMS, Trixi.ParallelTree{NDIMS}}}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, - MIME"text/plain", - TreeMesh{NDIMS, Trixi.ParallelTree{NDIMS}}}) + @assert Base.precompile( + Tuple{ + typeof(show), Base.TTY, + TreeMesh{NDIMS, Trixi.ParallelTree{NDIMS}}, + } + ) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, + MIME"text/plain", + TreeMesh{NDIMS, Trixi.ParallelTree{NDIMS}}, + } + ) end # equations for eq_type in equations_types(RealT) @assert Base.precompile(Tuple{typeof(show), Base.TTY, eq_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, - MIME"text/plain", eq_type}) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, + MIME"text/plain", eq_type, + } + ) end # mortars, analyzers, adaptors, DG @@ -484,99 +727,177 @@ function _precompile_manual_() adaptor_type = adaptor_type_dgsem(RealT, nnodes_) @assert Base.precompile(Tuple{typeof(show), Base.TTY, basis_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, - MIME"text/plain", basis_type}) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, + MIME"text/plain", basis_type, + } + ) @assert Base.precompile(Tuple{typeof(show), Base.TTY, mortar_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, - MIME"text/plain", mortar_type}) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, + MIME"text/plain", mortar_type, + } + ) @assert Base.precompile(Tuple{typeof(show), Base.TTY, analyzer_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, - MIME"text/plain", analyzer_type}) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, + MIME"text/plain", analyzer_type, + } + ) @assert Base.precompile(Tuple{typeof(show), Base.TTY, adaptor_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, - MIME"text/plain", adaptor_type}) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, + MIME"text/plain", adaptor_type, + } + ) # we could also use more numerical fluxes and volume integral types here - @assert Base.precompile(Tuple{typeof(show), Base.TTY, - DG{basis_type, mortar_type, - typeof(flux_lax_friedrichs), - VolumeIntegralWeakForm}}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, - MIME"text/plain", - DG{basis_type, mortar_type, - typeof(flux_lax_friedrichs), - VolumeIntegralWeakForm}}) + @assert Base.precompile( + Tuple{ + typeof(show), Base.TTY, + DG{ + basis_type, mortar_type, + typeof(flux_lax_friedrichs), + VolumeIntegralWeakForm, + }, + } + ) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, + MIME"text/plain", + DG{ + basis_type, mortar_type, + typeof(flux_lax_friedrichs), + VolumeIntegralWeakForm, + }, + } + ) end # semidiscretizations - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, MIME"text/plain", - SemidiscretizationHyperbolic}) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, MIME"text/plain", + SemidiscretizationHyperbolic, + } + ) # callbacks - summary_callback_type = DiscreteCallback{typeof(Trixi.summary_callback), - typeof(Trixi.summary_callback), - typeof(Trixi.initialize_summary_callback), - typeof(SciMLBase.FINALIZE_DEFAULT)} + summary_callback_type = DiscreteCallback{ + typeof(Trixi.summary_callback), + typeof(Trixi.summary_callback), + typeof(Trixi.initialize_summary_callback), + typeof(SciMLBase.FINALIZE_DEFAULT), + } @assert Base.precompile(Tuple{typeof(show), Base.TTY, summary_callback_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, MIME"text/plain", - summary_callback_type}) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, MIME"text/plain", + summary_callback_type, + } + ) @assert Base.precompile(Tuple{summary_callback_type, Base.TTY}) # TODO: SteadyStateCallback, AnalysisCallback - alive_callback_type = DiscreteCallback{AliveCallback, AliveCallback, - typeof(Trixi.initialize!), - typeof(SciMLBase.FINALIZE_DEFAULT)} + alive_callback_type = DiscreteCallback{ + AliveCallback, AliveCallback, + typeof(Trixi.initialize!), + typeof(SciMLBase.FINALIZE_DEFAULT), + } @assert Base.precompile(Tuple{typeof(show), Base.TTY, alive_callback_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, MIME"text/plain", - alive_callback_type}) - - restart_callback_type = DiscreteCallback{SaveRestartCallback, SaveRestartCallback, - typeof(Trixi.initialize!), - typeof(SciMLBase.FINALIZE_DEFAULT)} + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, MIME"text/plain", + alive_callback_type, + } + ) + + restart_callback_type = DiscreteCallback{ + SaveRestartCallback, SaveRestartCallback, + typeof(Trixi.initialize!), + typeof(SciMLBase.FINALIZE_DEFAULT), + } @assert Base.precompile(Tuple{typeof(show), Base.TTY, restart_callback_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, MIME"text/plain", - restart_callback_type}) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, MIME"text/plain", + restart_callback_type, + } + ) for solution_variables in (cons2cons, cons2prim) - save_solution_callback_type = DiscreteCallback{SaveSolutionCallback{typeof(solution_variables)}, - SaveSolutionCallback{typeof(solution_variables)}, - typeof(Trixi.initialize!), - typeof(SciMLBase.FINALIZE_DEFAULT)} - @assert Base.precompile(Tuple{typeof(show), Base.TTY, - save_solution_callback_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, - MIME"text/plain", save_solution_callback_type}) + save_solution_callback_type = DiscreteCallback{ + SaveSolutionCallback{typeof(solution_variables)}, + SaveSolutionCallback{typeof(solution_variables)}, + typeof(Trixi.initialize!), + typeof(SciMLBase.FINALIZE_DEFAULT), + } + @assert Base.precompile( + Tuple{ + typeof(show), Base.TTY, + save_solution_callback_type, + } + ) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, + MIME"text/plain", save_solution_callback_type, + } + ) end # TODO: AMRCallback - stepsize_callback_type = DiscreteCallback{StepsizeCallback{RealT}, - StepsizeCallback{RealT}, - typeof(Trixi.initialize!), - typeof(SciMLBase.FINALIZE_DEFAULT)} + stepsize_callback_type = DiscreteCallback{ + StepsizeCallback{RealT}, + StepsizeCallback{RealT}, + typeof(Trixi.initialize!), + typeof(SciMLBase.FINALIZE_DEFAULT), + } @assert Base.precompile(Tuple{typeof(show), Base.TTY, stepsize_callback_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, MIME"text/plain", - stepsize_callback_type}) - - glm_speed_callback_type = DiscreteCallback{GlmSpeedCallback{RealT}, - GlmSpeedCallback{RealT}, - typeof(Trixi.initialize!), - typeof(SciMLBase.FINALIZE_DEFAULT)} + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, MIME"text/plain", + stepsize_callback_type, + } + ) + + glm_speed_callback_type = DiscreteCallback{ + GlmSpeedCallback{RealT}, + GlmSpeedCallback{RealT}, + typeof(Trixi.initialize!), + typeof(SciMLBase.FINALIZE_DEFAULT), + } @assert Base.precompile(Tuple{typeof(show), Base.TTY, glm_speed_callback_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, MIME"text/plain", - glm_speed_callback_type}) - - lbm_collision_callback_type = DiscreteCallback{typeof(Trixi.lbm_collision_callback), - typeof(Trixi.lbm_collision_callback), - typeof(Trixi.initialize!), - typeof(SciMLBase.FINALIZE_DEFAULT)} + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, MIME"text/plain", + glm_speed_callback_type, + } + ) + + lbm_collision_callback_type = DiscreteCallback{ + typeof(Trixi.lbm_collision_callback), + typeof(Trixi.lbm_collision_callback), + typeof(Trixi.initialize!), + typeof(SciMLBase.FINALIZE_DEFAULT), + } @assert Base.precompile(Tuple{typeof(show), Base.TTY, lbm_collision_callback_type}) - @assert Base.precompile(Tuple{typeof(show), IOContext{Base.TTY}, MIME"text/plain", - lbm_collision_callback_type}) + @assert Base.precompile( + Tuple{ + typeof(show), IOContext{Base.TTY}, MIME"text/plain", + lbm_collision_callback_type, + } + ) end @assert Base.precompile(Tuple{typeof(init_mpi)}) diff --git a/src/auxiliary/special_elixirs.jl b/src/auxiliary/special_elixirs.jl index d71a27aa96..e29b1e0b9c 100644 --- a/src/auxiliary/special_elixirs.jl +++ b/src/auxiliary/special_elixirs.jl @@ -3,184 +3,206 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - convergence_test([mod::Module=Main,] elixir::AbstractString, iterations; kwargs...) + """ + convergence_test([mod::Module=Main,] elixir::AbstractString, iterations; kwargs...) -Run `iterations` Trixi.jl simulations using the setup given in `elixir` and compute -the experimental order of convergence (EOC) in the ``L^2`` and ``L^\\infty`` norm. -In each iteration, the resolution of the respective mesh will be doubled. -Additional keyword arguments `kwargs...` and the optional module `mod` are passed directly -to [`trixi_include`](@ref). + Run `iterations` Trixi.jl simulations using the setup given in `elixir` and compute + the experimental order of convergence (EOC) in the ``L^2`` and ``L^\\infty`` norm. + In each iteration, the resolution of the respective mesh will be doubled. + Additional keyword arguments `kwargs...` and the optional module `mod` are passed directly + to [`trixi_include`](@ref). -This function assumes that the spatial resolution is set via the keywords -`initial_refinement_level` (an integer) or `cells_per_dimension` (a tuple of -integers, one per spatial dimension). -""" -function convergence_test(mod::Module, elixir::AbstractString, iterations; kwargs...) - @assert(iterations>1, - "Number of iterations must be bigger than 1 for a convergence analysis") + This function assumes that the spatial resolution is set via the keywords + `initial_refinement_level` (an integer) or `cells_per_dimension` (a tuple of + integers, one per spatial dimension). + """ + function convergence_test(mod::Module, elixir::AbstractString, iterations; kwargs...) + @assert( + iterations > 1, + "Number of iterations must be bigger than 1 for a convergence analysis" + ) - # Types of errors to be calculated - errors = Dict(:l2 => Float64[], :linf => Float64[]) + # Types of errors to be calculated + errors = Dict(:l2 => Float64[], :linf => Float64[]) - initial_resolution = extract_initial_resolution(elixir, kwargs) + initial_resolution = extract_initial_resolution(elixir, kwargs) - # run simulations and extract errors - for iter in 1:iterations - println("Running convtest iteration ", iter, "/", iterations) + # run simulations and extract errors + for iter in 1:iterations + println("Running convtest iteration ", iter, "/", iterations) - include_refined(mod, elixir, initial_resolution, iter; kwargs) + include_refined(mod, elixir, initial_resolution, iter; kwargs) - l2_error, linf_error = mod.analysis_callback(mod.sol) + l2_error, linf_error = mod.analysis_callback(mod.sol) - # collect errors as one vector to reshape later - append!(errors[:l2], l2_error) - append!(errors[:linf], linf_error) + # collect errors as one vector to reshape later + append!(errors[:l2], l2_error) + append!(errors[:linf], linf_error) - println("\n\n") - println("#"^100) + println("\n\n") + println("#"^100) + end + + # Use raw error values to compute EOC + analyze_convergence(errors, iterations, mod.semi) end - # Use raw error values to compute EOC - analyze_convergence(errors, iterations, mod.semi) -end - -# Analyze convergence for any semidiscretization -# Note: this intermediate method is to allow dispatching on the semidiscretization -function analyze_convergence(errors, iterations, semi::AbstractSemidiscretization) - _, equations, _, _ = mesh_equations_solver_cache(semi) - variablenames = varnames(cons2cons, equations) - analyze_convergence(errors, iterations, variablenames) -end - -# This method is called with the collected error values to actually compute and print the EOC -function analyze_convergence(errors, iterations, - variablenames::Union{Tuple, AbstractArray}) - nvariables = length(variablenames) - - # Reshape errors to get a matrix where the i-th row represents the i-th iteration - # and the j-th column represents the j-th variable - errorsmatrix = Dict(kind => transpose(reshape(error, (nvariables, iterations))) - for (kind, error) in errors) - - # Calculate EOCs where the columns represent the variables - # As dx halves in every iteration the denominator needs to be log(1/2) - eocs = Dict(kind => log.(error[2:end, :] ./ error[1:(end - 1), :]) ./ log(1 / 2) - for (kind, error) in errorsmatrix) - - eoc_mean_values = Dict{Symbol, Any}() - eoc_mean_values[:variables] = variablenames - - for (kind, error) in errorsmatrix - println(kind) - - for v in variablenames - @printf("%-20s", v) - end - println("") + # Analyze convergence for any semidiscretization + # Note: this intermediate method is to allow dispatching on the semidiscretization + function analyze_convergence(errors, iterations, semi::AbstractSemidiscretization) + _, equations, _, _ = mesh_equations_solver_cache(semi) + variablenames = varnames(cons2cons, equations) + analyze_convergence(errors, iterations, variablenames) + end - for k in 1:nvariables - @printf("%-10s", "error") - @printf("%-10s", "EOC") - end - println("") + # This method is called with the collected error values to actually compute and print the EOC + function analyze_convergence( + errors, iterations, + variablenames::Union{Tuple, AbstractArray} + ) + nvariables = length(variablenames) + + # Reshape errors to get a matrix where the i-th row represents the i-th iteration + # and the j-th column represents the j-th variable + errorsmatrix = Dict( + kind => transpose(reshape(error, (nvariables, iterations))) + for (kind, error) in errors + ) + + # Calculate EOCs where the columns represent the variables + # As dx halves in every iteration the denominator needs to be log(1/2) + eocs = Dict( + kind => log.(error[2:end, :] ./ error[1:(end - 1), :]) ./ log(1 / 2) + for (kind, error) in errorsmatrix + ) + + eoc_mean_values = Dict{Symbol, Any}() + eoc_mean_values[:variables] = variablenames + + for (kind, error) in errorsmatrix + println(kind) + + for v in variablenames + @printf("%-20s", v) + end + println("") - # Print errors for the first iteration - for k in 1:nvariables - @printf("%-10.2e", error[1, k]) - @printf("%-10s", "-") - end - println("") + for k in 1:nvariables + @printf("%-10s", "error") + @printf("%-10s", "EOC") + end + println("") - # For the following iterations print errors and EOCs - for j in 2:iterations + # Print errors for the first iteration for k in 1:nvariables - @printf("%-10.2e", error[j, k]) - @printf("%-10.2f", eocs[kind][j - 1, k]) + @printf("%-10.2e", error[1, k]) + @printf("%-10s", "-") end println("") + + # For the following iterations print errors and EOCs + for j in 2:iterations + for k in 1:nvariables + @printf("%-10.2e", error[j, k]) + @printf("%-10.2f", eocs[kind][j - 1, k]) + end + println("") + end + println("") + + # Print mean EOCs + mean_values = zeros(nvariables) + for v in 1:nvariables + mean_values[v] = sum(eocs[kind][:, v]) ./ length(eocs[kind][:, v]) + @printf("%-10s", "mean") + @printf("%-10.2f", mean_values[v]) + end + eoc_mean_values[kind] = mean_values + println("") + println("-"^100) end - println("") - - # Print mean EOCs - mean_values = zeros(nvariables) - for v in 1:nvariables - mean_values[v] = sum(eocs[kind][:, v]) ./ length(eocs[kind][:, v]) - @printf("%-10s", "mean") - @printf("%-10.2f", mean_values[v]) - end - eoc_mean_values[kind] = mean_values - println("") - println("-"^100) - end - return eoc_mean_values -end + return eoc_mean_values + end -function convergence_test(elixir::AbstractString, iterations; kwargs...) - convergence_test(Main, elixir::AbstractString, iterations; kwargs...) -end + function convergence_test(elixir::AbstractString, iterations; kwargs...) + convergence_test(Main, elixir::AbstractString, iterations; kwargs...) + end -# Helper methods used in the functions defined above + # Helper methods used in the functions defined above -# Searches for the assignment that specifies the mesh resolution in the elixir -function extract_initial_resolution(elixir, kwargs) - code = read(elixir, String) - expr = Meta.parse("begin \n$code \nend") + # Searches for the assignment that specifies the mesh resolution in the elixir + function extract_initial_resolution(elixir, kwargs) + code = read(elixir, String) + expr = Meta.parse("begin \n$code \nend") - try - # get the initial_refinement_level from the elixir - initial_refinement_level = TrixiBase.find_assignment(expr, - :initial_refinement_level) + try + # get the initial_refinement_level from the elixir + initial_refinement_level = TrixiBase.find_assignment( + expr, + :initial_refinement_level + ) - if haskey(kwargs, :initial_refinement_level) - return kwargs[:initial_refinement_level] - else - return initial_refinement_level - end - catch e - # If `initial_refinement_level` is not found, we will get an `ArgumentError` - if isa(e, ArgumentError) - try - # get cells_per_dimension from the elixir - cells_per_dimension = eval(TrixiBase.find_assignment(expr, - :cells_per_dimension)) - - if haskey(kwargs, :cells_per_dimension) - return kwargs[:cells_per_dimension] - else - return cells_per_dimension - end - catch e2 - # If `cells_per_dimension` is not found either - if isa(e2, ArgumentError) - throw(ArgumentError("`convergence_test` requires the elixir to define " * - "`initial_refinement_level` or `cells_per_dimension`")) - else - rethrow() + if haskey(kwargs, :initial_refinement_level) + return kwargs[:initial_refinement_level] + else + return initial_refinement_level + end + catch e + # If `initial_refinement_level` is not found, we will get an `ArgumentError` + if isa(e, ArgumentError) + try + # get cells_per_dimension from the elixir + cells_per_dimension = eval( + TrixiBase.find_assignment( + expr, + :cells_per_dimension + ) + ) + + if haskey(kwargs, :cells_per_dimension) + return kwargs[:cells_per_dimension] + else + return cells_per_dimension + end + catch e2 + # If `cells_per_dimension` is not found either + if isa(e2, ArgumentError) + throw( + ArgumentError( + "`convergence_test` requires the elixir to define " * + "`initial_refinement_level` or `cells_per_dimension`" + ) + ) + else + rethrow() + end end + else + rethrow() end - else - rethrow() end end -end - -# runs the specified elixir with a doubled resolution each time iter is increased by 1 -# works for TreeMesh -function include_refined(mod, elixir, initial_refinement_level::Int, iter; kwargs) - trixi_include(mod, elixir; kwargs..., - initial_refinement_level = initial_refinement_level + iter - 1) -end - -# runs the specified elixir with a doubled resolution each time iter is increased by 1 -# works for StructuredMesh -function include_refined(mod, elixir, cells_per_dimension::NTuple{NDIMS, Int}, iter; - kwargs) where {NDIMS} - new_cells_per_dimension = cells_per_dimension .* 2^(iter - 1) - - trixi_include(mod, elixir; kwargs..., cells_per_dimension = new_cells_per_dimension) -end + + # runs the specified elixir with a doubled resolution each time iter is increased by 1 + # works for TreeMesh + function include_refined(mod, elixir, initial_refinement_level::Int, iter; kwargs) + trixi_include( + mod, elixir; kwargs..., + initial_refinement_level = initial_refinement_level + iter - 1 + ) + end + + # runs the specified elixir with a doubled resolution each time iter is increased by 1 + # works for StructuredMesh + function include_refined( + mod, elixir, cells_per_dimension::NTuple{NDIMS, Int}, iter; + kwargs + ) where {NDIMS} + new_cells_per_dimension = cells_per_dimension .* 2^(iter - 1) + + trixi_include(mod, elixir; kwargs..., cells_per_dimension = new_cells_per_dimension) + end end # @muladd diff --git a/src/auxiliary/t8code.jl b/src/auxiliary/t8code.jl index 7c1399fc80..58b1855112 100644 --- a/src/auxiliary/t8code.jl +++ b/src/auxiliary/t8code.jl @@ -16,8 +16,10 @@ function init_t8code() # Initialize the sc library, has to happen before we initialize t8code. let catch_signals = 0, print_backtrace = 0, log_handler = C_NULL - T8code.Libt8.sc_init(mpi_comm(), catch_signals, print_backtrace, log_handler, - T8code.Libt8.SC_LP_ERROR) + T8code.Libt8.sc_init( + mpi_comm(), catch_signals, print_backtrace, log_handler, + T8code.Libt8.SC_LP_ERROR + ) end if T8code.Libt8.p4est_is_initialized() == 0 @@ -40,7 +42,7 @@ function init_t8code() end else @warn "Preferences for T8code.jl are not set correctly. Until fixed, using `T8codeMesh` will result in a crash. " * - "See also https://trixi-framework.github.io/Trixi.jl/stable/parallelization/#parallel_system_MPI" + "See also https://trixi-framework.github.io/Trixi.jl/stable/parallelization/#parallel_system_MPI" end return nothing @@ -79,7 +81,7 @@ end # form a family and we decide whether this family should be coarsened # or only the first element should be refined. # Otherwise `is_family` must equal zero and we consider the first entry -# of the element array for refinement. +# of the element array for refinement. # Entries of the element array beyond the first `num_elements` are undefined. # \param [in] forest the forest to which the new elements belong # \param [in] forest_from the forest that is adapted. @@ -93,14 +95,16 @@ end # \return greater zero if the first entry in `elements` should be refined, # smaller zero if the family `elements` shall be coarsened, # zero else. -function adapt_callback(forest, - forest_from, - which_tree, - lelement_id, - ts, - is_family, - num_elements, - elements)::Cint +function adapt_callback( + forest, + forest_from, + which_tree, + lelement_id, + ts, + is_family, + num_elements, + elements + )::Cint num_levels = t8_forest_get_local_num_elements(forest_from) indicator_ptr = Ptr{Int}(t8_forest_get_user_data(forest)) @@ -123,8 +127,10 @@ function trixi_t8_adapt_new(old_forest, indicators) let set_from = C_NULL, recursive = 0, no_repartition = 1, do_ghost = 1 t8_forest_set_user_data(new_forest, pointer(indicators)) - t8_forest_set_adapt(new_forest, old_forest, @t8_adapt_callback(adapt_callback), - recursive) + t8_forest_set_adapt( + new_forest, old_forest, @t8_adapt_callback(adapt_callback), + recursive + ) t8_forest_set_balance(new_forest, set_from, no_repartition) t8_forest_set_ghost(new_forest, do_ghost, T8_GHOST_FACES) t8_forest_commit(new_forest) diff --git a/src/basic_types.jl b/src/basic_types.jl index ee479a6203..9b8764bbff 100644 --- a/src/basic_types.jl +++ b/src/basic_types.jl @@ -3,97 +3,101 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# abstract supertype of specific semidiscretizations such as -# - SemidiscretizationHyperbolic for hyperbolic conservation laws -# - SemidiscretizationEulerGravity for Euler with self-gravity -abstract type AbstractSemidiscretization end - -""" - AbstractEquations{NDIMS, NVARS} - -An abstract supertype of specific equations such as the compressible Euler equations. -The type parameters encode the number of spatial dimensions (`NDIMS`) and the -number of primary variables (`NVARS`) of the physics model. -""" -abstract type AbstractEquations{NDIMS, NVARS} end - -""" - AbstractMesh{NDIMS} - -An abstract supertype of specific mesh types such as `TreeMesh` or `StructuredMesh`. -The type parameters encode the number of spatial dimensions (`NDIMS`). -""" -abstract type AbstractMesh{NDIMS} end - -# abstract supertype of specific SBP bases such as a Lobatto-Legendre nodal basis -abstract type AbstractBasisSBP{RealT <: Real} end - -# abstract supertype of mortar methods, e.g. using L² projections -abstract type AbstractMortar{RealT <: Real} end - -# abstract supertype of mortar methods using L² projection -# which will be specialized for different SBP bases -abstract type AbstractMortarL2{RealT <: Real} <: AbstractMortar{RealT} end - -# abstract supertype of functionality related to the analysis of -# numerical solutions, e.g. the calculation of errors -abstract type SolutionAnalyzer{RealT <: Real} end - -# abstract supertype of grid-transfer methods used for AMR, -# e.g. refinement and coarsening based on L² projections -abstract type AdaptorAMR{RealT <: Real} end - -# abstract supertype of AMR grid-transfer operations using L² projections -# which will be specialized for different SBP bases -abstract type AdaptorL2{RealT <: Real} <: AdaptorAMR{RealT} end - -# TODO: Taal decide, which abstract types shall be defined here? - -struct BoundaryConditionPeriodic end - -""" - boundary_condition_periodic = Trixi.BoundaryConditionPeriodic() - -A singleton struct indicating periodic boundary conditions. -""" -const boundary_condition_periodic = BoundaryConditionPeriodic() - -function Base.show(io::IO, ::BoundaryConditionPeriodic) - print(io, "boundary_condition_periodic") -end - -struct BoundaryConditionDoNothing end - -# This version can be called by hyperbolic solvers on logically Cartesian meshes -@inline function (::BoundaryConditionDoNothing)(u_inner, - orientation_or_normal_direction, - direction::Integer, x, t, surface_flux, - equations) - return flux(u_inner, orientation_or_normal_direction, equations) -end - -# This version can be called by hyperbolic solvers on unstructured, curved meshes -@inline function (::BoundaryConditionDoNothing)(u_inner, - outward_direction::AbstractVector, - x, t, surface_flux, equations) - return flux(u_inner, outward_direction, equations) -end - -# This version can be called by parabolic solvers -@inline function (::BoundaryConditionDoNothing)(inner_flux_or_state, other_args...) - return inner_flux_or_state -end - -""" - boundary_condition_do_nothing = Trixi.BoundaryConditionDoNothing() - -Imposing no boundary condition just evaluates the flux at the inner state. -""" -const boundary_condition_do_nothing = BoundaryConditionDoNothing() - -function Base.show(io::IO, ::BoundaryConditionDoNothing) - print(io, "boundary_condition_do_nothing") -end + #! format: noindent + + # abstract supertype of specific semidiscretizations such as + # - SemidiscretizationHyperbolic for hyperbolic conservation laws + # - SemidiscretizationEulerGravity for Euler with self-gravity + abstract type AbstractSemidiscretization end + + """ + AbstractEquations{NDIMS, NVARS} + + An abstract supertype of specific equations such as the compressible Euler equations. + The type parameters encode the number of spatial dimensions (`NDIMS`) and the + number of primary variables (`NVARS`) of the physics model. + """ + abstract type AbstractEquations{NDIMS, NVARS} end + + """ + AbstractMesh{NDIMS} + + An abstract supertype of specific mesh types such as `TreeMesh` or `StructuredMesh`. + The type parameters encode the number of spatial dimensions (`NDIMS`). + """ + abstract type AbstractMesh{NDIMS} end + + # abstract supertype of specific SBP bases such as a Lobatto-Legendre nodal basis + abstract type AbstractBasisSBP{RealT <: Real} end + + # abstract supertype of mortar methods, e.g. using L² projections + abstract type AbstractMortar{RealT <: Real} end + + # abstract supertype of mortar methods using L² projection + # which will be specialized for different SBP bases + abstract type AbstractMortarL2{RealT <: Real} <: AbstractMortar{RealT} end + + # abstract supertype of functionality related to the analysis of + # numerical solutions, e.g. the calculation of errors + abstract type SolutionAnalyzer{RealT <: Real} end + + # abstract supertype of grid-transfer methods used for AMR, + # e.g. refinement and coarsening based on L² projections + abstract type AdaptorAMR{RealT <: Real} end + + # abstract supertype of AMR grid-transfer operations using L² projections + # which will be specialized for different SBP bases + abstract type AdaptorL2{RealT <: Real} <: AdaptorAMR{RealT} end + + # TODO: Taal decide, which abstract types shall be defined here? + + struct BoundaryConditionPeriodic end + + """ + boundary_condition_periodic = Trixi.BoundaryConditionPeriodic() + + A singleton struct indicating periodic boundary conditions. + """ + const boundary_condition_periodic = BoundaryConditionPeriodic() + + function Base.show(io::IO, ::BoundaryConditionPeriodic) + print(io, "boundary_condition_periodic") + end + + struct BoundaryConditionDoNothing end + + # This version can be called by hyperbolic solvers on logically Cartesian meshes + @inline function (::BoundaryConditionDoNothing)( + u_inner, + orientation_or_normal_direction, + direction::Integer, x, t, surface_flux, + equations + ) + return flux(u_inner, orientation_or_normal_direction, equations) + end + + # This version can be called by hyperbolic solvers on unstructured, curved meshes + @inline function (::BoundaryConditionDoNothing)( + u_inner, + outward_direction::AbstractVector, + x, t, surface_flux, equations + ) + return flux(u_inner, outward_direction, equations) + end + + # This version can be called by parabolic solvers + @inline function (::BoundaryConditionDoNothing)(inner_flux_or_state, other_args...) + return inner_flux_or_state + end + + """ + boundary_condition_do_nothing = Trixi.BoundaryConditionDoNothing() + + Imposing no boundary condition just evaluates the flux at the inner state. + """ + const boundary_condition_do_nothing = BoundaryConditionDoNothing() + + function Base.show(io::IO, ::BoundaryConditionDoNothing) + print(io, "boundary_condition_do_nothing") + end end # @muladd diff --git a/src/callbacks_stage/callbacks_stage.jl b/src/callbacks_stage/callbacks_stage.jl index d5abc1d227..8f315ee5a6 100644 --- a/src/callbacks_stage/callbacks_stage.jl +++ b/src/callbacks_stage/callbacks_stage.jl @@ -3,9 +3,9 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -include("positivity_zhang_shu.jl") -include("subcell_limiter_idp_correction.jl") -include("subcell_bounds_check.jl") + include("positivity_zhang_shu.jl") + include("subcell_limiter_idp_correction.jl") + include("subcell_bounds_check.jl") end # @muladd diff --git a/src/callbacks_stage/positivity_zhang_shu.jl b/src/callbacks_stage/positivity_zhang_shu.jl index 92141c4b26..00c9e5c79d 100644 --- a/src/callbacks_stage/positivity_zhang_shu.jl +++ b/src/callbacks_stage/positivity_zhang_shu.jl @@ -3,69 +3,81 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - PositivityPreservingLimiterZhangShu(; threshold, variables) + """ + PositivityPreservingLimiterZhangShu(; threshold, variables) -The fully-discrete positivity-preserving limiter of -- Zhang, Shu (2011) - Maximum-principle-satisfying and positivity-preserving high-order schemes - for conservation laws: survey and new developments - [doi: 10.1098/rspa.2011.0153](https://doi.org/10.1098/rspa.2011.0153) -The limiter is applied to all scalar `variables` in their given order -using the associated `thresholds` to determine the minimal acceptable values. -The order of the `variables` is important and might have a strong influence -on the robustness. -""" -struct PositivityPreservingLimiterZhangShu{N, Thresholds <: NTuple{N, <:Real}, - Variables <: NTuple{N, Any}} - thresholds::Thresholds - variables::Variables -end + The fully-discrete positivity-preserving limiter of + - Zhang, Shu (2011) + Maximum-principle-satisfying and positivity-preserving high-order schemes + for conservation laws: survey and new developments + [doi: 10.1098/rspa.2011.0153](https://doi.org/10.1098/rspa.2011.0153) + The limiter is applied to all scalar `variables` in their given order + using the associated `thresholds` to determine the minimal acceptable values. + The order of the `variables` is important and might have a strong influence + on the robustness. + """ + struct PositivityPreservingLimiterZhangShu{ + N, Thresholds <: NTuple{N, <:Real}, + Variables <: NTuple{N, Any}, + } + thresholds::Thresholds + variables::Variables + end -function PositivityPreservingLimiterZhangShu(; thresholds, variables) - PositivityPreservingLimiterZhangShu(thresholds, variables) -end + function PositivityPreservingLimiterZhangShu(; thresholds, variables) + PositivityPreservingLimiterZhangShu(thresholds, variables) + end -function (limiter!::PositivityPreservingLimiterZhangShu)(u_ode, integrator, - semi::AbstractSemidiscretization, - t) - u = wrap_array(u_ode, semi) - @trixi_timeit timer() "positivity-preserving limiter" begin - limiter_zhang_shu!(u, limiter!.thresholds, limiter!.variables, - mesh_equations_solver_cache(semi)...) + function (limiter!::PositivityPreservingLimiterZhangShu)( + u_ode, integrator, + semi::AbstractSemidiscretization, + t + ) + u = wrap_array(u_ode, semi) + @trixi_timeit timer() "positivity-preserving limiter" begin + limiter_zhang_shu!( + u, limiter!.thresholds, limiter!.variables, + mesh_equations_solver_cache(semi)... + ) + end end -end -# Iterate over tuples in a type-stable way using "lispy tuple programming", -# similar to https://stackoverflow.com/a/55849398: -# Iterating over tuples of different functions isn't type-stable in general -# but accessing the first element of a tuple is type-stable. Hence, it's good -# to process one element at a time and replace iteration by recursion here. -# Note that you shouldn't use this with too many elements per tuple since the -# compile times can increase otherwise - but a handful of elements per tuple -# is definitely fine. -function limiter_zhang_shu!(u, thresholds::NTuple{N, <:Real}, variables::NTuple{N, Any}, - mesh, equations, solver, cache) where {N} - threshold = first(thresholds) - remaining_thresholds = Base.tail(thresholds) - variable = first(variables) - remaining_variables = Base.tail(variables) + # Iterate over tuples in a type-stable way using "lispy tuple programming", + # similar to https://stackoverflow.com/a/55849398: + # Iterating over tuples of different functions isn't type-stable in general + # but accessing the first element of a tuple is type-stable. Hence, it's good + # to process one element at a time and replace iteration by recursion here. + # Note that you shouldn't use this with too many elements per tuple since the + # compile times can increase otherwise - but a handful of elements per tuple + # is definitely fine. + function limiter_zhang_shu!( + u, thresholds::NTuple{N, <:Real}, variables::NTuple{N, Any}, + mesh, equations, solver, cache + ) where {N} + threshold = first(thresholds) + remaining_thresholds = Base.tail(thresholds) + variable = first(variables) + remaining_variables = Base.tail(variables) - limiter_zhang_shu!(u, threshold, variable, mesh, equations, solver, cache) - limiter_zhang_shu!(u, remaining_thresholds, remaining_variables, mesh, equations, - solver, cache) - return nothing -end + limiter_zhang_shu!(u, threshold, variable, mesh, equations, solver, cache) + limiter_zhang_shu!( + u, remaining_thresholds, remaining_variables, mesh, equations, + solver, cache + ) + return nothing + end -# terminate the type-stable iteration over tuples -function limiter_zhang_shu!(u, thresholds::Tuple{}, variables::Tuple{}, - mesh, equations, solver, cache) - nothing -end + # terminate the type-stable iteration over tuples + function limiter_zhang_shu!( + u, thresholds::Tuple{}, variables::Tuple{}, + mesh, equations, solver, cache + ) + nothing + end -include("positivity_zhang_shu_dg1d.jl") -include("positivity_zhang_shu_dg2d.jl") -include("positivity_zhang_shu_dg3d.jl") + include("positivity_zhang_shu_dg1d.jl") + include("positivity_zhang_shu_dg2d.jl") + include("positivity_zhang_shu_dg3d.jl") end # @muladd diff --git a/src/callbacks_stage/positivity_zhang_shu_dg1d.jl b/src/callbacks_stage/positivity_zhang_shu_dg1d.jl index 7797eb95b0..ad9f310e1a 100644 --- a/src/callbacks_stage/positivity_zhang_shu_dg1d.jl +++ b/src/callbacks_stage/positivity_zhang_shu_dg1d.jl @@ -3,43 +3,47 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function limiter_zhang_shu!(u, threshold::Real, variable, - mesh::AbstractMesh{1}, equations, dg::DGSEM, cache) - @unpack weights = dg.basis + function limiter_zhang_shu!( + u, threshold::Real, variable, + mesh::AbstractMesh{1}, equations, dg::DGSEM, cache + ) + @unpack weights = dg.basis - @threaded for element in eachelement(dg, cache) - # determine minimum value - value_min = typemax(eltype(u)) - for i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, element) - value_min = min(value_min, variable(u_node, equations)) - end + @threaded for element in eachelement(dg, cache) + # determine minimum value + value_min = typemax(eltype(u)) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + value_min = min(value_min, variable(u_node, equations)) + end - # detect if limiting is necessary - value_min < threshold || continue + # detect if limiting is necessary + value_min < threshold || continue - # compute mean value - u_mean = zero(get_node_vars(u, equations, dg, 1, element)) - for i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, element) - u_mean += u_node * weights[i] - end - # note that the reference element is [-1,1]^ndims(dg), thus the weights sum to 2 - u_mean = u_mean / 2^ndims(mesh) + # compute mean value + u_mean = zero(get_node_vars(u, equations, dg, 1, element)) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + u_mean += u_node * weights[i] + end + # note that the reference element is [-1,1]^ndims(dg), thus the weights sum to 2 + u_mean = u_mean / 2^ndims(mesh) - # We compute the value directly with the mean values, as we assume that - # Jensen's inequality holds (e.g. pressure for compressible Euler equations). - value_mean = variable(u_mean, equations) - theta = (value_mean - threshold) / (value_mean - value_min) - for i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, element) - set_node_vars!(u, theta * u_node + (1 - theta) * u_mean, - equations, dg, i, element) + # We compute the value directly with the mean values, as we assume that + # Jensen's inequality holds (e.g. pressure for compressible Euler equations). + value_mean = variable(u_mean, equations) + theta = (value_mean - threshold) / (value_mean - value_min) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + set_node_vars!( + u, theta * u_node + (1 - theta) * u_mean, + equations, dg, i, element + ) + end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_stage/positivity_zhang_shu_dg2d.jl b/src/callbacks_stage/positivity_zhang_shu_dg2d.jl index 813dd65878..6f563fc24d 100644 --- a/src/callbacks_stage/positivity_zhang_shu_dg2d.jl +++ b/src/callbacks_stage/positivity_zhang_shu_dg2d.jl @@ -3,48 +3,58 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function limiter_zhang_shu!(u, threshold::Real, variable, - mesh::AbstractMesh{2}, equations, dg::DGSEM, cache) - @unpack weights = dg.basis - @unpack inverse_jacobian = cache.elements + function limiter_zhang_shu!( + u, threshold::Real, variable, + mesh::AbstractMesh{2}, equations, dg::DGSEM, cache + ) + @unpack weights = dg.basis + @unpack inverse_jacobian = cache.elements - @threaded for element in eachelement(dg, cache) - # determine minimum value - value_min = typemax(eltype(u)) - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - value_min = min(value_min, variable(u_node, equations)) - end + @threaded for element in eachelement(dg, cache) + # determine minimum value + value_min = typemax(eltype(u)) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + value_min = min(value_min, variable(u_node, equations)) + end - # detect if limiting is necessary - value_min < threshold || continue + # detect if limiting is necessary + value_min < threshold || continue - # compute mean value - u_mean = zero(get_node_vars(u, equations, dg, 1, 1, element)) - total_volume = zero(eltype(u)) - for j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(get_inverse_jacobian(inverse_jacobian, mesh, - i, j, element))) - u_node = get_node_vars(u, equations, dg, i, j, element) - u_mean += u_node * weights[i] * weights[j] * volume_jacobian - total_volume += weights[i] * weights[j] * volume_jacobian - end - # normalize with the total volume - u_mean = u_mean / total_volume + # compute mean value + u_mean = zero(get_node_vars(u, equations, dg, 1, 1, element)) + total_volume = zero(eltype(u)) + for j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs( + inv( + get_inverse_jacobian( + inverse_jacobian, mesh, + i, j, element + ) + ) + ) + u_node = get_node_vars(u, equations, dg, i, j, element) + u_mean += u_node * weights[i] * weights[j] * volume_jacobian + total_volume += weights[i] * weights[j] * volume_jacobian + end + # normalize with the total volume + u_mean = u_mean / total_volume - # We compute the value directly with the mean values, as we assume that - # Jensen's inequality holds (e.g. pressure for compressible Euler equations). - value_mean = variable(u_mean, equations) - theta = (value_mean - threshold) / (value_mean - value_min) - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - set_node_vars!(u, theta * u_node + (1 - theta) * u_mean, - equations, dg, i, j, element) + # We compute the value directly with the mean values, as we assume that + # Jensen's inequality holds (e.g. pressure for compressible Euler equations). + value_mean = variable(u_mean, equations) + theta = (value_mean - threshold) / (value_mean - value_min) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + set_node_vars!( + u, theta * u_node + (1 - theta) * u_mean, + equations, dg, i, j, element + ) + end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_stage/positivity_zhang_shu_dg3d.jl b/src/callbacks_stage/positivity_zhang_shu_dg3d.jl index 156abf35b4..2dc9358fdc 100644 --- a/src/callbacks_stage/positivity_zhang_shu_dg3d.jl +++ b/src/callbacks_stage/positivity_zhang_shu_dg3d.jl @@ -3,48 +3,58 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function limiter_zhang_shu!(u, threshold::Real, variable, - mesh::AbstractMesh{3}, equations, dg::DGSEM, cache) - @unpack weights = dg.basis - @unpack inverse_jacobian = cache.elements + function limiter_zhang_shu!( + u, threshold::Real, variable, + mesh::AbstractMesh{3}, equations, dg::DGSEM, cache + ) + @unpack weights = dg.basis + @unpack inverse_jacobian = cache.elements - @threaded for element in eachelement(dg, cache) - # determine minimum value - value_min = typemax(eltype(u)) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - value_min = min(value_min, variable(u_node, equations)) - end + @threaded for element in eachelement(dg, cache) + # determine minimum value + value_min = typemax(eltype(u)) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + value_min = min(value_min, variable(u_node, equations)) + end - # detect if limiting is necessary - value_min < threshold || continue + # detect if limiting is necessary + value_min < threshold || continue - # compute mean value - u_mean = zero(get_node_vars(u, equations, dg, 1, 1, 1, element)) - total_volume = zero(eltype(u)) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(get_inverse_jacobian(inverse_jacobian, mesh, - i, j, k, element))) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - u_mean += u_node * weights[i] * weights[j] * weights[k] * volume_jacobian - total_volume += weights[i] * weights[j] * weights[k] * volume_jacobian - end - # normalize with the total volume - u_mean = u_mean / total_volume + # compute mean value + u_mean = zero(get_node_vars(u, equations, dg, 1, 1, 1, element)) + total_volume = zero(eltype(u)) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs( + inv( + get_inverse_jacobian( + inverse_jacobian, mesh, + i, j, k, element + ) + ) + ) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + u_mean += u_node * weights[i] * weights[j] * weights[k] * volume_jacobian + total_volume += weights[i] * weights[j] * weights[k] * volume_jacobian + end + # normalize with the total volume + u_mean = u_mean / total_volume - # We compute the value directly with the mean values, as we assume that - # Jensen's inequality holds (e.g. pressure for compressible Euler equations). - value_mean = variable(u_mean, equations) - theta = (value_mean - threshold) / (value_mean - value_min) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - set_node_vars!(u, theta * u_node + (1 - theta) * u_mean, - equations, dg, i, j, k, element) + # We compute the value directly with the mean values, as we assume that + # Jensen's inequality holds (e.g. pressure for compressible Euler equations). + value_mean = variable(u_mean, equations) + theta = (value_mean - threshold) / (value_mean - value_min) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + set_node_vars!( + u, theta * u_node + (1 - theta) * u_mean, + equations, dg, i, j, k, element + ) + end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_stage/subcell_bounds_check.jl b/src/callbacks_stage/subcell_bounds_check.jl index 15231b2592..9ed6481e9d 100644 --- a/src/callbacks_stage/subcell_bounds_check.jl +++ b/src/callbacks_stage/subcell_bounds_check.jl @@ -3,193 +3,183 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - BoundsCheckCallback(; output_directory="out", save_errors=false, interval=1) - -Subcell limiting techniques with [`SubcellLimiterIDP`](@ref) are constructed to adhere certain -local or global bounds. To make sure that these bounds are actually met, this callback calculates -the maximum deviation from the bounds. The maximum deviation per applied bound is printed to -the screen at the end of the simulation. -For more insights, when setting `save_errors=true` the occurring errors are exported every -`interval` time steps during the simulation. Then, the maximum deviations since the last -export are saved in "`output_directory`/deviations.txt". -The `BoundsCheckCallback` has to be applied as a stage callback for the SSPRK time integration scheme. - -!!! note - For `SubcellLimiterIDP`, the solution is corrected in the a posteriori correction stage - [`SubcellLimiterIDPCorrection`](@ref). So, to check the final solution, this bounds check - callback must be called after the correction stage. -""" -struct BoundsCheckCallback - output_directory::String - save_errors::Bool - interval::Int -end - -function BoundsCheckCallback(; output_directory = "out", save_errors = false, - interval = 1) - BoundsCheckCallback(output_directory, save_errors, interval) -end - -function (callback::BoundsCheckCallback)(u_ode, integrator, stage) - mesh, equations, solver, cache = mesh_equations_solver_cache(integrator.p) - (; t, iter, alg) = integrator - u = wrap_array(u_ode, mesh, equations, solver, cache) - - @trixi_timeit timer() "check_bounds" check_bounds(u, equations, solver, cache, - solver.volume_integral) - - save_errors = callback.save_errors && (callback.interval > 0) && - (stage == length(alg.c)) && - ((iter + 1) % callback.interval == 0 || # Every `interval` time steps - integrator.finalstep || # Planned last time step - (iter + 1) >= integrator.opts.maxiters) # Maximum iterations reached - if save_errors - @trixi_timeit timer() "save_errors" save_bounds_check_errors(callback.output_directory, - t, iter + 1, - equations, - solver.volume_integral) + #! format: noindent + + """ + BoundsCheckCallback(; output_directory="out", save_errors=false, interval=1) + + Subcell limiting techniques with [`SubcellLimiterIDP`](@ref) are constructed to adhere certain + local or global bounds. To make sure that these bounds are actually met, this callback calculates + the maximum deviation from the bounds. The maximum deviation per applied bound is printed to + the screen at the end of the simulation. + For more insights, when setting `save_errors=true` the occurring errors are exported every + `interval` time steps during the simulation. Then, the maximum deviations since the last + export are saved in "`output_directory`/deviations.txt". + The `BoundsCheckCallback` has to be applied as a stage callback for the SSPRK time integration scheme. + + !!! note + For `SubcellLimiterIDP`, the solution is corrected in the a posteriori correction stage + [`SubcellLimiterIDPCorrection`](@ref). So, to check the final solution, this bounds check + callback must be called after the correction stage. + """ + struct BoundsCheckCallback + output_directory::String + save_errors::Bool + interval::Int end -end - -@inline function check_bounds(u, equations, solver, cache, - volume_integral::VolumeIntegralSubcellLimiting) - check_bounds(u, equations, solver, cache, volume_integral.limiter) -end - -@inline function save_bounds_check_errors(output_directory, t, iter, equations, - volume_integral::VolumeIntegralSubcellLimiting) - save_bounds_check_errors(output_directory, t, iter, equations, - volume_integral.limiter) -end - -function init_callback(callback::BoundsCheckCallback, semi) - init_callback(callback, semi, semi.solver.volume_integral) -end - -function init_callback(callback::BoundsCheckCallback, semi, - volume_integral::VolumeIntegralSubcellLimiting) - init_callback(callback, semi, volume_integral.limiter) -end - -function init_callback(callback::BoundsCheckCallback, semi, limiter::SubcellLimiterIDP) - if !callback.save_errors || (callback.interval == 0) - return nothing + + function BoundsCheckCallback(; + output_directory = "out", save_errors = false, + interval = 1 + ) + BoundsCheckCallback(output_directory, save_errors, interval) end - (; local_twosided, positivity, local_onesided) = limiter - (; output_directory) = callback - variables = varnames(cons2cons, semi.equations) + function (callback::BoundsCheckCallback)(u_ode, integrator, stage) + mesh, equations, solver, cache = mesh_equations_solver_cache(integrator.p) + (; t, iter, alg) = integrator + u = wrap_array(u_ode, mesh, equations, solver, cache) + + @trixi_timeit timer() "check_bounds" check_bounds( + u, equations, solver, cache, + solver.volume_integral + ) + + save_errors = callback.save_errors && (callback.interval > 0) && + (stage == length(alg.c)) && + ( + (iter + 1) % callback.interval == 0 || # Every `interval` time steps + integrator.finalstep || # Planned last time step + (iter + 1) >= integrator.opts.maxiters + ) # Maximum iterations reached + if save_errors + @trixi_timeit timer() "save_errors" save_bounds_check_errors( + callback.output_directory, + t, iter + 1, + equations, + solver.volume_integral + ) + end + end - mkpath(output_directory) - open("$output_directory/deviations.txt", "a") do f - print(f, "# iter, simu_time") - if local_twosided - for v in limiter.local_twosided_variables_cons - variable_string = string(variables[v]) - print(f, ", " * variable_string * "_min, " * variable_string * "_max") - end + @inline function check_bounds( + u, equations, solver, cache, + volume_integral::VolumeIntegralSubcellLimiting + ) + check_bounds(u, equations, solver, cache, volume_integral.limiter) + end + + @inline function save_bounds_check_errors( + output_directory, t, iter, equations, + volume_integral::VolumeIntegralSubcellLimiting + ) + save_bounds_check_errors( + output_directory, t, iter, equations, + volume_integral.limiter + ) + end + + function init_callback(callback::BoundsCheckCallback, semi) + init_callback(callback, semi, semi.solver.volume_integral) + end + + function init_callback( + callback::BoundsCheckCallback, semi, + volume_integral::VolumeIntegralSubcellLimiting + ) + init_callback(callback, semi, volume_integral.limiter) + end + + function init_callback(callback::BoundsCheckCallback, semi, limiter::SubcellLimiterIDP) + if !callback.save_errors || (callback.interval == 0) + return nothing end - if local_onesided - for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear - print(f, ", " * string(variable) * "_" * string(min_or_max)) + + (; local_twosided, positivity, local_onesided) = limiter + (; output_directory) = callback + variables = varnames(cons2cons, semi.equations) + + mkpath(output_directory) + open("$output_directory/deviations.txt", "a") do f + print(f, "# iter, simu_time") + if local_twosided + for v in limiter.local_twosided_variables_cons + variable_string = string(variables[v]) + print(f, ", " * variable_string * "_min, " * variable_string * "_max") + end end - end - if positivity - for v in limiter.positivity_variables_cons - if v in limiter.local_twosided_variables_cons - continue + if local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + print(f, ", " * string(variable) * "_" * string(min_or_max)) end - print(f, ", " * string(variables[v]) * "_min") end - for variable in limiter.positivity_variables_nonlinear - print(f, ", " * string(variable) * "_min") + if positivity + for v in limiter.positivity_variables_cons + if v in limiter.local_twosided_variables_cons + continue + end + print(f, ", " * string(variables[v]) * "_min") + end + for variable in limiter.positivity_variables_nonlinear + print(f, ", " * string(variable) * "_min") + end end + println(f) end - println(f) - end - return nothing -end - -function finalize_callback(callback::BoundsCheckCallback, semi) - finalize_callback(callback, semi, semi.solver.volume_integral) -end - -function finalize_callback(callback::BoundsCheckCallback, semi, - volume_integral::VolumeIntegralSubcellLimiting) - finalize_callback(callback, semi, volume_integral.limiter) -end - -@inline function finalize_callback(callback::BoundsCheckCallback, semi, - limiter::SubcellLimiterIDP) - (; local_twosided, positivity, local_onesided) = limiter - (; idp_bounds_delta_global) = limiter.cache - variables = varnames(cons2cons, semi.equations) - - println("─"^100) - println("Maximum deviation from bounds:") - println("─"^100) - if local_twosided - for v in limiter.local_twosided_variables_cons - v_string = string(v) - println("$(variables[v]):") - println("- lower bound: ", - idp_bounds_delta_global[Symbol(v_string, "_min")]) - println("- upper bound: ", - idp_bounds_delta_global[Symbol(v_string, "_max")]) - end - end - if local_onesided - for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear - variable_string = string(variable) - minmax_string = string(min_or_max) - println("$variable_string:") - println("- $minmax_string bound: ", - idp_bounds_delta_global[Symbol(variable_string, "_", - minmax_string)]) - end - end - if positivity - for v in limiter.positivity_variables_cons - if v in limiter.local_twosided_variables_cons - continue - end - println(string(variables[v]) * ":\n- positivity: ", - idp_bounds_delta_global[Symbol(string(v), "_min")]) - end - for variable in limiter.positivity_variables_nonlinear - variable_string = string(variable) - println(variable_string * ":\n- positivity: ", - idp_bounds_delta_global[Symbol(variable_string, "_min")]) - end + return nothing end - println("─"^100 * "\n") - return nothing -end + function finalize_callback(callback::BoundsCheckCallback, semi) + finalize_callback(callback, semi, semi.solver.volume_integral) + end -@inline function save_bounds_check_errors(output_directory, time, iter, equations, - limiter::SubcellLimiterIDP) - (; local_twosided, positivity, local_onesided) = limiter - (; idp_bounds_delta_local) = limiter.cache + function finalize_callback( + callback::BoundsCheckCallback, semi, + volume_integral::VolumeIntegralSubcellLimiting + ) + finalize_callback(callback, semi, volume_integral.limiter) + end - # Print to output file - open(joinpath(output_directory, "deviations.txt"), "a") do f - print(f, iter, ", ", time) + @inline function finalize_callback( + callback::BoundsCheckCallback, semi, + limiter::SubcellLimiterIDP + ) + (; local_twosided, positivity, local_onesided) = limiter + (; idp_bounds_delta_global) = limiter.cache + variables = varnames(cons2cons, semi.equations) + + println("─"^100) + println("Maximum deviation from bounds:") + println("─"^100) if local_twosided for v in limiter.local_twosided_variables_cons v_string = string(v) - print(f, ", ", idp_bounds_delta_local[Symbol(v_string, "_min")], - ", ", idp_bounds_delta_local[Symbol(v_string, "_max")]) + println("$(variables[v]):") + println( + "- lower bound: ", + idp_bounds_delta_global[Symbol(v_string, "_min")] + ) + println( + "- upper bound: ", + idp_bounds_delta_global[Symbol(v_string, "_max")] + ) end end if local_onesided for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear - key = Symbol(string(variable), "_", string(min_or_max)) - print(f, ", ", idp_bounds_delta_local[key]) + variable_string = string(variable) + minmax_string = string(min_or_max) + println("$variable_string:") + println( + "- $minmax_string bound: ", + idp_bounds_delta_global[ + Symbol( + variable_string, "_", + minmax_string + ), + ] + ) end end if positivity @@ -197,21 +187,69 @@ end if v in limiter.local_twosided_variables_cons continue end - print(f, ", ", idp_bounds_delta_local[Symbol(string(v), "_min")]) + println( + string(variables[v]) * ":\n- positivity: ", + idp_bounds_delta_global[Symbol(string(v), "_min")] + ) end for variable in limiter.positivity_variables_nonlinear - print(f, ", ", idp_bounds_delta_local[Symbol(string(variable), "_min")]) + variable_string = string(variable) + println( + variable_string * ":\n- positivity: ", + idp_bounds_delta_global[Symbol(variable_string, "_min")] + ) end end - println(f) - end - # Reset local maximum deviations - for (key, _) in idp_bounds_delta_local - idp_bounds_delta_local[key] = zero(eltype(idp_bounds_delta_local[key])) + println("─"^100 * "\n") + + return nothing end - return nothing -end + @inline function save_bounds_check_errors( + output_directory, time, iter, equations, + limiter::SubcellLimiterIDP + ) + (; local_twosided, positivity, local_onesided) = limiter + (; idp_bounds_delta_local) = limiter.cache + + # Print to output file + open(joinpath(output_directory, "deviations.txt"), "a") do f + print(f, iter, ", ", time) + if local_twosided + for v in limiter.local_twosided_variables_cons + v_string = string(v) + print( + f, ", ", idp_bounds_delta_local[Symbol(v_string, "_min")], + ", ", idp_bounds_delta_local[Symbol(v_string, "_max")] + ) + end + end + if local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + key = Symbol(string(variable), "_", string(min_or_max)) + print(f, ", ", idp_bounds_delta_local[key]) + end + end + if positivity + for v in limiter.positivity_variables_cons + if v in limiter.local_twosided_variables_cons + continue + end + print(f, ", ", idp_bounds_delta_local[Symbol(string(v), "_min")]) + end + for variable in limiter.positivity_variables_nonlinear + print(f, ", ", idp_bounds_delta_local[Symbol(string(variable), "_min")]) + end + end + println(f) + end + # Reset local maximum deviations + for (key, _) in idp_bounds_delta_local + idp_bounds_delta_local[key] = zero(eltype(idp_bounds_delta_local[key])) + end + + return nothing + end -include("subcell_bounds_check_2d.jl") + include("subcell_bounds_check_2d.jl") end # @muladd diff --git a/src/callbacks_stage/subcell_bounds_check_2d.jl b/src/callbacks_stage/subcell_bounds_check_2d.jl index 8ac4c74b51..7c02ab140d 100644 --- a/src/callbacks_stage/subcell_bounds_check_2d.jl +++ b/src/callbacks_stage/subcell_bounds_check_2d.jl @@ -3,103 +3,123 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -@inline function check_bounds(u, equations::AbstractEquations{2}, # only works for 2D - solver, cache, limiter::SubcellLimiterIDP) - (; local_twosided, positivity, local_onesided) = solver.volume_integral.limiter - (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - (; idp_bounds_delta_local, idp_bounds_delta_global) = limiter.cache + @inline function check_bounds( + u, equations::AbstractEquations{2}, # only works for 2D + solver, cache, limiter::SubcellLimiterIDP + ) + (; local_twosided, positivity, local_onesided) = solver.volume_integral.limiter + (; variable_bounds) = limiter.cache.subcell_limiter_coefficients + (; idp_bounds_delta_local, idp_bounds_delta_global) = limiter.cache - # Note: In order to get the maximum deviation from the target bounds, this bounds check - # requires a reduction in every RK stage and for every enabled limiting option. To make - # this Thread-parallel we are using Polyester.jl's (at least v0.7.10) `@batch reduction` - # functionality. - # Although `@threaded` and `@batch` are currently used equivalently in Trixi.jl, we use - # `@batch` here to allow a possible redefinition of `@threaded` without creating errors here. - # See also https://github.com/trixi-framework/Trixi.jl/pull/1888#discussion_r1537785293. + # Note: In order to get the maximum deviation from the target bounds, this bounds check + # requires a reduction in every RK stage and for every enabled limiting option. To make + # this Thread-parallel we are using Polyester.jl's (at least v0.7.10) `@batch reduction` + # functionality. + # Although `@threaded` and `@batch` are currently used equivalently in Trixi.jl, we use + # `@batch` here to allow a possible redefinition of `@threaded` without creating errors here. + # See also https://github.com/trixi-framework/Trixi.jl/pull/1888#discussion_r1537785293. - if local_twosided - for v in limiter.local_twosided_variables_cons - v_string = string(v) - key_min = Symbol(v_string, "_min") - key_max = Symbol(v_string, "_max") - deviation_min = idp_bounds_delta_local[key_min] - deviation_max = idp_bounds_delta_local[key_max] - @batch reduction=((max, deviation_min), (max, deviation_max)) for element in eachelement(solver, - cache) - for j in eachnode(solver), i in eachnode(solver) - var = u[v, i, j, element] - # Note: We always save the absolute deviations >= 0 and therefore use the - # `max` operator for the lower and upper bound. The different directions of - # upper and lower bound are considered in their calculations with a - # different sign. - deviation_min = max(deviation_min, - variable_bounds[key_min][i, j, element] - var) - deviation_max = max(deviation_max, - var - variable_bounds[key_max][i, j, element]) + if local_twosided + for v in limiter.local_twosided_variables_cons + v_string = string(v) + key_min = Symbol(v_string, "_min") + key_max = Symbol(v_string, "_max") + deviation_min = idp_bounds_delta_local[key_min] + deviation_max = idp_bounds_delta_local[key_max] + @batch reduction = ((max, deviation_min), (max, deviation_max)) for element in eachelement( + solver, + cache + ) + for j in eachnode(solver), i in eachnode(solver) + var = u[v, i, j, element] + # Note: We always save the absolute deviations >= 0 and therefore use the + # `max` operator for the lower and upper bound. The different directions of + # upper and lower bound are considered in their calculations with a + # different sign. + deviation_min = max( + deviation_min, + variable_bounds[key_min][i, j, element] - var + ) + deviation_max = max( + deviation_max, + var - variable_bounds[key_max][i, j, element] + ) + end end + idp_bounds_delta_local[key_min] = deviation_min + idp_bounds_delta_local[key_max] = deviation_max end - idp_bounds_delta_local[key_min] = deviation_min - idp_bounds_delta_local[key_max] = deviation_max end - end - if local_onesided - for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear - key = Symbol(string(variable), "_", string(min_or_max)) - deviation = idp_bounds_delta_local[key] - sign_ = min_or_max(1.0, -1.0) - @batch reduction=(max, deviation) for element in eachelement(solver, cache) - for j in eachnode(solver), i in eachnode(solver) - v = variable(get_node_vars(u, equations, solver, i, j, element), - equations) - # Note: We always save the absolute deviations >= 0 and therefore use the - # `max` operator for lower and upper bounds. The different directions of - # upper and lower bounds are considered with `sign_`. - deviation = max(deviation, - sign_ * (v - variable_bounds[key][i, j, element])) + if local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + key = Symbol(string(variable), "_", string(min_or_max)) + deviation = idp_bounds_delta_local[key] + sign_ = min_or_max(1.0, -1.0) + @batch reduction = (max, deviation) for element in eachelement(solver, cache) + for j in eachnode(solver), i in eachnode(solver) + v = variable( + get_node_vars(u, equations, solver, i, j, element), + equations + ) + # Note: We always save the absolute deviations >= 0 and therefore use the + # `max` operator for lower and upper bounds. The different directions of + # upper and lower bounds are considered with `sign_`. + deviation = max( + deviation, + sign_ * (v - variable_bounds[key][i, j, element]) + ) + end end + idp_bounds_delta_local[key] = deviation end - idp_bounds_delta_local[key] = deviation end - end - if positivity - for v in limiter.positivity_variables_cons - if v in limiter.local_twosided_variables_cons - continue - end - key = Symbol(string(v), "_min") - deviation = idp_bounds_delta_local[key] - @batch reduction=(max, deviation) for element in eachelement(solver, cache) - for j in eachnode(solver), i in eachnode(solver) - var = u[v, i, j, element] - deviation = max(deviation, - variable_bounds[key][i, j, element] - var) + if positivity + for v in limiter.positivity_variables_cons + if v in limiter.local_twosided_variables_cons + continue end + key = Symbol(string(v), "_min") + deviation = idp_bounds_delta_local[key] + @batch reduction = (max, deviation) for element in eachelement(solver, cache) + for j in eachnode(solver), i in eachnode(solver) + var = u[v, i, j, element] + deviation = max( + deviation, + variable_bounds[key][i, j, element] - var + ) + end + end + idp_bounds_delta_local[key] = deviation end - idp_bounds_delta_local[key] = deviation - end - for variable in limiter.positivity_variables_nonlinear - key = Symbol(string(variable), "_min") - deviation = idp_bounds_delta_local[key] - @batch reduction=(max, deviation) for element in eachelement(solver, cache) - for j in eachnode(solver), i in eachnode(solver) - var = variable(get_node_vars(u, equations, solver, i, j, element), - equations) - deviation = max(deviation, - variable_bounds[key][i, j, element] - var) + for variable in limiter.positivity_variables_nonlinear + key = Symbol(string(variable), "_min") + deviation = idp_bounds_delta_local[key] + @batch reduction = (max, deviation) for element in eachelement(solver, cache) + for j in eachnode(solver), i in eachnode(solver) + var = variable( + get_node_vars(u, equations, solver, i, j, element), + equations + ) + deviation = max( + deviation, + variable_bounds[key][i, j, element] - var + ) + end end + idp_bounds_delta_local[key] = deviation end - idp_bounds_delta_local[key] = deviation end - end - for (key, _) in idp_bounds_delta_local - # Update global maximum deviations - idp_bounds_delta_global[key] = max(idp_bounds_delta_global[key], - idp_bounds_delta_local[key]) - end + for (key, _) in idp_bounds_delta_local + # Update global maximum deviations + idp_bounds_delta_global[key] = max( + idp_bounds_delta_global[key], + idp_bounds_delta_local[key] + ) + end - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_stage/subcell_limiter_idp_correction.jl b/src/callbacks_stage/subcell_limiter_idp_correction.jl index 69125ebecd..737d5d7a09 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction.jl @@ -3,67 +3,79 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - SubcellLimiterIDPCorrection() - -Perform antidiffusive correction stage for the a posteriori IDP limiter [`SubcellLimiterIDP`](@ref) -called with [`VolumeIntegralSubcellLimiting`](@ref). - -!!! note - This callback and the actual limiter [`SubcellLimiterIDP`](@ref) only work together. - This is not a replacement but a necessary addition. - -## References - -- Rueda-Ramírez, Pazner, Gassner (2022) - Subcell Limiting Strategies for Discontinuous Galerkin Spectral Element Methods - [DOI: 10.1016/j.compfluid.2022.105627](https://doi.org/10.1016/j.compfluid.2022.105627) -- Pazner (2020) - Sparse invariant domain preserving discontinuous Galerkin methods with subcell convex limiting - [DOI: 10.1016/j.cma.2021.113876](https://doi.org/10.1016/j.cma.2021.113876) - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -struct SubcellLimiterIDPCorrection end - -function (limiter!::SubcellLimiterIDPCorrection)(u_ode, - integrator::Trixi.SimpleIntegratorSSP, - stage) - semi = integrator.p - limiter!(u_ode, semi, integrator.t, integrator.dt, - semi.solver.volume_integral) -end - -function (limiter!::SubcellLimiterIDPCorrection)(u_ode, semi, t, dt, - volume_integral::VolumeIntegralSubcellLimiting) - @trixi_timeit timer() "a posteriori limiter" limiter!(u_ode, semi, t, dt, - volume_integral.limiter) -end - -function (limiter!::SubcellLimiterIDPCorrection)(u_ode, semi, t, dt, - limiter::SubcellLimiterIDP) - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - - u = wrap_array(u_ode, mesh, equations, solver, cache) - - # Calculate blending factor alpha in [0,1] - # f_ij = alpha_ij * f^(FV)_ij + (1 - alpha_ij) * f^(DG)_ij - # = f^(FV)_ij + (1 - alpha_ij) * f^(antidiffusive)_ij - @trixi_timeit timer() "blending factors" solver.volume_integral.limiter(u, semi, - solver, t, - dt) - - perform_idp_correction!(u, dt, mesh, equations, solver, cache) - - return nothing -end - -init_callback(limiter!::SubcellLimiterIDPCorrection, semi) = nothing - -finalize_callback(limiter!::SubcellLimiterIDPCorrection, semi) = nothing - -include("subcell_limiter_idp_correction_2d.jl") + #! format: noindent + + """ + SubcellLimiterIDPCorrection() + + Perform antidiffusive correction stage for the a posteriori IDP limiter [`SubcellLimiterIDP`](@ref) + called with [`VolumeIntegralSubcellLimiting`](@ref). + + !!! note + This callback and the actual limiter [`SubcellLimiterIDP`](@ref) only work together. + This is not a replacement but a necessary addition. + + ## References + + - Rueda-Ramírez, Pazner, Gassner (2022) + Subcell Limiting Strategies for Discontinuous Galerkin Spectral Element Methods + [DOI: 10.1016/j.compfluid.2022.105627](https://doi.org/10.1016/j.compfluid.2022.105627) + - Pazner (2020) + Sparse invariant domain preserving discontinuous Galerkin methods with subcell convex limiting + [DOI: 10.1016/j.cma.2021.113876](https://doi.org/10.1016/j.cma.2021.113876) + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + struct SubcellLimiterIDPCorrection end + + function (limiter!::SubcellLimiterIDPCorrection)( + u_ode, + integrator::Trixi.SimpleIntegratorSSP, + stage + ) + semi = integrator.p + limiter!( + u_ode, semi, integrator.t, integrator.dt, + semi.solver.volume_integral + ) + end + + function (limiter!::SubcellLimiterIDPCorrection)( + u_ode, semi, t, dt, + volume_integral::VolumeIntegralSubcellLimiting + ) + @trixi_timeit timer() "a posteriori limiter" limiter!( + u_ode, semi, t, dt, + volume_integral.limiter + ) + end + + function (limiter!::SubcellLimiterIDPCorrection)( + u_ode, semi, t, dt, + limiter::SubcellLimiterIDP + ) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + + u = wrap_array(u_ode, mesh, equations, solver, cache) + + # Calculate blending factor alpha in [0,1] + # f_ij = alpha_ij * f^(FV)_ij + (1 - alpha_ij) * f^(DG)_ij + # = f^(FV)_ij + (1 - alpha_ij) * f^(antidiffusive)_ij + @trixi_timeit timer() "blending factors" solver.volume_integral.limiter( + u, semi, + solver, t, + dt + ) + + perform_idp_correction!(u, dt, mesh, equations, solver, cache) + + return nothing + end + + init_callback(limiter!::SubcellLimiterIDPCorrection, semi) = nothing + + finalize_callback(limiter!::SubcellLimiterIDPCorrection, semi) = nothing + + include("subcell_limiter_idp_correction_2d.jl") end # @muladd diff --git a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl index e3b5616d5e..f4631198f2 100644 --- a/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl +++ b/src/callbacks_stage/subcell_limiter_idp_correction_2d.jl @@ -3,46 +3,62 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function perform_idp_correction!(u, dt, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - P4estMesh{2}}, - equations, dg, cache) - @unpack inverse_weights = dg.basis - @unpack antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R = cache.antidiffusive_fluxes - @unpack alpha1, alpha2 = dg.volume_integral.limiter.cache.subcell_limiter_coefficients + function perform_idp_correction!( + u, dt, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + P4estMesh{2}, + }, + equations, dg, cache + ) + @unpack inverse_weights = dg.basis + @unpack antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R = cache.antidiffusive_fluxes + @unpack alpha1, alpha2 = dg.volume_integral.limiter.cache.subcell_limiter_coefficients - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - # Sign switch as in apply_jacobian! - inverse_jacobian = -get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, i, j, element) + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + # Sign switch as in apply_jacobian! + inverse_jacobian = -get_inverse_jacobian( + cache.elements.inverse_jacobian, + mesh, i, j, element + ) - # Note: antidiffusive_flux1[v, i, xi, element] = antidiffusive_flux2[v, xi, i, element] = 0 for all i in 1:nnodes and xi in {1, nnodes+1} - alpha_flux1 = (1 - alpha1[i, j, element]) * - get_node_vars(antidiffusive_flux1_R, equations, dg, - i, j, element) - alpha_flux1_ip1 = (1 - alpha1[i + 1, j, element]) * - get_node_vars(antidiffusive_flux1_L, equations, dg, - i + 1, j, element) - alpha_flux2 = (1 - alpha2[i, j, element]) * - get_node_vars(antidiffusive_flux2_R, equations, dg, - i, j, element) - alpha_flux2_jp1 = (1 - alpha2[i, j + 1, element]) * - get_node_vars(antidiffusive_flux2_L, equations, dg, - i, j + 1, element) + # Note: antidiffusive_flux1[v, i, xi, element] = antidiffusive_flux2[v, xi, i, element] = 0 for all i in 1:nnodes and xi in {1, nnodes+1} + alpha_flux1 = (1 - alpha1[i, j, element]) * + get_node_vars( + antidiffusive_flux1_R, equations, dg, + i, j, element + ) + alpha_flux1_ip1 = (1 - alpha1[i + 1, j, element]) * + get_node_vars( + antidiffusive_flux1_L, equations, dg, + i + 1, j, element + ) + alpha_flux2 = (1 - alpha2[i, j, element]) * + get_node_vars( + antidiffusive_flux2_R, equations, dg, + i, j, element + ) + alpha_flux2_jp1 = (1 - alpha2[i, j + 1, element]) * + get_node_vars( + antidiffusive_flux2_L, equations, dg, + i, j + 1, element + ) - for v in eachvariable(equations) - u[v, i, j, element] += dt * inverse_jacobian * - (inverse_weights[i] * - (alpha_flux1_ip1[v] - alpha_flux1[v]) + - inverse_weights[j] * - (alpha_flux2_jp1[v] - alpha_flux2[v])) + for v in eachvariable(equations) + u[v, i, j, element] += dt * inverse_jacobian * + ( + inverse_weights[i] * + (alpha_flux1_ip1[v] - alpha_flux1[v]) + + inverse_weights[j] * + (alpha_flux2_jp1[v] - alpha_flux2[v]) + ) + end end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_step/alive.jl b/src/callbacks_step/alive.jl index 9700f7e4cd..9ffeeca98c 100644 --- a/src/callbacks_step/alive.jl +++ b/src/callbacks_step/alive.jl @@ -3,101 +3,123 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - AliveCallback(analysis_interval=0, alive_interval=analysis_interval÷10) + """ + AliveCallback(analysis_interval=0, alive_interval=analysis_interval÷10) -Inexpensive callback showing that a simulation is still running by printing -some information such as the current time to the screen every `alive_interval` -time steps. If `analysis_interval ≂̸ 0`, the output is omitted every -`analysis_interval` time steps. -""" -mutable struct AliveCallback - start_time::Float64 - alive_interval::Int - analysis_interval::Int -end + Inexpensive callback showing that a simulation is still running by printing + some information such as the current time to the screen every `alive_interval` + time steps. If `analysis_interval ≂̸ 0`, the output is omitted every + `analysis_interval` time steps. + """ + mutable struct AliveCallback + start_time::Float64 + alive_interval::Int + analysis_interval::Int + end -function AliveCallback(; analysis_interval = 0, - alive_interval = analysis_interval ÷ 10) - alive_callback = AliveCallback(0.0, alive_interval, analysis_interval) + function AliveCallback(; + analysis_interval = 0, + alive_interval = analysis_interval ÷ 10 + ) + alive_callback = AliveCallback(0.0, alive_interval, analysis_interval) - DiscreteCallback(alive_callback, alive_callback, # the first one is the condition, the second the affect! - save_positions = (false, false), - initialize = initialize!) -end + DiscreteCallback( + alive_callback, alive_callback, # the first one is the condition, the second the affect! + save_positions = (false, false), + initialize = initialize! + ) + end -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:AliveCallback}) - @nospecialize cb # reduce precompilation time + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:AliveCallback}) + @nospecialize cb # reduce precompilation time - alive_callback = cb.affect! - print(io, "AliveCallback(alive_interval=", alive_callback.alive_interval, ")") -end + alive_callback = cb.affect! + print(io, "AliveCallback(alive_interval=", alive_callback.alive_interval, ")") + end -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:AliveCallback}) - @nospecialize cb # reduce precompilation time + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:AliveCallback} + ) + @nospecialize cb # reduce precompilation time - if get(io, :compact, false) - show(io, cb) - else - alive_callback = cb.affect! + if get(io, :compact, false) + show(io, cb) + else + alive_callback = cb.affect! - setup = [ - "interval" => alive_callback.alive_interval, - ] - summary_box(io, "AliveCallback", setup) + setup = [ + "interval" => alive_callback.alive_interval, + ] + summary_box(io, "AliveCallback", setup) + end end -end -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t, - integrator) where {Condition, Affect! <: AliveCallback} - alive_callback = cb.affect! - alive_callback.start_time = time_ns() - return nothing -end + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u, t, + integrator + ) where {Condition, Affect! <: AliveCallback} + alive_callback = cb.affect! + alive_callback.start_time = time_ns() + return nothing + end -# this method is called to determine whether the callback should be activated -function (alive_callback::AliveCallback)(u, t, integrator) - @unpack alive_interval, analysis_interval = alive_callback + # this method is called to determine whether the callback should be activated + function (alive_callback::AliveCallback)(u, t, integrator) + @unpack alive_interval, analysis_interval = alive_callback - # With error-based step size control, some steps can be rejected. Thus, - # `integrator.iter >= integrator.stats.naccept` - # (total #steps) (#accepted steps) - # We need to check the number of accepted steps since callbacks are not - # activated after a rejected step. - return alive_interval > 0 && ((integrator.stats.naccept % alive_interval == 0 && - (analysis_interval == 0 || - integrator.stats.naccept % analysis_interval != 0)) || - isfinished(integrator)) -end + # With error-based step size control, some steps can be rejected. Thus, + # `integrator.iter >= integrator.stats.naccept` + # (total #steps) (#accepted steps) + # We need to check the number of accepted steps since callbacks are not + # activated after a rejected step. + return alive_interval > 0 && ( + ( + integrator.stats.naccept % alive_interval == 0 && + ( + analysis_interval == 0 || + integrator.stats.naccept % analysis_interval != 0 + ) + ) || + isfinished(integrator) + ) + end -# this method is called when the callback is activated -function (alive_callback::AliveCallback)(integrator) - # Checking for floating point equality is OK here as `DifferentialEquations.jl` - # sets the time exactly to the final time in the last iteration - if isfinished(integrator) && mpi_isroot() - println("─"^100) - println("Trixi.jl simulation finished. Final time: ", integrator.t, + # this method is called when the callback is activated + function (alive_callback::AliveCallback)(integrator) + # Checking for floating point equality is OK here as `DifferentialEquations.jl` + # sets the time exactly to the final time in the last iteration + if isfinished(integrator) && mpi_isroot() + println("─"^100) + println( + "Trixi.jl simulation finished. Final time: ", integrator.t, " Time steps: ", integrator.stats.naccept, " (accepted), ", - integrator.iter, " (total)") - println("─"^100) - println() - elseif mpi_isroot() - t = integrator.t - t_initial = first(integrator.sol.prob.tspan) - t_final = last(integrator.sol.prob.tspan) - sim_time_percentage = (t - t_initial) / (t_final - t_initial) * 100 - runtime_absolute = 1.0e-9 * (time_ns() - alive_callback.start_time) - println(rpad(@sprintf("#timesteps: %6d │ Δt: %.4e │ sim. time: %.4e (%5.3f%%)", - integrator.stats.naccept, integrator.dt, t, - sim_time_percentage), 71) * - @sprintf("│ run time: %.4e s", runtime_absolute)) - end + integrator.iter, " (total)" + ) + println("─"^100) + println() + elseif mpi_isroot() + t = integrator.t + t_initial = first(integrator.sol.prob.tspan) + t_final = last(integrator.sol.prob.tspan) + sim_time_percentage = (t - t_initial) / (t_final - t_initial) * 100 + runtime_absolute = 1.0e-9 * (time_ns() - alive_callback.start_time) + println( + rpad( + @sprintf( + "#timesteps: %6d │ Δt: %.4e │ sim. time: %.4e (%5.3f%%)", + integrator.stats.naccept, integrator.dt, t, + sim_time_percentage + ), 71 + ) * + @sprintf("│ run time: %.4e s", runtime_absolute) + ) + end - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) - return nothing -end + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) + return nothing + end end # @muladd diff --git a/src/callbacks_step/amr.jl b/src/callbacks_step/amr.jl index 155e909e29..19fef42b53 100644 --- a/src/callbacks_step/amr.jl +++ b/src/callbacks_step/amr.jl @@ -3,1159 +3,1313 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - AMRCallback(semi, controller [,adaptor=AdaptorAMR(semi)]; - interval, - adapt_initial_condition=true, - adapt_initial_condition_only_refine=true, - dynamic_load_balancing=true) - -Performs adaptive mesh refinement (AMR) every `interval` time steps -for a given semidiscretization `semi` using the chosen `controller`. -""" -struct AMRCallback{Controller, Adaptor, Cache} - controller::Controller - interval::Int - adapt_initial_condition::Bool - adapt_initial_condition_only_refine::Bool - dynamic_load_balancing::Bool - adaptor::Adaptor - amr_cache::Cache -end - -function AMRCallback(semi, controller, adaptor; - interval, - adapt_initial_condition = true, - adapt_initial_condition_only_refine = true, - dynamic_load_balancing = true) - # check arguments - if !(interval isa Integer && interval >= 0) - throw(ArgumentError("`interval` must be a non-negative integer (provided `interval = $interval`)")) + #! format: noindent + + """ + AMRCallback(semi, controller [,adaptor=AdaptorAMR(semi)]; + interval, + adapt_initial_condition=true, + adapt_initial_condition_only_refine=true, + dynamic_load_balancing=true) + + Performs adaptive mesh refinement (AMR) every `interval` time steps + for a given semidiscretization `semi` using the chosen `controller`. + """ + struct AMRCallback{Controller, Adaptor, Cache} + controller::Controller + interval::Int + adapt_initial_condition::Bool + adapt_initial_condition_only_refine::Bool + dynamic_load_balancing::Bool + adaptor::Adaptor + amr_cache::Cache end - # AMR every `interval` time steps, but not after the final step - # With error-based step size control, some steps can be rejected. Thus, - # `integrator.iter >= integrator.stats.naccept` - # (total #steps) (#accepted steps) - # We need to check the number of accepted steps since callbacks are not - # activated after a rejected step. - if interval > 0 - condition = (u, t, integrator) -> ((integrator.stats.naccept % interval == 0) && - !(integrator.stats.naccept == 0 && - integrator.iter > 0) && - !isfinished(integrator)) - else # disable the AMR callback except possibly for initial refinement during initialization - condition = (u, t, integrator) -> false + function AMRCallback( + semi, controller, adaptor; + interval, + adapt_initial_condition = true, + adapt_initial_condition_only_refine = true, + dynamic_load_balancing = true + ) + # check arguments + if !(interval isa Integer && interval >= 0) + throw(ArgumentError("`interval` must be a non-negative integer (provided `interval = $interval`)")) + end + + # AMR every `interval` time steps, but not after the final step + # With error-based step size control, some steps can be rejected. Thus, + # `integrator.iter >= integrator.stats.naccept` + # (total #steps) (#accepted steps) + # We need to check the number of accepted steps since callbacks are not + # activated after a rejected step. + if interval > 0 + condition = (u, t, integrator) -> ( + (integrator.stats.naccept % interval == 0) && + !( + integrator.stats.naccept == 0 && + integrator.iter > 0 + ) && + !isfinished(integrator) + ) + else # disable the AMR callback except possibly for initial refinement during initialization + condition = (u, t, integrator) -> false + end + + to_refine = Int[] + to_coarsen = Int[] + amr_cache = (; to_refine, to_coarsen) + + amr_callback = AMRCallback{typeof(controller), typeof(adaptor), typeof(amr_cache)}( + controller, + interval, + adapt_initial_condition, + adapt_initial_condition_only_refine, + dynamic_load_balancing, + adaptor, + amr_cache + ) + + DiscreteCallback( + condition, amr_callback, + save_positions = (false, false), + initialize = initialize! + ) end - to_refine = Int[] - to_coarsen = Int[] - amr_cache = (; to_refine, to_coarsen) - - amr_callback = AMRCallback{typeof(controller), typeof(adaptor), typeof(amr_cache)}(controller, - interval, - adapt_initial_condition, - adapt_initial_condition_only_refine, - dynamic_load_balancing, - adaptor, - amr_cache) - - DiscreteCallback(condition, amr_callback, - save_positions = (false, false), - initialize = initialize!) -end - -function AMRCallback(semi, controller; kwargs...) - adaptor = AdaptorAMR(semi) - AMRCallback(semi, controller, adaptor; kwargs...) -end - -function AdaptorAMR(semi; kwargs...) - mesh, _, solver, _ = mesh_equations_solver_cache(semi) - AdaptorAMR(mesh, solver; kwargs...) -end - -# TODO: Taal bikeshedding, implement a method with less information and the signature -# function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:AMRCallback}) -# @nospecialize cb # reduce precompilation time -# -# amr_callback = cb.affect! -# print(io, "AMRCallback") -# end -function Base.show(io::IO, mime::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:AMRCallback}) - @nospecialize cb # reduce precompilation time - - if get(io, :compact, false) - show(io, cb) - else - amr_callback = cb.affect! + function AMRCallback(semi, controller; kwargs...) + adaptor = AdaptorAMR(semi) + AMRCallback(semi, controller, adaptor; kwargs...) + end + + function AdaptorAMR(semi; kwargs...) + mesh, _, solver, _ = mesh_equations_solver_cache(semi) + AdaptorAMR(mesh, solver; kwargs...) + end - summary_header(io, "AMRCallback") - summary_line(io, "controller", amr_callback.controller |> typeof |> nameof) - show(increment_indent(io), mime, amr_callback.controller) - summary_line(io, "interval", amr_callback.interval) - summary_line(io, "adapt IC", - amr_callback.adapt_initial_condition ? "yes" : "no") - if amr_callback.adapt_initial_condition - summary_line(io, "│ only refine", - amr_callback.adapt_initial_condition_only_refine ? "yes" : - "no") + # TODO: Taal bikeshedding, implement a method with less information and the signature + # function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:AMRCallback}) + # @nospecialize cb # reduce precompilation time + # + # amr_callback = cb.affect! + # print(io, "AMRCallback") + # end + function Base.show( + io::IO, mime::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:AMRCallback} + ) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + amr_callback = cb.affect! + + summary_header(io, "AMRCallback") + summary_line(io, "controller", amr_callback.controller |> typeof |> nameof) + show(increment_indent(io), mime, amr_callback.controller) + summary_line(io, "interval", amr_callback.interval) + summary_line( + io, "adapt IC", + amr_callback.adapt_initial_condition ? "yes" : "no" + ) + if amr_callback.adapt_initial_condition + summary_line( + io, "│ only refine", + amr_callback.adapt_initial_condition_only_refine ? "yes" : + "no" + ) + end + summary_footer(io) end - summary_footer(io) end -end - -# The function below is used to control the output depending on whether or not AMR is enabled. -""" - uses_amr(callback) - -Checks whether the provided callback or `CallbackSet` is an [`AMRCallback`](@ref) -or contains one. -""" -uses_amr(cb) = false -function uses_amr(cb::DiscreteCallback{Condition, Affect!}) where {Condition, - Affect! <: - AMRCallback} - true -end -uses_amr(callbacks::CallbackSet) = mapreduce(uses_amr, |, callbacks.discrete_callbacks) - -function get_element_variables!(element_variables, u, mesh, equations, solver, cache, - amr_callback::AMRCallback; kwargs...) - get_element_variables!(element_variables, u, mesh, equations, solver, cache, - amr_callback.controller, amr_callback; kwargs...) -end - -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t, - integrator) where {Condition, Affect! <: AMRCallback} - amr_callback = cb.affect! - semi = integrator.p - - @trixi_timeit timer() "initial condition AMR" if amr_callback.adapt_initial_condition - # iterate until mesh does not change anymore - has_changed = amr_callback(integrator, - only_refine = amr_callback.adapt_initial_condition_only_refine) - iterations = 1 - while has_changed - compute_coefficients!(integrator.u, t, semi) - u_modified!(integrator, true) - has_changed = amr_callback(integrator, - only_refine = amr_callback.adapt_initial_condition_only_refine) - iterations = iterations + 1 - if iterations > 10 - @warn "AMR for initial condition did not settle within 10 iterations!\n" * - "Consider adjusting thresholds or setting `adapt_initial_condition_only_refine`." - break + + # The function below is used to control the output depending on whether or not AMR is enabled. + """ + uses_amr(callback) + + Checks whether the provided callback or `CallbackSet` is an [`AMRCallback`](@ref) + or contains one. + """ + uses_amr(cb) = false + function uses_amr(cb::DiscreteCallback{Condition, Affect!}) where { + Condition, + Affect! <: + AMRCallback, + } + true + end + uses_amr(callbacks::CallbackSet) = mapreduce(uses_amr, |, callbacks.discrete_callbacks) + + function get_element_variables!( + element_variables, u, mesh, equations, solver, cache, + amr_callback::AMRCallback; kwargs... + ) + get_element_variables!( + element_variables, u, mesh, equations, solver, cache, + amr_callback.controller, amr_callback; kwargs... + ) + end + + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u, t, + integrator + ) where {Condition, Affect! <: AMRCallback} + amr_callback = cb.affect! + semi = integrator.p + + @trixi_timeit timer() "initial condition AMR" if amr_callback.adapt_initial_condition + # iterate until mesh does not change anymore + has_changed = amr_callback( + integrator, + only_refine = amr_callback.adapt_initial_condition_only_refine + ) + iterations = 1 + while has_changed + compute_coefficients!(integrator.u, t, semi) + u_modified!(integrator, true) + has_changed = amr_callback( + integrator, + only_refine = amr_callback.adapt_initial_condition_only_refine + ) + iterations = iterations + 1 + if iterations > 10 + @warn "AMR for initial condition did not settle within 10 iterations!\n" * + "Consider adjusting thresholds or setting `adapt_initial_condition_only_refine`." + break + end end end + + return nothing end - return nothing -end - -# TODO: Taal remove? -# function (cb::DiscreteCallback{Condition,Affect!})(ode::ODEProblem) where {Condition, Affect!<:AMRCallback} -# amr_callback = cb.affect! -# semi = ode.p - -# @trixi_timeit timer() "initial condition AMR" if amr_callback.adapt_initial_condition -# # iterate until mesh does not change anymore -# has_changed = true -# while has_changed -# has_changed = amr_callback(ode.u0, semi, -# only_refine=amr_callback.adapt_initial_condition_only_refine) -# compute_coefficients!(ode.u0, ode.tspan[1], semi) -# end -# end - -# return nothing -# end - -function (amr_callback::AMRCallback)(integrator; kwargs...) - u_ode = integrator.u - semi = integrator.p - - @trixi_timeit timer() "AMR" begin - has_changed = amr_callback(u_ode, semi, - integrator.t, integrator.iter; kwargs...) - if has_changed - resize!(integrator, length(u_ode)) - u_modified!(integrator, true) + # TODO: Taal remove? + # function (cb::DiscreteCallback{Condition,Affect!})(ode::ODEProblem) where {Condition, Affect!<:AMRCallback} + # amr_callback = cb.affect! + # semi = ode.p + + # @trixi_timeit timer() "initial condition AMR" if amr_callback.adapt_initial_condition + # # iterate until mesh does not change anymore + # has_changed = true + # while has_changed + # has_changed = amr_callback(ode.u0, semi, + # only_refine=amr_callback.adapt_initial_condition_only_refine) + # compute_coefficients!(ode.u0, ode.tspan[1], semi) + # end + # end + + # return nothing + # end + + function (amr_callback::AMRCallback)(integrator; kwargs...) + u_ode = integrator.u + semi = integrator.p + + @trixi_timeit timer() "AMR" begin + has_changed = amr_callback( + u_ode, semi, + integrator.t, integrator.iter; kwargs... + ) + if has_changed + resize!(integrator, length(u_ode)) + u_modified!(integrator, true) + end end + + return has_changed end - return has_changed -end - -@inline function (amr_callback::AMRCallback)(u_ode::AbstractVector, - semi::SemidiscretizationHyperbolic, - t, iter; - kwargs...) - # Note that we don't `wrap_array` the vector `u_ode` to be able to `resize!` - # it when doing AMR while still dispatching on the `mesh` etc. - amr_callback(u_ode, mesh_equations_solver_cache(semi)..., semi, t, iter; kwargs...) -end - -@inline function (amr_callback::AMRCallback)(u_ode::AbstractVector, - semi::SemidiscretizationHyperbolicParabolic, - t, iter; - kwargs...) - # Note that we don't `wrap_array` the vector `u_ode` to be able to `resize!` - # it when doing AMR while still dispatching on the `mesh` etc. - amr_callback(u_ode, mesh_equations_solver_cache(semi)..., semi.cache_parabolic, - semi, t, iter; kwargs...) -end - -# `passive_args` is currently used for Euler with self-gravity to adapt the gravity solver -# passively without querying its indicator, based on the assumption that both solvers use -# the same mesh. That's a hack and should be improved in the future once we have more examples -# and a better understanding of such a coupling. -# `passive_args` is expected to be an iterable of `Tuple`s of the form -# `(p_u_ode, p_mesh, p_equations, p_dg, p_cache)`. -function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::TreeMesh, - equations, dg::DG, cache, semi, - t, iter; - only_refine = false, only_coarsen = false, - passive_args = ()) - @unpack controller, adaptor = amr_callback - - u = wrap_array(u_ode, mesh, equations, dg, cache) - lambda = @trixi_timeit timer() "indicator" controller(u, mesh, equations, dg, cache, - t = t, iter = iter) - - if mpi_isparallel() - # Collect lambda for all elements - lambda_global = Vector{eltype(lambda)}(undef, nelementsglobal(mesh, dg, cache)) - # Use parent because n_elements_by_rank is an OffsetArray - recvbuf = MPI.VBuffer(lambda_global, parent(cache.mpi_cache.n_elements_by_rank)) - MPI.Allgatherv!(lambda, recvbuf, mpi_comm()) - lambda = lambda_global + @inline function (amr_callback::AMRCallback)( + u_ode::AbstractVector, + semi::SemidiscretizationHyperbolic, + t, iter; + kwargs... + ) + # Note that we don't `wrap_array` the vector `u_ode` to be able to `resize!` + # it when doing AMR while still dispatching on the `mesh` etc. + amr_callback(u_ode, mesh_equations_solver_cache(semi)..., semi, t, iter; kwargs...) end - leaf_cell_ids = leaf_cells(mesh.tree) - @boundscheck begin - @assert axes(lambda)==axes(leaf_cell_ids) ("Indicator (axes = $(axes(lambda))) and leaf cell (axes = $(axes(leaf_cell_ids))) arrays have different axes") + @inline function (amr_callback::AMRCallback)( + u_ode::AbstractVector, + semi::SemidiscretizationHyperbolicParabolic, + t, iter; + kwargs... + ) + # Note that we don't `wrap_array` the vector `u_ode` to be able to `resize!` + # it when doing AMR while still dispatching on the `mesh` etc. + amr_callback( + u_ode, mesh_equations_solver_cache(semi)..., semi.cache_parabolic, + semi, t, iter; kwargs... + ) end - @unpack to_refine, to_coarsen = amr_callback.amr_cache - empty!(to_refine) - empty!(to_coarsen) - # Note: This assumes that the entries of `lambda` are sorted with ascending cell ids - for element in eachindex(lambda) - controller_value = lambda[element] - if controller_value > 0 - push!(to_refine, leaf_cell_ids[element]) - elseif controller_value < 0 - push!(to_coarsen, leaf_cell_ids[element]) + # `passive_args` is currently used for Euler with self-gravity to adapt the gravity solver + # passively without querying its indicator, based on the assumption that both solvers use + # the same mesh. That's a hack and should be improved in the future once we have more examples + # and a better understanding of such a coupling. + # `passive_args` is expected to be an iterable of `Tuple`s of the form + # `(p_u_ode, p_mesh, p_equations, p_dg, p_cache)`. + function (amr_callback::AMRCallback)( + u_ode::AbstractVector, mesh::TreeMesh, + equations, dg::DG, cache, semi, + t, iter; + only_refine = false, only_coarsen = false, + passive_args = () + ) + @unpack controller, adaptor = amr_callback + + u = wrap_array(u_ode, mesh, equations, dg, cache) + lambda = @trixi_timeit timer() "indicator" controller( + u, mesh, equations, dg, cache, + t = t, iter = iter + ) + + if mpi_isparallel() + # Collect lambda for all elements + lambda_global = Vector{eltype(lambda)}(undef, nelementsglobal(mesh, dg, cache)) + # Use parent because n_elements_by_rank is an OffsetArray + recvbuf = MPI.VBuffer(lambda_global, parent(cache.mpi_cache.n_elements_by_rank)) + MPI.Allgatherv!(lambda, recvbuf, mpi_comm()) + lambda = lambda_global end - end - @trixi_timeit timer() "refine" if !only_coarsen && !isempty(to_refine) - # refine mesh - refined_original_cells = @trixi_timeit timer() "mesh" refine!(mesh.tree, - to_refine) - - # Find all indices of elements whose cell ids are in refined_original_cells - elements_to_refine = findall(in(refined_original_cells), - cache.elements.cell_ids) - - # refine solver - @trixi_timeit timer() "solver" refine!(u_ode, adaptor, mesh, equations, dg, - cache, elements_to_refine) - for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args - @trixi_timeit timer() "passive solver" refine!(p_u_ode, adaptor, p_mesh, - p_equations, p_dg, p_cache, - elements_to_refine) + leaf_cell_ids = leaf_cells(mesh.tree) + @boundscheck begin + @assert axes(lambda) == axes(leaf_cell_ids) ("Indicator (axes = $(axes(lambda))) and leaf cell (axes = $(axes(leaf_cell_ids))) arrays have different axes") + end + + @unpack to_refine, to_coarsen = amr_callback.amr_cache + empty!(to_refine) + empty!(to_coarsen) + # Note: This assumes that the entries of `lambda` are sorted with ascending cell ids + for element in eachindex(lambda) + controller_value = lambda[element] + if controller_value > 0 + push!(to_refine, leaf_cell_ids[element]) + elseif controller_value < 0 + push!(to_coarsen, leaf_cell_ids[element]) + end end - else - # If there is nothing to refine, create empty array for later use - refined_original_cells = Int[] - end - @trixi_timeit timer() "coarsen" if !only_refine && !isempty(to_coarsen) - # Since the cells may have been shifted due to refinement, first we need to - # translate the old cell ids to the new cell ids - if !isempty(to_coarsen) - to_coarsen = original2refined(to_coarsen, refined_original_cells, mesh) + @trixi_timeit timer() "refine" if !only_coarsen && !isempty(to_refine) + # refine mesh + refined_original_cells = @trixi_timeit timer() "mesh" refine!( + mesh.tree, + to_refine + ) + + # Find all indices of elements whose cell ids are in refined_original_cells + elements_to_refine = findall( + in(refined_original_cells), + cache.elements.cell_ids + ) + + # refine solver + @trixi_timeit timer() "solver" refine!( + u_ode, adaptor, mesh, equations, dg, + cache, elements_to_refine + ) + for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args + @trixi_timeit timer() "passive solver" refine!( + p_u_ode, adaptor, p_mesh, + p_equations, p_dg, p_cache, + elements_to_refine + ) + end + else + # If there is nothing to refine, create empty array for later use + refined_original_cells = Int[] end - # Next, determine the parent cells from which the fine cells are to be - # removed, since these are needed for the coarsen! function. However, since - # we only want to coarsen if *all* child cells are marked for coarsening, - # we count the coarsening indicators for each parent cell and only coarsen - # if all children are marked as such (i.e., where the count is 2^ndims). At - # the same time, check if a cell is marked for coarsening even though it is - # *not* a leaf cell -> this can only happen if it was refined due to 2:1 - # smoothing during the preceding refinement operation. - parents_to_coarsen = zeros(Int, length(mesh.tree)) - for cell_id in to_coarsen - # If cell has no parent, it cannot be coarsened - if !has_parent(mesh.tree, cell_id) - continue + @trixi_timeit timer() "coarsen" if !only_refine && !isempty(to_coarsen) + # Since the cells may have been shifted due to refinement, first we need to + # translate the old cell ids to the new cell ids + if !isempty(to_coarsen) + to_coarsen = original2refined(to_coarsen, refined_original_cells, mesh) end - # If cell is not leaf (anymore), it cannot be coarsened - if !is_leaf(mesh.tree, cell_id) - continue + # Next, determine the parent cells from which the fine cells are to be + # removed, since these are needed for the coarsen! function. However, since + # we only want to coarsen if *all* child cells are marked for coarsening, + # we count the coarsening indicators for each parent cell and only coarsen + # if all children are marked as such (i.e., where the count is 2^ndims). At + # the same time, check if a cell is marked for coarsening even though it is + # *not* a leaf cell -> this can only happen if it was refined due to 2:1 + # smoothing during the preceding refinement operation. + parents_to_coarsen = zeros(Int, length(mesh.tree)) + for cell_id in to_coarsen + # If cell has no parent, it cannot be coarsened + if !has_parent(mesh.tree, cell_id) + continue + end + + # If cell is not leaf (anymore), it cannot be coarsened + if !is_leaf(mesh.tree, cell_id) + continue + end + + # Increase count for parent cell + parent_id = mesh.tree.parent_ids[cell_id] + parents_to_coarsen[parent_id] += 1 end - # Increase count for parent cell - parent_id = mesh.tree.parent_ids[cell_id] - parents_to_coarsen[parent_id] += 1 - end + # Extract only those parent cells for which all children should be coarsened + to_coarsen = collect(eachindex(parents_to_coarsen))[parents_to_coarsen .== 2^ndims(mesh)] + + # Finally, coarsen mesh + coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!( + mesh.tree, + to_coarsen + ) + + # Convert coarsened parent cell ids to the list of child cell ids that have + # been removed, since this is the information that is expected by the solver + removed_child_cells = zeros( + Int, + n_children_per_cell(mesh.tree) * + length(coarsened_original_cells) + ) + for (index, coarse_cell_id) in enumerate(coarsened_original_cells) + for child in 1:n_children_per_cell(mesh.tree) + removed_child_cells[n_children_per_cell(mesh.tree) * (index - 1) + child] = coarse_cell_id + + child + end + end - # Extract only those parent cells for which all children should be coarsened - to_coarsen = collect(eachindex(parents_to_coarsen))[parents_to_coarsen .== 2^ndims(mesh)] - - # Finally, coarsen mesh - coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!(mesh.tree, - to_coarsen) - - # Convert coarsened parent cell ids to the list of child cell ids that have - # been removed, since this is the information that is expected by the solver - removed_child_cells = zeros(Int, - n_children_per_cell(mesh.tree) * - length(coarsened_original_cells)) - for (index, coarse_cell_id) in enumerate(coarsened_original_cells) - for child in 1:n_children_per_cell(mesh.tree) - removed_child_cells[n_children_per_cell(mesh.tree) * (index - 1) + child] = coarse_cell_id + - child + # Find all indices of elements whose cell ids are in removed_child_cells + elements_to_remove = findall(in(removed_child_cells), cache.elements.cell_ids) + + # coarsen solver + @trixi_timeit timer() "solver" coarsen!( + u_ode, adaptor, mesh, equations, dg, + cache, elements_to_remove + ) + for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args + @trixi_timeit timer() "passive solver" coarsen!( + p_u_ode, adaptor, p_mesh, + p_equations, p_dg, p_cache, + elements_to_remove + ) end + else + # If there is nothing to coarsen, create empty array for later use + coarsened_original_cells = Int[] end - # Find all indices of elements whose cell ids are in removed_child_cells - elements_to_remove = findall(in(removed_child_cells), cache.elements.cell_ids) + # Store whether there were any cells coarsened or refined + has_changed = !isempty(refined_original_cells) || !isempty(coarsened_original_cells) + if has_changed # TODO: Taal decide, where shall we set this? + # don't set it to has_changed since there can be changes from earlier calls + mesh.unsaved_changes = true + end + + # Dynamically balance computational load by first repartitioning the mesh and then redistributing the cells/elements + if has_changed && mpi_isparallel() && amr_callback.dynamic_load_balancing + @trixi_timeit timer() "dynamic load balancing" begin + old_mpi_ranks_per_cell = copy(mesh.tree.mpi_ranks) + + partition!(mesh) - # coarsen solver - @trixi_timeit timer() "solver" coarsen!(u_ode, adaptor, mesh, equations, dg, - cache, elements_to_remove) - for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args - @trixi_timeit timer() "passive solver" coarsen!(p_u_ode, adaptor, p_mesh, - p_equations, p_dg, p_cache, - elements_to_remove) + rebalance_solver!(u_ode, mesh, equations, dg, cache, old_mpi_ranks_per_cell) + end end - else - # If there is nothing to coarsen, create empty array for later use - coarsened_original_cells = Int[] - end - # Store whether there were any cells coarsened or refined - has_changed = !isempty(refined_original_cells) || !isempty(coarsened_original_cells) - if has_changed # TODO: Taal decide, where shall we set this? - # don't set it to has_changed since there can be changes from earlier calls - mesh.unsaved_changes = true + # Return true if there were any cells coarsened or refined, otherwise false + return has_changed end - # Dynamically balance computational load by first repartitioning the mesh and then redistributing the cells/elements - if has_changed && mpi_isparallel() && amr_callback.dynamic_load_balancing - @trixi_timeit timer() "dynamic load balancing" begin - old_mpi_ranks_per_cell = copy(mesh.tree.mpi_ranks) + function (amr_callback::AMRCallback)( + u_ode::AbstractVector, mesh::TreeMesh, + equations, dg::DG, + cache, cache_parabolic, + semi::SemidiscretizationHyperbolicParabolic, + t, iter; + only_refine = false, only_coarsen = false + ) + @unpack controller, adaptor = amr_callback + + u = wrap_array(u_ode, mesh, equations, dg, cache) + # Indicator kept based on hyperbolic variables + lambda = @trixi_timeit timer() "indicator" controller( + u, mesh, equations, dg, cache, + t = t, iter = iter + ) + + if mpi_isparallel() + error("MPI has not been verified yet for parabolic AMR") + + # Collect lambda for all elements + lambda_global = Vector{eltype(lambda)}(undef, nelementsglobal(mesh, dg, cache)) + # Use parent because n_elements_by_rank is an OffsetArray + recvbuf = MPI.VBuffer(lambda_global, parent(cache.mpi_cache.n_elements_by_rank)) + MPI.Allgatherv!(lambda, recvbuf, mpi_comm()) + lambda = lambda_global + end - partition!(mesh) + leaf_cell_ids = leaf_cells(mesh.tree) + @boundscheck begin + @assert axes(lambda) == axes(leaf_cell_ids) ("Indicator (axes = $(axes(lambda))) and leaf cell (axes = $(axes(leaf_cell_ids))) arrays have different axes") + end - rebalance_solver!(u_ode, mesh, equations, dg, cache, old_mpi_ranks_per_cell) + @unpack to_refine, to_coarsen = amr_callback.amr_cache + empty!(to_refine) + empty!(to_coarsen) + # Note: This assumes that the entries of `lambda` are sorted with ascending cell ids + for element in eachindex(lambda) + controller_value = lambda[element] + if controller_value > 0 + push!(to_refine, leaf_cell_ids[element]) + elseif controller_value < 0 + push!(to_coarsen, leaf_cell_ids[element]) + end end - end - # Return true if there were any cells coarsened or refined, otherwise false - return has_changed -end - -function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::TreeMesh, - equations, dg::DG, - cache, cache_parabolic, - semi::SemidiscretizationHyperbolicParabolic, - t, iter; - only_refine = false, only_coarsen = false) - @unpack controller, adaptor = amr_callback - - u = wrap_array(u_ode, mesh, equations, dg, cache) - # Indicator kept based on hyperbolic variables - lambda = @trixi_timeit timer() "indicator" controller(u, mesh, equations, dg, cache, - t = t, iter = iter) - - if mpi_isparallel() - error("MPI has not been verified yet for parabolic AMR") - - # Collect lambda for all elements - lambda_global = Vector{eltype(lambda)}(undef, nelementsglobal(mesh, dg, cache)) - # Use parent because n_elements_by_rank is an OffsetArray - recvbuf = MPI.VBuffer(lambda_global, parent(cache.mpi_cache.n_elements_by_rank)) - MPI.Allgatherv!(lambda, recvbuf, mpi_comm()) - lambda = lambda_global - end + @trixi_timeit timer() "refine" if !only_coarsen && !isempty(to_refine) + # refine mesh + refined_original_cells = @trixi_timeit timer() "mesh" refine!( + mesh.tree, + to_refine + ) + + # Find all indices of elements whose cell ids are in refined_original_cells + # Note: This assumes same indices for hyperbolic and parabolic part. + elements_to_refine = findall( + in(refined_original_cells), + cache.elements.cell_ids + ) + + # refine solver + @trixi_timeit timer() "solver" refine!( + u_ode, adaptor, mesh, equations, dg, + cache, cache_parabolic, + elements_to_refine + ) + else + # If there is nothing to refine, create empty array for later use + refined_original_cells = Int[] + end - leaf_cell_ids = leaf_cells(mesh.tree) - @boundscheck begin - @assert axes(lambda)==axes(leaf_cell_ids) ("Indicator (axes = $(axes(lambda))) and leaf cell (axes = $(axes(leaf_cell_ids))) arrays have different axes") - end + @trixi_timeit timer() "coarsen" if !only_refine && !isempty(to_coarsen) + # Since the cells may have been shifted due to refinement, first we need to + # translate the old cell ids to the new cell ids + if !isempty(to_coarsen) + to_coarsen = original2refined(to_coarsen, refined_original_cells, mesh) + end + + # Next, determine the parent cells from which the fine cells are to be + # removed, since these are needed for the coarsen! function. However, since + # we only want to coarsen if *all* child cells are marked for coarsening, + # we count the coarsening indicators for each parent cell and only coarsen + # if all children are marked as such (i.e., where the count is 2^ndims). At + # the same time, check if a cell is marked for coarsening even though it is + # *not* a leaf cell -> this can only happen if it was refined due to 2:1 + # smoothing during the preceding refinement operation. + parents_to_coarsen = zeros(Int, length(mesh.tree)) + for cell_id in to_coarsen + # If cell has no parent, it cannot be coarsened + if !has_parent(mesh.tree, cell_id) + continue + end + + # If cell is not leaf (anymore), it cannot be coarsened + if !is_leaf(mesh.tree, cell_id) + continue + end + + # Increase count for parent cell + parent_id = mesh.tree.parent_ids[cell_id] + parents_to_coarsen[parent_id] += 1 + end + + # Extract only those parent cells for which all children should be coarsened + to_coarsen = collect(eachindex(parents_to_coarsen))[parents_to_coarsen .== 2^ndims(mesh)] + + # Finally, coarsen mesh + coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!( + mesh.tree, + to_coarsen + ) + + # Convert coarsened parent cell ids to the list of child cell ids that have + # been removed, since this is the information that is expected by the solver + removed_child_cells = zeros( + Int, + n_children_per_cell(mesh.tree) * + length(coarsened_original_cells) + ) + for (index, coarse_cell_id) in enumerate(coarsened_original_cells) + for child in 1:n_children_per_cell(mesh.tree) + removed_child_cells[n_children_per_cell(mesh.tree) * (index - 1) + child] = coarse_cell_id + + child + end + end + + # Find all indices of elements whose cell ids are in removed_child_cells + # Note: This assumes same indices for hyperbolic and parabolic part. + elements_to_remove = findall(in(removed_child_cells), cache.elements.cell_ids) + + # coarsen solver + @trixi_timeit timer() "solver" coarsen!( + u_ode, adaptor, mesh, equations, dg, + cache, cache_parabolic, + elements_to_remove + ) + else + # If there is nothing to coarsen, create empty array for later use + coarsened_original_cells = Int[] + end + + # Store whether there were any cells coarsened or refined + has_changed = !isempty(refined_original_cells) || !isempty(coarsened_original_cells) + if has_changed # TODO: Taal decide, where shall we set this? + # don't set it to has_changed since there can be changes from earlier calls + mesh.unsaved_changes = true + end - @unpack to_refine, to_coarsen = amr_callback.amr_cache - empty!(to_refine) - empty!(to_coarsen) - # Note: This assumes that the entries of `lambda` are sorted with ascending cell ids - for element in eachindex(lambda) - controller_value = lambda[element] - if controller_value > 0 - push!(to_refine, leaf_cell_ids[element]) - elseif controller_value < 0 - push!(to_coarsen, leaf_cell_ids[element]) + # Dynamically balance computational load by first repartitioning the mesh and then redistributing the cells/elements + if has_changed && mpi_isparallel() && amr_callback.dynamic_load_balancing + error("MPI has not been verified yet for parabolic AMR") + + @trixi_timeit timer() "dynamic load balancing" begin + old_mpi_ranks_per_cell = copy(mesh.tree.mpi_ranks) + + partition!(mesh) + + rebalance_solver!(u_ode, mesh, equations, dg, cache, old_mpi_ranks_per_cell) + end end + + # Return true if there were any cells coarsened or refined, otherwise false + return has_changed end - @trixi_timeit timer() "refine" if !only_coarsen && !isempty(to_refine) - # refine mesh - refined_original_cells = @trixi_timeit timer() "mesh" refine!(mesh.tree, - to_refine) - - # Find all indices of elements whose cell ids are in refined_original_cells - # Note: This assumes same indices for hyperbolic and parabolic part. - elements_to_refine = findall(in(refined_original_cells), - cache.elements.cell_ids) - - # refine solver - @trixi_timeit timer() "solver" refine!(u_ode, adaptor, mesh, equations, dg, - cache, cache_parabolic, - elements_to_refine) - else - # If there is nothing to refine, create empty array for later use - refined_original_cells = Int[] + # Copy controller values to quad user data storage, will be called below + function copy_to_quad_iter_volume(info, user_data) + info_pw = PointerWrapper(info) + + # Load tree from global trees array, one-based indexing + tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1) + # Quadrant numbering offset of this quadrant + offset = tree_pw.quadrants_offset[] + # Global quad ID + quad_id = offset + info_pw.quadid[] + + # Access user_data = lambda + user_data_pw = PointerWrapper(Int, user_data) + # Load controller_value = lambda[quad_id + 1] + controller_value = user_data_pw[quad_id + 1] + + # Access quadrant's user data ([global quad ID, controller_value]) + quad_data_pw = PointerWrapper(Int, info_pw.quad.p.user_data[]) + # Save controller value to quadrant's user data. + quad_data_pw[2] = controller_value + + return nothing end - @trixi_timeit timer() "coarsen" if !only_refine && !isempty(to_coarsen) - # Since the cells may have been shifted due to refinement, first we need to - # translate the old cell ids to the new cell ids - if !isempty(to_coarsen) - to_coarsen = original2refined(to_coarsen, refined_original_cells, mesh) + # specialized callback which includes the `cache_parabolic` argument + function (amr_callback::AMRCallback)( + u_ode::AbstractVector, mesh::P4estMesh, + equations, dg::DG, cache, cache_parabolic, + semi, + t, iter; + only_refine = false, only_coarsen = false, + passive_args = () + ) + @unpack controller, adaptor = amr_callback + + u = wrap_array(u_ode, mesh, equations, dg, cache) + lambda = @trixi_timeit timer() "indicator" controller( + u, mesh, equations, dg, cache, + t = t, iter = iter + ) + + @boundscheck begin + @assert axes(lambda) == (Base.OneTo(ncells(mesh)),) ("Indicator array (axes = $(axes(lambda))) and mesh cells (axes = $(Base.OneTo(ncells(mesh)))) have different axes") end - # Next, determine the parent cells from which the fine cells are to be - # removed, since these are needed for the coarsen! function. However, since - # we only want to coarsen if *all* child cells are marked for coarsening, - # we count the coarsening indicators for each parent cell and only coarsen - # if all children are marked as such (i.e., where the count is 2^ndims). At - # the same time, check if a cell is marked for coarsening even though it is - # *not* a leaf cell -> this can only happen if it was refined due to 2:1 - # smoothing during the preceding refinement operation. - parents_to_coarsen = zeros(Int, length(mesh.tree)) - for cell_id in to_coarsen - # If cell has no parent, it cannot be coarsened - if !has_parent(mesh.tree, cell_id) - continue + # Copy controller value of each quad to the quad's user data storage + iter_volume_c = cfunction(copy_to_quad_iter_volume, Val(ndims(mesh))) + + # The pointer to lambda will be interpreted as Ptr{Int} below + @assert lambda isa Vector{Int} + iterate_p4est(mesh.p4est, lambda; iter_volume_c = iter_volume_c) + + @trixi_timeit timer() "refine" if !only_coarsen + # Refine mesh + refined_original_cells = @trixi_timeit timer() "mesh" refine!(mesh) + + # Refine solver + @trixi_timeit timer() "solver" refine!( + u_ode, adaptor, mesh, equations, dg, + cache, cache_parabolic, + refined_original_cells + ) + for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args + @trixi_timeit timer() "passive solver" refine!( + p_u_ode, adaptor, p_mesh, + p_equations, + p_dg, p_cache, + refined_original_cells + ) end + else + # If there is nothing to refine, create empty array for later use + refined_original_cells = Int[] + end - # If cell is not leaf (anymore), it cannot be coarsened - if !is_leaf(mesh.tree, cell_id) - continue + @trixi_timeit timer() "coarsen" if !only_refine + # Coarsen mesh + coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!(mesh) + + # coarsen solver + @trixi_timeit timer() "solver" coarsen!( + u_ode, adaptor, mesh, equations, dg, + cache, cache_parabolic, + coarsened_original_cells + ) + for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args + @trixi_timeit timer() "passive solver" coarsen!( + p_u_ode, adaptor, p_mesh, + p_equations, + p_dg, p_cache, + coarsened_original_cells + ) end + else + # If there is nothing to coarsen, create empty array for later use + coarsened_original_cells = Int[] + end - # Increase count for parent cell - parent_id = mesh.tree.parent_ids[cell_id] - parents_to_coarsen[parent_id] += 1 + # Store whether there were any cells coarsened or refined and perform load balancing + has_changed = !isempty(refined_original_cells) || !isempty(coarsened_original_cells) + # Check if mesh changed on other processes + if mpi_isparallel() + has_changed = MPI.Allreduce!(Ref(has_changed), |, mpi_comm())[] end - # Extract only those parent cells for which all children should be coarsened - to_coarsen = collect(eachindex(parents_to_coarsen))[parents_to_coarsen .== 2^ndims(mesh)] - - # Finally, coarsen mesh - coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!(mesh.tree, - to_coarsen) - - # Convert coarsened parent cell ids to the list of child cell ids that have - # been removed, since this is the information that is expected by the solver - removed_child_cells = zeros(Int, - n_children_per_cell(mesh.tree) * - length(coarsened_original_cells)) - for (index, coarse_cell_id) in enumerate(coarsened_original_cells) - for child in 1:n_children_per_cell(mesh.tree) - removed_child_cells[n_children_per_cell(mesh.tree) * (index - 1) + child] = coarse_cell_id + - child + if has_changed # TODO: Taal decide, where shall we set this? + # don't set it to has_changed since there can be changes from earlier calls + mesh.unsaved_changes = true + + if mpi_isparallel() && amr_callback.dynamic_load_balancing + @trixi_timeit timer() "dynamic load balancing" begin + global_first_quadrant = unsafe_wrap( + Array, + unsafe_load(mesh.p4est).global_first_quadrant, + mpi_nranks() + 1 + ) + old_global_first_quadrant = copy(global_first_quadrant) + partition!(mesh) + rebalance_solver!( + u_ode, mesh, equations, dg, cache, + old_global_first_quadrant + ) + end + end + + reinitialize_boundaries!(semi.boundary_conditions, cache) + # if the semidiscretization also stores parabolic boundary conditions, + # reinitialize them after each refinement step as well. + if hasproperty(semi, :boundary_conditions_parabolic) + reinitialize_boundaries!(semi.boundary_conditions_parabolic, cache) end end - # Find all indices of elements whose cell ids are in removed_child_cells - # Note: This assumes same indices for hyperbolic and parabolic part. - elements_to_remove = findall(in(removed_child_cells), cache.elements.cell_ids) - - # coarsen solver - @trixi_timeit timer() "solver" coarsen!(u_ode, adaptor, mesh, equations, dg, - cache, cache_parabolic, - elements_to_remove) - else - # If there is nothing to coarsen, create empty array for later use - coarsened_original_cells = Int[] + # Return true if there were any cells coarsened or refined, otherwise false + return has_changed end - # Store whether there were any cells coarsened or refined - has_changed = !isempty(refined_original_cells) || !isempty(coarsened_original_cells) - if has_changed # TODO: Taal decide, where shall we set this? - # don't set it to has_changed since there can be changes from earlier calls - mesh.unsaved_changes = true + # 2D + function cfunction(::typeof(copy_to_quad_iter_volume), ::Val{2}) + @cfunction( + copy_to_quad_iter_volume, Cvoid, + (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}) + ) + end + # 3D + function cfunction(::typeof(copy_to_quad_iter_volume), ::Val{3}) + @cfunction( + copy_to_quad_iter_volume, Cvoid, + (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}) + ) end - # Dynamically balance computational load by first repartitioning the mesh and then redistributing the cells/elements - if has_changed && mpi_isparallel() && amr_callback.dynamic_load_balancing - error("MPI has not been verified yet for parabolic AMR") + function (amr_callback::AMRCallback)( + u_ode::AbstractVector, mesh::P4estMesh, + equations, dg::DG, cache, semi, + t, iter; + only_refine = false, only_coarsen = false, + passive_args = () + ) + @unpack controller, adaptor = amr_callback + + u = wrap_array(u_ode, mesh, equations, dg, cache) + lambda = @trixi_timeit timer() "indicator" controller( + u, mesh, equations, dg, cache, + t = t, iter = iter + ) + + @boundscheck begin + @assert axes(lambda) == (Base.OneTo(ncells(mesh)),) ("Indicator array (axes = $(axes(lambda))) and mesh cells (axes = $(Base.OneTo(ncells(mesh)))) have different axes") + end - @trixi_timeit timer() "dynamic load balancing" begin - old_mpi_ranks_per_cell = copy(mesh.tree.mpi_ranks) + # Copy controller value of each quad to the quad's user data storage + iter_volume_c = cfunction(copy_to_quad_iter_volume, Val(ndims(mesh))) + + # The pointer to lambda will be interpreted as Ptr{Int} above + @assert lambda isa Vector{Int} + iterate_p4est(mesh.p4est, lambda; iter_volume_c = iter_volume_c) + + @trixi_timeit timer() "refine" if !only_coarsen + # Refine mesh + refined_original_cells = @trixi_timeit timer() "mesh" refine!(mesh) + + # Refine solver + @trixi_timeit timer() "solver" refine!( + u_ode, adaptor, mesh, equations, dg, + cache, + refined_original_cells + ) + for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args + @trixi_timeit timer() "passive solver" refine!( + p_u_ode, adaptor, p_mesh, + p_equations, + p_dg, p_cache, + refined_original_cells + ) + end + else + # If there is nothing to refine, create empty array for later use + refined_original_cells = Int[] + end - partition!(mesh) + @trixi_timeit timer() "coarsen" if !only_refine + # Coarsen mesh + coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!(mesh) + + # coarsen solver + @trixi_timeit timer() "solver" coarsen!( + u_ode, adaptor, mesh, equations, dg, + cache, + coarsened_original_cells + ) + for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args + @trixi_timeit timer() "passive solver" coarsen!( + p_u_ode, adaptor, p_mesh, + p_equations, + p_dg, p_cache, + coarsened_original_cells + ) + end + else + # If there is nothing to coarsen, create empty array for later use + coarsened_original_cells = Int[] + end - rebalance_solver!(u_ode, mesh, equations, dg, cache, old_mpi_ranks_per_cell) + # Store whether there were any cells coarsened or refined and perform load balancing + has_changed = !isempty(refined_original_cells) || !isempty(coarsened_original_cells) + # Check if mesh changed on other processes + if mpi_isparallel() + has_changed = MPI.Allreduce!(Ref(has_changed), |, mpi_comm())[] end - end - # Return true if there were any cells coarsened or refined, otherwise false - return has_changed -end - -# Copy controller values to quad user data storage, will be called below -function copy_to_quad_iter_volume(info, user_data) - info_pw = PointerWrapper(info) - - # Load tree from global trees array, one-based indexing - tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1) - # Quadrant numbering offset of this quadrant - offset = tree_pw.quadrants_offset[] - # Global quad ID - quad_id = offset + info_pw.quadid[] - - # Access user_data = lambda - user_data_pw = PointerWrapper(Int, user_data) - # Load controller_value = lambda[quad_id + 1] - controller_value = user_data_pw[quad_id + 1] - - # Access quadrant's user data ([global quad ID, controller_value]) - quad_data_pw = PointerWrapper(Int, info_pw.quad.p.user_data[]) - # Save controller value to quadrant's user data. - quad_data_pw[2] = controller_value - - return nothing -end - -# specialized callback which includes the `cache_parabolic` argument -function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::P4estMesh, - equations, dg::DG, cache, cache_parabolic, - semi, - t, iter; - only_refine = false, only_coarsen = false, - passive_args = ()) - @unpack controller, adaptor = amr_callback - - u = wrap_array(u_ode, mesh, equations, dg, cache) - lambda = @trixi_timeit timer() "indicator" controller(u, mesh, equations, dg, cache, - t = t, iter = iter) - - @boundscheck begin - @assert axes(lambda)==(Base.OneTo(ncells(mesh)),) ("Indicator array (axes = $(axes(lambda))) and mesh cells (axes = $(Base.OneTo(ncells(mesh)))) have different axes") - end + if has_changed # TODO: Taal decide, where shall we set this? + # don't set it to has_changed since there can be changes from earlier calls + mesh.unsaved_changes = true + + if mpi_isparallel() && amr_callback.dynamic_load_balancing + @trixi_timeit timer() "dynamic load balancing" begin + global_first_quadrant = unsafe_wrap( + Array, + unsafe_load(mesh.p4est).global_first_quadrant, + mpi_nranks() + 1 + ) + old_global_first_quadrant = copy(global_first_quadrant) + partition!(mesh) + rebalance_solver!( + u_ode, mesh, equations, dg, cache, + old_global_first_quadrant + ) + end + end - # Copy controller value of each quad to the quad's user data storage - iter_volume_c = cfunction(copy_to_quad_iter_volume, Val(ndims(mesh))) - - # The pointer to lambda will be interpreted as Ptr{Int} below - @assert lambda isa Vector{Int} - iterate_p4est(mesh.p4est, lambda; iter_volume_c = iter_volume_c) - - @trixi_timeit timer() "refine" if !only_coarsen - # Refine mesh - refined_original_cells = @trixi_timeit timer() "mesh" refine!(mesh) - - # Refine solver - @trixi_timeit timer() "solver" refine!(u_ode, adaptor, mesh, equations, dg, - cache, cache_parabolic, - refined_original_cells) - for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args - @trixi_timeit timer() "passive solver" refine!(p_u_ode, adaptor, p_mesh, - p_equations, - p_dg, p_cache, - refined_original_cells) + reinitialize_boundaries!(semi.boundary_conditions, cache) end - else - # If there is nothing to refine, create empty array for later use - refined_original_cells = Int[] + + # Return true if there were any cells coarsened or refined, otherwise false + return has_changed end - @trixi_timeit timer() "coarsen" if !only_refine - # Coarsen mesh - coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!(mesh) - - # coarsen solver - @trixi_timeit timer() "solver" coarsen!(u_ode, adaptor, mesh, equations, dg, - cache, cache_parabolic, - coarsened_original_cells) - for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args - @trixi_timeit timer() "passive solver" coarsen!(p_u_ode, adaptor, p_mesh, - p_equations, - p_dg, p_cache, - coarsened_original_cells) + function (amr_callback::AMRCallback)( + u_ode::AbstractVector, mesh::T8codeMesh, + equations, dg::DG, cache, semi, + t, iter; + only_refine = false, only_coarsen = false, + passive_args = () + ) + has_changed = false + + @unpack controller, adaptor = amr_callback + + u = wrap_array(u_ode, mesh, equations, dg, cache) + indicators = @trixi_timeit timer() "indicator" controller( + u, mesh, equations, dg, + cache, t = t, iter = iter + ) + + if only_coarsen + indicators[indicators .> 0] .= 0 end - else - # If there is nothing to coarsen, create empty array for later use - coarsened_original_cells = Int[] - end - # Store whether there were any cells coarsened or refined and perform load balancing - has_changed = !isempty(refined_original_cells) || !isempty(coarsened_original_cells) - # Check if mesh changed on other processes - if mpi_isparallel() - has_changed = MPI.Allreduce!(Ref(has_changed), |, mpi_comm())[] - end + if only_refine + indicators[indicators .< 0] .= 0 + end - if has_changed # TODO: Taal decide, where shall we set this? - # don't set it to has_changed since there can be changes from earlier calls - mesh.unsaved_changes = true + @boundscheck begin + @assert axes(indicators) == (Base.OneTo(ncells(mesh)),) ("Indicator array (axes = $(axes(indicators))) and mesh cells (axes = $(Base.OneTo(ncells(mesh)))) have different axes") + end - if mpi_isparallel() && amr_callback.dynamic_load_balancing - @trixi_timeit timer() "dynamic load balancing" begin - global_first_quadrant = unsafe_wrap(Array, - unsafe_load(mesh.p4est).global_first_quadrant, - mpi_nranks() + 1) - old_global_first_quadrant = copy(global_first_quadrant) - partition!(mesh) - rebalance_solver!(u_ode, mesh, equations, dg, cache, - old_global_first_quadrant) + @trixi_timeit timer() "adapt" begin + difference = @trixi_timeit timer() "mesh" trixi_t8_adapt!(mesh, indicators) + + # Store whether there were any cells coarsened or refined and perform load balancing. + has_changed = any(difference .!= 0) + + # Check if mesh changed on other processes + if mpi_isparallel() + has_changed = MPI.Allreduce!(Ref(has_changed), |, mpi_comm())[] + end + + if has_changed + @trixi_timeit timer() "solver" adapt!( + u_ode, adaptor, mesh, equations, dg, + cache, difference + ) end end - reinitialize_boundaries!(semi.boundary_conditions, cache) - # if the semidiscretization also stores parabolic boundary conditions, - # reinitialize them after each refinement step as well. - if hasproperty(semi, :boundary_conditions_parabolic) - reinitialize_boundaries!(semi.boundary_conditions_parabolic, cache) + if has_changed + if mpi_isparallel() && amr_callback.dynamic_load_balancing + @trixi_timeit timer() "dynamic load balancing" begin + old_global_first_element_ids = get_global_first_element_ids(mesh) + partition!(mesh) + rebalance_solver!( + u_ode, mesh, equations, dg, cache, + old_global_first_element_ids + ) + end + end + + reinitialize_boundaries!(semi.boundary_conditions, cache) end + + # Return true if there were any cells coarsened or refined, otherwise false. + return has_changed end - # Return true if there were any cells coarsened or refined, otherwise false - return has_changed -end - -# 2D -function cfunction(::typeof(copy_to_quad_iter_volume), ::Val{2}) - @cfunction(copy_to_quad_iter_volume, Cvoid, - (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(copy_to_quad_iter_volume), ::Val{3}) - @cfunction(copy_to_quad_iter_volume, Cvoid, - (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid})) -end - -function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::P4estMesh, - equations, dg::DG, cache, semi, - t, iter; - only_refine = false, only_coarsen = false, - passive_args = ()) - @unpack controller, adaptor = amr_callback - - u = wrap_array(u_ode, mesh, equations, dg, cache) - lambda = @trixi_timeit timer() "indicator" controller(u, mesh, equations, dg, cache, - t = t, iter = iter) - - @boundscheck begin - @assert axes(lambda)==(Base.OneTo(ncells(mesh)),) ("Indicator array (axes = $(axes(lambda))) and mesh cells (axes = $(Base.OneTo(ncells(mesh)))) have different axes") + function reinitialize_boundaries!( + boundary_conditions::UnstructuredSortedBoundaryTypes, + cache + ) + # Reinitialize boundary types container because boundaries may have changed. + initialize!(boundary_conditions, cache) end - # Copy controller value of each quad to the quad's user data storage - iter_volume_c = cfunction(copy_to_quad_iter_volume, Val(ndims(mesh))) - - # The pointer to lambda will be interpreted as Ptr{Int} above - @assert lambda isa Vector{Int} - iterate_p4est(mesh.p4est, lambda; iter_volume_c = iter_volume_c) - - @trixi_timeit timer() "refine" if !only_coarsen - # Refine mesh - refined_original_cells = @trixi_timeit timer() "mesh" refine!(mesh) - - # Refine solver - @trixi_timeit timer() "solver" refine!(u_ode, adaptor, mesh, equations, dg, - cache, - refined_original_cells) - for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args - @trixi_timeit timer() "passive solver" refine!(p_u_ode, adaptor, p_mesh, - p_equations, - p_dg, p_cache, - refined_original_cells) - end - else - # If there is nothing to refine, create empty array for later use - refined_original_cells = Int[] + function reinitialize_boundaries!(boundary_conditions, cache) + return boundary_conditions end - @trixi_timeit timer() "coarsen" if !only_refine - # Coarsen mesh - coarsened_original_cells = @trixi_timeit timer() "mesh" coarsen!(mesh) - - # coarsen solver - @trixi_timeit timer() "solver" coarsen!(u_ode, adaptor, mesh, equations, dg, - cache, - coarsened_original_cells) - for (p_u_ode, p_mesh, p_equations, p_dg, p_cache) in passive_args - @trixi_timeit timer() "passive solver" coarsen!(p_u_ode, adaptor, p_mesh, - p_equations, - p_dg, p_cache, - coarsened_original_cells) + # After refining cells, shift original cell ids to match new locations + # Note: Assumes sorted lists of original and refined cell ids! + # Note: `mesh` is only required to extract ndims + function original2refined(original_cell_ids, refined_original_cells, mesh) + # Sanity check + @assert issorted(original_cell_ids) "`original_cell_ids` not sorted" + @assert issorted(refined_original_cells) "`refined_cell_ids` not sorted" + + # Create array with original cell ids (not yet shifted) + shifted_cell_ids = collect(1:original_cell_ids[end]) + + # Loop over refined original cells and apply shift for all following cells + for cell_id in refined_original_cells + # Only calculate shifts for cell ids that are relevant + if cell_id > length(shifted_cell_ids) + break + end + + # Shift all subsequent cells by 2^ndims ids + shifted_cell_ids[(cell_id + 1):end] .+= 2^ndims(mesh) end - else - # If there is nothing to coarsen, create empty array for later use - coarsened_original_cells = Int[] + + # Convert original cell ids to their shifted values + return shifted_cell_ids[original_cell_ids] end - # Store whether there were any cells coarsened or refined and perform load balancing - has_changed = !isempty(refined_original_cells) || !isempty(coarsened_original_cells) - # Check if mesh changed on other processes - if mpi_isparallel() - has_changed = MPI.Allreduce!(Ref(has_changed), |, mpi_comm())[] + """ + ControllerThreeLevel(semi, indicator; base_level=1, + med_level=base_level, med_threshold=0.0, + max_level=base_level, max_threshold=1.0) + + An AMR controller based on three levels (in descending order of precedence): + - set the target level to `max_level` if `indicator > max_threshold` + - set the target level to `med_level` if `indicator > med_threshold`; + if `med_level < 0`, set the target level to the current level + - set the target level to `base_level` otherwise + """ + struct ControllerThreeLevel{RealT <: Real, Indicator, Cache} + base_level::Int + med_level::Int + max_level::Int + med_threshold::RealT + max_threshold::RealT + indicator::Indicator + cache::Cache end - if has_changed # TODO: Taal decide, where shall we set this? - # don't set it to has_changed since there can be changes from earlier calls - mesh.unsaved_changes = true + function ControllerThreeLevel( + semi, indicator; base_level = 1, + med_level = base_level, med_threshold = 0.0, + max_level = base_level, max_threshold = 1.0 + ) + med_threshold, max_threshold = promote(med_threshold, max_threshold) + cache = create_cache(ControllerThreeLevel, semi) + ControllerThreeLevel{typeof(max_threshold), typeof(indicator), typeof(cache)}( + base_level, + med_level, + max_level, + med_threshold, + max_threshold, + indicator, + cache + ) + end - if mpi_isparallel() && amr_callback.dynamic_load_balancing - @trixi_timeit timer() "dynamic load balancing" begin - global_first_quadrant = unsafe_wrap(Array, - unsafe_load(mesh.p4est).global_first_quadrant, - mpi_nranks() + 1) - old_global_first_quadrant = copy(global_first_quadrant) - partition!(mesh) - rebalance_solver!(u_ode, mesh, equations, dg, cache, - old_global_first_quadrant) - end - end + function create_cache(indicator_type::Type{ControllerThreeLevel}, semi) + create_cache(indicator_type, mesh_equations_solver_cache(semi)...) + end - reinitialize_boundaries!(semi.boundary_conditions, cache) + function Base.show(io::IO, controller::ControllerThreeLevel) + @nospecialize controller # reduce precompilation time + + print(io, "ControllerThreeLevel(") + print(io, controller.indicator) + print(io, ", base_level=", controller.base_level) + print(io, ", med_level=", controller.med_level) + print(io, ", max_level=", controller.max_level) + print(io, ", med_threshold=", controller.med_threshold) + print(io, ", max_threshold=", controller.max_threshold) + print(io, ")") end - # Return true if there were any cells coarsened or refined, otherwise false - return has_changed -end + function Base.show(io::IO, mime::MIME"text/plain", controller::ControllerThreeLevel) + @nospecialize controller # reduce precompilation time -function (amr_callback::AMRCallback)(u_ode::AbstractVector, mesh::T8codeMesh, - equations, dg::DG, cache, semi, - t, iter; - only_refine = false, only_coarsen = false, - passive_args = ()) - has_changed = false + if get(io, :compact, false) + show(io, controller) + else + summary_header(io, "ControllerThreeLevel") + summary_line(io, "indicator", controller.indicator |> typeof |> nameof) + show(increment_indent(io), mime, controller.indicator) + summary_line(io, "base_level", controller.base_level) + summary_line(io, "med_level", controller.med_level) + summary_line(io, "max_level", controller.max_level) + summary_line(io, "med_threshold", controller.med_threshold) + summary_line(io, "max_threshold", controller.max_threshold) + summary_footer(io) + end + end + + function get_element_variables!( + element_variables, u, mesh, equations, solver, cache, + controller::ControllerThreeLevel, + amr_callback::AMRCallback; + kwargs... + ) + # call the indicator to get up-to-date values for IO + controller.indicator(u, mesh, equations, solver, cache; kwargs...) + get_element_variables!(element_variables, controller.indicator, amr_callback) + end - @unpack controller, adaptor = amr_callback + function get_element_variables!( + element_variables, indicator::AbstractIndicator, + ::AMRCallback + ) + element_variables[:indicator_amr] = indicator.cache.alpha + return nothing + end - u = wrap_array(u_ode, mesh, equations, dg, cache) - indicators = @trixi_timeit timer() "indicator" controller(u, mesh, equations, dg, - cache, t = t, iter = iter) + function current_element_levels(mesh::TreeMesh, solver, cache) + cell_ids = cache.elements.cell_ids[eachelement(solver, cache)] - if only_coarsen - indicators[indicators .> 0] .= 0 + return mesh.tree.levels[cell_ids] end - if only_refine - indicators[indicators .< 0] .= 0 + function extract_levels_iter_volume(info, user_data) + info_pw = PointerWrapper(info) + + # Load tree from global trees array, one-based indexing + tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1) + # Quadrant numbering offset of this quadrant + offset = tree_pw.quadrants_offset[] + # Global quad ID + quad_id = offset + info_pw.quadid[] + # Julia element ID + element_id = quad_id + 1 + + current_level = info_pw.quad.level[] + + # Unpack user_data = current_levels and save current element level + pw = PointerWrapper(Int, user_data) + pw[element_id] = current_level + + return nothing end - @boundscheck begin - @assert axes(indicators)==(Base.OneTo(ncells(mesh)),) ("Indicator array (axes = $(axes(indicators))) and mesh cells (axes = $(Base.OneTo(ncells(mesh)))) have different axes") + # 2D + function cfunction(::typeof(extract_levels_iter_volume), ::Val{2}) + @cfunction( + extract_levels_iter_volume, Cvoid, + (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}) + ) + end + # 3D + function cfunction(::typeof(extract_levels_iter_volume), ::Val{3}) + @cfunction( + extract_levels_iter_volume, Cvoid, + (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}) + ) end - @trixi_timeit timer() "adapt" begin - difference = @trixi_timeit timer() "mesh" trixi_t8_adapt!(mesh, indicators) + function current_element_levels(mesh::P4estMesh, solver, cache) + current_levels = Vector{Int}(undef, nelements(solver, cache)) - # Store whether there were any cells coarsened or refined and perform load balancing. - has_changed = any(difference .!= 0) + iter_volume_c = cfunction(extract_levels_iter_volume, Val(ndims(mesh))) + iterate_p4est(mesh.p4est, current_levels; iter_volume_c = iter_volume_c) - # Check if mesh changed on other processes - if mpi_isparallel() - has_changed = MPI.Allreduce!(Ref(has_changed), |, mpi_comm())[] - end + return current_levels + end - if has_changed - @trixi_timeit timer() "solver" adapt!(u_ode, adaptor, mesh, equations, dg, - cache, difference) - end + function current_element_levels(mesh::T8codeMesh, solver, cache) + return trixi_t8_get_local_element_levels(mesh.forest) end - if has_changed - if mpi_isparallel() && amr_callback.dynamic_load_balancing - @trixi_timeit timer() "dynamic load balancing" begin - old_global_first_element_ids = get_global_first_element_ids(mesh) - partition!(mesh) - rebalance_solver!(u_ode, mesh, equations, dg, cache, - old_global_first_element_ids) + # TODO: Taal refactor, merge the two loops of ControllerThreeLevel and IndicatorLöhner etc.? + # But that would remove the simplest possibility to write that stuff to a file... + # We could of course implement some additional logic and workarounds, but is it worth the effort? + function (controller::ControllerThreeLevel)( + u::AbstractArray{<:Any}, + mesh, equations, dg::DG, cache; + kwargs... + ) + @unpack controller_value = controller.cache + resize!(controller_value, nelements(dg, cache)) + + alpha = controller.indicator(u, mesh, equations, dg, cache; kwargs...) + current_levels = current_element_levels(mesh, dg, cache) + + @threaded for element in eachelement(dg, cache) + current_level = current_levels[element] + + # set target level + target_level = current_level + if alpha[element] > controller.max_threshold + target_level = controller.max_level + elseif alpha[element] > controller.med_threshold + if controller.med_level > 0 + target_level = controller.med_level + # otherwise, target_level = current_level + # set med_level = -1 to implicitly use med_level = current_level + end + else + target_level = controller.base_level + end + + # compare target level with actual level to set controller + if current_level < target_level + controller_value[element] = 1 # refine! + elseif current_level > target_level + controller_value[element] = -1 # coarsen! + else + controller_value[element] = 0 # we're good end end - reinitialize_boundaries!(semi.boundary_conditions, cache) + return controller_value end - # Return true if there were any cells coarsened or refined, otherwise false. - return has_changed -end - -function reinitialize_boundaries!(boundary_conditions::UnstructuredSortedBoundaryTypes, - cache) - # Reinitialize boundary types container because boundaries may have changed. - initialize!(boundary_conditions, cache) -end - -function reinitialize_boundaries!(boundary_conditions, cache) - return boundary_conditions -end - -# After refining cells, shift original cell ids to match new locations -# Note: Assumes sorted lists of original and refined cell ids! -# Note: `mesh` is only required to extract ndims -function original2refined(original_cell_ids, refined_original_cells, mesh) - # Sanity check - @assert issorted(original_cell_ids) "`original_cell_ids` not sorted" - @assert issorted(refined_original_cells) "`refined_cell_ids` not sorted" - - # Create array with original cell ids (not yet shifted) - shifted_cell_ids = collect(1:original_cell_ids[end]) - - # Loop over refined original cells and apply shift for all following cells - for cell_id in refined_original_cells - # Only calculate shifts for cell ids that are relevant - if cell_id > length(shifted_cell_ids) - break - end + """ + ControllerThreeLevelCombined(semi, indicator_primary, indicator_secondary; + base_level=1, + med_level=base_level, med_threshold=0.0, + max_level=base_level, max_threshold=1.0, + max_threshold_secondary=1.0) + + An AMR controller based on three levels (in descending order of precedence): + - set the target level to `max_level` if `indicator_primary > max_threshold` + - set the target level to `med_level` if `indicator_primary > med_threshold`; + if `med_level < 0`, set the target level to the current level + - set the target level to `base_level` otherwise + If `indicator_secondary >= max_threshold_secondary`, + set the target level to `max_level`. + """ + struct ControllerThreeLevelCombined{ + RealT <: Real, IndicatorPrimary, IndicatorSecondary, + Cache, + } + base_level::Int + med_level::Int + max_level::Int + med_threshold::RealT + max_threshold::RealT + max_threshold_secondary::RealT + indicator_primary::IndicatorPrimary + indicator_secondary::IndicatorSecondary + cache::Cache + end - # Shift all subsequent cells by 2^ndims ids - shifted_cell_ids[(cell_id + 1):end] .+= 2^ndims(mesh) + function ControllerThreeLevelCombined( + semi, indicator_primary, indicator_secondary; + base_level = 1, + med_level = base_level, med_threshold = 0.0, + max_level = base_level, max_threshold = 1.0, + max_threshold_secondary = 1.0 + ) + med_threshold, max_threshold, max_threshold_secondary = promote( + med_threshold, + max_threshold, + max_threshold_secondary + ) + cache = create_cache(ControllerThreeLevelCombined, semi) + ControllerThreeLevelCombined{ + typeof(max_threshold), typeof(indicator_primary), + typeof(indicator_secondary), typeof(cache), + }( + base_level, + med_level, + max_level, + med_threshold, + max_threshold, + max_threshold_secondary, + indicator_primary, + indicator_secondary, + cache + ) end - # Convert original cell ids to their shifted values - return shifted_cell_ids[original_cell_ids] -end - -""" - ControllerThreeLevel(semi, indicator; base_level=1, - med_level=base_level, med_threshold=0.0, - max_level=base_level, max_threshold=1.0) - -An AMR controller based on three levels (in descending order of precedence): -- set the target level to `max_level` if `indicator > max_threshold` -- set the target level to `med_level` if `indicator > med_threshold`; - if `med_level < 0`, set the target level to the current level -- set the target level to `base_level` otherwise -""" -struct ControllerThreeLevel{RealT <: Real, Indicator, Cache} - base_level::Int - med_level::Int - max_level::Int - med_threshold::RealT - max_threshold::RealT - indicator::Indicator - cache::Cache -end - -function ControllerThreeLevel(semi, indicator; base_level = 1, - med_level = base_level, med_threshold = 0.0, - max_level = base_level, max_threshold = 1.0) - med_threshold, max_threshold = promote(med_threshold, max_threshold) - cache = create_cache(ControllerThreeLevel, semi) - ControllerThreeLevel{typeof(max_threshold), typeof(indicator), typeof(cache)}(base_level, - med_level, - max_level, - med_threshold, - max_threshold, - indicator, - cache) -end - -function create_cache(indicator_type::Type{ControllerThreeLevel}, semi) - create_cache(indicator_type, mesh_equations_solver_cache(semi)...) -end - -function Base.show(io::IO, controller::ControllerThreeLevel) - @nospecialize controller # reduce precompilation time - - print(io, "ControllerThreeLevel(") - print(io, controller.indicator) - print(io, ", base_level=", controller.base_level) - print(io, ", med_level=", controller.med_level) - print(io, ", max_level=", controller.max_level) - print(io, ", med_threshold=", controller.med_threshold) - print(io, ", max_threshold=", controller.max_threshold) - print(io, ")") -end - -function Base.show(io::IO, mime::MIME"text/plain", controller::ControllerThreeLevel) - @nospecialize controller # reduce precompilation time - - if get(io, :compact, false) - show(io, controller) - else - summary_header(io, "ControllerThreeLevel") - summary_line(io, "indicator", controller.indicator |> typeof |> nameof) - show(increment_indent(io), mime, controller.indicator) - summary_line(io, "base_level", controller.base_level) - summary_line(io, "med_level", controller.med_level) - summary_line(io, "max_level", controller.max_level) - summary_line(io, "med_threshold", controller.med_threshold) - summary_line(io, "max_threshold", controller.max_threshold) - summary_footer(io) + function create_cache(indicator_type::Type{ControllerThreeLevelCombined}, semi) + create_cache(indicator_type, mesh_equations_solver_cache(semi)...) end -end - -function get_element_variables!(element_variables, u, mesh, equations, solver, cache, - controller::ControllerThreeLevel, - amr_callback::AMRCallback; - kwargs...) - # call the indicator to get up-to-date values for IO - controller.indicator(u, mesh, equations, solver, cache; kwargs...) - get_element_variables!(element_variables, controller.indicator, amr_callback) -end - -function get_element_variables!(element_variables, indicator::AbstractIndicator, - ::AMRCallback) - element_variables[:indicator_amr] = indicator.cache.alpha - return nothing -end - -function current_element_levels(mesh::TreeMesh, solver, cache) - cell_ids = cache.elements.cell_ids[eachelement(solver, cache)] - - return mesh.tree.levels[cell_ids] -end - -function extract_levels_iter_volume(info, user_data) - info_pw = PointerWrapper(info) - - # Load tree from global trees array, one-based indexing - tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1) - # Quadrant numbering offset of this quadrant - offset = tree_pw.quadrants_offset[] - # Global quad ID - quad_id = offset + info_pw.quadid[] - # Julia element ID - element_id = quad_id + 1 - - current_level = info_pw.quad.level[] - - # Unpack user_data = current_levels and save current element level - pw = PointerWrapper(Int, user_data) - pw[element_id] = current_level - - return nothing -end - -# 2D -function cfunction(::typeof(extract_levels_iter_volume), ::Val{2}) - @cfunction(extract_levels_iter_volume, Cvoid, - (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(extract_levels_iter_volume), ::Val{3}) - @cfunction(extract_levels_iter_volume, Cvoid, - (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid})) -end - -function current_element_levels(mesh::P4estMesh, solver, cache) - current_levels = Vector{Int}(undef, nelements(solver, cache)) - - iter_volume_c = cfunction(extract_levels_iter_volume, Val(ndims(mesh))) - iterate_p4est(mesh.p4est, current_levels; iter_volume_c = iter_volume_c) - - return current_levels -end - -function current_element_levels(mesh::T8codeMesh, solver, cache) - return trixi_t8_get_local_element_levels(mesh.forest) -end - -# TODO: Taal refactor, merge the two loops of ControllerThreeLevel and IndicatorLöhner etc.? -# But that would remove the simplest possibility to write that stuff to a file... -# We could of course implement some additional logic and workarounds, but is it worth the effort? -function (controller::ControllerThreeLevel)(u::AbstractArray{<:Any}, - mesh, equations, dg::DG, cache; - kwargs...) - @unpack controller_value = controller.cache - resize!(controller_value, nelements(dg, cache)) - - alpha = controller.indicator(u, mesh, equations, dg, cache; kwargs...) - current_levels = current_element_levels(mesh, dg, cache) - - @threaded for element in eachelement(dg, cache) - current_level = current_levels[element] - - # set target level - target_level = current_level - if alpha[element] > controller.max_threshold - target_level = controller.max_level - elseif alpha[element] > controller.med_threshold - if controller.med_level > 0 - target_level = controller.med_level - # otherwise, target_level = current_level - # set med_level = -1 to implicitly use med_level = current_level - end - else - target_level = controller.base_level - end - # compare target level with actual level to set controller - if current_level < target_level - controller_value[element] = 1 # refine! - elseif current_level > target_level - controller_value[element] = -1 # coarsen! + function Base.show(io::IO, controller::ControllerThreeLevelCombined) + @nospecialize controller # reduce precompilation time + + print(io, "ControllerThreeLevelCombined(") + print(io, controller.indicator_primary) + print(io, ", ", controller.indicator_secondary) + print(io, ", base_level=", controller.base_level) + print(io, ", med_level=", controller.med_level) + print(io, ", max_level=", controller.max_level) + print(io, ", med_threshold=", controller.med_threshold) + print(io, ", max_threshold_secondary=", controller.max_threshold_secondary) + print(io, ")") + end + + function Base.show( + io::IO, mime::MIME"text/plain", + controller::ControllerThreeLevelCombined + ) + @nospecialize controller # reduce precompilation time + + if get(io, :compact, false) + show(io, controller) else - controller_value[element] = 0 # we're good + summary_header(io, "ControllerThreeLevelCombined") + summary_line( + io, "primary indicator", + controller.indicator_primary |> typeof |> nameof + ) + show(increment_indent(io), mime, controller.indicator_primary) + summary_line( + io, "secondary indicator", + controller.indicator_secondary |> typeof |> nameof + ) + show(increment_indent(io), mime, controller.indicator_secondary) + summary_line(io, "base_level", controller.base_level) + summary_line(io, "med_level", controller.med_level) + summary_line(io, "max_level", controller.max_level) + summary_line(io, "med_threshold", controller.med_threshold) + summary_line(io, "max_threshold", controller.max_threshold) + summary_line(io, "max_threshold_secondary", controller.max_threshold_secondary) + summary_footer(io) end end - return controller_value -end - -""" - ControllerThreeLevelCombined(semi, indicator_primary, indicator_secondary; - base_level=1, - med_level=base_level, med_threshold=0.0, - max_level=base_level, max_threshold=1.0, - max_threshold_secondary=1.0) - -An AMR controller based on three levels (in descending order of precedence): -- set the target level to `max_level` if `indicator_primary > max_threshold` -- set the target level to `med_level` if `indicator_primary > med_threshold`; - if `med_level < 0`, set the target level to the current level -- set the target level to `base_level` otherwise -If `indicator_secondary >= max_threshold_secondary`, -set the target level to `max_level`. -""" -struct ControllerThreeLevelCombined{RealT <: Real, IndicatorPrimary, IndicatorSecondary, - Cache} - base_level::Int - med_level::Int - max_level::Int - med_threshold::RealT - max_threshold::RealT - max_threshold_secondary::RealT - indicator_primary::IndicatorPrimary - indicator_secondary::IndicatorSecondary - cache::Cache -end - -function ControllerThreeLevelCombined(semi, indicator_primary, indicator_secondary; - base_level = 1, - med_level = base_level, med_threshold = 0.0, - max_level = base_level, max_threshold = 1.0, - max_threshold_secondary = 1.0) - med_threshold, max_threshold, max_threshold_secondary = promote(med_threshold, - max_threshold, - max_threshold_secondary) - cache = create_cache(ControllerThreeLevelCombined, semi) - ControllerThreeLevelCombined{typeof(max_threshold), typeof(indicator_primary), - typeof(indicator_secondary), typeof(cache)}(base_level, - med_level, - max_level, - med_threshold, - max_threshold, - max_threshold_secondary, - indicator_primary, - indicator_secondary, - cache) -end - -function create_cache(indicator_type::Type{ControllerThreeLevelCombined}, semi) - create_cache(indicator_type, mesh_equations_solver_cache(semi)...) -end - -function Base.show(io::IO, controller::ControllerThreeLevelCombined) - @nospecialize controller # reduce precompilation time - - print(io, "ControllerThreeLevelCombined(") - print(io, controller.indicator_primary) - print(io, ", ", controller.indicator_secondary) - print(io, ", base_level=", controller.base_level) - print(io, ", med_level=", controller.med_level) - print(io, ", max_level=", controller.max_level) - print(io, ", med_threshold=", controller.med_threshold) - print(io, ", max_threshold_secondary=", controller.max_threshold_secondary) - print(io, ")") -end - -function Base.show(io::IO, mime::MIME"text/plain", - controller::ControllerThreeLevelCombined) - @nospecialize controller # reduce precompilation time - - if get(io, :compact, false) - show(io, controller) - else - summary_header(io, "ControllerThreeLevelCombined") - summary_line(io, "primary indicator", - controller.indicator_primary |> typeof |> nameof) - show(increment_indent(io), mime, controller.indicator_primary) - summary_line(io, "secondary indicator", - controller.indicator_secondary |> typeof |> nameof) - show(increment_indent(io), mime, controller.indicator_secondary) - summary_line(io, "base_level", controller.base_level) - summary_line(io, "med_level", controller.med_level) - summary_line(io, "max_level", controller.max_level) - summary_line(io, "med_threshold", controller.med_threshold) - summary_line(io, "max_threshold", controller.max_threshold) - summary_line(io, "max_threshold_secondary", controller.max_threshold_secondary) - summary_footer(io) + function get_element_variables!( + element_variables, u, mesh, equations, solver, cache, + controller::ControllerThreeLevelCombined, + amr_callback::AMRCallback; + kwargs... + ) + # call the indicator to get up-to-date values for IO + controller.indicator_primary(u, mesh, equations, solver, cache; kwargs...) + get_element_variables!( + element_variables, controller.indicator_primary, + amr_callback + ) end -end - -function get_element_variables!(element_variables, u, mesh, equations, solver, cache, - controller::ControllerThreeLevelCombined, - amr_callback::AMRCallback; - kwargs...) - # call the indicator to get up-to-date values for IO - controller.indicator_primary(u, mesh, equations, solver, cache; kwargs...) - get_element_variables!(element_variables, controller.indicator_primary, - amr_callback) -end - -function (controller::ControllerThreeLevelCombined)(u::AbstractArray{<:Any}, - mesh, equations, dg::DG, cache; - kwargs...) - @unpack controller_value = controller.cache - resize!(controller_value, nelements(dg, cache)) - - alpha = controller.indicator_primary(u, mesh, equations, dg, cache; kwargs...) - alpha_secondary = controller.indicator_secondary(u, mesh, equations, dg, cache) - - current_levels = current_element_levels(mesh, dg, cache) - - @threaded for element in eachelement(dg, cache) - current_level = current_levels[element] - - # set target level - target_level = current_level - if alpha[element] > controller.max_threshold - target_level = controller.max_level - elseif alpha[element] > controller.med_threshold - if controller.med_level > 0 - target_level = controller.med_level - # otherwise, target_level = current_level - # set med_level = -1 to implicitly use med_level = current_level + + function (controller::ControllerThreeLevelCombined)( + u::AbstractArray{<:Any}, + mesh, equations, dg::DG, cache; + kwargs... + ) + @unpack controller_value = controller.cache + resize!(controller_value, nelements(dg, cache)) + + alpha = controller.indicator_primary(u, mesh, equations, dg, cache; kwargs...) + alpha_secondary = controller.indicator_secondary(u, mesh, equations, dg, cache) + + current_levels = current_element_levels(mesh, dg, cache) + + @threaded for element in eachelement(dg, cache) + current_level = current_levels[element] + + # set target level + target_level = current_level + if alpha[element] > controller.max_threshold + target_level = controller.max_level + elseif alpha[element] > controller.med_threshold + if controller.med_level > 0 + target_level = controller.med_level + # otherwise, target_level = current_level + # set med_level = -1 to implicitly use med_level = current_level + end + else + target_level = controller.base_level end - else - target_level = controller.base_level - end - if alpha_secondary[element] >= controller.max_threshold_secondary - target_level = controller.max_level - end + if alpha_secondary[element] >= controller.max_threshold_secondary + target_level = controller.max_level + end - # compare target level with actual level to set controller - if current_level < target_level - controller_value[element] = 1 # refine! - elseif current_level > target_level - controller_value[element] = -1 # coarsen! - else - controller_value[element] = 0 # we're good + # compare target level with actual level to set controller + if current_level < target_level + controller_value[element] = 1 # refine! + elseif current_level > target_level + controller_value[element] = -1 # coarsen! + else + controller_value[element] = 0 # we're good + end end - end - return controller_value -end + return controller_value + end -include("amr_dg.jl") -include("amr_dg1d.jl") -include("amr_dg2d.jl") -include("amr_dg3d.jl") + include("amr_dg.jl") + include("amr_dg1d.jl") + include("amr_dg2d.jl") + include("amr_dg3d.jl") end # @muladd diff --git a/src/callbacks_step/amr_dg.jl b/src/callbacks_step/amr_dg.jl index 0a7055af40..f3e86f377b 100644 --- a/src/callbacks_step/amr_dg.jl +++ b/src/callbacks_step/amr_dg.jl @@ -3,91 +3,105 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Redistribute data for load balancing after partitioning the mesh -function rebalance_solver!(u_ode::AbstractVector, - mesh::Union{ParallelP4estMesh, ParallelT8codeMesh}, - equations, - dg::DGSEM, cache, old_global_first_quadrant) + # Redistribute data for load balancing after partitioning the mesh + function rebalance_solver!( + u_ode::AbstractVector, + mesh::Union{ParallelP4estMesh, ParallelT8codeMesh}, + equations, + dg::DGSEM, cache, old_global_first_quadrant + ) - # MPI ranks are 0-based. This array uses 1-based indices. - global_first_quadrant = get_global_first_element_ids(mesh) + # MPI ranks are 0-based. This array uses 1-based indices. + global_first_quadrant = get_global_first_element_ids(mesh) - if global_first_quadrant[mpi_rank() + 1] == - old_global_first_quadrant[mpi_rank() + 1] && - global_first_quadrant[mpi_rank() + 2] == - old_global_first_quadrant[mpi_rank() + 2] - # Global ids of first and last local quadrants are the same for newly partitioned mesh so the - # solver does not need to be rebalanced on this rank. - # Container init uses all-to-all communication -> reinitialize even if there is nothing to do - # locally (there are other MPI ranks that need to be rebalanced if this function is called) - reinitialize_containers!(mesh, equations, dg, cache) - return - end - # Retain current solution data - old_n_elements = nelements(dg, cache) - old_u_ode = copy(u_ode) - GC.@preserve old_u_ode begin # OBS! If we don't GC.@preserve old_u_ode, it might be GC'ed - # Use `wrap_array_native` instead of `wrap_array` since MPI might not interact - # nicely with non-base array types - old_u = wrap_array_native(old_u_ode, mesh, equations, dg, cache) - - @trixi_timeit timer() "reinitialize data structures" begin + if global_first_quadrant[mpi_rank() + 1] == + old_global_first_quadrant[mpi_rank() + 1] && + global_first_quadrant[mpi_rank() + 2] == + old_global_first_quadrant[mpi_rank() + 2] + # Global ids of first and last local quadrants are the same for newly partitioned mesh so the + # solver does not need to be rebalanced on this rank. + # Container init uses all-to-all communication -> reinitialize even if there is nothing to do + # locally (there are other MPI ranks that need to be rebalanced if this function is called) reinitialize_containers!(mesh, equations, dg, cache) + return end + # Retain current solution data + old_n_elements = nelements(dg, cache) + old_u_ode = copy(u_ode) + GC.@preserve old_u_ode begin # OBS! If we don't GC.@preserve old_u_ode, it might be GC'ed + # Use `wrap_array_native` instead of `wrap_array` since MPI might not interact + # nicely with non-base array types + old_u = wrap_array_native(old_u_ode, mesh, equations, dg, cache) - resize!(u_ode, - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) - u = wrap_array_native(u_ode, mesh, equations, dg, cache) + @trixi_timeit timer() "reinitialize data structures" begin + reinitialize_containers!(mesh, equations, dg, cache) + end - @trixi_timeit timer() "exchange data" begin - # Collect MPI requests for MPI_Waitall - requests = Vector{MPI.Request}() - # Find elements that will change their rank and send their data to the new rank - for old_element_id in 1:old_n_elements - # Get global quad ID of old element; local quad id is element id - 1 - global_quad_id = old_global_first_quadrant[mpi_rank() + 1] + - old_element_id - 1 - if !(global_first_quadrant[mpi_rank() + 1] <= global_quad_id < - global_first_quadrant[mpi_rank() + 2]) - # Send element data to new rank, use global_quad_id as tag (non-blocking) - dest = findfirst(r -> global_first_quadrant[r] <= global_quad_id < - global_first_quadrant[r + 1], - 1:mpi_nranks()) - 1 # mpi ranks 0-based - request = MPI.Isend(@view(old_u[:, .., old_element_id]), dest, - global_quad_id, mpi_comm()) - push!(requests, request) + resize!( + u_ode, + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + ) + u = wrap_array_native(u_ode, mesh, equations, dg, cache) + + @trixi_timeit timer() "exchange data" begin + # Collect MPI requests for MPI_Waitall + requests = Vector{MPI.Request}() + # Find elements that will change their rank and send their data to the new rank + for old_element_id in 1:old_n_elements + # Get global quad ID of old element; local quad id is element id - 1 + global_quad_id = old_global_first_quadrant[mpi_rank() + 1] + + old_element_id - 1 + if !( + global_first_quadrant[mpi_rank() + 1] <= global_quad_id < + global_first_quadrant[mpi_rank() + 2] + ) + # Send element data to new rank, use global_quad_id as tag (non-blocking) + dest = findfirst( + r -> global_first_quadrant[r] <= global_quad_id < + global_first_quadrant[r + 1], + 1:mpi_nranks() + ) - 1 # mpi ranks 0-based + request = MPI.Isend( + @view(old_u[:, .., old_element_id]), dest, + global_quad_id, mpi_comm() + ) + push!(requests, request) + end end - end - # Loop over all elements in new container and either copy them from old container - # or receive them with MPI - for element in eachelement(dg, cache) - # Get global quad ID of element; local quad id is element id - 1 - global_quad_id = global_first_quadrant[mpi_rank() + 1] + element - 1 - if old_global_first_quadrant[mpi_rank() + 1] <= global_quad_id < - old_global_first_quadrant[mpi_rank() + 2] - # Quad ids are 0-based, element ids are 1-based, hence add 1 - old_element_id = global_quad_id - - old_global_first_quadrant[mpi_rank() + 1] + 1 - # Copy old element data to new element container - @views u[:, .., element] .= old_u[:, .., old_element_id] - else - # Receive old element data - src = findfirst(r -> old_global_first_quadrant[r] <= - global_quad_id < - old_global_first_quadrant[r + 1], - 1:mpi_nranks()) - 1 # mpi ranks 0-based - request = MPI.Irecv!(@view(u[:, .., element]), src, global_quad_id, - mpi_comm()) - push!(requests, request) + # Loop over all elements in new container and either copy them from old container + # or receive them with MPI + for element in eachelement(dg, cache) + # Get global quad ID of element; local quad id is element id - 1 + global_quad_id = global_first_quadrant[mpi_rank() + 1] + element - 1 + if old_global_first_quadrant[mpi_rank() + 1] <= global_quad_id < + old_global_first_quadrant[mpi_rank() + 2] + # Quad ids are 0-based, element ids are 1-based, hence add 1 + old_element_id = global_quad_id - + old_global_first_quadrant[mpi_rank() + 1] + 1 + # Copy old element data to new element container + @views u[:, .., element] .= old_u[:, .., old_element_id] + else + # Receive old element data + src = findfirst( + r -> old_global_first_quadrant[r] <= + global_quad_id < + old_global_first_quadrant[r + 1], + 1:mpi_nranks() + ) - 1 # mpi ranks 0-based + request = MPI.Irecv!( + @view(u[:, .., element]), src, global_quad_id, + mpi_comm() + ) + push!(requests, request) + end end - end - # Wait for all non-blocking MPI send/receive operations to finish - MPI.Waitall(requests, MPI.Status) - end - end # GC.@preserve old_u_ode -end + # Wait for all non-blocking MPI send/receive operations to finish + MPI.Waitall(requests, MPI.Status) + end + end # GC.@preserve old_u_ode + end end # @muladd diff --git a/src/callbacks_step/amr_dg1d.jl b/src/callbacks_step/amr_dg1d.jl index b4cd6a0027..e42c76fd3f 100644 --- a/src/callbacks_step/amr_dg1d.jl +++ b/src/callbacks_step/amr_dg1d.jl @@ -3,297 +3,321 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Refine elements in the DG solver based on a list of cell_ids that should be refined -function refine!(u_ode::AbstractVector, adaptor, mesh::TreeMesh{1}, - equations, dg::DGSEM, cache, elements_to_refine) - # Return early if there is nothing to do - if isempty(elements_to_refine) - return - end + #! format: noindent + + # Refine elements in the DG solver based on a list of cell_ids that should be refined + function refine!( + u_ode::AbstractVector, adaptor, mesh::TreeMesh{1}, + equations, dg::DGSEM, cache, elements_to_refine + ) + # Return early if there is nothing to do + if isempty(elements_to_refine) + return + end - # Determine for each existing element whether it needs to be refined - needs_refinement = falses(nelements(dg, cache)) - needs_refinement[elements_to_refine] .= true - - # Retain current solution data - old_n_elements = nelements(dg, cache) - old_u_ode = copy(u_ode) - GC.@preserve old_u_ode begin # OBS! If we don't GC.@preserve old_u_ode, it might be GC'ed - old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) - - # Get new list of leaf cells - leaf_cell_ids = local_leaf_cells(mesh.tree) - - # re-initialize elements container - @unpack elements = cache - resize!(elements, length(leaf_cell_ids)) - init_elements!(elements, leaf_cell_ids, mesh, dg.basis) - @assert nelements(dg, cache) > old_n_elements - - resize!(u_ode, - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) - u = wrap_array(u_ode, mesh, equations, dg, cache) - - # Loop over all elements in old container and either copy them or refine them - element_id = 1 - for old_element_id in 1:old_n_elements - if needs_refinement[old_element_id] - # Refine element and store solution directly in new data structure - refine_element!(u, element_id, old_u, old_element_id, - adaptor, equations, dg) - element_id += 2^ndims(mesh) - else - # Copy old element data to new element container - @views u[:, .., element_id] .= old_u[:, .., old_element_id] - element_id += 1 + # Determine for each existing element whether it needs to be refined + needs_refinement = falses(nelements(dg, cache)) + needs_refinement[elements_to_refine] .= true + + # Retain current solution data + old_n_elements = nelements(dg, cache) + old_u_ode = copy(u_ode) + GC.@preserve old_u_ode begin # OBS! If we don't GC.@preserve old_u_ode, it might be GC'ed + old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + + # Get new list of leaf cells + leaf_cell_ids = local_leaf_cells(mesh.tree) + + # re-initialize elements container + @unpack elements = cache + resize!(elements, length(leaf_cell_ids)) + init_elements!(elements, leaf_cell_ids, mesh, dg.basis) + @assert nelements(dg, cache) > old_n_elements + + resize!( + u_ode, + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + ) + u = wrap_array(u_ode, mesh, equations, dg, cache) + + # Loop over all elements in old container and either copy them or refine them + element_id = 1 + for old_element_id in 1:old_n_elements + if needs_refinement[old_element_id] + # Refine element and store solution directly in new data structure + refine_element!( + u, element_id, old_u, old_element_id, + adaptor, equations, dg + ) + element_id += 2^ndims(mesh) + else + # Copy old element data to new element container + @views u[:, .., element_id] .= old_u[:, .., old_element_id] + element_id += 1 + end end - end - # If everything is correct, we should have processed all elements. - # Depending on whether the last element processed above had to be refined or not, - # the counter `element_id` can have two different values at the end. - @assert element_id == + # If everything is correct, we should have processed all elements. + # Depending on whether the last element processed above had to be refined or not, + # the counter `element_id` can have two different values at the end. + @assert element_id == nelements(dg, cache) + 1||element_id == nelements(dg, cache) + 2^ndims(mesh) "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))" - end # GC.@preserve old_u_ode + end # GC.@preserve old_u_ode - # re-initialize interfaces container - @unpack interfaces = cache - resize!(interfaces, count_required_interfaces(mesh, leaf_cell_ids)) - init_interfaces!(interfaces, elements, mesh) + # re-initialize interfaces container + @unpack interfaces = cache + resize!(interfaces, count_required_interfaces(mesh, leaf_cell_ids)) + init_interfaces!(interfaces, elements, mesh) - # re-initialize boundaries container - @unpack boundaries = cache - resize!(boundaries, count_required_boundaries(mesh, leaf_cell_ids)) - init_boundaries!(boundaries, elements, mesh) + # re-initialize boundaries container + @unpack boundaries = cache + resize!(boundaries, count_required_boundaries(mesh, leaf_cell_ids)) + init_boundaries!(boundaries, elements, mesh) - # Sanity check - if isperiodic(mesh.tree) - @assert ninterfaces(interfaces)==1 * nelements(dg, cache) ("For 1D and periodic domains, the number of interfaces must be the same as the number of elements") - end + # Sanity check + if isperiodic(mesh.tree) + @assert ninterfaces(interfaces) == 1 * nelements(dg, cache) ("For 1D and periodic domains, the number of interfaces must be the same as the number of elements") + end - return nothing -end - -function refine!(u_ode::AbstractVector, adaptor, mesh::TreeMesh{1}, - equations, dg::DGSEM, cache, cache_parabolic, - elements_to_refine) - # Call `refine!` for the hyperbolic part, which does the heavy lifting of - # actually transferring the solution to the refined cells - refine!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_refine) - - # Resize parabolic helper variables - @unpack viscous_container = cache_parabolic - resize!(viscous_container, equations, dg, cache) - reinitialize_containers!(mesh, equations, dg, cache_parabolic) - - # Sanity check - @unpack interfaces = cache_parabolic - if isperiodic(mesh.tree) - @assert ninterfaces(interfaces)==1 * nelements(dg, cache_parabolic) ("For 1D and periodic domains, the number of interfaces must be the same as the number of elements") + return nothing end - return nothing -end - -# TODO: Taal compare performance of different implementations -# Refine solution data u for an element, using L2 projection (interpolation) -function refine_element!(u::AbstractArray{<:Any, 3}, element_id, - old_u, old_element_id, - adaptor::LobattoLegendreAdaptorL2, equations, dg) - @unpack forward_upper, forward_lower = adaptor - - # Store new element ids - left_id = element_id - right_id = element_id + 1 - - @boundscheck begin - @assert old_element_id >= 1 - @assert size(old_u, 1) == nvariables(equations) - @assert size(old_u, 2) == nnodes(dg) - @assert size(old_u, 3) >= old_element_id - @assert element_id >= 1 - @assert size(u, 1) == nvariables(equations) - @assert size(u, 2) == nnodes(dg) - @assert size(u, 3) >= element_id + 1 + function refine!( + u_ode::AbstractVector, adaptor, mesh::TreeMesh{1}, + equations, dg::DGSEM, cache, cache_parabolic, + elements_to_refine + ) + # Call `refine!` for the hyperbolic part, which does the heavy lifting of + # actually transferring the solution to the refined cells + refine!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_refine) + + # Resize parabolic helper variables + @unpack viscous_container = cache_parabolic + resize!(viscous_container, equations, dg, cache) + reinitialize_containers!(mesh, equations, dg, cache_parabolic) + + # Sanity check + @unpack interfaces = cache_parabolic + if isperiodic(mesh.tree) + @assert ninterfaces(interfaces) == 1 * nelements(dg, cache_parabolic) ("For 1D and periodic domains, the number of interfaces must be the same as the number of elements") + end + + return nothing end - # Interpolate to left element - for i in eachnode(dg) - acc = zero(get_node_vars(u, equations, dg, i, element_id)) - for k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, old_element_id) * - forward_lower[i, k] + # TODO: Taal compare performance of different implementations + # Refine solution data u for an element, using L2 projection (interpolation) + function refine_element!( + u::AbstractArray{<:Any, 3}, element_id, + old_u, old_element_id, + adaptor::LobattoLegendreAdaptorL2, equations, dg + ) + @unpack forward_upper, forward_lower = adaptor + + # Store new element ids + left_id = element_id + right_id = element_id + 1 + + @boundscheck begin + @assert old_element_id >= 1 + @assert size(old_u, 1) == nvariables(equations) + @assert size(old_u, 2) == nnodes(dg) + @assert size(old_u, 3) >= old_element_id + @assert element_id >= 1 + @assert size(u, 1) == nvariables(equations) + @assert size(u, 2) == nnodes(dg) + @assert size(u, 3) >= element_id + 1 end - set_node_vars!(u, acc, equations, dg, i, left_id) - end - # Interpolate to right element - for i in eachnode(dg) - acc = zero(get_node_vars(u, equations, dg, i, element_id)) - for k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, old_element_id) * - forward_upper[i, k] + # Interpolate to left element + for i in eachnode(dg) + acc = zero(get_node_vars(u, equations, dg, i, element_id)) + for k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, old_element_id) * + forward_lower[i, k] + end + set_node_vars!(u, acc, equations, dg, i, left_id) end - set_node_vars!(u, acc, equations, dg, i, right_id) - end - return nothing -end + # Interpolate to right element + for i in eachnode(dg) + acc = zero(get_node_vars(u, equations, dg, i, element_id)) + for k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, old_element_id) * + forward_upper[i, k] + end + set_node_vars!(u, acc, equations, dg, i, right_id) + end -# Coarsen elements in the DG solver based on a list of cell_ids that should be removed -function coarsen!(u_ode::AbstractVector, adaptor, mesh::TreeMesh{1}, - equations, dg::DGSEM, cache, elements_to_remove) - # Return early if there is nothing to do - if isempty(elements_to_remove) - return + return nothing end - # Determine for each old element whether it needs to be removed - to_be_removed = falses(nelements(dg, cache)) - to_be_removed[elements_to_remove] .= true - - # Retain current solution data - old_n_elements = nelements(dg, cache) - old_u_ode = copy(u_ode) - GC.@preserve old_u_ode begin # OBS! If we don't GC.@preserve old_u_ode, it might be GC'ed - old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) - - # Get new list of leaf cells - leaf_cell_ids = local_leaf_cells(mesh.tree) - - # re-initialize elements container - @unpack elements = cache - resize!(elements, length(leaf_cell_ids)) - init_elements!(elements, leaf_cell_ids, mesh, dg.basis) - @assert nelements(dg, cache) < old_n_elements - - resize!(u_ode, - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) - u = wrap_array(u_ode, mesh, equations, dg, cache) - - # Loop over all elements in old container and either copy them or coarsen them - skip = 0 - element_id = 1 - for old_element_id in 1:old_n_elements - # If skip is non-zero, we just coarsened 2^ndims elements and need to omit the following elements - if skip > 0 - skip -= 1 - continue - end + # Coarsen elements in the DG solver based on a list of cell_ids that should be removed + function coarsen!( + u_ode::AbstractVector, adaptor, mesh::TreeMesh{1}, + equations, dg::DGSEM, cache, elements_to_remove + ) + # Return early if there is nothing to do + if isempty(elements_to_remove) + return + end - if to_be_removed[old_element_id] - # If an element is to be removed, sanity check if the following elements - # are also marked - otherwise there would be an error in the way the - # cells/elements are sorted - @assert all(to_be_removed[old_element_id:(old_element_id + 2^ndims(mesh) - 1)]) "bad cell/element order" - - # Coarsen elements and store solution directly in new data structure - coarsen_elements!(u, element_id, old_u, old_element_id, - adaptor, equations, dg) - element_id += 1 - skip = 2^ndims(mesh) - 1 - else - # Copy old element data to new element container - @views u[:, .., element_id] .= old_u[:, .., old_element_id] - element_id += 1 + # Determine for each old element whether it needs to be removed + to_be_removed = falses(nelements(dg, cache)) + to_be_removed[elements_to_remove] .= true + + # Retain current solution data + old_n_elements = nelements(dg, cache) + old_u_ode = copy(u_ode) + GC.@preserve old_u_ode begin # OBS! If we don't GC.@preserve old_u_ode, it might be GC'ed + old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + + # Get new list of leaf cells + leaf_cell_ids = local_leaf_cells(mesh.tree) + + # re-initialize elements container + @unpack elements = cache + resize!(elements, length(leaf_cell_ids)) + init_elements!(elements, leaf_cell_ids, mesh, dg.basis) + @assert nelements(dg, cache) < old_n_elements + + resize!( + u_ode, + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + ) + u = wrap_array(u_ode, mesh, equations, dg, cache) + + # Loop over all elements in old container and either copy them or coarsen them + skip = 0 + element_id = 1 + for old_element_id in 1:old_n_elements + # If skip is non-zero, we just coarsened 2^ndims elements and need to omit the following elements + if skip > 0 + skip -= 1 + continue + end + + if to_be_removed[old_element_id] + # If an element is to be removed, sanity check if the following elements + # are also marked - otherwise there would be an error in the way the + # cells/elements are sorted + @assert all(to_be_removed[old_element_id:(old_element_id + 2^ndims(mesh) - 1)]) "bad cell/element order" + + # Coarsen elements and store solution directly in new data structure + coarsen_elements!( + u, element_id, old_u, old_element_id, + adaptor, equations, dg + ) + element_id += 1 + skip = 2^ndims(mesh) - 1 + else + # Copy old element data to new element container + @views u[:, .., element_id] .= old_u[:, .., old_element_id] + element_id += 1 + end end + # If everything is correct, we should have processed all elements. + @assert element_id == nelements(dg, cache) + 1 "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))" + end # GC.@preserve old_u_ode + + # re-initialize interfaces container + @unpack interfaces = cache + resize!(interfaces, count_required_interfaces(mesh, leaf_cell_ids)) + init_interfaces!(interfaces, elements, mesh) + + # re-initialize boundaries container + @unpack boundaries = cache + resize!(boundaries, count_required_boundaries(mesh, leaf_cell_ids)) + init_boundaries!(boundaries, elements, mesh) + + # Sanity check + if isperiodic(mesh.tree) + @assert ninterfaces(interfaces) == 1 * nelements(dg, cache) ("For 1D and periodic domains, the number of interfaces must be the same as the number of elements") end - # If everything is correct, we should have processed all elements. - @assert element_id==nelements(dg, cache) + 1 "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))" - end # GC.@preserve old_u_ode - - # re-initialize interfaces container - @unpack interfaces = cache - resize!(interfaces, count_required_interfaces(mesh, leaf_cell_ids)) - init_interfaces!(interfaces, elements, mesh) - - # re-initialize boundaries container - @unpack boundaries = cache - resize!(boundaries, count_required_boundaries(mesh, leaf_cell_ids)) - init_boundaries!(boundaries, elements, mesh) - - # Sanity check - if isperiodic(mesh.tree) - @assert ninterfaces(interfaces)==1 * nelements(dg, cache) ("For 1D and periodic domains, the number of interfaces must be the same as the number of elements") - end - return nothing -end - -function coarsen!(u_ode::AbstractVector, adaptor, mesh::TreeMesh{1}, - equations, dg::DGSEM, cache, cache_parabolic, - elements_to_remove) - # Call `coarsen!` for the hyperbolic part, which does the heavy lifting of - # actually transferring the solution to the coarsened cells - coarsen!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_remove) - - # Resize parabolic helper variables - @unpack viscous_container = cache_parabolic - resize!(viscous_container, equations, dg, cache) - reinitialize_containers!(mesh, equations, dg, cache_parabolic) - - # Sanity check - @unpack interfaces = cache_parabolic - if isperiodic(mesh.tree) - @assert ninterfaces(interfaces)==1 * nelements(dg, cache_parabolic) ("For 1D and periodic domains, the number of interfaces must be the same as the number of elements") + return nothing end - return nothing -end - -# TODO: Taal compare performance of different implementations -# Coarsen solution data u for two elements, using L2 projection -function coarsen_elements!(u::AbstractArray{<:Any, 3}, element_id, - old_u, old_element_id, - adaptor::LobattoLegendreAdaptorL2, equations, dg) - @unpack reverse_upper, reverse_lower = adaptor - - # Store old element ids - left_id = old_element_id - right_id = old_element_id + 1 - - @boundscheck begin - @assert old_element_id >= 1 - @assert size(old_u, 1) == nvariables(equations) - @assert size(old_u, 2) == nnodes(dg) - @assert size(old_u, 3) >= old_element_id + 1 - @assert element_id >= 1 - @assert size(u, 1) == nvariables(equations) - @assert size(u, 2) == nnodes(dg) - @assert size(u, 3) >= element_id - end + function coarsen!( + u_ode::AbstractVector, adaptor, mesh::TreeMesh{1}, + equations, dg::DGSEM, cache, cache_parabolic, + elements_to_remove + ) + # Call `coarsen!` for the hyperbolic part, which does the heavy lifting of + # actually transferring the solution to the coarsened cells + coarsen!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_remove) + + # Resize parabolic helper variables + @unpack viscous_container = cache_parabolic + resize!(viscous_container, equations, dg, cache) + reinitialize_containers!(mesh, equations, dg, cache_parabolic) + + # Sanity check + @unpack interfaces = cache_parabolic + if isperiodic(mesh.tree) + @assert ninterfaces(interfaces) == 1 * nelements(dg, cache_parabolic) ("For 1D and periodic domains, the number of interfaces must be the same as the number of elements") + end - for i in eachnode(dg) - acc = zero(get_node_vars(u, equations, dg, i, element_id)) + return nothing + end - # Project from lower left element - for k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, left_id) * reverse_lower[i, k] + # TODO: Taal compare performance of different implementations + # Coarsen solution data u for two elements, using L2 projection + function coarsen_elements!( + u::AbstractArray{<:Any, 3}, element_id, + old_u, old_element_id, + adaptor::LobattoLegendreAdaptorL2, equations, dg + ) + @unpack reverse_upper, reverse_lower = adaptor + + # Store old element ids + left_id = old_element_id + right_id = old_element_id + 1 + + @boundscheck begin + @assert old_element_id >= 1 + @assert size(old_u, 1) == nvariables(equations) + @assert size(old_u, 2) == nnodes(dg) + @assert size(old_u, 3) >= old_element_id + 1 + @assert element_id >= 1 + @assert size(u, 1) == nvariables(equations) + @assert size(u, 2) == nnodes(dg) + @assert size(u, 3) >= element_id end - # Project from lower right element - for k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, right_id) * - reverse_upper[i, k] + for i in eachnode(dg) + acc = zero(get_node_vars(u, equations, dg, i, element_id)) + + # Project from lower left element + for k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, left_id) * reverse_lower[i, k] + end + + # Project from lower right element + for k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, right_id) * + reverse_upper[i, k] + end + + # Update value + set_node_vars!(u, acc, equations, dg, i, element_id) end + end + + # this method is called when an `ControllerThreeLevel` is constructed + function create_cache( + ::Type{ControllerThreeLevel}, mesh::TreeMesh{1}, equations, + dg::DG, cache + ) + controller_value = Vector{Int}(undef, nelements(dg, cache)) + return (; controller_value) + end - # Update value - set_node_vars!(u, acc, equations, dg, i, element_id) + function create_cache( + ::Type{ControllerThreeLevelCombined}, mesh::TreeMesh{1}, + equations, dg::DG, cache + ) + controller_value = Vector{Int}(undef, nelements(dg, cache)) + return (; controller_value) end -end - -# this method is called when an `ControllerThreeLevel` is constructed -function create_cache(::Type{ControllerThreeLevel}, mesh::TreeMesh{1}, equations, - dg::DG, cache) - controller_value = Vector{Int}(undef, nelements(dg, cache)) - return (; controller_value) -end - -function create_cache(::Type{ControllerThreeLevelCombined}, mesh::TreeMesh{1}, - equations, dg::DG, cache) - controller_value = Vector{Int}(undef, nelements(dg, cache)) - return (; controller_value) -end end # @muladd diff --git a/src/callbacks_step/amr_dg2d.jl b/src/callbacks_step/amr_dg2d.jl index 062020b6d4..7faef2e270 100644 --- a/src/callbacks_step/amr_dg2d.jl +++ b/src/callbacks_step/amr_dg2d.jl @@ -3,585 +3,659 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Redistribute data for load balancing after partitioning the mesh -function rebalance_solver!(u_ode::AbstractVector, mesh::TreeMesh{2}, equations, - dg::DGSEM, cache, old_mpi_ranks_per_cell) - if cache.elements.cell_ids == local_leaf_cells(mesh.tree) - # Cell ids of the current elements are the same as the local leaf cells of the - # newly partitioned mesh, so the solver doesn't need to be rebalanced on this rank. - # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do - # locally (there are other MPI ranks that need to be rebalanced if this function is called) - reinitialize_containers!(mesh, equations, dg, cache) - return - end - - # Retain current solution data - old_n_elements = nelements(dg, cache) - old_cell_ids = copy(cache.elements.cell_ids) - old_u_ode = copy(u_ode) - GC.@preserve old_u_ode begin # OBS! If we don't GC.@preserve old_u_ode, it might be GC'ed - # Use `wrap_array_native` instead of `wrap_array` since MPI might not interact - # nicely with non-base array types - old_u = wrap_array_native(old_u_ode, mesh, equations, dg, cache) - - @trixi_timeit timer() "reinitialize data structures" begin + #! format: noindent + + # Redistribute data for load balancing after partitioning the mesh + function rebalance_solver!( + u_ode::AbstractVector, mesh::TreeMesh{2}, equations, + dg::DGSEM, cache, old_mpi_ranks_per_cell + ) + if cache.elements.cell_ids == local_leaf_cells(mesh.tree) + # Cell ids of the current elements are the same as the local leaf cells of the + # newly partitioned mesh, so the solver doesn't need to be rebalanced on this rank. + # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do + # locally (there are other MPI ranks that need to be rebalanced if this function is called) reinitialize_containers!(mesh, equations, dg, cache) + return end - resize!(u_ode, - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) - u = wrap_array_native(u_ode, mesh, equations, dg, cache) - - # Get new list of leaf cells - leaf_cell_ids = local_leaf_cells(mesh.tree) - - @trixi_timeit timer() "exchange data" begin - # Collect MPI requests for MPI_Waitall - requests = Vector{MPI.Request}() + # Retain current solution data + old_n_elements = nelements(dg, cache) + old_cell_ids = copy(cache.elements.cell_ids) + old_u_ode = copy(u_ode) + GC.@preserve old_u_ode begin # OBS! If we don't GC.@preserve old_u_ode, it might be GC'ed + # Use `wrap_array_native` instead of `wrap_array` since MPI might not interact + # nicely with non-base array types + old_u = wrap_array_native(old_u_ode, mesh, equations, dg, cache) + + @trixi_timeit timer() "reinitialize data structures" begin + reinitialize_containers!(mesh, equations, dg, cache) + end - # Find elements that will change their rank and send their data to the new rank - for old_element_id in 1:old_n_elements - cell_id = old_cell_ids[old_element_id] - if !(cell_id in leaf_cell_ids) - # Send element data to new rank, use cell_id as tag (non-blocking) - dest = mesh.tree.mpi_ranks[cell_id] - request = MPI.Isend(@view(old_u[:, .., old_element_id]), dest, - cell_id, mpi_comm()) - push!(requests, request) + resize!( + u_ode, + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + ) + u = wrap_array_native(u_ode, mesh, equations, dg, cache) + + # Get new list of leaf cells + leaf_cell_ids = local_leaf_cells(mesh.tree) + + @trixi_timeit timer() "exchange data" begin + # Collect MPI requests for MPI_Waitall + requests = Vector{MPI.Request}() + + # Find elements that will change their rank and send their data to the new rank + for old_element_id in 1:old_n_elements + cell_id = old_cell_ids[old_element_id] + if !(cell_id in leaf_cell_ids) + # Send element data to new rank, use cell_id as tag (non-blocking) + dest = mesh.tree.mpi_ranks[cell_id] + request = MPI.Isend( + @view(old_u[:, .., old_element_id]), dest, + cell_id, mpi_comm() + ) + push!(requests, request) + end end - end - # Loop over all elements in new container and either copy them from old container - # or receive them with MPI - for element in eachelement(dg, cache) - cell_id = cache.elements.cell_ids[element] - if cell_id in old_cell_ids - old_element_id = searchsortedfirst(old_cell_ids, cell_id) - # Copy old element data to new element container - @views u[:, .., element] .= old_u[:, .., old_element_id] - else - # Receive old element data - src = old_mpi_ranks_per_cell[cell_id] - request = MPI.Irecv!(@view(u[:, .., element]), src, cell_id, - mpi_comm()) - push!(requests, request) + # Loop over all elements in new container and either copy them from old container + # or receive them with MPI + for element in eachelement(dg, cache) + cell_id = cache.elements.cell_ids[element] + if cell_id in old_cell_ids + old_element_id = searchsortedfirst(old_cell_ids, cell_id) + # Copy old element data to new element container + @views u[:, .., element] .= old_u[:, .., old_element_id] + else + # Receive old element data + src = old_mpi_ranks_per_cell[cell_id] + request = MPI.Irecv!( + @view(u[:, .., element]), src, cell_id, + mpi_comm() + ) + push!(requests, request) + end end - end - # Wait for all non-blocking MPI send/receive operations to finish - MPI.Waitall(requests, MPI.Status) - end - end # GC.@preserve old_u_ode -end - -# Refine elements in the DG solver based on a list of cell_ids that should be refined. -# On `P4estMesh`, if an element refines the solution scaled by the Jacobian `J*u` is interpolated -# from the parent element into the four children elements. The solution on each child -# element is then recovered by dividing by the new element Jacobians. -function refine!(u_ode::AbstractVector, adaptor, mesh::Union{TreeMesh{2}, P4estMesh{2}}, - equations, dg::DGSEM, cache, elements_to_refine) - # Return early if there is nothing to do - if isempty(elements_to_refine) - if mpi_isparallel() - # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do - # locally (there still might be other MPI ranks that have refined elements) - reinitialize_containers!(mesh, equations, dg, cache) - end - return + # Wait for all non-blocking MPI send/receive operations to finish + MPI.Waitall(requests, MPI.Status) + end + end # GC.@preserve old_u_ode end - # Determine for each existing element whether it needs to be refined - needs_refinement = falses(nelements(dg, cache)) - needs_refinement[elements_to_refine] .= true - - # Retain current solution data - old_n_elements = nelements(dg, cache) - old_u_ode = copy(u_ode) - old_inverse_jacobian = copy(cache.elements.inverse_jacobian) - # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed - GC.@preserve old_u_ode old_inverse_jacobian begin - old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) - - if mesh isa P4estMesh - # Loop over all elements in old container and scale the old solution by the Jacobian - # prior to projection - for old_element_id in 1:old_n_elements - for v in eachvariable(equations) - old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./ - old_inverse_jacobian[.., - old_element_id]) - end + # Refine elements in the DG solver based on a list of cell_ids that should be refined. + # On `P4estMesh`, if an element refines the solution scaled by the Jacobian `J*u` is interpolated + # from the parent element into the four children elements. The solution on each child + # element is then recovered by dividing by the new element Jacobians. + function refine!( + u_ode::AbstractVector, adaptor, mesh::Union{TreeMesh{2}, P4estMesh{2}}, + equations, dg::DGSEM, cache, elements_to_refine + ) + # Return early if there is nothing to do + if isempty(elements_to_refine) + if mpi_isparallel() + # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do + # locally (there still might be other MPI ranks that have refined elements) + reinitialize_containers!(mesh, equations, dg, cache) end + return end - reinitialize_containers!(mesh, equations, dg, cache) + # Determine for each existing element whether it needs to be refined + needs_refinement = falses(nelements(dg, cache)) + needs_refinement[elements_to_refine] .= true + + # Retain current solution data + old_n_elements = nelements(dg, cache) + old_u_ode = copy(u_ode) + old_inverse_jacobian = copy(cache.elements.inverse_jacobian) + # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed + GC.@preserve old_u_ode old_inverse_jacobian begin + old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + + if mesh isa P4estMesh + # Loop over all elements in old container and scale the old solution by the Jacobian + # prior to projection + for old_element_id in 1:old_n_elements + for v in eachvariable(equations) + old_u[v, .., old_element_id] .= ( + old_u[v, .., old_element_id] ./ + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end + end + end - resize!(u_ode, - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) - u = wrap_array(u_ode, mesh, equations, dg, cache) + reinitialize_containers!(mesh, equations, dg, cache) - # Loop over all elements in old container and either copy them or refine them - element_id = 1 - for old_element_id in 1:old_n_elements - if needs_refinement[old_element_id] - # Refine element and store solution directly in new data structure - refine_element!(u, element_id, old_u, old_element_id, - adaptor, equations, dg) + resize!( + u_ode, + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + ) + u = wrap_array(u_ode, mesh, equations, dg, cache) - if mesh isa P4estMesh - # Before `element_id` is incremented, divide by the new Jacobians on each - # child element and save the result - for m in 0:3 # loop over the children - for v in eachvariable(equations) - u[v, .., element_id + m] .*= (0.25f0 .* - cache.elements.inverse_jacobian[.., - element_id + m]) + # Loop over all elements in old container and either copy them or refine them + element_id = 1 + for old_element_id in 1:old_n_elements + if needs_refinement[old_element_id] + # Refine element and store solution directly in new data structure + refine_element!( + u, element_id, old_u, old_element_id, + adaptor, equations, dg + ) + + if mesh isa P4estMesh + # Before `element_id` is incremented, divide by the new Jacobians on each + # child element and save the result + for m in 0:3 # loop over the children + for v in eachvariable(equations) + u[v, .., element_id + m] .*= ( + 0.25f0 .* + cache.elements.inverse_jacobian[ + .., + element_id + m, + ] + ) + end end end - end - # Increment `element_id` on the refined mesh with the number - # of children, i.e., 4 in 2D - element_id += 2^ndims(mesh) - else - if mesh isa P4estMesh - # Copy old element data to new element container and remove Jacobian scaling - for v in eachvariable(equations) - u[v, .., element_id] .= (old_u[v, .., old_element_id] .* - old_inverse_jacobian[.., - old_element_id]) + # Increment `element_id` on the refined mesh with the number + # of children, i.e., 4 in 2D + element_id += 2^ndims(mesh) + else + if mesh isa P4estMesh + # Copy old element data to new element container and remove Jacobian scaling + for v in eachvariable(equations) + u[v, .., element_id] .= ( + old_u[v, .., old_element_id] .* + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end + else # isa TreeMesh + @views u[:, .., element_id] .= old_u[:, .., old_element_id] end - else # isa TreeMesh - @views u[:, .., element_id] .= old_u[:, .., old_element_id] + # No refinement occurred, so increment `element_id` on the new mesh by one + element_id += 1 end - # No refinement occurred, so increment `element_id` on the new mesh by one - element_id += 1 end - end - # If everything is correct, we should have processed all elements. - # Depending on whether the last element processed above had to be refined or not, - # the counter `element_id` can have two different values at the end. - @assert element_id == + # If everything is correct, we should have processed all elements. + # Depending on whether the last element processed above had to be refined or not, + # the counter `element_id` can have two different values at the end. + @assert element_id == nelements(dg, cache) + 1||element_id == nelements(dg, cache) + 2^ndims(mesh) "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))" - end # GC.@preserve old_u_ode old_inverse_jacobian - - # Sanity check - if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 && - !mpi_isparallel() - @assert ninterfaces(cache.interfaces)==ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") - end + end # GC.@preserve old_u_ode old_inverse_jacobian - return nothing -end - -function refine!(u_ode::AbstractVector, adaptor, - mesh::Union{TreeMesh{2}, P4estMesh{2}, TreeMesh{3}, P4estMesh{3}}, - equations, dg::DGSEM, cache, cache_parabolic, - elements_to_refine) - # Call `refine!` for the hyperbolic part, which does the heavy lifting of - # actually transferring the solution to the refined cells - refine!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_refine) - - # Resize parabolic helper variables - @unpack viscous_container = cache_parabolic - resize!(viscous_container, equations, dg, cache) - reinitialize_containers!(mesh, equations, dg, cache_parabolic) - - # Sanity check - if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 && - !mpi_isparallel() - @assert ninterfaces(cache_parabolic.interfaces)==ndims(mesh) * - nelements(dg, cache_parabolic) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") - end + # Sanity check + if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 && + !mpi_isparallel() + @assert ninterfaces(cache.interfaces) == ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") + end - return nothing -end - -# TODO: Taal compare performance of different implementations -# Refine solution data u for an element, using L2 projection (interpolation) -function refine_element!(u::AbstractArray{<:Any, 4}, element_id, - old_u, old_element_id, - adaptor::LobattoLegendreAdaptorL2, equations, dg) - @unpack forward_upper, forward_lower = adaptor - - # Store new element ids - lower_left_id = element_id - lower_right_id = element_id + 1 - upper_left_id = element_id + 2 - upper_right_id = element_id + 3 - - @boundscheck begin - @assert old_element_id >= 1 - @assert size(old_u, 1) == nvariables(equations) - @assert size(old_u, 2) == nnodes(dg) - @assert size(old_u, 3) == nnodes(dg) - @assert size(old_u, 4) >= old_element_id - @assert element_id >= 1 - @assert size(u, 1) == nvariables(equations) - @assert size(u, 2) == nnodes(dg) - @assert size(u, 3) == nnodes(dg) - @assert size(u, 4) >= element_id + 3 + return nothing end - # Interpolate to lower left element - for j in eachnode(dg), i in eachnode(dg) - acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) - for l in eachnode(dg), k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) * - forward_lower[i, k] * forward_lower[j, l] + function refine!( + u_ode::AbstractVector, adaptor, + mesh::Union{TreeMesh{2}, P4estMesh{2}, TreeMesh{3}, P4estMesh{3}}, + equations, dg::DGSEM, cache, cache_parabolic, + elements_to_refine + ) + # Call `refine!` for the hyperbolic part, which does the heavy lifting of + # actually transferring the solution to the refined cells + refine!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_refine) + + # Resize parabolic helper variables + @unpack viscous_container = cache_parabolic + resize!(viscous_container, equations, dg, cache) + reinitialize_containers!(mesh, equations, dg, cache_parabolic) + + # Sanity check + if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 && + !mpi_isparallel() + @assert ninterfaces(cache_parabolic.interfaces) == ndims(mesh) * + nelements(dg, cache_parabolic) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") end - set_node_vars!(u, acc, equations, dg, i, j, lower_left_id) - end - # Interpolate to lower right element - for j in eachnode(dg), i in eachnode(dg) - acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) - for l in eachnode(dg), k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) * - forward_upper[i, k] * forward_lower[j, l] - end - set_node_vars!(u, acc, equations, dg, i, j, lower_right_id) + return nothing end - # Interpolate to upper left element - for j in eachnode(dg), i in eachnode(dg) - acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) - for l in eachnode(dg), k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) * - forward_lower[i, k] * forward_upper[j, l] + # TODO: Taal compare performance of different implementations + # Refine solution data u for an element, using L2 projection (interpolation) + function refine_element!( + u::AbstractArray{<:Any, 4}, element_id, + old_u, old_element_id, + adaptor::LobattoLegendreAdaptorL2, equations, dg + ) + @unpack forward_upper, forward_lower = adaptor + + # Store new element ids + lower_left_id = element_id + lower_right_id = element_id + 1 + upper_left_id = element_id + 2 + upper_right_id = element_id + 3 + + @boundscheck begin + @assert old_element_id >= 1 + @assert size(old_u, 1) == nvariables(equations) + @assert size(old_u, 2) == nnodes(dg) + @assert size(old_u, 3) == nnodes(dg) + @assert size(old_u, 4) >= old_element_id + @assert element_id >= 1 + @assert size(u, 1) == nvariables(equations) + @assert size(u, 2) == nnodes(dg) + @assert size(u, 3) == nnodes(dg) + @assert size(u, 4) >= element_id + 3 end - set_node_vars!(u, acc, equations, dg, i, j, upper_left_id) - end - # Interpolate to upper right element - for j in eachnode(dg), i in eachnode(dg) - acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) - for l in eachnode(dg), k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) * - forward_upper[i, k] * forward_upper[j, l] + # Interpolate to lower left element + for j in eachnode(dg), i in eachnode(dg) + acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) + for l in eachnode(dg), k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) * + forward_lower[i, k] * forward_lower[j, l] + end + set_node_vars!(u, acc, equations, dg, i, j, lower_left_id) end - set_node_vars!(u, acc, equations, dg, i, j, upper_right_id) - end - return nothing -end - -# Coarsen elements in the DG solver based on a list of cell_ids that should be removed. -# On `P4estMesh`, if an element coarsens the solution scaled by the Jacobian `J*u` is projected -# from the four children elements back onto the parent element. The solution on the parent -# element is then recovered by dividing by the new element Jacobian. -function coarsen!(u_ode::AbstractVector, adaptor, - mesh::Union{TreeMesh{2}, P4estMesh{2}}, - equations, dg::DGSEM, cache, elements_to_remove) - # Return early if there is nothing to do - if isempty(elements_to_remove) - if mpi_isparallel() - # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do - # locally (there still might be other MPI ranks that have coarsened elements) - reinitialize_containers!(mesh, equations, dg, cache) + # Interpolate to lower right element + for j in eachnode(dg), i in eachnode(dg) + acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) + for l in eachnode(dg), k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) * + forward_upper[i, k] * forward_lower[j, l] + end + set_node_vars!(u, acc, equations, dg, i, j, lower_right_id) end - return - end - - # Determine for each old element whether it needs to be removed - to_be_removed = falses(nelements(dg, cache)) - to_be_removed[elements_to_remove] .= true - # Retain current solution data and Jacobians - old_n_elements = nelements(dg, cache) - old_u_ode = copy(u_ode) - old_inverse_jacobian = copy(cache.elements.inverse_jacobian) - # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed - GC.@preserve old_u_ode old_inverse_jacobian begin - old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + # Interpolate to upper left element + for j in eachnode(dg), i in eachnode(dg) + acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) + for l in eachnode(dg), k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) * + forward_lower[i, k] * forward_upper[j, l] + end + set_node_vars!(u, acc, equations, dg, i, j, upper_left_id) + end - if mesh isa P4estMesh - # Loop over all elements in old container and scale the old solution by the Jacobian - # prior to projection - for old_element_id in 1:old_n_elements - for v in eachvariable(equations) - old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./ - old_inverse_jacobian[.., - old_element_id]) - end + # Interpolate to upper right element + for j in eachnode(dg), i in eachnode(dg) + acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) + for l in eachnode(dg), k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, l, old_element_id) * + forward_upper[i, k] * forward_upper[j, l] end + set_node_vars!(u, acc, equations, dg, i, j, upper_right_id) end - reinitialize_containers!(mesh, equations, dg, cache) + return nothing + end - resize!(u_ode, - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) - u = wrap_array(u_ode, mesh, equations, dg, cache) + # Coarsen elements in the DG solver based on a list of cell_ids that should be removed. + # On `P4estMesh`, if an element coarsens the solution scaled by the Jacobian `J*u` is projected + # from the four children elements back onto the parent element. The solution on the parent + # element is then recovered by dividing by the new element Jacobian. + function coarsen!( + u_ode::AbstractVector, adaptor, + mesh::Union{TreeMesh{2}, P4estMesh{2}}, + equations, dg::DGSEM, cache, elements_to_remove + ) + # Return early if there is nothing to do + if isempty(elements_to_remove) + if mpi_isparallel() + # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do + # locally (there still might be other MPI ranks that have coarsened elements) + reinitialize_containers!(mesh, equations, dg, cache) + end + return + end - # Loop over all elements in old container and either copy them or coarsen them - skip = 0 - element_id = 1 - for old_element_id in 1:old_n_elements - # If skip is non-zero, we just coarsened 2^ndims elements and need to omit the following elements - if skip > 0 - skip -= 1 - continue + # Determine for each old element whether it needs to be removed + to_be_removed = falses(nelements(dg, cache)) + to_be_removed[elements_to_remove] .= true + + # Retain current solution data and Jacobians + old_n_elements = nelements(dg, cache) + old_u_ode = copy(u_ode) + old_inverse_jacobian = copy(cache.elements.inverse_jacobian) + # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed + GC.@preserve old_u_ode old_inverse_jacobian begin + old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + + if mesh isa P4estMesh + # Loop over all elements in old container and scale the old solution by the Jacobian + # prior to projection + for old_element_id in 1:old_n_elements + for v in eachvariable(equations) + old_u[v, .., old_element_id] .= ( + old_u[v, .., old_element_id] ./ + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end + end end - if to_be_removed[old_element_id] - # If an element is to be removed, sanity check if the following elements - # are also marked - otherwise there would be an error in the way the - # cells/elements are sorted - @assert all(to_be_removed[old_element_id:(old_element_id + 2^ndims(mesh) - 1)]) "bad cell/element order" + reinitialize_containers!(mesh, equations, dg, cache) - # Coarsen elements and store solution directly in new data structure - coarsen_elements!(u, element_id, old_u, old_element_id, - adaptor, equations, dg) + resize!( + u_ode, + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + ) + u = wrap_array(u_ode, mesh, equations, dg, cache) - if mesh isa P4estMesh - # Before `element_id` is incremented, divide by the new Jacobian and save - # the result in the parent element - for v in eachvariable(equations) - u[v, .., element_id] .*= (4 .* - cache.elements.inverse_jacobian[.., - element_id]) - end + # Loop over all elements in old container and either copy them or coarsen them + skip = 0 + element_id = 1 + for old_element_id in 1:old_n_elements + # If skip is non-zero, we just coarsened 2^ndims elements and need to omit the following elements + if skip > 0 + skip -= 1 + continue end - # Increment `element_id` on the coarsened mesh by one and `skip` = 3 in 2D - # because 4 children elements become 1 parent element - element_id += 1 - skip = 2^ndims(mesh) - 1 - else - if mesh isa P4estMesh - # Copy old element data to new element container and remove Jacobian scaling - for v in eachvariable(equations) - u[v, .., element_id] .= (old_u[v, .., old_element_id] .* - old_inverse_jacobian[.., - old_element_id]) + if to_be_removed[old_element_id] + # If an element is to be removed, sanity check if the following elements + # are also marked - otherwise there would be an error in the way the + # cells/elements are sorted + @assert all(to_be_removed[old_element_id:(old_element_id + 2^ndims(mesh) - 1)]) "bad cell/element order" + + # Coarsen elements and store solution directly in new data structure + coarsen_elements!( + u, element_id, old_u, old_element_id, + adaptor, equations, dg + ) + + if mesh isa P4estMesh + # Before `element_id` is incremented, divide by the new Jacobian and save + # the result in the parent element + for v in eachvariable(equations) + u[v, .., element_id] .*= ( + 4 .* + cache.elements.inverse_jacobian[ + .., + element_id, + ] + ) + end end - else # isa TreeMesh - @views u[:, .., element_id] .= old_u[:, .., old_element_id] + + # Increment `element_id` on the coarsened mesh by one and `skip` = 3 in 2D + # because 4 children elements become 1 parent element + element_id += 1 + skip = 2^ndims(mesh) - 1 + else + if mesh isa P4estMesh + # Copy old element data to new element container and remove Jacobian scaling + for v in eachvariable(equations) + u[v, .., element_id] .= ( + old_u[v, .., old_element_id] .* + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end + else # isa TreeMesh + @views u[:, .., element_id] .= old_u[:, .., old_element_id] + end + # No coarsening occurred, so increment `element_id` on the new mesh by one + element_id += 1 end - # No coarsening occurred, so increment `element_id` on the new mesh by one - element_id += 1 end + # If everything is correct, we should have processed all elements. + @assert element_id == nelements(dg, cache) + 1 "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))" + end # GC.@preserve old_u_ode old_inverse_jacobian + + # Sanity check + if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 && + !mpi_isparallel() + @assert ninterfaces(cache.interfaces) == ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") end - # If everything is correct, we should have processed all elements. - @assert element_id==nelements(dg, cache) + 1 "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))" - end # GC.@preserve old_u_ode old_inverse_jacobian - - # Sanity check - if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 && - !mpi_isparallel() - @assert ninterfaces(cache.interfaces)==ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") - end - return nothing -end - -function coarsen!(u_ode::AbstractVector, adaptor, - mesh::Union{TreeMesh{2}, P4estMesh{2}, TreeMesh{3}, P4estMesh{3}}, - equations, dg::DGSEM, cache, cache_parabolic, - elements_to_remove) - # Call `coarsen!` for the hyperbolic part, which does the heavy lifting of - # actually transferring the solution to the coarsened cells - coarsen!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_remove) - - # Resize parabolic helper variables - @unpack viscous_container = cache_parabolic - resize!(viscous_container, equations, dg, cache) - reinitialize_containers!(mesh, equations, dg, cache_parabolic) - - # Sanity check - if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 && - !mpi_isparallel() - @assert ninterfaces(cache_parabolic.interfaces)==ndims(mesh) * - nelements(dg, cache_parabolic) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") + return nothing end - return nothing -end - -# TODO: Taal compare performance of different implementations -# Coarsen solution data u for four elements, using L2 projection -function coarsen_elements!(u::AbstractArray{<:Any, 4}, element_id, - old_u, old_element_id, - adaptor::LobattoLegendreAdaptorL2, equations, dg) - @unpack reverse_upper, reverse_lower = adaptor - - # Store old element ids - lower_left_id = old_element_id - lower_right_id = old_element_id + 1 - upper_left_id = old_element_id + 2 - upper_right_id = old_element_id + 3 - - @boundscheck begin - @assert old_element_id >= 1 - @assert size(old_u, 1) == nvariables(equations) - @assert size(old_u, 2) == nnodes(dg) - @assert size(old_u, 3) == nnodes(dg) - @assert size(old_u, 4) >= old_element_id + 3 - @assert element_id >= 1 - @assert size(u, 1) == nvariables(equations) - @assert size(u, 2) == nnodes(dg) - @assert size(u, 3) == nnodes(dg) - @assert size(u, 4) >= element_id - end + function coarsen!( + u_ode::AbstractVector, adaptor, + mesh::Union{TreeMesh{2}, P4estMesh{2}, TreeMesh{3}, P4estMesh{3}}, + equations, dg::DGSEM, cache, cache_parabolic, + elements_to_remove + ) + # Call `coarsen!` for the hyperbolic part, which does the heavy lifting of + # actually transferring the solution to the coarsened cells + coarsen!(u_ode, adaptor, mesh, equations, dg, cache, elements_to_remove) + + # Resize parabolic helper variables + @unpack viscous_container = cache_parabolic + resize!(viscous_container, equations, dg, cache) + reinitialize_containers!(mesh, equations, dg, cache_parabolic) + + # Sanity check + if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 && + !mpi_isparallel() + @assert ninterfaces(cache_parabolic.interfaces) == ndims(mesh) * + nelements(dg, cache_parabolic) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") + end - for j in eachnode(dg), i in eachnode(dg) - acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) + return nothing + end - # Project from lower left element - for l in eachnode(dg), k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, l, lower_left_id) * - reverse_lower[i, k] * reverse_lower[j, l] + # TODO: Taal compare performance of different implementations + # Coarsen solution data u for four elements, using L2 projection + function coarsen_elements!( + u::AbstractArray{<:Any, 4}, element_id, + old_u, old_element_id, + adaptor::LobattoLegendreAdaptorL2, equations, dg + ) + @unpack reverse_upper, reverse_lower = adaptor + + # Store old element ids + lower_left_id = old_element_id + lower_right_id = old_element_id + 1 + upper_left_id = old_element_id + 2 + upper_right_id = old_element_id + 3 + + @boundscheck begin + @assert old_element_id >= 1 + @assert size(old_u, 1) == nvariables(equations) + @assert size(old_u, 2) == nnodes(dg) + @assert size(old_u, 3) == nnodes(dg) + @assert size(old_u, 4) >= old_element_id + 3 + @assert element_id >= 1 + @assert size(u, 1) == nvariables(equations) + @assert size(u, 2) == nnodes(dg) + @assert size(u, 3) == nnodes(dg) + @assert size(u, 4) >= element_id end - # Project from lower right element - for l in eachnode(dg), k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, l, lower_right_id) * - reverse_upper[i, k] * reverse_lower[j, l] - end + for j in eachnode(dg), i in eachnode(dg) + acc = zero(get_node_vars(u, equations, dg, i, j, element_id)) - # Project from upper left element - for l in eachnode(dg), k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, l, upper_left_id) * - reverse_lower[i, k] * reverse_upper[j, l] - end + # Project from lower left element + for l in eachnode(dg), k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, l, lower_left_id) * + reverse_lower[i, k] * reverse_lower[j, l] + end - # Project from upper right element - for l in eachnode(dg), k in eachnode(dg) - acc += get_node_vars(old_u, equations, dg, k, l, upper_right_id) * - reverse_upper[i, k] * reverse_upper[j, l] - end + # Project from lower right element + for l in eachnode(dg), k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, l, lower_right_id) * + reverse_upper[i, k] * reverse_lower[j, l] + end - # Update value - set_node_vars!(u, acc, equations, dg, i, j, element_id) - end -end + # Project from upper left element + for l in eachnode(dg), k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, l, upper_left_id) * + reverse_lower[i, k] * reverse_upper[j, l] + end -# Coarsen and refine elements in the DG solver based on a difference list. -function adapt!(u_ode::AbstractVector, adaptor, mesh::T8codeMesh{2}, equations, - dg::DGSEM, cache, difference) + # Project from upper right element + for l in eachnode(dg), k in eachnode(dg) + acc += get_node_vars(old_u, equations, dg, k, l, upper_right_id) * + reverse_upper[i, k] * reverse_upper[j, l] + end - # Return early if there is nothing to do. - if !any(difference .!= 0) - if mpi_isparallel() - # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do - # locally (there still might be other MPI ranks that have refined elements) - reinitialize_containers!(mesh, equations, dg, cache) + # Update value + set_node_vars!(u, acc, equations, dg, i, j, element_id) end - return end - # Number of (local) cells/elements. - old_nelems = nelements(dg, cache) - new_nelems = ncells(mesh) + # Coarsen and refine elements in the DG solver based on a difference list. + function adapt!( + u_ode::AbstractVector, adaptor, mesh::T8codeMesh{2}, equations, + dg::DGSEM, cache, difference + ) + + # Return early if there is nothing to do. + if !any(difference .!= 0) + if mpi_isparallel() + # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do + # locally (there still might be other MPI ranks that have refined elements) + reinitialize_containers!(mesh, equations, dg, cache) + end + return + end + + # Number of (local) cells/elements. + old_nelems = nelements(dg, cache) + new_nelems = ncells(mesh) - # Local element indices. - old_index = 1 - new_index = 1 + # Local element indices. + old_index = 1 + new_index = 1 - # Note: This is true for `quads`. - T8_CHILDREN = 4 + # Note: This is true for `quads`. + T8_CHILDREN = 4 - # Retain current solution and inverse Jacobian data. - old_u_ode = copy(u_ode) - old_inverse_jacobian = copy(cache.elements.inverse_jacobian) + # Retain current solution and inverse Jacobian data. + old_u_ode = copy(u_ode) + old_inverse_jacobian = copy(cache.elements.inverse_jacobian) - # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed - GC.@preserve old_u_ode begin - old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed + GC.@preserve old_u_ode begin + old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) - # Loop over all elements in old container and scale the old solution by the Jacobian - # prior to interpolation or projection - for old_element_id in 1:old_nelems - for v in eachvariable(equations) - old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./ - old_inverse_jacobian[.., - old_element_id]) + # Loop over all elements in old container and scale the old solution by the Jacobian + # prior to interpolation or projection + for old_element_id in 1:old_nelems + for v in eachvariable(equations) + old_u[v, .., old_element_id] .= ( + old_u[v, .., old_element_id] ./ + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end end - end - reinitialize_containers!(mesh, equations, dg, cache) + reinitialize_containers!(mesh, equations, dg, cache) - resize!(u_ode, nvariables(equations) * ndofs(mesh, dg, cache)) - u = wrap_array(u_ode, mesh, equations, dg, cache) + resize!(u_ode, nvariables(equations) * ndofs(mesh, dg, cache)) + u = wrap_array(u_ode, mesh, equations, dg, cache) - while old_index <= old_nelems && new_index <= new_nelems - if difference[old_index] > 0 # Refine. + while old_index <= old_nelems && new_index <= new_nelems + if difference[old_index] > 0 # Refine. - # Refine element and store solution directly in new data structure. - refine_element!(u, new_index, old_u, old_index, adaptor, equations, dg) + # Refine element and store solution directly in new data structure. + refine_element!(u, new_index, old_u, old_index, adaptor, equations, dg) - # Before indices are incremented divide by the new Jacobians on each - # child element and save the result - for m in 0:3 # loop over the children - for v in eachvariable(equations) - u[v, .., new_index + m] .*= (0.25f0 .* - cache.elements.inverse_jacobian[.., - new_index + m]) + # Before indices are incremented divide by the new Jacobians on each + # child element and save the result + for m in 0:3 # loop over the children + for v in eachvariable(equations) + u[v, .., new_index + m] .*= ( + 0.25f0 .* + cache.elements.inverse_jacobian[ + .., + new_index + m, + ] + ) + end end - end - # Increment `old_index` on the original mesh and the `new_index` - # on the refined mesh with the number of children, i.e., T8_CHILDREN = 4 - old_index += 1 - new_index += T8_CHILDREN + # Increment `old_index` on the original mesh and the `new_index` + # on the refined mesh with the number of children, i.e., T8_CHILDREN = 4 + old_index += 1 + new_index += T8_CHILDREN - elseif difference[old_index] < 0 # Coarsen. + elseif difference[old_index] < 0 # Coarsen. - # If an element is to be removed, sanity check if the following elements - # are also marked - otherwise there would be an error in the way the - # cells/elements are sorted. - @assert all(difference[old_index:(old_index + T8_CHILDREN - 1)] .< 0) "bad cell/element order" + # If an element is to be removed, sanity check if the following elements + # are also marked - otherwise there would be an error in the way the + # cells/elements are sorted. + @assert all(difference[old_index:(old_index + T8_CHILDREN - 1)] .< 0) "bad cell/element order" - # Coarsen elements and store solution directly in new data structure. - coarsen_elements!(u, new_index, old_u, old_index, adaptor, equations, - dg) + # Coarsen elements and store solution directly in new data structure. + coarsen_elements!( + u, new_index, old_u, old_index, adaptor, equations, + dg + ) - # Before the indices are incremented divide by the new Jacobian and save - # the result again in the parent element - for v in eachvariable(equations) - u[v, .., new_index] .*= (4 .* cache.elements.inverse_jacobian[.., - new_index]) - end + # Before the indices are incremented divide by the new Jacobian and save + # the result again in the parent element + for v in eachvariable(equations) + u[v, .., new_index] .*= ( + 4 .* cache.elements.inverse_jacobian[ + .., + new_index, + ] + ) + end - # Increment `old_index` on the original mesh with the number of children - # (T8_CHILDREN = 4 in 2D) and the `new_index` by one for the single - # coarsened element - old_index += T8_CHILDREN - new_index += 1 + # Increment `old_index` on the original mesh with the number of children + # (T8_CHILDREN = 4 in 2D) and the `new_index` by one for the single + # coarsened element + old_index += T8_CHILDREN + new_index += 1 - else # No changes. + else # No changes. - # Copy old element data to new element container and remove Jacobian scaling - for v in eachvariable(equations) - u[v, .., new_index] .= (old_u[v, .., old_index] .* - old_inverse_jacobian[.., old_index]) + # Copy old element data to new element container and remove Jacobian scaling + for v in eachvariable(equations) + u[v, .., new_index] .= ( + old_u[v, .., old_index] .* + old_inverse_jacobian[.., old_index] + ) + end + + # No refinement / coarsening occurred, so increment element index + # on each mesh by one + old_index += 1 + new_index += 1 end + end # while + end # GC.@preserve old_u_ode old_inverse_jacobian - # No refinement / coarsening occurred, so increment element index - # on each mesh by one - old_index += 1 - new_index += 1 - end - end # while - end # GC.@preserve old_u_ode old_inverse_jacobian - - return nothing -end - -# this method is called when an `ControllerThreeLevel` is constructed -function create_cache(::Type{ControllerThreeLevel}, - mesh::Union{TreeMesh{2}, P4estMesh{2}, T8codeMesh{2}}, equations, - dg::DG, cache) - controller_value = Vector{Int}(undef, nelements(dg, cache)) - return (; controller_value) -end - -function create_cache(::Type{ControllerThreeLevelCombined}, mesh::TreeMesh{2}, - equations, dg::DG, cache) - controller_value = Vector{Int}(undef, nelements(dg, cache)) - return (; controller_value) -end + return nothing + end + + # this method is called when an `ControllerThreeLevel` is constructed + function create_cache( + ::Type{ControllerThreeLevel}, + mesh::Union{TreeMesh{2}, P4estMesh{2}, T8codeMesh{2}}, equations, + dg::DG, cache + ) + controller_value = Vector{Int}(undef, nelements(dg, cache)) + return (; controller_value) + end + + function create_cache( + ::Type{ControllerThreeLevelCombined}, mesh::TreeMesh{2}, + equations, dg::DG, cache + ) + controller_value = Vector{Int}(undef, nelements(dg, cache)) + return (; controller_value) + end end # @muladd diff --git a/src/callbacks_step/amr_dg3d.jl b/src/callbacks_step/amr_dg3d.jl index 594c30dcca..11f71b73a9 100644 --- a/src/callbacks_step/amr_dg3d.jl +++ b/src/callbacks_step/amr_dg3d.jl @@ -3,507 +3,613 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Refine elements in the DG solver based on a list of cell_ids that should be refined. -# On `P4estMesh`, if an element refines the solution scaled by the Jacobian `J*u` is interpolated -# from the parent element into the eight children elements. The solution on each child -# element is then recovered by dividing by the new element Jacobians. -function refine!(u_ode::AbstractVector, adaptor, mesh::Union{TreeMesh{3}, P4estMesh{3}}, - equations, dg::DGSEM, cache, elements_to_refine) - # Return early if there is nothing to do - if isempty(elements_to_refine) - if mpi_isparallel() - # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do - # locally (there still might be other MPI ranks that have refined elements) - reinitialize_containers!(mesh, equations, dg, cache) + #! format: noindent + + # Refine elements in the DG solver based on a list of cell_ids that should be refined. + # On `P4estMesh`, if an element refines the solution scaled by the Jacobian `J*u` is interpolated + # from the parent element into the eight children elements. The solution on each child + # element is then recovered by dividing by the new element Jacobians. + function refine!( + u_ode::AbstractVector, adaptor, mesh::Union{TreeMesh{3}, P4estMesh{3}}, + equations, dg::DGSEM, cache, elements_to_refine + ) + # Return early if there is nothing to do + if isempty(elements_to_refine) + if mpi_isparallel() + # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do + # locally (there still might be other MPI ranks that have refined elements) + reinitialize_containers!(mesh, equations, dg, cache) + end + return end - return - end - - # Determine for each existing element whether it needs to be refined - needs_refinement = falses(nelements(dg, cache)) - needs_refinement[elements_to_refine] .= true - - # Retain current solution data - old_n_elements = nelements(dg, cache) - old_u_ode = copy(u_ode) - old_inverse_jacobian = copy(cache.elements.inverse_jacobian) - # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed - GC.@preserve old_u_ode old_inverse_jacobian begin - old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) - if mesh isa P4estMesh - # Loop over all elements in old container and scale the old solution by the Jacobian - # prior to projection - for old_element_id in 1:old_n_elements - for v in eachvariable(equations) - old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./ - old_inverse_jacobian[.., - old_element_id]) + # Determine for each existing element whether it needs to be refined + needs_refinement = falses(nelements(dg, cache)) + needs_refinement[elements_to_refine] .= true + + # Retain current solution data + old_n_elements = nelements(dg, cache) + old_u_ode = copy(u_ode) + old_inverse_jacobian = copy(cache.elements.inverse_jacobian) + # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed + GC.@preserve old_u_ode old_inverse_jacobian begin + old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + + if mesh isa P4estMesh + # Loop over all elements in old container and scale the old solution by the Jacobian + # prior to projection + for old_element_id in 1:old_n_elements + for v in eachvariable(equations) + old_u[v, .., old_element_id] .= ( + old_u[v, .., old_element_id] ./ + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end end end - end - reinitialize_containers!(mesh, equations, dg, cache) - - resize!(u_ode, - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) - u = wrap_array(u_ode, mesh, equations, dg, cache) - - # Loop over all elements in old container and either copy them or refine them - u_tmp1 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg)) - u_tmp2 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg)) - element_id = 1 - for old_element_id in 1:old_n_elements - if needs_refinement[old_element_id] - # Refine element and store solution directly in new data structure - refine_element!(u, element_id, old_u, old_element_id, adaptor, - equations, dg, u_tmp1, u_tmp2) - - if mesh isa P4estMesh - # Before `element_id` is incremented, divide by the new Jacobians on each - # child element and save the result - for m in 0:7 # loop over the children - for v in eachvariable(equations) - u[v, .., element_id + m] .*= (0.125f0 .* - cache.elements.inverse_jacobian[.., - element_id + m]) + reinitialize_containers!(mesh, equations, dg, cache) + + resize!( + u_ode, + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + ) + u = wrap_array(u_ode, mesh, equations, dg, cache) + + # Loop over all elements in old container and either copy them or refine them + u_tmp1 = Array{eltype(u), 4}( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + ) + u_tmp2 = Array{eltype(u), 4}( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + ) + element_id = 1 + for old_element_id in 1:old_n_elements + if needs_refinement[old_element_id] + # Refine element and store solution directly in new data structure + refine_element!( + u, element_id, old_u, old_element_id, adaptor, + equations, dg, u_tmp1, u_tmp2 + ) + + if mesh isa P4estMesh + # Before `element_id` is incremented, divide by the new Jacobians on each + # child element and save the result + for m in 0:7 # loop over the children + for v in eachvariable(equations) + u[v, .., element_id + m] .*= ( + 0.125f0 .* + cache.elements.inverse_jacobian[ + .., + element_id + m, + ] + ) + end end end - end - # Increment `element_id` on the refined mesh with the number - # of children, i.e., 8 in 3D - element_id += 2^ndims(mesh) - else - if mesh isa P4estMesh - # Copy old element data to new element container and remove Jacobian scaling - for v in eachvariable(equations) - u[v, .., element_id] .= (old_u[v, .., old_element_id] .* - old_inverse_jacobian[.., - old_element_id]) + # Increment `element_id` on the refined mesh with the number + # of children, i.e., 8 in 3D + element_id += 2^ndims(mesh) + else + if mesh isa P4estMesh + # Copy old element data to new element container and remove Jacobian scaling + for v in eachvariable(equations) + u[v, .., element_id] .= ( + old_u[v, .., old_element_id] .* + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end + else # isa TreeMesh + @views u[:, .., element_id] .= old_u[:, .., old_element_id] end - else # isa TreeMesh - @views u[:, .., element_id] .= old_u[:, .., old_element_id] + # No refinement occurred, so increment `element_id` on the new mesh by one + element_id += 1 end - # No refinement occurred, so increment `element_id` on the new mesh by one - element_id += 1 end - end - # If everything is correct, we should have processed all elements. - # Depending on whether the last element processed above had to be refined or not, - # the counter `element_id` can have two different values at the end. - @assert element_id == + # If everything is correct, we should have processed all elements. + # Depending on whether the last element processed above had to be refined or not, + # the counter `element_id` can have two different values at the end. + @assert element_id == nelements(dg, cache) + 1||element_id == nelements(dg, cache) + 2^ndims(mesh) "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))" - end # GC.@preserve old_u_ode old_inverse_jacobian + end # GC.@preserve old_u_ode old_inverse_jacobian - # Sanity check - if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 - @assert ninterfaces(cache.interfaces)==ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") - end + # Sanity check + if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 + @assert ninterfaces(cache.interfaces) == ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") + end - return nothing -end - -# TODO: Taal compare performance of different implementations -# Refine solution data u for an element, using L2 projection (interpolation) -function refine_element!(u::AbstractArray{<:Any, 5}, element_id, - old_u, old_element_id, - adaptor::LobattoLegendreAdaptorL2, equations, dg, - u_tmp1, u_tmp2) - @unpack forward_upper, forward_lower = adaptor - - # Store new element ids - bottom_lower_left_id = element_id - bottom_lower_right_id = element_id + 1 - bottom_upper_left_id = element_id + 2 - bottom_upper_right_id = element_id + 3 - top_lower_left_id = element_id + 4 - top_lower_right_id = element_id + 5 - top_upper_left_id = element_id + 6 - top_upper_right_id = element_id + 7 - - @boundscheck begin - @assert old_element_id >= 1 - @assert size(old_u, 1) == nvariables(equations) - @assert size(old_u, 2) == nnodes(dg) - @assert size(old_u, 3) == nnodes(dg) - @assert size(old_u, 4) == nnodes(dg) - @assert size(old_u, 5) >= old_element_id - @assert element_id >= 1 - @assert size(u, 1) == nvariables(equations) - @assert size(u, 2) == nnodes(dg) - @assert size(u, 3) == nnodes(dg) - @assert size(u, 4) == nnodes(dg) - @assert size(u, 5) >= element_id + 7 + return nothing end - # Interpolate to bottom lower left element - multiply_dimensionwise!(view(u, :, :, :, :, bottom_lower_left_id), forward_lower, - forward_lower, forward_lower, - view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2) - - # Interpolate to bottom lower right element - multiply_dimensionwise!(view(u, :, :, :, :, bottom_lower_right_id), forward_upper, - forward_lower, forward_lower, - view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2) - - # Interpolate to bottom upper left element - multiply_dimensionwise!(view(u, :, :, :, :, bottom_upper_left_id), forward_lower, - forward_upper, forward_lower, - view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2) - - # Interpolate to bottom upper right element - multiply_dimensionwise!(view(u, :, :, :, :, bottom_upper_right_id), forward_upper, - forward_upper, forward_lower, - view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2) - - # Interpolate to top lower left element - multiply_dimensionwise!(view(u, :, :, :, :, top_lower_left_id), forward_lower, - forward_lower, forward_upper, - view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2) - - # Interpolate to top lower right element - multiply_dimensionwise!(view(u, :, :, :, :, top_lower_right_id), forward_upper, - forward_lower, forward_upper, - view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2) - - # Interpolate to top upper left element - multiply_dimensionwise!(view(u, :, :, :, :, top_upper_left_id), forward_lower, - forward_upper, forward_upper, - view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2) - - # Interpolate to top upper right element - multiply_dimensionwise!(view(u, :, :, :, :, top_upper_right_id), forward_upper, - forward_upper, forward_upper, - view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2) - - return nothing -end - -# Coarsen elements in the DG solver based on a list of cell_ids that should be removed. -# On `P4estMesh`, if an element coarsens the solution scaled by the Jacobian `J*u` is projected -# from the eight children elements back onto the parent element. The solution on the parent -# element is then recovered by dividing by the new element Jacobian. -function coarsen!(u_ode::AbstractVector, adaptor, - mesh::Union{TreeMesh{3}, P4estMesh{3}}, - equations, dg::DGSEM, cache, elements_to_remove) - # Return early if there is nothing to do - if isempty(elements_to_remove) - if mpi_isparallel() - # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do - # locally (there still might be other MPI ranks that have coarsened elements) - reinitialize_containers!(mesh, equations, dg, cache) + # TODO: Taal compare performance of different implementations + # Refine solution data u for an element, using L2 projection (interpolation) + function refine_element!( + u::AbstractArray{<:Any, 5}, element_id, + old_u, old_element_id, + adaptor::LobattoLegendreAdaptorL2, equations, dg, + u_tmp1, u_tmp2 + ) + @unpack forward_upper, forward_lower = adaptor + + # Store new element ids + bottom_lower_left_id = element_id + bottom_lower_right_id = element_id + 1 + bottom_upper_left_id = element_id + 2 + bottom_upper_right_id = element_id + 3 + top_lower_left_id = element_id + 4 + top_lower_right_id = element_id + 5 + top_upper_left_id = element_id + 6 + top_upper_right_id = element_id + 7 + + @boundscheck begin + @assert old_element_id >= 1 + @assert size(old_u, 1) == nvariables(equations) + @assert size(old_u, 2) == nnodes(dg) + @assert size(old_u, 3) == nnodes(dg) + @assert size(old_u, 4) == nnodes(dg) + @assert size(old_u, 5) >= old_element_id + @assert element_id >= 1 + @assert size(u, 1) == nvariables(equations) + @assert size(u, 2) == nnodes(dg) + @assert size(u, 3) == nnodes(dg) + @assert size(u, 4) == nnodes(dg) + @assert size(u, 5) >= element_id + 7 end - return - end - - # Determine for each old element whether it needs to be removed - to_be_removed = falses(nelements(dg, cache)) - to_be_removed[elements_to_remove] .= true - # Retain current solution data and Jacobians - old_n_elements = nelements(dg, cache) - old_u_ode = copy(u_ode) - old_inverse_jacobian = copy(cache.elements.inverse_jacobian) - # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed - GC.@preserve old_u_ode old_inverse_jacobian begin - old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + # Interpolate to bottom lower left element + multiply_dimensionwise!( + view(u, :, :, :, :, bottom_lower_left_id), forward_lower, + forward_lower, forward_lower, + view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2 + ) + + # Interpolate to bottom lower right element + multiply_dimensionwise!( + view(u, :, :, :, :, bottom_lower_right_id), forward_upper, + forward_lower, forward_lower, + view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2 + ) + + # Interpolate to bottom upper left element + multiply_dimensionwise!( + view(u, :, :, :, :, bottom_upper_left_id), forward_lower, + forward_upper, forward_lower, + view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2 + ) + + # Interpolate to bottom upper right element + multiply_dimensionwise!( + view(u, :, :, :, :, bottom_upper_right_id), forward_upper, + forward_upper, forward_lower, + view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2 + ) + + # Interpolate to top lower left element + multiply_dimensionwise!( + view(u, :, :, :, :, top_lower_left_id), forward_lower, + forward_lower, forward_upper, + view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2 + ) + + # Interpolate to top lower right element + multiply_dimensionwise!( + view(u, :, :, :, :, top_lower_right_id), forward_upper, + forward_lower, forward_upper, + view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2 + ) + + # Interpolate to top upper left element + multiply_dimensionwise!( + view(u, :, :, :, :, top_upper_left_id), forward_lower, + forward_upper, forward_upper, + view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2 + ) + + # Interpolate to top upper right element + multiply_dimensionwise!( + view(u, :, :, :, :, top_upper_right_id), forward_upper, + forward_upper, forward_upper, + view(old_u, :, :, :, :, old_element_id), u_tmp1, u_tmp2 + ) + + return nothing + end - if mesh isa P4estMesh - # Loop over all elements in old container and scale the old solution by the Jacobian - # prior to projection - for old_element_id in 1:old_n_elements - for v in eachvariable(equations) - old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./ - old_inverse_jacobian[.., - old_element_id]) - end + # Coarsen elements in the DG solver based on a list of cell_ids that should be removed. + # On `P4estMesh`, if an element coarsens the solution scaled by the Jacobian `J*u` is projected + # from the eight children elements back onto the parent element. The solution on the parent + # element is then recovered by dividing by the new element Jacobian. + function coarsen!( + u_ode::AbstractVector, adaptor, + mesh::Union{TreeMesh{3}, P4estMesh{3}}, + equations, dg::DGSEM, cache, elements_to_remove + ) + # Return early if there is nothing to do + if isempty(elements_to_remove) + if mpi_isparallel() + # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do + # locally (there still might be other MPI ranks that have coarsened elements) + reinitialize_containers!(mesh, equations, dg, cache) end + return end - reinitialize_containers!(mesh, equations, dg, cache) - - resize!(u_ode, - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) - u = wrap_array(u_ode, mesh, equations, dg, cache) - - # Loop over all elements in old container and either copy them or coarsen them - u_tmp1 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg)) - u_tmp2 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg)) - skip = 0 - element_id = 1 - for old_element_id in 1:old_n_elements - # If skip is non-zero, we just coarsened 2^ndims elements and need to omit the following elements - if skip > 0 - skip -= 1 - continue + # Determine for each old element whether it needs to be removed + to_be_removed = falses(nelements(dg, cache)) + to_be_removed[elements_to_remove] .= true + + # Retain current solution data and Jacobians + old_n_elements = nelements(dg, cache) + old_u_ode = copy(u_ode) + old_inverse_jacobian = copy(cache.elements.inverse_jacobian) + # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed + GC.@preserve old_u_ode old_inverse_jacobian begin + old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + + if mesh isa P4estMesh + # Loop over all elements in old container and scale the old solution by the Jacobian + # prior to projection + for old_element_id in 1:old_n_elements + for v in eachvariable(equations) + old_u[v, .., old_element_id] .= ( + old_u[v, .., old_element_id] ./ + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end + end end - if to_be_removed[old_element_id] - # If an element is to be removed, sanity check if the following elements - # are also marked - otherwise there would be an error in the way the - # cells/elements are sorted - @assert all(to_be_removed[old_element_id:(old_element_id + 2^ndims(mesh) - 1)]) "bad cell/element order" + reinitialize_containers!(mesh, equations, dg, cache) - # Coarsen elements and store solution directly in new data structure - coarsen_elements!(u, element_id, old_u, old_element_id, adaptor, - equations, dg, u_tmp1, u_tmp2) + resize!( + u_ode, + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + ) + u = wrap_array(u_ode, mesh, equations, dg, cache) + + # Loop over all elements in old container and either copy them or coarsen them + u_tmp1 = Array{eltype(u), 4}( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + ) + u_tmp2 = Array{eltype(u), 4}( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + ) + skip = 0 + element_id = 1 + for old_element_id in 1:old_n_elements + # If skip is non-zero, we just coarsened 2^ndims elements and need to omit the following elements + if skip > 0 + skip -= 1 + continue + end - if mesh isa P4estMesh - # Before `element_id` is incremented, divide by the new Jacobian and save - # the result in the parent element - for v in eachvariable(equations) - u[v, .., element_id] .*= (8 .* - cache.elements.inverse_jacobian[.., - element_id]) + if to_be_removed[old_element_id] + # If an element is to be removed, sanity check if the following elements + # are also marked - otherwise there would be an error in the way the + # cells/elements are sorted + @assert all(to_be_removed[old_element_id:(old_element_id + 2^ndims(mesh) - 1)]) "bad cell/element order" + + # Coarsen elements and store solution directly in new data structure + coarsen_elements!( + u, element_id, old_u, old_element_id, adaptor, + equations, dg, u_tmp1, u_tmp2 + ) + + if mesh isa P4estMesh + # Before `element_id` is incremented, divide by the new Jacobian and save + # the result in the parent element + for v in eachvariable(equations) + u[v, .., element_id] .*= ( + 8 .* + cache.elements.inverse_jacobian[ + .., + element_id, + ] + ) + end end - end - # Increment `element_id` on the coarsened mesh by one and `skip` = 7 in 3D - # because 8 children elements become 1 parent element - element_id += 1 - skip = 2^ndims(mesh) - 1 - else - if mesh isa P4estMesh - # Copy old element data to new element container and remove Jacobian scaling - for v in eachvariable(equations) - u[v, .., element_id] .= (old_u[v, .., old_element_id] .* - old_inverse_jacobian[.., - old_element_id]) + # Increment `element_id` on the coarsened mesh by one and `skip` = 7 in 3D + # because 8 children elements become 1 parent element + element_id += 1 + skip = 2^ndims(mesh) - 1 + else + if mesh isa P4estMesh + # Copy old element data to new element container and remove Jacobian scaling + for v in eachvariable(equations) + u[v, .., element_id] .= ( + old_u[v, .., old_element_id] .* + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end + else # isa TreeMesh + @views u[:, .., element_id] .= old_u[:, .., old_element_id] end - else # isa TreeMesh - @views u[:, .., element_id] .= old_u[:, .., old_element_id] + # No coarsening occurred, so increment `element_id` on the new mesh by one + element_id += 1 end - # No coarsening occurred, so increment `element_id` on the new mesh by one - element_id += 1 end + # If everything is correct, we should have processed all elements. + @assert element_id == nelements(dg, cache) + 1 "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))" + end # GC.@preserve old_u_ode old_inverse_jacobian + + # Sanity check + if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 + @assert ninterfaces(cache.interfaces) == ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") + end + + return nothing + end + + # TODO: Taal compare performance of different implementations + # Coarsen solution data u for four elements, using L2 projection + function coarsen_elements!( + u::AbstractArray{<:Any, 5}, element_id, + old_u, old_element_id, + adaptor::LobattoLegendreAdaptorL2, equations, dg, + u_tmp1, u_tmp2 + ) + @unpack reverse_upper, reverse_lower = adaptor + + # Store old element ids + bottom_lower_left_id = old_element_id + bottom_lower_right_id = old_element_id + 1 + bottom_upper_left_id = old_element_id + 2 + bottom_upper_right_id = old_element_id + 3 + top_lower_left_id = old_element_id + 4 + top_lower_right_id = old_element_id + 5 + top_upper_left_id = old_element_id + 6 + top_upper_right_id = old_element_id + 7 + + @boundscheck begin + @assert old_element_id >= 1 + @assert size(old_u, 1) == nvariables(equations) + @assert size(old_u, 2) == nnodes(dg) + @assert size(old_u, 3) == nnodes(dg) + @assert size(old_u, 4) == nnodes(dg) + @assert size(old_u, 5) >= old_element_id + 7 + @assert element_id >= 1 + @assert size(u, 1) == nvariables(equations) + @assert size(u, 2) == nnodes(dg) + @assert size(u, 3) == nnodes(dg) + @assert size(u, 4) == nnodes(dg) + @assert size(u, 5) >= element_id end - # If everything is correct, we should have processed all elements. - @assert element_id==nelements(dg, cache) + 1 "element_id = $element_id, nelements(dg, cache) = $(nelements(dg, cache))" - end # GC.@preserve old_u_ode old_inverse_jacobian - # Sanity check - if mesh isa TreeMesh && isperiodic(mesh.tree) && nmortars(cache.mortars) == 0 - @assert ninterfaces(cache.interfaces)==ndims(mesh) * nelements(dg, cache) ("For $(ndims(mesh))D and periodic domains and conforming elements, the number of interfaces must be $(ndims(mesh)) times the number of elements") + # Project from bottom lower left element + multiply_dimensionwise!( + view(u, :, :, :, :, element_id), reverse_lower, + reverse_lower, reverse_lower, + view(old_u, :, :, :, :, bottom_lower_left_id), u_tmp1, + u_tmp2 + ) + + # Project from bottom lower right element_variables + add_multiply_dimensionwise!( + view(u, :, :, :, :, element_id), reverse_upper, + reverse_lower, reverse_lower, + view(old_u, :, :, :, :, bottom_lower_right_id), u_tmp1, + u_tmp2 + ) + + # Project from bottom upper left element + add_multiply_dimensionwise!( + view(u, :, :, :, :, element_id), reverse_lower, + reverse_upper, reverse_lower, + view(old_u, :, :, :, :, bottom_upper_left_id), u_tmp1, + u_tmp2 + ) + + # Project from bottom upper right element + add_multiply_dimensionwise!( + view(u, :, :, :, :, element_id), reverse_upper, + reverse_upper, reverse_lower, + view(old_u, :, :, :, :, bottom_upper_right_id), u_tmp1, + u_tmp2 + ) + + # Project from top lower left element + add_multiply_dimensionwise!( + view(u, :, :, :, :, element_id), reverse_lower, + reverse_lower, reverse_upper, + view(old_u, :, :, :, :, top_lower_left_id), u_tmp1, + u_tmp2 + ) + + # Project from top lower right element + add_multiply_dimensionwise!( + view(u, :, :, :, :, element_id), reverse_upper, + reverse_lower, reverse_upper, + view(old_u, :, :, :, :, top_lower_right_id), u_tmp1, + u_tmp2 + ) + + # Project from top upper left element + add_multiply_dimensionwise!( + view(u, :, :, :, :, element_id), reverse_lower, + reverse_upper, reverse_upper, + view(old_u, :, :, :, :, top_upper_left_id), u_tmp1, + u_tmp2 + ) + + # Project from top upper right element + add_multiply_dimensionwise!( + view(u, :, :, :, :, element_id), reverse_upper, + reverse_upper, reverse_upper, + view(old_u, :, :, :, :, top_upper_right_id), u_tmp1, + u_tmp2 + ) + + return nothing end - return nothing -end - -# TODO: Taal compare performance of different implementations -# Coarsen solution data u for four elements, using L2 projection -function coarsen_elements!(u::AbstractArray{<:Any, 5}, element_id, - old_u, old_element_id, - adaptor::LobattoLegendreAdaptorL2, equations, dg, - u_tmp1, u_tmp2) - @unpack reverse_upper, reverse_lower = adaptor - - # Store old element ids - bottom_lower_left_id = old_element_id - bottom_lower_right_id = old_element_id + 1 - bottom_upper_left_id = old_element_id + 2 - bottom_upper_right_id = old_element_id + 3 - top_lower_left_id = old_element_id + 4 - top_lower_right_id = old_element_id + 5 - top_upper_left_id = old_element_id + 6 - top_upper_right_id = old_element_id + 7 - - @boundscheck begin - @assert old_element_id >= 1 - @assert size(old_u, 1) == nvariables(equations) - @assert size(old_u, 2) == nnodes(dg) - @assert size(old_u, 3) == nnodes(dg) - @assert size(old_u, 4) == nnodes(dg) - @assert size(old_u, 5) >= old_element_id + 7 - @assert element_id >= 1 - @assert size(u, 1) == nvariables(equations) - @assert size(u, 2) == nnodes(dg) - @assert size(u, 3) == nnodes(dg) - @assert size(u, 4) == nnodes(dg) - @assert size(u, 5) >= element_id + # this method is called when an `ControllerThreeLevel` is constructed + function create_cache( + ::Type{ControllerThreeLevel}, + mesh::Union{TreeMesh{3}, P4estMesh{3}, T8codeMesh{3}}, + equations, dg::DG, cache + ) + controller_value = Vector{Int}(undef, nelements(dg, cache)) + return (; controller_value) end - # Project from bottom lower left element - multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_lower, - reverse_lower, reverse_lower, - view(old_u, :, :, :, :, bottom_lower_left_id), u_tmp1, - u_tmp2) - - # Project from bottom lower right element_variables - add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_upper, - reverse_lower, reverse_lower, - view(old_u, :, :, :, :, bottom_lower_right_id), u_tmp1, - u_tmp2) - - # Project from bottom upper left element - add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_lower, - reverse_upper, reverse_lower, - view(old_u, :, :, :, :, bottom_upper_left_id), u_tmp1, - u_tmp2) - - # Project from bottom upper right element - add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_upper, - reverse_upper, reverse_lower, - view(old_u, :, :, :, :, bottom_upper_right_id), u_tmp1, - u_tmp2) - - # Project from top lower left element - add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_lower, - reverse_lower, reverse_upper, - view(old_u, :, :, :, :, top_lower_left_id), u_tmp1, - u_tmp2) - - # Project from top lower right element - add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_upper, - reverse_lower, reverse_upper, - view(old_u, :, :, :, :, top_lower_right_id), u_tmp1, - u_tmp2) - - # Project from top upper left element - add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_lower, - reverse_upper, reverse_upper, - view(old_u, :, :, :, :, top_upper_left_id), u_tmp1, - u_tmp2) - - # Project from top upper right element - add_multiply_dimensionwise!(view(u, :, :, :, :, element_id), reverse_upper, - reverse_upper, reverse_upper, - view(old_u, :, :, :, :, top_upper_right_id), u_tmp1, - u_tmp2) - - return nothing -end - -# this method is called when an `ControllerThreeLevel` is constructed -function create_cache(::Type{ControllerThreeLevel}, - mesh::Union{TreeMesh{3}, P4estMesh{3}, T8codeMesh{3}}, - equations, dg::DG, cache) - controller_value = Vector{Int}(undef, nelements(dg, cache)) - return (; controller_value) -end - -# Coarsen and refine elements in the DG solver based on a difference list. -function adapt!(u_ode::AbstractVector, adaptor, mesh::T8codeMesh{3}, equations, - dg::DGSEM, cache, difference) - - # Return early if there is nothing to do. - if !any(difference .!= 0) - if mpi_isparallel() - # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do - # locally (there still might be other MPI ranks that have refined elements) - reinitialize_containers!(mesh, equations, dg, cache) + # Coarsen and refine elements in the DG solver based on a difference list. + function adapt!( + u_ode::AbstractVector, adaptor, mesh::T8codeMesh{3}, equations, + dg::DGSEM, cache, difference + ) + + # Return early if there is nothing to do. + if !any(difference .!= 0) + if mpi_isparallel() + # MPICache init uses all-to-all communication -> reinitialize even if there is nothing to do + # locally (there still might be other MPI ranks that have refined elements) + reinitialize_containers!(mesh, equations, dg, cache) + end + return end - return - end - # Number of (local) cells/elements. - old_nelems = nelements(dg, cache) - new_nelems = ncells(mesh) + # Number of (local) cells/elements. + old_nelems = nelements(dg, cache) + new_nelems = ncells(mesh) - # Local element indices. - old_index = 1 - new_index = 1 + # Local element indices. + old_index = 1 + new_index = 1 - # Note: This is only true for `hexs`. - T8_CHILDREN = 8 + # Note: This is only true for `hexs`. + T8_CHILDREN = 8 - # Retain current solution and inverse Jacobian data. - old_u_ode = copy(u_ode) - old_inverse_jacobian = copy(cache.elements.inverse_jacobian) + # Retain current solution and inverse Jacobian data. + old_u_ode = copy(u_ode) + old_inverse_jacobian = copy(cache.elements.inverse_jacobian) - # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed - GC.@preserve old_u_ode begin - old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) + # OBS! If we don't GC.@preserve old_u_ode and old_inverse_jacobian, they might be GC'ed + GC.@preserve old_u_ode begin + old_u = wrap_array(old_u_ode, mesh, equations, dg, cache) - # Loop over all elements in old container and scale the old solution by the Jacobian - # prior to interpolation or projection - for old_element_id in 1:old_nelems - for v in eachvariable(equations) - old_u[v, .., old_element_id] .= (old_u[v, .., old_element_id] ./ - old_inverse_jacobian[.., - old_element_id]) + # Loop over all elements in old container and scale the old solution by the Jacobian + # prior to interpolation or projection + for old_element_id in 1:old_nelems + for v in eachvariable(equations) + old_u[v, .., old_element_id] .= ( + old_u[v, .., old_element_id] ./ + old_inverse_jacobian[ + .., + old_element_id, + ] + ) + end end - end - reinitialize_containers!(mesh, equations, dg, cache) + reinitialize_containers!(mesh, equations, dg, cache) - resize!(u_ode, nvariables(equations) * ndofs(mesh, dg, cache)) - u = wrap_array(u_ode, mesh, equations, dg, cache) + resize!(u_ode, nvariables(equations) * ndofs(mesh, dg, cache)) + u = wrap_array(u_ode, mesh, equations, dg, cache) - u_tmp1 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg)) - u_tmp2 = Array{eltype(u), 4}(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg)) + u_tmp1 = Array{eltype(u), 4}( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + ) + u_tmp2 = Array{eltype(u), 4}( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + ) - while old_index <= old_nelems && new_index <= new_nelems - if difference[old_index] > 0 # Refine. + while old_index <= old_nelems && new_index <= new_nelems + if difference[old_index] > 0 # Refine. - # Refine element and store solution directly in new data structure. - refine_element!(u, new_index, old_u, old_index, adaptor, equations, dg, - u_tmp1, u_tmp2) + # Refine element and store solution directly in new data structure. + refine_element!( + u, new_index, old_u, old_index, adaptor, equations, dg, + u_tmp1, u_tmp2 + ) - # Before indices are incremented divide by the new Jacobians on each - # child element and save the result - for m in 0:7 # loop over the children - for v in eachvariable(equations) - u[v, .., new_index + m] .*= (0.125f0 .* - cache.elements.inverse_jacobian[.., - new_index + m]) + # Before indices are incremented divide by the new Jacobians on each + # child element and save the result + for m in 0:7 # loop over the children + for v in eachvariable(equations) + u[v, .., new_index + m] .*= ( + 0.125f0 .* + cache.elements.inverse_jacobian[ + .., + new_index + m, + ] + ) + end end - end - # Increment `old_index` on the original mesh and the `new_index` - # on the refined mesh with the number of children, i.e., T8_CHILDREN = 8 - old_index += 1 - new_index += T8_CHILDREN + # Increment `old_index` on the original mesh and the `new_index` + # on the refined mesh with the number of children, i.e., T8_CHILDREN = 8 + old_index += 1 + new_index += T8_CHILDREN - elseif difference[old_index] < 0 # Coarsen. + elseif difference[old_index] < 0 # Coarsen. - # If an element is to be removed, sanity check if the following elements - # are also marked - otherwise there would be an error in the way the - # cells/elements are sorted. - @assert all(difference[old_index:(old_index + T8_CHILDREN - 1)] .< 0) "bad cell/element order" + # If an element is to be removed, sanity check if the following elements + # are also marked - otherwise there would be an error in the way the + # cells/elements are sorted. + @assert all(difference[old_index:(old_index + T8_CHILDREN - 1)] .< 0) "bad cell/element order" - # Coarsen elements and store solution directly in new data structure. - coarsen_elements!(u, new_index, old_u, old_index, adaptor, equations, - dg, u_tmp1, u_tmp2) + # Coarsen elements and store solution directly in new data structure. + coarsen_elements!( + u, new_index, old_u, old_index, adaptor, equations, + dg, u_tmp1, u_tmp2 + ) - # Before the indices are incremented divide by the new Jacobian and save - # the result again in the parent element - for v in eachvariable(equations) - u[v, .., new_index] .*= (8 .* cache.elements.inverse_jacobian[.., - new_index]) - end + # Before the indices are incremented divide by the new Jacobian and save + # the result again in the parent element + for v in eachvariable(equations) + u[v, .., new_index] .*= ( + 8 .* cache.elements.inverse_jacobian[ + .., + new_index, + ] + ) + end - # Increment `old_index` on the original mesh with the number of children - # (T8_CHILDREN = 8 in 3D) and the `new_index` by one for the single - # coarsened element - old_index += T8_CHILDREN - new_index += 1 + # Increment `old_index` on the original mesh with the number of children + # (T8_CHILDREN = 8 in 3D) and the `new_index` by one for the single + # coarsened element + old_index += T8_CHILDREN + new_index += 1 - else # No changes. + else # No changes. - # Copy old element data to new element container and remove Jacobian scaling - for v in eachvariable(equations) - u[v, .., new_index] .= (old_u[v, .., old_index] .* - old_inverse_jacobian[.., old_index]) - end + # Copy old element data to new element container and remove Jacobian scaling + for v in eachvariable(equations) + u[v, .., new_index] .= ( + old_u[v, .., old_index] .* + old_inverse_jacobian[.., old_index] + ) + end - # No refinement / coarsening occurred, so increment element index - # on each mesh by one - old_index += 1 - new_index += 1 - end - end # while - end # GC.@preserve old_u_ode old_inverse_jacobian + # No refinement / coarsening occurred, so increment element index + # on each mesh by one + old_index += 1 + new_index += 1 + end + end # while + end # GC.@preserve old_u_ode old_inverse_jacobian - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_step/analysis.jl b/src/callbacks_step/analysis.jl index 860e3fa21d..db4d38b116 100644 --- a/src/callbacks_step/analysis.jl +++ b/src/callbacks_step/analysis.jl @@ -3,650 +3,734 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# TODO: Taal refactor -# - analysis_interval part as PeriodicCallback called after a certain amount of simulation time -""" - AnalysisCallback(semi; interval=0, - save_analysis=false, - output_directory="out", - analysis_filename="analysis.dat", - extra_analysis_errors=Symbol[], - extra_analysis_integrals=()) - -Analyze a numerical solution every `interval` time steps and print the -results to the screen. If `save_analysis`, the results are also saved in -`joinpath(output_directory, analysis_filename)`. - -Additional errors can be computed, e.g. by passing -`extra_analysis_errors = (:l2_error_primitive, :linf_error_primitive)` -or `extra_analysis_errors = (:conservation_error,)`. - -If you want to omit the computation (to safe compute-time) of the [`default_analysis_errors`](@ref), specify -`analysis_errors = Symbol[]`. -Note: `default_analysis_errors` are `:l2_error` and `:linf_error` for all equations. -If you want to compute `extra_analysis_errors` such as `:conservation_error` solely, i.e., -without `:l2_error, :linf_error` you need to specify -`analysis_errors = [:conservation_error]` instead of `extra_analysis_errors = [:conservation_error]`. - -Further scalar functions `func` in `extra_analysis_integrals` are applied to the numerical -solution and integrated over the computational domain. Some examples for this are -[`entropy`](@ref), [`energy_kinetic`](@ref), [`energy_internal`](@ref), and [`energy_total`](@ref). -You can also write your own function with the same signature as the examples listed above and -pass it via `extra_analysis_integrals`. -See the developer comments about `Trixi.analyze`, `Trixi.pretty_form_utf`, and -`Trixi.pretty_form_ascii` for further information on how to create custom analysis quantities. - -In addition, the analysis callback records and outputs a number of quantities that are useful for -evaluating the computational performance, such as the total runtime, the performance index -(time/DOF/rhs!), the time spent in garbage collection (GC), or the current memory usage (alloc'd -memory). -""" -mutable struct AnalysisCallback{Analyzer, AnalysisIntegrals, InitialStateIntegrals, - Cache} - start_time::Float64 - start_time_last_analysis::Float64 - ncalls_rhs_last_analysis::Int - start_gc_time::Float64 - interval::Int - save_analysis::Bool - output_directory::String - analysis_filename::String - analyzer::Analyzer - analysis_errors::Vector{Symbol} - analysis_integrals::AnalysisIntegrals - initial_state_integrals::InitialStateIntegrals - cache::Cache -end - -# TODO: Taal bikeshedding, implement a method with less information and the signature -# function Base.show(io::IO, analysis_callback::AnalysisCallback) -# end -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:AnalysisCallback}) - @nospecialize cb # reduce precompilation time - - if get(io, :compact, false) - show(io, cb) - else - analysis_callback = cb.affect! + #! format: noindent + + # TODO: Taal refactor + # - analysis_interval part as PeriodicCallback called after a certain amount of simulation time + """ + AnalysisCallback(semi; interval=0, + save_analysis=false, + output_directory="out", + analysis_filename="analysis.dat", + extra_analysis_errors=Symbol[], + extra_analysis_integrals=()) + + Analyze a numerical solution every `interval` time steps and print the + results to the screen. If `save_analysis`, the results are also saved in + `joinpath(output_directory, analysis_filename)`. + + Additional errors can be computed, e.g. by passing + `extra_analysis_errors = (:l2_error_primitive, :linf_error_primitive)` + or `extra_analysis_errors = (:conservation_error,)`. + + If you want to omit the computation (to safe compute-time) of the [`default_analysis_errors`](@ref), specify + `analysis_errors = Symbol[]`. + Note: `default_analysis_errors` are `:l2_error` and `:linf_error` for all equations. + If you want to compute `extra_analysis_errors` such as `:conservation_error` solely, i.e., + without `:l2_error, :linf_error` you need to specify + `analysis_errors = [:conservation_error]` instead of `extra_analysis_errors = [:conservation_error]`. + + Further scalar functions `func` in `extra_analysis_integrals` are applied to the numerical + solution and integrated over the computational domain. Some examples for this are + [`entropy`](@ref), [`energy_kinetic`](@ref), [`energy_internal`](@ref), and [`energy_total`](@ref). + You can also write your own function with the same signature as the examples listed above and + pass it via `extra_analysis_integrals`. + See the developer comments about `Trixi.analyze`, `Trixi.pretty_form_utf`, and + `Trixi.pretty_form_ascii` for further information on how to create custom analysis quantities. + + In addition, the analysis callback records and outputs a number of quantities that are useful for + evaluating the computational performance, such as the total runtime, the performance index + (time/DOF/rhs!), the time spent in garbage collection (GC), or the current memory usage (alloc'd + memory). + """ + mutable struct AnalysisCallback{ + Analyzer, AnalysisIntegrals, InitialStateIntegrals, + Cache, + } + start_time::Float64 + start_time_last_analysis::Float64 + ncalls_rhs_last_analysis::Int + start_gc_time::Float64 + interval::Int + save_analysis::Bool + output_directory::String + analysis_filename::String + analyzer::Analyzer + analysis_errors::Vector{Symbol} + analysis_integrals::AnalysisIntegrals + initial_state_integrals::InitialStateIntegrals + cache::Cache + end - setup = Pair{String, Any}["interval" => analysis_callback.interval, - "analyzer" => analysis_callback.analyzer] - for (idx, error) in enumerate(analysis_callback.analysis_errors) - push!(setup, "│ error " * string(idx) => error) - end - for (idx, integral) in enumerate(analysis_callback.analysis_integrals) - push!(setup, "│ integral " * string(idx) => integral) - end - push!(setup, - "save analysis to file" => analysis_callback.save_analysis ? "yes" : "no") - if analysis_callback.save_analysis - push!(setup, "│ filename" => analysis_callback.analysis_filename) - push!(setup, - "│ output directory" => abspath(normpath(analysis_callback.output_directory))) + # TODO: Taal bikeshedding, implement a method with less information and the signature + # function Base.show(io::IO, analysis_callback::AnalysisCallback) + # end + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:AnalysisCallback} + ) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + analysis_callback = cb.affect! + + setup = Pair{String, Any}[ + "interval" => analysis_callback.interval, + "analyzer" => analysis_callback.analyzer, + ] + for (idx, error) in enumerate(analysis_callback.analysis_errors) + push!(setup, "│ error " * string(idx) => error) + end + for (idx, integral) in enumerate(analysis_callback.analysis_integrals) + push!(setup, "│ integral " * string(idx) => integral) + end + push!( + setup, + "save analysis to file" => analysis_callback.save_analysis ? "yes" : "no" + ) + if analysis_callback.save_analysis + push!(setup, "│ filename" => analysis_callback.analysis_filename) + push!( + setup, + "│ output directory" => abspath(normpath(analysis_callback.output_directory)) + ) + end + summary_box(io, "AnalysisCallback", setup) end - summary_box(io, "AnalysisCallback", setup) end -end -# This is the convenience constructor that gets called from the elixirs -function AnalysisCallback(semi::AbstractSemidiscretization; kwargs...) - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - AnalysisCallback(mesh, equations, solver, cache; kwargs...) -end + # This is the convenience constructor that gets called from the elixirs + function AnalysisCallback(semi::AbstractSemidiscretization; kwargs...) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + AnalysisCallback(mesh, equations, solver, cache; kwargs...) + end -# This is the actual constructor -function AnalysisCallback(mesh, equations::AbstractEquations, solver, cache; - interval = 0, - save_analysis = false, - output_directory = "out", - analysis_filename = "analysis.dat", - extra_analysis_errors = Symbol[], - analysis_errors = union(default_analysis_errors(equations), - extra_analysis_errors), - extra_analysis_integrals = (), - analysis_integrals = union(default_analysis_integrals(equations), - extra_analysis_integrals), - RealT = real(solver), - uEltype = eltype(cache.elements), - kwargs...) - # Decide when the callback is activated. - # With error-based step size control, some steps can be rejected. Thus, - # `integrator.iter >= integrator.stats.naccept` - # (total #steps) (#accepted steps) - # We need to check the number of accepted steps since callbacks are not - # activated after a rejected step. - condition = (u, t, integrator) -> interval > 0 && - (integrator.stats.naccept % interval == 0 || isfinished(integrator)) - - analyzer = SolutionAnalyzer(solver; kwargs...) - cache_analysis = create_cache_analysis(analyzer, mesh, equations, solver, cache, - RealT, uEltype) - - analysis_callback = AnalysisCallback(0.0, 0.0, 0, 0.0, - interval, save_analysis, output_directory, - analysis_filename, - analyzer, - analysis_errors, Tuple(analysis_integrals), - SVector(ntuple(_ -> zero(uEltype), - Val(nvariables(equations)))), - cache_analysis) - - DiscreteCallback(condition, analysis_callback, - save_positions = (false, false), - initialize = initialize!) -end + # This is the actual constructor + function AnalysisCallback( + mesh, equations::AbstractEquations, solver, cache; + interval = 0, + save_analysis = false, + output_directory = "out", + analysis_filename = "analysis.dat", + extra_analysis_errors = Symbol[], + analysis_errors = union( + default_analysis_errors(equations), + extra_analysis_errors + ), + extra_analysis_integrals = (), + analysis_integrals = union( + default_analysis_integrals(equations), + extra_analysis_integrals + ), + RealT = real(solver), + uEltype = eltype(cache.elements), + kwargs... + ) + # Decide when the callback is activated. + # With error-based step size control, some steps can be rejected. Thus, + # `integrator.iter >= integrator.stats.naccept` + # (total #steps) (#accepted steps) + # We need to check the number of accepted steps since callbacks are not + # activated after a rejected step. + condition = (u, t, integrator) -> interval > 0 && + (integrator.stats.naccept % interval == 0 || isfinished(integrator)) + + analyzer = SolutionAnalyzer(solver; kwargs...) + cache_analysis = create_cache_analysis( + analyzer, mesh, equations, solver, cache, + RealT, uEltype + ) + + analysis_callback = AnalysisCallback( + 0.0, 0.0, 0, 0.0, + interval, save_analysis, output_directory, + analysis_filename, + analyzer, + analysis_errors, Tuple(analysis_integrals), + SVector( + ntuple( + _ -> zero(uEltype), + Val(nvariables(equations)) + ) + ), + cache_analysis + ) + + DiscreteCallback( + condition, analysis_callback, + save_positions = (false, false), + initialize = initialize! + ) + end -# This method gets called from OrdinaryDiffEq's `solve(...)` -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u_ode, t, - integrator) where {Condition, Affect! <: AnalysisCallback} - semi = integrator.p - du_ode = first(get_tmp_cache(integrator)) - initialize!(cb, u_ode, du_ode, t, integrator, semi) -end + # This method gets called from OrdinaryDiffEq's `solve(...)` + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u_ode, t, + integrator + ) where {Condition, Affect! <: AnalysisCallback} + semi = integrator.p + du_ode = first(get_tmp_cache(integrator)) + initialize!(cb, u_ode, du_ode, t, integrator, semi) + end + + # This is the actual initialization method + # Note: we have this indirection to allow initializing a callback from the AnalysisCallbackCoupled + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u_ode, du_ode, t, + integrator, semi + ) where {Condition, Affect! <: AnalysisCallback} + initial_state_integrals = integrate(u_ode, semi) + _, equations, _, _ = mesh_equations_solver_cache(semi) -# This is the actual initialization method -# Note: we have this indirection to allow initializing a callback from the AnalysisCallbackCoupled -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u_ode, du_ode, t, - integrator, semi) where {Condition, Affect! <: AnalysisCallback} - initial_state_integrals = integrate(u_ode, semi) - _, equations, _, _ = mesh_equations_solver_cache(semi) - - analysis_callback = cb.affect! - analysis_callback.initial_state_integrals = initial_state_integrals - @unpack save_analysis, output_directory, analysis_filename, analysis_errors, analysis_integrals = analysis_callback - - if save_analysis && mpi_isroot() - mkpath(output_directory) - - # write header of output file - open(joinpath(output_directory, analysis_filename), "w") do io - @printf(io, "#%-8s", "timestep") - @printf(io, " %-14s", "time") - @printf(io, " %-14s", "dt") - if :l2_error in analysis_errors - for v in varnames(cons2cons, equations) - @printf(io, " %-14s", "l2_"*v) + analysis_callback = cb.affect! + analysis_callback.initial_state_integrals = initial_state_integrals + @unpack save_analysis, output_directory, analysis_filename, analysis_errors, analysis_integrals = analysis_callback + + if save_analysis && mpi_isroot() + mkpath(output_directory) + + # write header of output file + open(joinpath(output_directory, analysis_filename), "w") do io + @printf(io, "#%-8s", "timestep") + @printf(io, " %-14s", "time") + @printf(io, " %-14s", "dt") + if :l2_error in analysis_errors + for v in varnames(cons2cons, equations) + @printf(io, " %-14s", "l2_" * v) + end end - end - if :linf_error in analysis_errors - for v in varnames(cons2cons, equations) - @printf(io, " %-14s", "linf_"*v) + if :linf_error in analysis_errors + for v in varnames(cons2cons, equations) + @printf(io, " %-14s", "linf_" * v) + end end - end - if :conservation_error in analysis_errors - for v in varnames(cons2cons, equations) - @printf(io, " %-14s", "cons_"*v) + if :conservation_error in analysis_errors + for v in varnames(cons2cons, equations) + @printf(io, " %-14s", "cons_" * v) + end end - end - if :residual in analysis_errors - for v in varnames(cons2cons, equations) - @printf(io, " %-14s", "res_"*v) + if :residual in analysis_errors + for v in varnames(cons2cons, equations) + @printf(io, " %-14s", "res_" * v) + end end - end - if :l2_error_primitive in analysis_errors - for v in varnames(cons2prim, equations) - @printf(io, " %-14s", "l2_"*v) + if :l2_error_primitive in analysis_errors + for v in varnames(cons2prim, equations) + @printf(io, " %-14s", "l2_" * v) + end end - end - if :linf_error_primitive in analysis_errors - for v in varnames(cons2prim, equations) - @printf(io, " %-14s", "linf_"*v) + if :linf_error_primitive in analysis_errors + for v in varnames(cons2prim, equations) + @printf(io, " %-14s", "linf_" * v) + end end - end - for quantity in analysis_integrals - @printf(io, " %-14s", pretty_form_ascii(quantity)) - end + for quantity in analysis_integrals + @printf(io, " %-14s", pretty_form_ascii(quantity)) + end - println(io) + println(io) + end end - end - # Record current time using a high-resolution clock - analysis_callback.start_time = time_ns() + # Record current time using a high-resolution clock + analysis_callback.start_time = time_ns() - # Record current time for performance index computation - analysis_callback.start_time_last_analysis = time_ns() + # Record current time for performance index computation + analysis_callback.start_time_last_analysis = time_ns() - # Record current number of `rhs!` calls for performance index computation - analysis_callback.ncalls_rhs_last_analysis = ncalls(semi.performance_counter) + # Record current number of `rhs!` calls for performance index computation + analysis_callback.ncalls_rhs_last_analysis = ncalls(semi.performance_counter) - # Record total time spent in garbage collection so far using a high-resolution clock - # Note: For details see the actual callback function below - analysis_callback.start_gc_time = Base.gc_time_ns() + # Record total time spent in garbage collection so far using a high-resolution clock + # Note: For details see the actual callback function below + analysis_callback.start_gc_time = Base.gc_time_ns() - analysis_callback(u_ode, du_ode, integrator, semi) - return nothing -end + analysis_callback(u_ode, du_ode, integrator, semi) + return nothing + end -# This method gets called from OrdinaryDiffEq's `solve(...)` -function (analysis_callback::AnalysisCallback)(integrator) - semi = integrator.p - du_ode = first(get_tmp_cache(integrator)) - u_ode = integrator.u - analysis_callback(u_ode, du_ode, integrator, semi) -end + # This method gets called from OrdinaryDiffEq's `solve(...)` + function (analysis_callback::AnalysisCallback)(integrator) + semi = integrator.p + du_ode = first(get_tmp_cache(integrator)) + u_ode = integrator.u + analysis_callback(u_ode, du_ode, integrator, semi) + end -# This method gets called internally as the main entry point to the AnalysiCallback -# TODO: Taal refactor, allow passing an IO object (which could be devnull to avoid cluttering the console) -function (analysis_callback::AnalysisCallback)(u_ode, du_ode, integrator, semi) - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - @unpack dt, t = integrator - iter = integrator.stats.naccept - - # Compute the percentage of the simulation that is done - t = integrator.t - t_initial = first(integrator.sol.prob.tspan) - t_final = last(integrator.sol.prob.tspan) - sim_time_percentage = (t - t_initial) / (t_final - t_initial) * 100 - - # Record performance measurements and compute performance index (PID) - runtime_since_last_analysis = 1.0e-9 * (time_ns() - - analysis_callback.start_time_last_analysis) - # PID is an MPI-aware measure of how much time per global degree of freedom (i.e., over all ranks) - # and per `rhs!` evaluation is required. MPI-aware means that it essentially adds up the time - # spent on each MPI rank. Thus, in an ideally parallelized program, the PID should be constant - # independent of the number of MPI ranks used, since, e.g., using 4x the number of ranks should - # divide the runtime on each rank by 4. See also the Trixi.jl docs ("Performance" section) for - # more information. - ncalls_rhs_since_last_analysis = (ncalls(semi.performance_counter) - - - analysis_callback.ncalls_rhs_last_analysis) - performance_index = runtime_since_last_analysis * mpi_nranks() / - (ndofsglobal(mesh, solver, cache) - * - ncalls_rhs_since_last_analysis) - - # Compute the total runtime since the analysis callback has been initialized, in seconds - runtime_absolute = 1.0e-9 * (time_ns() - analysis_callback.start_time) - - # Compute the relative runtime as time spent in `rhs!` divided by the number of calls to `rhs!` - # and the number of local degrees of freedom - # OBS! This computation must happen *after* the PID computation above, since `take!(...)` - # will reset the number of calls to `rhs!` - runtime_relative = 1.0e-9 * take!(semi.performance_counter) / ndofs(semi) - - # Compute the total time spent in garbage collection since the analysis callback has been - # initialized, in seconds - # Note: `Base.gc_time_ns()` is not part of the public Julia API but has been available at least - # since Julia 1.6. Should this function be removed without replacement in a future Julia - # release, just delete this analysis quantity from the callback. - # Source: https://github.com/JuliaLang/julia/blob/b540315cb4bd91e6f3a3e4ab8129a58556947628/base/timing.jl#L83-L84 - gc_time_absolute = 1.0e-9 * (Base.gc_time_ns() - analysis_callback.start_gc_time) - - # Compute the percentage of total time that was spent in garbage collection - gc_time_percentage = gc_time_absolute / runtime_absolute * 100 - - # Obtain the current memory usage of the Julia garbage collector, in MiB, i.e., the total size of - # objects in memory that have been allocated by the JIT compiler or the user code. - # Note: `Base.gc_live_bytes()` is not part of the public Julia API but has been available at least - # since Julia 1.6. Should this function be removed without replacement in a future Julia - # release, just delete this analysis quantity from the callback. - # Source: https://github.com/JuliaLang/julia/blob/b540315cb4bd91e6f3a3e4ab8129a58556947628/base/timing.jl#L86-L97 - memory_use = Base.gc_live_bytes() / 2^20 # bytes -> MiB - - @trixi_timeit timer() "analyze solution" begin - # General information - mpi_println() - mpi_println("─"^100) - mpi_println(" Simulation running '", get_name(equations), "' with ", - summary(solver)) - mpi_println("─"^100) - mpi_println(" #timesteps: " * @sprintf("% 14d", iter) * + # This method gets called internally as the main entry point to the AnalysiCallback + # TODO: Taal refactor, allow passing an IO object (which could be devnull to avoid cluttering the console) + function (analysis_callback::AnalysisCallback)(u_ode, du_ode, integrator, semi) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + @unpack dt, t = integrator + iter = integrator.stats.naccept + + # Compute the percentage of the simulation that is done + t = integrator.t + t_initial = first(integrator.sol.prob.tspan) + t_final = last(integrator.sol.prob.tspan) + sim_time_percentage = (t - t_initial) / (t_final - t_initial) * 100 + + # Record performance measurements and compute performance index (PID) + runtime_since_last_analysis = 1.0e-9 * ( + time_ns() - + analysis_callback.start_time_last_analysis + ) + # PID is an MPI-aware measure of how much time per global degree of freedom (i.e., over all ranks) + # and per `rhs!` evaluation is required. MPI-aware means that it essentially adds up the time + # spent on each MPI rank. Thus, in an ideally parallelized program, the PID should be constant + # independent of the number of MPI ranks used, since, e.g., using 4x the number of ranks should + # divide the runtime on each rank by 4. See also the Trixi.jl docs ("Performance" section) for + # more information. + ncalls_rhs_since_last_analysis = ( + ncalls(semi.performance_counter) + - + analysis_callback.ncalls_rhs_last_analysis + ) + performance_index = runtime_since_last_analysis * mpi_nranks() / + ( + ndofsglobal(mesh, solver, cache) + * + ncalls_rhs_since_last_analysis + ) + + # Compute the total runtime since the analysis callback has been initialized, in seconds + runtime_absolute = 1.0e-9 * (time_ns() - analysis_callback.start_time) + + # Compute the relative runtime as time spent in `rhs!` divided by the number of calls to `rhs!` + # and the number of local degrees of freedom + # OBS! This computation must happen *after* the PID computation above, since `take!(...)` + # will reset the number of calls to `rhs!` + runtime_relative = 1.0e-9 * take!(semi.performance_counter) / ndofs(semi) + + # Compute the total time spent in garbage collection since the analysis callback has been + # initialized, in seconds + # Note: `Base.gc_time_ns()` is not part of the public Julia API but has been available at least + # since Julia 1.6. Should this function be removed without replacement in a future Julia + # release, just delete this analysis quantity from the callback. + # Source: https://github.com/JuliaLang/julia/blob/b540315cb4bd91e6f3a3e4ab8129a58556947628/base/timing.jl#L83-L84 + gc_time_absolute = 1.0e-9 * (Base.gc_time_ns() - analysis_callback.start_gc_time) + + # Compute the percentage of total time that was spent in garbage collection + gc_time_percentage = gc_time_absolute / runtime_absolute * 100 + + # Obtain the current memory usage of the Julia garbage collector, in MiB, i.e., the total size of + # objects in memory that have been allocated by the JIT compiler or the user code. + # Note: `Base.gc_live_bytes()` is not part of the public Julia API but has been available at least + # since Julia 1.6. Should this function be removed without replacement in a future Julia + # release, just delete this analysis quantity from the callback. + # Source: https://github.com/JuliaLang/julia/blob/b540315cb4bd91e6f3a3e4ab8129a58556947628/base/timing.jl#L86-L97 + memory_use = Base.gc_live_bytes() / 2^20 # bytes -> MiB + + @trixi_timeit timer() "analyze solution" begin + # General information + mpi_println() + mpi_println("─"^100) + mpi_println( + " Simulation running '", get_name(equations), "' with ", + summary(solver) + ) + mpi_println("─"^100) + mpi_println( + " #timesteps: " * @sprintf("% 14d", iter) * " " * - " run time: " * @sprintf("%10.8e s", runtime_absolute)) - mpi_println(" Δt: " * @sprintf("%10.8e", dt) * + " run time: " * @sprintf("%10.8e s", runtime_absolute) + ) + mpi_println( + " Δt: " * @sprintf("%10.8e", dt) * " " * " └── GC time: " * - @sprintf("%10.8e s (%5.3f%%)", gc_time_absolute, gc_time_percentage)) - mpi_println(rpad(" sim. time: " * - @sprintf("%10.8e (%5.3f%%)", t, sim_time_percentage), 46) * - " time/DOF/rhs!: " * @sprintf("%10.8e s", runtime_relative)) - mpi_println(" " * " " * + @sprintf("%10.8e s (%5.3f%%)", gc_time_absolute, gc_time_percentage) + ) + mpi_println( + rpad( + " sim. time: " * + @sprintf("%10.8e (%5.3f%%)", t, sim_time_percentage), 46 + ) * + " time/DOF/rhs!: " * @sprintf("%10.8e s", runtime_relative) + ) + mpi_println( + " " * " " * " " * - " PID: " * @sprintf("%10.8e s", performance_index)) - mpi_println(" #DOFs per field:" * @sprintf("% 14d", ndofsglobal(semi)) * + " PID: " * @sprintf("%10.8e s", performance_index) + ) + mpi_println( + " #DOFs per field:" * @sprintf("% 14d", ndofsglobal(semi)) * " " * - " alloc'd memory: " * @sprintf("%14.3f MiB", memory_use)) - mpi_println(" #elements: " * - @sprintf("% 14d", nelementsglobal(mesh, solver, cache))) - - # Level information (only show for AMR) - print_amr_information(integrator.opts.callback, mesh, solver, cache) - mpi_println() - - # Open file for appending and store time step and time information - if mpi_isroot() && analysis_callback.save_analysis - io = open(joinpath(analysis_callback.output_directory, - analysis_callback.analysis_filename), "a") - @printf(io, "% 9d", iter) - @printf(io, " %10.8e", t) - @printf(io, " %10.8e", dt) - else - io = devnull - end - - # Calculate current time derivative (needed for semidiscrete entropy time derivative, residual, etc.) - # `integrator.f` is usually just a call to `rhs!` - # However, we want to allow users to modify the ODE RHS outside of Trixi.jl - # and allow us to pass a combined ODE RHS to OrdinaryDiffEq, e.g., for - # hyperbolic-parabolic systems. - @notimeit timer() integrator.f(du_ode, u_ode, semi, t) - u = wrap_array(u_ode, mesh, equations, solver, cache) - du = wrap_array(du_ode, mesh, equations, solver, cache) - # Compute l2_error, linf_error - analysis_callback(io, du, u, u_ode, t, semi) - - mpi_println("─"^100) - mpi_println() + " alloc'd memory: " * @sprintf("%14.3f MiB", memory_use) + ) + mpi_println( + " #elements: " * + @sprintf("% 14d", nelementsglobal(mesh, solver, cache)) + ) + + # Level information (only show for AMR) + print_amr_information(integrator.opts.callback, mesh, solver, cache) + mpi_println() + + # Open file for appending and store time step and time information + if mpi_isroot() && analysis_callback.save_analysis + io = open( + joinpath( + analysis_callback.output_directory, + analysis_callback.analysis_filename + ), "a" + ) + @printf(io, "% 9d", iter) + @printf(io, " %10.8e", t) + @printf(io, " %10.8e", dt) + else + io = devnull + end - # Add line break and close analysis file if it was opened - if mpi_isroot() && analysis_callback.save_analysis - # This resolves a possible type instability introduced above, since `io` - # can either be an `IOStream` or `devnull`, but we know that it must be - # an `IOStream here`. - println(io::IOStream) - close(io::IOStream) + # Calculate current time derivative (needed for semidiscrete entropy time derivative, residual, etc.) + # `integrator.f` is usually just a call to `rhs!` + # However, we want to allow users to modify the ODE RHS outside of Trixi.jl + # and allow us to pass a combined ODE RHS to OrdinaryDiffEq, e.g., for + # hyperbolic-parabolic systems. + @notimeit timer() integrator.f(du_ode, u_ode, semi, t) + u = wrap_array(u_ode, mesh, equations, solver, cache) + du = wrap_array(du_ode, mesh, equations, solver, cache) + # Compute l2_error, linf_error + analysis_callback(io, du, u, u_ode, t, semi) + + mpi_println("─"^100) + mpi_println() + + # Add line break and close analysis file if it was opened + if mpi_isroot() && analysis_callback.save_analysis + # This resolves a possible type instability introduced above, since `io` + # can either be an `IOStream` or `devnull`, but we know that it must be + # an `IOStream here`. + println(io::IOStream) + close(io::IOStream) + end end - end - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) - # Reset performance measurements - analysis_callback.start_time_last_analysis = time_ns() - analysis_callback.ncalls_rhs_last_analysis = ncalls(semi.performance_counter) + # Reset performance measurements + analysis_callback.start_time_last_analysis = time_ns() + analysis_callback.ncalls_rhs_last_analysis = ncalls(semi.performance_counter) - return nothing -end + return nothing + end -# This method is just called internally from `(analysis_callback::AnalysisCallback)(integrator)` -# and serves as a function barrier. Additionally, it makes the code easier to profile and optimize. -function (analysis_callback::AnalysisCallback)(io, du, u, u_ode, t, semi) - @unpack analyzer, analysis_errors, analysis_integrals = analysis_callback - cache_analysis = analysis_callback.cache - _, equations, _, _ = mesh_equations_solver_cache(semi) - - # Calculate and print derived quantities (error norms, entropy etc.) - # Variable names required for L2 error, Linf error, and conservation error - if any(q in analysis_errors - for q in (:l2_error, :linf_error, :conservation_error, :residual)) && - mpi_isroot() - print(" Variable: ") - for v in eachvariable(equations) - @printf(" %-14s", varnames(cons2cons, equations)[v]) + # This method is just called internally from `(analysis_callback::AnalysisCallback)(integrator)` + # and serves as a function barrier. Additionally, it makes the code easier to profile and optimize. + function (analysis_callback::AnalysisCallback)(io, du, u, u_ode, t, semi) + @unpack analyzer, analysis_errors, analysis_integrals = analysis_callback + cache_analysis = analysis_callback.cache + _, equations, _, _ = mesh_equations_solver_cache(semi) + + # Calculate and print derived quantities (error norms, entropy etc.) + # Variable names required for L2 error, Linf error, and conservation error + if any( + q in analysis_errors + for q in (:l2_error, :linf_error, :conservation_error, :residual) + ) && + mpi_isroot() + print(" Variable: ") + for v in eachvariable(equations) + @printf(" %-14s", varnames(cons2cons, equations)[v]) + end + println() end - println() - end - if :l2_error in analysis_errors || :linf_error in analysis_errors - # Calculate L2/Linf errors - l2_error, linf_error = calc_error_norms(u_ode, t, analyzer, semi, - cache_analysis) + if :l2_error in analysis_errors || :linf_error in analysis_errors + # Calculate L2/Linf errors + l2_error, linf_error = calc_error_norms( + u_ode, t, analyzer, semi, + cache_analysis + ) - if mpi_isroot() - # L2 error - if :l2_error in analysis_errors - print(" L2 error: ") - for v in eachvariable(equations) - @printf(" % 10.8e", l2_error[v]) - @printf(io, " % 10.8e", l2_error[v]) + if mpi_isroot() + # L2 error + if :l2_error in analysis_errors + print(" L2 error: ") + for v in eachvariable(equations) + @printf(" % 10.8e", l2_error[v]) + @printf(io, " % 10.8e", l2_error[v]) + end + println() end - println() - end - # Linf error - if :linf_error in analysis_errors - print(" Linf error: ") - for v in eachvariable(equations) - @printf(" % 10.8e", linf_error[v]) - @printf(io, " % 10.8e", linf_error[v]) + # Linf error + if :linf_error in analysis_errors + print(" Linf error: ") + for v in eachvariable(equations) + @printf(" % 10.8e", linf_error[v]) + @printf(io, " % 10.8e", linf_error[v]) + end + println() end - println() end end - end - # Conservation error - if :conservation_error in analysis_errors - @unpack initial_state_integrals = analysis_callback - state_integrals = integrate(u_ode, semi) + # Conservation error + if :conservation_error in analysis_errors + @unpack initial_state_integrals = analysis_callback + state_integrals = integrate(u_ode, semi) - if mpi_isroot() - print(" |∑U - ∑U₀|: ") - for v in eachvariable(equations) - err = abs(state_integrals[v] - initial_state_integrals[v]) - @printf(" % 10.8e", err) - @printf(io, " % 10.8e", err) + if mpi_isroot() + print(" |∑U - ∑U₀|: ") + for v in eachvariable(equations) + err = abs(state_integrals[v] - initial_state_integrals[v]) + @printf(" % 10.8e", err) + @printf(io, " % 10.8e", err) + end + println() end - println() end - end - # Residual (defined here as the vector maximum of the absolute values of the time derivatives) - if :residual in analysis_errors - mpi_print(" max(|Uₜ|): ") - for v in eachvariable(equations) - # Calculate maximum absolute value of Uₜ - res = maximum(abs, view(du, v, ..)) - if mpi_isparallel() - # TODO: Debugging, here is a type instability - global_res = MPI.Reduce!(Ref(res), max, mpi_root(), mpi_comm()) + # Residual (defined here as the vector maximum of the absolute values of the time derivatives) + if :residual in analysis_errors + mpi_print(" max(|Uₜ|): ") + for v in eachvariable(equations) + # Calculate maximum absolute value of Uₜ + res = maximum(abs, view(du, v, ..)) + if mpi_isparallel() + # TODO: Debugging, here is a type instability + global_res = MPI.Reduce!(Ref(res), max, mpi_root(), mpi_comm()) + if mpi_isroot() + res::eltype(du) = global_res[] + end + end if mpi_isroot() - res::eltype(du) = global_res[] + @printf(" % 10.8e", res) + @printf(io, " % 10.8e", res) end end - if mpi_isroot() - @printf(" % 10.8e", res) - @printf(io, " % 10.8e", res) - end + mpi_println() end - mpi_println() - end - - # L2/L∞ errors of the primitive variables - if :l2_error_primitive in analysis_errors || - :linf_error_primitive in analysis_errors - l2_error_prim, linf_error_prim = calc_error_norms(cons2prim, u_ode, t, analyzer, - semi, cache_analysis) - if mpi_isroot() - print(" Variable: ") - for v in eachvariable(equations) - @printf(" %-14s", varnames(cons2prim, equations)[v]) - end - println() + # L2/L∞ errors of the primitive variables + if :l2_error_primitive in analysis_errors || + :linf_error_primitive in analysis_errors + l2_error_prim, linf_error_prim = calc_error_norms( + cons2prim, u_ode, t, analyzer, + semi, cache_analysis + ) - # L2 error - if :l2_error_primitive in analysis_errors - print(" L2 error prim.: ") + if mpi_isroot() + print(" Variable: ") for v in eachvariable(equations) - @printf("%10.8e ", l2_error_prim[v]) - @printf(io, " % 10.8e", l2_error_prim[v]) + @printf(" %-14s", varnames(cons2prim, equations)[v]) end println() - end - # L∞ error - if :linf_error_primitive in analysis_errors - print(" Linf error pri.:") - for v in eachvariable(equations) - @printf("%10.8e ", linf_error_prim[v]) - @printf(io, " % 10.8e", linf_error_prim[v]) + # L2 error + if :l2_error_primitive in analysis_errors + print(" L2 error prim.: ") + for v in eachvariable(equations) + @printf("%10.8e ", l2_error_prim[v]) + @printf(io, " % 10.8e", l2_error_prim[v]) + end + println() + end + + # L∞ error + if :linf_error_primitive in analysis_errors + print(" Linf error pri.:") + for v in eachvariable(equations) + @printf("%10.8e ", linf_error_prim[v]) + @printf(io, " % 10.8e", linf_error_prim[v]) + end + println() end - println() end end - end - # additional integrals - analyze_integrals(analysis_integrals, io, du, u, t, semi) + # additional integrals + analyze_integrals(analysis_integrals, io, du, u, t, semi) - return nothing -end + return nothing + end -# Print level information only if AMR is enabled -function print_amr_information(callbacks, mesh, solver, cache) + # Print level information only if AMR is enabled + function print_amr_information(callbacks, mesh, solver, cache) - # Return early if there is nothing to print - uses_amr(callbacks) || return nothing + # Return early if there is nothing to print + uses_amr(callbacks) || return nothing - # Get global minimum and maximum level from the AMRController - min_level = max_level = 0 - for cb in callbacks.discrete_callbacks - if cb.affect! isa AMRCallback - min_level = cb.affect!.controller.base_level - max_level = cb.affect!.controller.max_level + # Get global minimum and maximum level from the AMRController + min_level = max_level = 0 + for cb in callbacks.discrete_callbacks + if cb.affect! isa AMRCallback + min_level = cb.affect!.controller.base_level + max_level = cb.affect!.controller.max_level + end end - end - # Get local element count per level - elements_per_level = get_elements_per_level(min_level, max_level, mesh, solver, - cache) - - # Sum up across all ranks - MPI.Reduce!(elements_per_level, +, mpi_root(), mpi_comm()) + # Get local element count per level + elements_per_level = get_elements_per_level( + min_level, max_level, mesh, solver, + cache + ) + + # Sum up across all ranks + MPI.Reduce!(elements_per_level, +, mpi_root(), mpi_comm()) + + # Print + for level in max_level:-1:(min_level + 1) + mpi_println( + " ├── level $level: " * + @sprintf("% 14d", elements_per_level[level + 1 - min_level]) + ) + end + mpi_println( + " └── level $min_level: " * + @sprintf("% 14d", elements_per_level[1]) + ) - # Print - for level in max_level:-1:(min_level + 1) - mpi_println(" ├── level $level: " * - @sprintf("% 14d", elements_per_level[level + 1 - min_level])) + return nothing end - mpi_println(" └── level $min_level: " * - @sprintf("% 14d", elements_per_level[1])) - return nothing -end + function get_elements_per_level(min_level, max_level, mesh::P4estMesh, solver, cache) + elements_per_level = zeros(P4EST_MAXLEVEL + 1) -function get_elements_per_level(min_level, max_level, mesh::P4estMesh, solver, cache) - elements_per_level = zeros(P4EST_MAXLEVEL + 1) + for tree in unsafe_wrap_sc(p4est_tree_t, mesh.p4est.trees) + elements_per_level .+= tree.quadrants_per_level + end - for tree in unsafe_wrap_sc(p4est_tree_t, mesh.p4est.trees) - elements_per_level .+= tree.quadrants_per_level + return @view(elements_per_level[(min_level + 1):(max_level + 1)]) end - return @view(elements_per_level[(min_level + 1):(max_level + 1)]) -end + function get_elements_per_level(min_level, max_level, mesh::T8codeMesh, solver, cache) + levels = trixi_t8_get_local_element_levels(mesh.forest) -function get_elements_per_level(min_level, max_level, mesh::T8codeMesh, solver, cache) - levels = trixi_t8_get_local_element_levels(mesh.forest) + return [count(==(l), levels) for l in min_level:max_level] + end - return [count(==(l), levels) for l in min_level:max_level] -end + function get_elements_per_level(min_level, max_level, mesh::TreeMesh, solver, cache) + levels = [ + mesh.tree.levels[cache.elements.cell_ids[element]] + for element in eachelement(solver, cache) + ] + return [count(==(l), levels) for l in min_level:max_level] + end -function get_elements_per_level(min_level, max_level, mesh::TreeMesh, solver, cache) - levels = [mesh.tree.levels[cache.elements.cell_ids[element]] - for element in eachelement(solver, cache)] - return [count(==(l), levels) for l in min_level:max_level] -end + # Iterate over tuples of analysis integrals in a type-stable way using "lispy tuple programming". + function analyze_integrals( + analysis_integrals::NTuple{N, Any}, io, du, u, t, + semi + ) where {N} -# Iterate over tuples of analysis integrals in a type-stable way using "lispy tuple programming". -function analyze_integrals(analysis_integrals::NTuple{N, Any}, io, du, u, t, - semi) where {N} + # Extract the first analysis integral and process it; keep the remaining to be processed later + quantity = first(analysis_integrals) + remaining_quantities = Base.tail(analysis_integrals) - # Extract the first analysis integral and process it; keep the remaining to be processed later - quantity = first(analysis_integrals) - remaining_quantities = Base.tail(analysis_integrals) + res = analyze(quantity, du, u, t, semi) + if mpi_isroot() + @printf(" %-12s:", pretty_form_utf(quantity)) + @printf(" % 10.8e", res) + @printf(io, " % 10.8e", res) + end + mpi_println() - res = analyze(quantity, du, u, t, semi) - if mpi_isroot() - @printf(" %-12s:", pretty_form_utf(quantity)) - @printf(" % 10.8e", res) - @printf(io, " % 10.8e", res) + # Recursively call this method with the unprocessed integrals + analyze_integrals(remaining_quantities, io, du, u, t, semi) + return nothing end - mpi_println() - - # Recursively call this method with the unprocessed integrals - analyze_integrals(remaining_quantities, io, du, u, t, semi) - return nothing -end -# terminate the type-stable iteration over tuples -function analyze_integrals(analysis_integrals::Tuple{}, io, du, u, t, semi) - nothing -end - -# used for error checks and EOC analysis -function (cb::DiscreteCallback{Condition, Affect!})(sol) where {Condition, - Affect! <: - AnalysisCallback} - analysis_callback = cb.affect! - semi = sol.prob.p - @unpack analyzer = analysis_callback - cache_analysis = analysis_callback.cache - - l2_error, linf_error = calc_error_norms(sol.u[end], sol.t[end], analyzer, semi, - cache_analysis) - (; l2 = l2_error, linf = linf_error) -end + # terminate the type-stable iteration over tuples + function analyze_integrals(analysis_integrals::Tuple{}, io, du, u, t, semi) + nothing + end -# some common analysis_integrals -# to support another analysis integral, you can overload -# Trixi.analyze, Trixi.pretty_form_utf, Trixi.pretty_form_ascii -function analyze(quantity, du, u, t, semi::AbstractSemidiscretization) - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - analyze(quantity, du, u, t, mesh, equations, solver, cache) -end -function analyze(quantity, du, u, t, mesh, equations, solver, cache) - integrate(quantity, u, mesh, equations, solver, cache, normalize = true) -end -pretty_form_utf(quantity) = get_name(quantity) -pretty_form_ascii(quantity) = get_name(quantity) + # used for error checks and EOC analysis + function (cb::DiscreteCallback{Condition, Affect!})(sol) where { + Condition, + Affect! <: + AnalysisCallback, + } + analysis_callback = cb.affect! + semi = sol.prob.p + @unpack analyzer = analysis_callback + cache_analysis = analysis_callback.cache + + l2_error, linf_error = calc_error_norms( + sol.u[end], sol.t[end], analyzer, semi, + cache_analysis + ) + (; l2 = l2_error, linf = linf_error) + end -# Special analyze for `SemidiscretizationHyperbolicParabolic` such that -# precomputed gradients are available. -function analyze(quantity::typeof(enstrophy), du, u, t, - semi::SemidiscretizationHyperbolicParabolic) - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - equations_parabolic = semi.equations_parabolic - cache_parabolic = semi.cache_parabolic - analyze(quantity, du, u, t, mesh, equations, equations_parabolic, solver, cache, - cache_parabolic) -end -function analyze(quantity, du, u, t, mesh, equations, equations_parabolic, solver, - cache, cache_parabolic) - integrate(quantity, u, mesh, equations, equations_parabolic, solver, cache, - cache_parabolic, normalize = true) -end + # some common analysis_integrals + # to support another analysis integral, you can overload + # Trixi.analyze, Trixi.pretty_form_utf, Trixi.pretty_form_ascii + function analyze(quantity, du, u, t, semi::AbstractSemidiscretization) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + analyze(quantity, du, u, t, mesh, equations, solver, cache) + end + function analyze(quantity, du, u, t, mesh, equations, solver, cache) + integrate(quantity, u, mesh, equations, solver, cache, normalize = true) + end + pretty_form_utf(quantity) = get_name(quantity) + pretty_form_ascii(quantity) = get_name(quantity) + + # Special analyze for `SemidiscretizationHyperbolicParabolic` such that + # precomputed gradients are available. + function analyze( + quantity::typeof(enstrophy), du, u, t, + semi::SemidiscretizationHyperbolicParabolic + ) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + equations_parabolic = semi.equations_parabolic + cache_parabolic = semi.cache_parabolic + analyze( + quantity, du, u, t, mesh, equations, equations_parabolic, solver, cache, + cache_parabolic + ) + end + function analyze( + quantity, du, u, t, mesh, equations, equations_parabolic, solver, + cache, cache_parabolic + ) + integrate( + quantity, u, mesh, equations, equations_parabolic, solver, cache, + cache_parabolic, normalize = true + ) + end -function entropy_timederivative end -pretty_form_utf(::typeof(entropy_timederivative)) = "∑∂S/∂U ⋅ Uₜ" -pretty_form_ascii(::typeof(entropy_timederivative)) = "dsdu_ut" + function entropy_timederivative end + pretty_form_utf(::typeof(entropy_timederivative)) = "∑∂S/∂U ⋅ Uₜ" + pretty_form_ascii(::typeof(entropy_timederivative)) = "dsdu_ut" -pretty_form_utf(::typeof(entropy)) = "∑S" + pretty_form_utf(::typeof(entropy)) = "∑S" -pretty_form_utf(::typeof(energy_total)) = "∑e_total" -pretty_form_ascii(::typeof(energy_total)) = "e_total" + pretty_form_utf(::typeof(energy_total)) = "∑e_total" + pretty_form_ascii(::typeof(energy_total)) = "e_total" -pretty_form_utf(::typeof(energy_kinetic)) = "∑e_kinetic" -pretty_form_ascii(::typeof(energy_kinetic)) = "e_kinetic" + pretty_form_utf(::typeof(energy_kinetic)) = "∑e_kinetic" + pretty_form_ascii(::typeof(energy_kinetic)) = "e_kinetic" -pretty_form_utf(::typeof(energy_kinetic_nondimensional)) = "∑e_kinetic*" -pretty_form_ascii(::typeof(energy_kinetic_nondimensional)) = "e_kinetic*" + pretty_form_utf(::typeof(energy_kinetic_nondimensional)) = "∑e_kinetic*" + pretty_form_ascii(::typeof(energy_kinetic_nondimensional)) = "e_kinetic*" -pretty_form_utf(::typeof(energy_internal)) = "∑e_internal" -pretty_form_ascii(::typeof(energy_internal)) = "e_internal" + pretty_form_utf(::typeof(energy_internal)) = "∑e_internal" + pretty_form_ascii(::typeof(energy_internal)) = "e_internal" -pretty_form_utf(::typeof(energy_magnetic)) = "∑e_magnetic" -pretty_form_ascii(::typeof(energy_magnetic)) = "e_magnetic" + pretty_form_utf(::typeof(energy_magnetic)) = "∑e_magnetic" + pretty_form_ascii(::typeof(energy_magnetic)) = "e_magnetic" -pretty_form_utf(::typeof(cross_helicity)) = "∑v⋅B" -pretty_form_ascii(::typeof(cross_helicity)) = "v_dot_B" + pretty_form_utf(::typeof(cross_helicity)) = "∑v⋅B" + pretty_form_ascii(::typeof(cross_helicity)) = "v_dot_B" -pretty_form_utf(::typeof(enstrophy)) = "∑enstrophy" -pretty_form_ascii(::typeof(enstrophy)) = "enstrophy" + pretty_form_utf(::typeof(enstrophy)) = "∑enstrophy" + pretty_form_ascii(::typeof(enstrophy)) = "enstrophy" -pretty_form_utf(::Val{:l2_divb}) = "L2 ∇⋅B" -pretty_form_ascii(::Val{:l2_divb}) = "l2_divb" + pretty_form_utf(::Val{:l2_divb}) = "L2 ∇⋅B" + pretty_form_ascii(::Val{:l2_divb}) = "l2_divb" -pretty_form_utf(::Val{:linf_divb}) = "L∞ ∇⋅B" -pretty_form_ascii(::Val{:linf_divb}) = "linf_divb" + pretty_form_utf(::Val{:linf_divb}) = "L∞ ∇⋅B" + pretty_form_ascii(::Val{:linf_divb}) = "linf_divb" -pretty_form_utf(::typeof(lake_at_rest_error)) = "∑|H₀-(h+b)|" -pretty_form_ascii(::typeof(lake_at_rest_error)) = "|H0-(h+b)|" + pretty_form_utf(::typeof(lake_at_rest_error)) = "∑|H₀-(h+b)|" + pretty_form_ascii(::typeof(lake_at_rest_error)) = "|H0-(h+b)|" end # @muladd # specialized implementations specific to some solvers @@ -658,10 +742,12 @@ include("analysis_dg3d.jl") include("analysis_dg3d_parallel.jl") # This version of `analyze` is used for [`AnalysisSurfaceIntegral`](@ref) which requires -# `semi` to be passed along to retrieve the current boundary indices, which are non-static +# `semi` to be passed along to retrieve the current boundary indices, which are non-static # in the case of AMR. -function analyze(quantity::AnalysisSurfaceIntegral, du, u, t, - semi::AbstractSemidiscretization) +function analyze( + quantity::AnalysisSurfaceIntegral, du, u, t, + semi::AbstractSemidiscretization + ) mesh, equations, solver, cache = mesh_equations_solver_cache(semi) analyze(quantity, du, u, t, mesh, equations, solver, cache, semi) end @@ -670,14 +756,19 @@ end # precomputed gradients are available. Required for `enstrophy` (see above) and viscous forces. # Note that this needs to be included after `analysis_surface_integral_2d.jl` to # have `VariableViscous` available. -function analyze(quantity::AnalysisSurfaceIntegral{Variable}, - du, u, t, - semi::SemidiscretizationHyperbolicParabolic) where { - Variable <: - VariableViscous} +function analyze( + quantity::AnalysisSurfaceIntegral{Variable}, + du, u, t, + semi::SemidiscretizationHyperbolicParabolic + ) where { + Variable <: + VariableViscous, + } mesh, equations, solver, cache = mesh_equations_solver_cache(semi) equations_parabolic = semi.equations_parabolic cache_parabolic = semi.cache_parabolic - analyze(quantity, du, u, t, mesh, equations, equations_parabolic, solver, cache, semi, - cache_parabolic) + analyze( + quantity, du, u, t, mesh, equations, equations_parabolic, solver, cache, semi, + cache_parabolic + ) end diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index d2613c325b..1bc6167d30 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -3,226 +3,272 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function create_cache_analysis(analyzer, mesh::TreeMesh{1}, - equations, dg::DG, cache, - RealT, uEltype) - - # pre-allocate buffers - # We use `StrideArray`s here since these buffers are used in performance-critical - # places and the additional information passed to the compiler makes them faster - # than native `Array`s. - u_local = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer))) - x_local = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer))) - - return (; u_local, x_local) -end - -function create_cache_analysis(analyzer, mesh::StructuredMesh{1}, - equations, dg::DG, cache, - RealT, uEltype) - - # pre-allocate buffers - # We use `StrideArray`s here since these buffers are used in performance-critical - # places and the additional information passed to the compiler makes them faster - # than native `Array`s. - u_local = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer))) - x_local = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer))) - jacobian_local = StrideArray(undef, RealT, - StaticInt(nnodes(analyzer))) - - return (; u_local, x_local, jacobian_local) -end - -function calc_error_norms(func, u, t, analyzer, - mesh::StructuredMesh{1}, equations, initial_condition, - dg::DGSEM, cache, cache_analysis) - @unpack vandermonde, weights = analyzer - @unpack node_coordinates, inverse_jacobian = cache.elements - @unpack u_local, x_local, jacobian_local = cache_analysis - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1), equations)) - linf_error = copy(l2_error) - total_volume = zero(real(mesh)) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Interpolate solution and node locations to analysis nodes - multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, element)) - multiply_dimensionwise!(x_local, vandermonde, - view(node_coordinates, :, :, element)) - multiply_scalar_dimensionwise!(jacobian_local, vandermonde, - inv.(view(inverse_jacobian, :, element))) - - # Calculate errors at each analysis node - @. jacobian_local = abs(jacobian_local) - - for i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(x_local, equations, dg, i), t, - equations) - diff = func(u_exact, equations) - - func(get_node_vars(u_local, equations, dg, i), equations) - l2_error += diff .^ 2 * (weights[i] * jacobian_local[i]) - linf_error = @. max(linf_error, abs(diff)) - total_volume += weights[i] * jacobian_local[i] - end + #! format: noindent + + function create_cache_analysis( + analyzer, mesh::TreeMesh{1}, + equations, dg::DG, cache, + RealT, uEltype + ) + + # pre-allocate buffers + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + u_local = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)) + ) + x_local = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)) + ) + + return (; u_local, x_local) end - # For L2 error, divide by total volume - l2_error = @. sqrt(l2_error / total_volume) - - return l2_error, linf_error -end - -function calc_error_norms(func, u, t, analyzer, - mesh::TreeMesh{1}, equations, initial_condition, - dg::DGSEM, cache, cache_analysis) - @unpack vandermonde, weights = analyzer - @unpack node_coordinates = cache.elements - @unpack u_local, x_local = cache_analysis - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1), equations)) - linf_error = copy(l2_error) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Interpolate solution and node locations to analysis nodes - multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, element)) - multiply_dimensionwise!(x_local, vandermonde, - view(node_coordinates, :, :, element)) - - # Calculate errors at each analysis node - volume_jacobian_ = volume_jacobian(element, mesh, cache) - - for i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(x_local, equations, dg, i), t, - equations) - diff = func(u_exact, equations) - - func(get_node_vars(u_local, equations, dg, i), equations) - l2_error += diff .^ 2 * (weights[i] * volume_jacobian_) - linf_error = @. max(linf_error, abs(diff)) - end + function create_cache_analysis( + analyzer, mesh::StructuredMesh{1}, + equations, dg::DG, cache, + RealT, uEltype + ) + + # pre-allocate buffers + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + u_local = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)) + ) + x_local = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)) + ) + jacobian_local = StrideArray( + undef, RealT, + StaticInt(nnodes(analyzer)) + ) + + return (; u_local, x_local, jacobian_local) end - # For L2 error, divide by total volume - total_volume_ = total_volume(mesh) - l2_error = @. sqrt(l2_error / total_volume_) - - return l2_error, linf_error -end - -function integrate_via_indices(func::Func, u, - mesh::StructuredMesh{1}, equations, dg::DGSEM, cache, - args...; normalize = true) where {Func} - @unpack weights = dg.basis - - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, equations, dg, args...)) - total_volume = zero(real(mesh)) - - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - for i in eachnode(dg) - jacobian_volume = abs(inv(cache.elements.inverse_jacobian[i, element])) - integral += jacobian_volume * weights[i] * - func(u, i, element, equations, dg, args...) - total_volume += jacobian_volume * weights[i] + function calc_error_norms( + func, u, t, analyzer, + mesh::StructuredMesh{1}, equations, initial_condition, + dg::DGSEM, cache, cache_analysis + ) + @unpack vandermonde, weights = analyzer + @unpack node_coordinates, inverse_jacobian = cache.elements + @unpack u_local, x_local, jacobian_local = cache_analysis + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1), equations)) + linf_error = copy(l2_error) + total_volume = zero(real(mesh)) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Interpolate solution and node locations to analysis nodes + multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, element)) + multiply_dimensionwise!( + x_local, vandermonde, + view(node_coordinates, :, :, element) + ) + multiply_scalar_dimensionwise!( + jacobian_local, vandermonde, + inv.(view(inverse_jacobian, :, element)) + ) + + # Calculate errors at each analysis node + @. jacobian_local = abs(jacobian_local) + + for i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords(x_local, equations, dg, i), t, + equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u_local, equations, dg, i), equations) + l2_error += diff .^ 2 * (weights[i] * jacobian_local[i]) + linf_error = @. max(linf_error, abs(diff)) + total_volume += weights[i] * jacobian_local[i] + end end - end - # Normalize with total volume - if normalize - integral = integral / total_volume + + # For L2 error, divide by total volume + l2_error = @. sqrt(l2_error / total_volume) + + return l2_error, linf_error end - return integral -end + function calc_error_norms( + func, u, t, analyzer, + mesh::TreeMesh{1}, equations, initial_condition, + dg::DGSEM, cache, cache_analysis + ) + @unpack vandermonde, weights = analyzer + @unpack node_coordinates = cache.elements + @unpack u_local, x_local = cache_analysis + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1), equations)) + linf_error = copy(l2_error) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Interpolate solution and node locations to analysis nodes + multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, element)) + multiply_dimensionwise!( + x_local, vandermonde, + view(node_coordinates, :, :, element) + ) + + # Calculate errors at each analysis node + volume_jacobian_ = volume_jacobian(element, mesh, cache) + + for i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords(x_local, equations, dg, i), t, + equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u_local, equations, dg, i), equations) + l2_error += diff .^ 2 * (weights[i] * volume_jacobian_) + linf_error = @. max(linf_error, abs(diff)) + end + end -function integrate_via_indices(func::Func, u, - mesh::TreeMesh{1}, equations, dg::DGSEM, cache, - args...; normalize = true) where {Func} - @unpack weights = dg.basis + # For L2 error, divide by total volume + total_volume_ = total_volume(mesh) + l2_error = @. sqrt(l2_error / total_volume_) - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, equations, dg, args...)) + return l2_error, linf_error + end - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - volume_jacobian_ = volume_jacobian(element, mesh, cache) - for i in eachnode(dg) - integral += volume_jacobian_ * weights[i] * - func(u, i, element, equations, dg, args...) + function integrate_via_indices( + func::Func, u, + mesh::StructuredMesh{1}, equations, dg::DGSEM, cache, + args...; normalize = true + ) where {Func} + @unpack weights = dg.basis + + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, equations, dg, args...)) + total_volume = zero(real(mesh)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + for i in eachnode(dg) + jacobian_volume = abs(inv(cache.elements.inverse_jacobian[i, element])) + integral += jacobian_volume * weights[i] * + func(u, i, element, equations, dg, args...) + total_volume += jacobian_volume * weights[i] + end + end + # Normalize with total volume + if normalize + integral = integral / total_volume end - end - # Normalize with total volume - if normalize - integral = integral / total_volume(mesh) + return integral end - return integral -end + function integrate_via_indices( + func::Func, u, + mesh::TreeMesh{1}, equations, dg::DGSEM, cache, + args...; normalize = true + ) where {Func} + @unpack weights = dg.basis + + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, equations, dg, args...)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + volume_jacobian_ = volume_jacobian(element, mesh, cache) + for i in eachnode(dg) + integral += volume_jacobian_ * weights[i] * + func(u, i, element, equations, dg, args...) + end + end + + # Normalize with total volume + if normalize + integral = integral / total_volume(mesh) + end -function integrate(func::Func, u, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - equations, dg::DG, cache; normalize = true) where {Func} - integrate_via_indices(u, mesh, equations, dg, cache; - normalize = normalize) do u, i, element, equations, dg - u_local = get_node_vars(u, equations, dg, i, element) - return func(u_local, equations) + return integral end -end - -function analyze(::typeof(entropy_timederivative), du, u, t, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, equations, dg::DG, cache) - # Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ - integrate_via_indices(u, mesh, equations, dg, cache, - du) do u, i, element, equations, dg, du - u_node = get_node_vars(u, equations, dg, i, element) - du_node = get_node_vars(du, equations, dg, i, element) - dot(cons2entropy(u_node, equations), du_node) + + function integrate( + func::Func, u, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + equations, dg::DG, cache; normalize = true + ) where {Func} + integrate_via_indices( + u, mesh, equations, dg, cache; + normalize = normalize + ) do u, i, element, equations, dg + u_local = get_node_vars(u, equations, dg, i, element) + return func(u_local, equations) + end end -end - -function analyze(::Val{:l2_divb}, du, u, t, - mesh::TreeMesh{1}, equations::IdealGlmMhdEquations1D, - dg::DG, cache) - integrate_via_indices(u, mesh, equations, dg, cache, - dg.basis.derivative_matrix) do u, i, element, equations, dg, - derivative_matrix - divb = zero(eltype(u)) - for k in eachnode(dg) - divb += derivative_matrix[i, k] * u[6, k, element] + + function analyze( + ::typeof(entropy_timederivative), du, u, t, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, equations, dg::DG, cache + ) + # Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ + integrate_via_indices( + u, mesh, equations, dg, cache, + du + ) do u, i, element, equations, dg, du + u_node = get_node_vars(u, equations, dg, i, element) + du_node = get_node_vars(du, equations, dg, i, element) + dot(cons2entropy(u_node, equations), du_node) end - divb *= cache.elements.inverse_jacobian[element] - divb^2 - end |> sqrt -end - -function analyze(::Val{:linf_divb}, du, u, t, - mesh::TreeMesh{1}, equations::IdealGlmMhdEquations1D, - dg::DG, cache) - @unpack derivative_matrix, weights = dg.basis - - # integrate over all elements to get the divergence-free condition errors - linf_divb = zero(eltype(u)) - for element in eachelement(dg, cache) - for i in eachnode(dg) + end + + function analyze( + ::Val{:l2_divb}, du, u, t, + mesh::TreeMesh{1}, equations::IdealGlmMhdEquations1D, + dg::DG, cache + ) + integrate_via_indices( + u, mesh, equations, dg, cache, + dg.basis.derivative_matrix + ) do u, i, element, equations, dg, + derivative_matrix divb = zero(eltype(u)) for k in eachnode(dg) divb += derivative_matrix[i, k] * u[6, k, element] end divb *= cache.elements.inverse_jacobian[element] - linf_divb = max(linf_divb, abs(divb)) - end + divb^2 + end |> sqrt end - return linf_divb -end + function analyze( + ::Val{:linf_divb}, du, u, t, + mesh::TreeMesh{1}, equations::IdealGlmMhdEquations1D, + dg::DG, cache + ) + @unpack derivative_matrix, weights = dg.basis + + # integrate over all elements to get the divergence-free condition errors + linf_divb = zero(eltype(u)) + for element in eachelement(dg, cache) + for i in eachnode(dg) + divb = zero(eltype(u)) + for k in eachnode(dg) + divb += derivative_matrix[i, k] * u[6, k, element] + end + divb *= cache.elements.inverse_jacobian[element] + linf_divb = max(linf_divb, abs(divb)) + end + end + + return linf_divb + end end # @muladd diff --git a/src/callbacks_step/analysis_dg2d.jl b/src/callbacks_step/analysis_dg2d.jl index de6b9a2a4a..fc0b1f9694 100644 --- a/src/callbacks_step/analysis_dg2d.jl +++ b/src/callbacks_step/analysis_dg2d.jl @@ -3,380 +3,488 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function create_cache_analysis(analyzer, mesh::TreeMesh{2}, - equations, dg::DG, cache, - RealT, uEltype) - - # pre-allocate buffers - # We use `StrideArray`s here since these buffers are used in performance-critical - # places and the additional information passed to the compiler makes them faster - # than native `Array`s. - u_local = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer))) - u_tmp1 = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(dg))) - x_local = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer))) - x_tmp1 = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(dg))) - - return (; u_local, u_tmp1, x_local, x_tmp1) -end - -function create_cache_analysis(analyzer, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, - UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, - equations, dg::DG, cache, - RealT, uEltype) - - # pre-allocate buffers - # We use `StrideArray`s here since these buffers are used in performance-critical - # places and the additional information passed to the compiler makes them faster - # than native `Array`s. - u_local = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer))) - u_tmp1 = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(dg))) - x_local = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer))) - x_tmp1 = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(dg))) - jacobian_local = StrideArray(undef, RealT, - StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer))) - jacobian_tmp1 = StrideArray(undef, RealT, - StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg))) - - return (; u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1) -end - -function calc_error_norms(func, u, t, analyzer, - mesh::TreeMesh{2}, equations, initial_condition, - dg::DGSEM, cache, cache_analysis) - @unpack vandermonde, weights = analyzer - @unpack node_coordinates = cache.elements - @unpack u_local, u_tmp1, x_local, x_tmp1 = cache_analysis - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) - linf_error = copy(l2_error) - - # Iterate over all elements for error calculations - # Accumulate L2 error on the element first so that the order of summation is the - # same as in the parallel case to ensure exact equality. This facilitates easier parallel - # development and debugging (see - # https://github.com/trixi-framework/Trixi.jl/pull/850#pullrequestreview-757463943 for details). - for element in eachelement(dg, cache) - # Set up data structures for local element L2 error - l2_error_local = zero(l2_error) - - # Interpolate solution and node locations to analysis nodes - multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1) - multiply_dimensionwise!(x_local, vandermonde, - view(node_coordinates, :, :, :, element), x_tmp1) - - # Calculate errors at each analysis node - volume_jacobian_ = volume_jacobian(element, mesh, cache) - - for j in eachnode(analyzer), i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j), - t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u_local, equations, dg, i, j), equations) - l2_error_local += diff .^ 2 * (weights[i] * weights[j] * volume_jacobian_) - linf_error = @. max(linf_error, abs(diff)) - end - l2_error += l2_error_local + #! format: noindent + + function create_cache_analysis( + analyzer, mesh::TreeMesh{2}, + equations, dg::DG, cache, + RealT, uEltype + ) + + # pre-allocate buffers + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + u_local = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)) + ) + u_tmp1 = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(dg)) + ) + x_local = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)) + ) + x_tmp1 = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(dg)) + ) + + return (; u_local, u_tmp1, x_local, x_tmp1) + end + + function create_cache_analysis( + analyzer, + mesh::Union{ + StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}, + }, + equations, dg::DG, cache, + RealT, uEltype + ) + + # pre-allocate buffers + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + u_local = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)) + ) + u_tmp1 = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(dg)) + ) + x_local = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)) + ) + x_tmp1 = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(dg)) + ) + jacobian_local = StrideArray( + undef, RealT, + StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)) + ) + jacobian_tmp1 = StrideArray( + undef, RealT, + StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)) + ) + + return (; u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1) end - # For L2 error, divide by total volume - total_volume_ = total_volume(mesh) - l2_error = @. sqrt(l2_error / total_volume_) - - return l2_error, linf_error -end - -function calc_error_norms(func, u, t, analyzer, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, - UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, - equations, - initial_condition, dg::DGSEM, cache, cache_analysis) - @unpack vandermonde, weights = analyzer - @unpack node_coordinates, inverse_jacobian = cache.elements - @unpack u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1 = cache_analysis - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) - linf_error = copy(l2_error) - total_volume = zero(real(mesh)) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Interpolate solution and node locations to analysis nodes - multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1) - multiply_dimensionwise!(x_local, vandermonde, - view(node_coordinates, :, :, :, element), x_tmp1) - multiply_scalar_dimensionwise!(jacobian_local, vandermonde, - inv.(view(inverse_jacobian, :, :, element)), - jacobian_tmp1) - - # Calculate errors at each analysis node - @. jacobian_local = abs(jacobian_local) - - for j in eachnode(analyzer), i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j), - t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u_local, equations, dg, i, j), equations) - l2_error += diff .^ 2 * (weights[i] * weights[j] * jacobian_local[i, j]) - linf_error = @. max(linf_error, abs(diff)) - total_volume += weights[i] * weights[j] * jacobian_local[i, j] + function calc_error_norms( + func, u, t, analyzer, + mesh::TreeMesh{2}, equations, initial_condition, + dg::DGSEM, cache, cache_analysis + ) + @unpack vandermonde, weights = analyzer + @unpack node_coordinates = cache.elements + @unpack u_local, u_tmp1, x_local, x_tmp1 = cache_analysis + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) + linf_error = copy(l2_error) + + # Iterate over all elements for error calculations + # Accumulate L2 error on the element first so that the order of summation is the + # same as in the parallel case to ensure exact equality. This facilitates easier parallel + # development and debugging (see + # https://github.com/trixi-framework/Trixi.jl/pull/850#pullrequestreview-757463943 for details). + for element in eachelement(dg, cache) + # Set up data structures for local element L2 error + l2_error_local = zero(l2_error) + + # Interpolate solution and node locations to analysis nodes + multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1) + multiply_dimensionwise!( + x_local, vandermonde, + view(node_coordinates, :, :, :, element), x_tmp1 + ) + + # Calculate errors at each analysis node + volume_jacobian_ = volume_jacobian(element, mesh, cache) + + for j in eachnode(analyzer), i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords(x_local, equations, dg, i, j), + t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u_local, equations, dg, i, j), equations) + l2_error_local += diff .^ 2 * (weights[i] * weights[j] * volume_jacobian_) + linf_error = @. max(linf_error, abs(diff)) + end + l2_error += l2_error_local end + + # For L2 error, divide by total volume + total_volume_ = total_volume(mesh) + l2_error = @. sqrt(l2_error / total_volume_) + + return l2_error, linf_error end - # For L2 error, divide by total volume - l2_error = @. sqrt(l2_error / total_volume) + function calc_error_norms( + func, u, t, analyzer, + mesh::Union{ + StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}, + }, + equations, + initial_condition, dg::DGSEM, cache, cache_analysis + ) + @unpack vandermonde, weights = analyzer + @unpack node_coordinates, inverse_jacobian = cache.elements + @unpack u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1 = cache_analysis + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) + linf_error = copy(l2_error) + total_volume = zero(real(mesh)) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Interpolate solution and node locations to analysis nodes + multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1) + multiply_dimensionwise!( + x_local, vandermonde, + view(node_coordinates, :, :, :, element), x_tmp1 + ) + multiply_scalar_dimensionwise!( + jacobian_local, vandermonde, + inv.(view(inverse_jacobian, :, :, element)), + jacobian_tmp1 + ) + + # Calculate errors at each analysis node + @. jacobian_local = abs(jacobian_local) + + for j in eachnode(analyzer), i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords(x_local, equations, dg, i, j), + t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u_local, equations, dg, i, j), equations) + l2_error += diff .^ 2 * (weights[i] * weights[j] * jacobian_local[i, j]) + linf_error = @. max(linf_error, abs(diff)) + total_volume += weights[i] * weights[j] * jacobian_local[i, j] + end + end - return l2_error, linf_error -end + # For L2 error, divide by total volume + l2_error = @. sqrt(l2_error / total_volume) -function integrate_via_indices(func::Func, u, - mesh::TreeMesh{2}, equations, dg::DGSEM, cache, - args...; normalize = true) where {Func} - @unpack weights = dg.basis + return l2_error, linf_error + end - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, 1, equations, dg, args...)) + function integrate_via_indices( + func::Func, u, + mesh::TreeMesh{2}, equations, dg::DGSEM, cache, + args...; normalize = true + ) where {Func} + @unpack weights = dg.basis + + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, 1, equations, dg, args...)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + volume_jacobian_ = volume_jacobian(element, mesh, cache) + for j in eachnode(dg), i in eachnode(dg) + integral += volume_jacobian_ * weights[i] * weights[j] * + func(u, i, j, element, equations, dg, args...) + end + end - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - volume_jacobian_ = volume_jacobian(element, mesh, cache) - for j in eachnode(dg), i in eachnode(dg) - integral += volume_jacobian_ * weights[i] * weights[j] * - func(u, i, j, element, equations, dg, args...) + # Normalize with total volume + if normalize + integral = integral / total_volume(mesh) end - end - # Normalize with total volume - if normalize - integral = integral / total_volume(mesh) + return integral end - return integral -end - -function integrate_via_indices(func::Func, u, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, - UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - equations, - dg::DGSEM, cache, args...; normalize = true) where {Func} - @unpack weights = dg.basis - - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, 1, equations, dg, args...)) - total_volume = zero(real(mesh)) - - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element])) - integral += volume_jacobian * weights[i] * weights[j] * - func(u, i, j, element, equations, dg, args...) - total_volume += volume_jacobian * weights[i] * weights[j] + function integrate_via_indices( + func::Func, u, + mesh::Union{ + StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + equations, + dg::DGSEM, cache, args...; normalize = true + ) where {Func} + @unpack weights = dg.basis + + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, 1, equations, dg, args...)) + total_volume = zero(real(mesh)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element])) + integral += volume_jacobian * weights[i] * weights[j] * + func(u, i, j, element, equations, dg, args...) + total_volume += volume_jacobian * weights[i] * weights[j] + end end - end - # Normalize with total volume - if normalize - integral = integral / total_volume - end + # Normalize with total volume + if normalize + integral = integral / total_volume + end - return integral -end - -function integrate(func::Func, u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2}, - UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, - equations, dg::DG, cache; normalize = true) where {Func} - integrate_via_indices(u, mesh, equations, dg, cache; - normalize = normalize) do u, i, j, element, equations, dg - u_local = get_node_vars(u, equations, dg, i, j, element) - return func(u_local, equations) - end -end - -function integrate(func::Func, u, - mesh::Union{TreeMesh{2}, P4estMesh{2}}, - equations, equations_parabolic, - dg::DGSEM, - cache, cache_parabolic; normalize = true) where {Func} - gradients_x, gradients_y = cache_parabolic.viscous_container.gradients - integrate_via_indices(u, mesh, equations, dg, cache; - normalize = normalize) do u, i, j, element, equations, dg - u_local = get_node_vars(u, equations, dg, i, j, element) - gradients_1_local = get_node_vars(gradients_x, equations_parabolic, dg, i, j, - element) - gradients_2_local = get_node_vars(gradients_y, equations_parabolic, dg, i, j, - element) - return func(u_local, (gradients_1_local, gradients_2_local), - equations_parabolic) + return integral end -end - -function analyze(::typeof(entropy_timederivative), du, u, t, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2}, - UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, - equations, dg::DG, cache) - # Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ - integrate_via_indices(u, mesh, equations, dg, cache, - du) do u, i, j, element, equations, dg, du - u_node = get_node_vars(u, equations, dg, i, j, element) - du_node = get_node_vars(du, equations, dg, i, j, element) - dot(cons2entropy(u_node, equations), du_node) - end -end - -function analyze(::Val{:l2_divb}, du, u, t, - mesh::TreeMesh{2}, - equations::IdealGlmMhdEquations2D, dg::DGSEM, cache) - integrate_via_indices(u, mesh, equations, dg, cache, cache, - dg.basis.derivative_matrix) do u, i, j, element, equations, - dg, cache, derivative_matrix - divb = zero(eltype(u)) - for k in eachnode(dg) - divb += (derivative_matrix[i, k] * u[6, k, j, element] + - derivative_matrix[j, k] * u[7, i, k, element]) + + function integrate( + func::Func, u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}, + }, + equations, dg::DG, cache; normalize = true + ) where {Func} + integrate_via_indices( + u, mesh, equations, dg, cache; + normalize = normalize + ) do u, i, j, element, equations, dg + u_local = get_node_vars(u, equations, dg, i, j, element) + return func(u_local, equations) end - divb *= cache.elements.inverse_jacobian[element] - divb^2 - end |> sqrt -end - -function analyze(::Val{:l2_divb}, du, u, t, - mesh::TreeMesh{2}, equations::IdealGlmMhdMulticomponentEquations2D, - dg::DG, cache) - integrate_via_indices(u, mesh, equations, dg, cache, cache, - dg.basis.derivative_matrix) do u, i, j, element, equations, - dg, cache, derivative_matrix - divb = zero(eltype(u)) - for k in eachnode(dg) - divb += (derivative_matrix[i, k] * u[5, k, j, element] + - derivative_matrix[j, k] * u[6, i, k, element]) + end + + function integrate( + func::Func, u, + mesh::Union{TreeMesh{2}, P4estMesh{2}}, + equations, equations_parabolic, + dg::DGSEM, + cache, cache_parabolic; normalize = true + ) where {Func} + gradients_x, gradients_y = cache_parabolic.viscous_container.gradients + integrate_via_indices( + u, mesh, equations, dg, cache; + normalize = normalize + ) do u, i, j, element, equations, dg + u_local = get_node_vars(u, equations, dg, i, j, element) + gradients_1_local = get_node_vars( + gradients_x, equations_parabolic, dg, i, j, + element + ) + gradients_2_local = get_node_vars( + gradients_y, equations_parabolic, dg, i, j, + element + ) + return func( + u_local, (gradients_1_local, gradients_2_local), + equations_parabolic + ) end - divb *= cache.elements.inverse_jacobian[element] - divb^2 - end |> sqrt -end - -function analyze(::Val{:l2_divb}, du, u, t, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - equations::IdealGlmMhdEquations2D, dg::DGSEM, cache) - @unpack contravariant_vectors = cache.elements - integrate_via_indices(u, mesh, equations, dg, cache, cache, - dg.basis.derivative_matrix) do u, i, j, element, equations, - dg, cache, derivative_matrix - divb = zero(eltype(u)) - # Get the contravariant vectors Ja^1 and Ja^2 - Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, element) - Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, element) - # Compute the transformed divergence - for k in eachnode(dg) - divb += (derivative_matrix[i, k] * - (Ja11 * u[6, k, j, element] + Ja12 * u[7, k, j, element]) + - derivative_matrix[j, k] * - (Ja21 * u[6, i, k, element] + Ja22 * u[7, i, k, element])) + end + + function analyze( + ::typeof(entropy_timederivative), du, u, t, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}, + }, + equations, dg::DG, cache + ) + # Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ + integrate_via_indices( + u, mesh, equations, dg, cache, + du + ) do u, i, j, element, equations, dg, du + u_node = get_node_vars(u, equations, dg, i, j, element) + du_node = get_node_vars(du, equations, dg, i, j, element) + dot(cons2entropy(u_node, equations), du_node) end - divb *= cache.elements.inverse_jacobian[i, j, element] - divb^2 - end |> sqrt -end - -function analyze(::Val{:linf_divb}, du, u, t, - mesh::TreeMesh{2}, - equations::IdealGlmMhdEquations2D, dg::DGSEM, cache) - @unpack derivative_matrix, weights = dg.basis - - # integrate over all elements to get the divergence-free condition errors - linf_divb = zero(eltype(u)) - for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) + end + + function analyze( + ::Val{:l2_divb}, du, u, t, + mesh::TreeMesh{2}, + equations::IdealGlmMhdEquations2D, dg::DGSEM, cache + ) + integrate_via_indices( + u, mesh, equations, dg, cache, cache, + dg.basis.derivative_matrix + ) do u, i, j, element, equations, + dg, cache, derivative_matrix divb = zero(eltype(u)) for k in eachnode(dg) - divb += (derivative_matrix[i, k] * u[6, k, j, element] + - derivative_matrix[j, k] * u[7, i, k, element]) + divb += ( + derivative_matrix[i, k] * u[6, k, j, element] + + derivative_matrix[j, k] * u[7, i, k, element] + ) end divb *= cache.elements.inverse_jacobian[element] - linf_divb = max(linf_divb, abs(divb)) - end + divb^2 + end |> sqrt end - return linf_divb -end - -function analyze(::Val{:linf_divb}, du, u, t, - mesh::TreeMesh{2}, equations::IdealGlmMhdMulticomponentEquations2D, - dg::DG, cache) - @unpack derivative_matrix, weights = dg.basis - - # integrate over all elements to get the divergence-free condition errors - linf_divb = zero(eltype(u)) - for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) + function analyze( + ::Val{:l2_divb}, du, u, t, + mesh::TreeMesh{2}, equations::IdealGlmMhdMulticomponentEquations2D, + dg::DG, cache + ) + integrate_via_indices( + u, mesh, equations, dg, cache, cache, + dg.basis.derivative_matrix + ) do u, i, j, element, equations, + dg, cache, derivative_matrix divb = zero(eltype(u)) for k in eachnode(dg) - divb += (derivative_matrix[i, k] * u[5, k, j, element] + - derivative_matrix[j, k] * u[6, i, k, element]) + divb += ( + derivative_matrix[i, k] * u[5, k, j, element] + + derivative_matrix[j, k] * u[6, i, k, element] + ) end divb *= cache.elements.inverse_jacobian[element] - linf_divb = max(linf_divb, abs(divb)) - end + divb^2 + end |> sqrt end - return linf_divb -end - -function analyze(::Val{:linf_divb}, du, u, t, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - equations::IdealGlmMhdEquations2D, dg::DGSEM, cache) - @unpack derivative_matrix, weights = dg.basis - @unpack contravariant_vectors = cache.elements - - # integrate over all elements to get the divergence-free condition errors - linf_divb = zero(eltype(u)) - for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) + function analyze( + ::Val{:l2_divb}, du, u, t, + mesh::Union{ + StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + equations::IdealGlmMhdEquations2D, dg::DGSEM, cache + ) + @unpack contravariant_vectors = cache.elements + integrate_via_indices( + u, mesh, equations, dg, cache, cache, + dg.basis.derivative_matrix + ) do u, i, j, element, equations, + dg, cache, derivative_matrix divb = zero(eltype(u)) # Get the contravariant vectors Ja^1 and Ja^2 - Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, - element) - Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, - element) + Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, element) + Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, element) # Compute the transformed divergence for k in eachnode(dg) - divb += (derivative_matrix[i, k] * - (Ja11 * u[6, k, j, element] + Ja12 * u[7, k, j, element]) + - derivative_matrix[j, k] * - (Ja21 * u[6, i, k, element] + Ja22 * u[7, i, k, element])) + divb += ( + derivative_matrix[i, k] * + (Ja11 * u[6, k, j, element] + Ja12 * u[7, k, j, element]) + + derivative_matrix[j, k] * + (Ja21 * u[6, i, k, element] + Ja22 * u[7, i, k, element]) + ) end divb *= cache.elements.inverse_jacobian[i, j, element] - linf_divb = max(linf_divb, abs(divb)) + divb^2 + end |> sqrt + end + + function analyze( + ::Val{:linf_divb}, du, u, t, + mesh::TreeMesh{2}, + equations::IdealGlmMhdEquations2D, dg::DGSEM, cache + ) + @unpack derivative_matrix, weights = dg.basis + + # integrate over all elements to get the divergence-free condition errors + linf_divb = zero(eltype(u)) + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + divb = zero(eltype(u)) + for k in eachnode(dg) + divb += ( + derivative_matrix[i, k] * u[6, k, j, element] + + derivative_matrix[j, k] * u[7, i, k, element] + ) + end + divb *= cache.elements.inverse_jacobian[element] + linf_divb = max(linf_divb, abs(divb)) + end end + + return linf_divb end - return linf_divb -end + function analyze( + ::Val{:linf_divb}, du, u, t, + mesh::TreeMesh{2}, equations::IdealGlmMhdMulticomponentEquations2D, + dg::DG, cache + ) + @unpack derivative_matrix, weights = dg.basis + + # integrate over all elements to get the divergence-free condition errors + linf_divb = zero(eltype(u)) + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + divb = zero(eltype(u)) + for k in eachnode(dg) + divb += ( + derivative_matrix[i, k] * u[5, k, j, element] + + derivative_matrix[j, k] * u[6, i, k, element] + ) + end + divb *= cache.elements.inverse_jacobian[element] + linf_divb = max(linf_divb, abs(divb)) + end + end + + return linf_divb + end + + function analyze( + ::Val{:linf_divb}, du, u, t, + mesh::Union{ + StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + equations::IdealGlmMhdEquations2D, dg::DGSEM, cache + ) + @unpack derivative_matrix, weights = dg.basis + @unpack contravariant_vectors = cache.elements + + # integrate over all elements to get the divergence-free condition errors + linf_divb = zero(eltype(u)) + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + divb = zero(eltype(u)) + # Get the contravariant vectors Ja^1 and Ja^2 + Ja11, Ja12 = get_contravariant_vector( + 1, contravariant_vectors, i, j, + element + ) + Ja21, Ja22 = get_contravariant_vector( + 2, contravariant_vectors, i, j, + element + ) + # Compute the transformed divergence + for k in eachnode(dg) + divb += ( + derivative_matrix[i, k] * + (Ja11 * u[6, k, j, element] + Ja12 * u[7, k, j, element]) + + derivative_matrix[j, k] * + (Ja21 * u[6, i, k, element] + Ja22 * u[7, i, k, element]) + ) + end + divb *= cache.elements.inverse_jacobian[i, j, element] + linf_divb = max(linf_divb, abs(divb)) + end + end + + return linf_divb + end end # @muladd diff --git a/src/callbacks_step/analysis_dg2d_parallel.jl b/src/callbacks_step/analysis_dg2d_parallel.jl index 000daa015d..5d536c48e8 100644 --- a/src/callbacks_step/analysis_dg2d_parallel.jl +++ b/src/callbacks_step/analysis_dg2d_parallel.jl @@ -3,211 +3,241 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function calc_error_norms(func, u, t, analyzer, - mesh::ParallelTreeMesh{2}, equations, initial_condition, - dg::DGSEM, cache, cache_analysis) - l2_errors, linf_errors = calc_error_norms_per_element(func, u, t, analyzer, - mesh, equations, - initial_condition, - dg, cache, cache_analysis) - - # Collect local error norms for each element on root process. That way, when aggregating the L2 - # errors, the order of summation is the same as in the serial case to ensure exact equality. - # This facilitates easier parallel development and debugging (see - # https://github.com/trixi-framework/Trixi.jl/pull/850#pullrequestreview-757463943 for details). - # Note that this approach does not scale. - if mpi_isroot() - global_l2_errors = zeros(eltype(l2_errors), cache.mpi_cache.n_elements_global) - global_linf_errors = similar(global_l2_errors) - - n_elements_by_rank = parent(cache.mpi_cache.n_elements_by_rank) # convert OffsetArray to Array - l2_buf = MPI.VBuffer(global_l2_errors, n_elements_by_rank) - linf_buf = MPI.VBuffer(global_linf_errors, n_elements_by_rank) - MPI.Gatherv!(l2_errors, l2_buf, mpi_root(), mpi_comm()) - MPI.Gatherv!(linf_errors, linf_buf, mpi_root(), mpi_comm()) - else - MPI.Gatherv!(l2_errors, nothing, mpi_root(), mpi_comm()) - MPI.Gatherv!(linf_errors, nothing, mpi_root(), mpi_comm()) - end + #! format: noindent + + function calc_error_norms( + func, u, t, analyzer, + mesh::ParallelTreeMesh{2}, equations, initial_condition, + dg::DGSEM, cache, cache_analysis + ) + l2_errors, linf_errors = calc_error_norms_per_element( + func, u, t, analyzer, + mesh, equations, + initial_condition, + dg, cache, cache_analysis + ) + + # Collect local error norms for each element on root process. That way, when aggregating the L2 + # errors, the order of summation is the same as in the serial case to ensure exact equality. + # This facilitates easier parallel development and debugging (see + # https://github.com/trixi-framework/Trixi.jl/pull/850#pullrequestreview-757463943 for details). + # Note that this approach does not scale. + if mpi_isroot() + global_l2_errors = zeros(eltype(l2_errors), cache.mpi_cache.n_elements_global) + global_linf_errors = similar(global_l2_errors) + + n_elements_by_rank = parent(cache.mpi_cache.n_elements_by_rank) # convert OffsetArray to Array + l2_buf = MPI.VBuffer(global_l2_errors, n_elements_by_rank) + linf_buf = MPI.VBuffer(global_linf_errors, n_elements_by_rank) + MPI.Gatherv!(l2_errors, l2_buf, mpi_root(), mpi_comm()) + MPI.Gatherv!(linf_errors, linf_buf, mpi_root(), mpi_comm()) + else + MPI.Gatherv!(l2_errors, nothing, mpi_root(), mpi_comm()) + MPI.Gatherv!(linf_errors, nothing, mpi_root(), mpi_comm()) + end - # Aggregate element error norms on root process - if mpi_isroot() - # sum(global_l2_errors) does not produce the same result as in the serial case, thus a - # hand-written loop is used - l2_error = zero(eltype(global_l2_errors)) - for error in global_l2_errors - l2_error += error + # Aggregate element error norms on root process + if mpi_isroot() + # sum(global_l2_errors) does not produce the same result as in the serial case, thus a + # hand-written loop is used + l2_error = zero(eltype(global_l2_errors)) + for error in global_l2_errors + l2_error += error + end + linf_error = reduce((x, y) -> max.(x, y), global_linf_errors) + + # For L2 error, divide by total volume + total_volume_ = total_volume(mesh) + l2_error = @. sqrt(l2_error / total_volume_) + else + l2_error = convert(eltype(l2_errors), NaN * zero(eltype(l2_errors))) + linf_error = convert(eltype(linf_errors), NaN * zero(eltype(linf_errors))) end - linf_error = reduce((x, y) -> max.(x, y), global_linf_errors) - - # For L2 error, divide by total volume - total_volume_ = total_volume(mesh) - l2_error = @. sqrt(l2_error / total_volume_) - else - l2_error = convert(eltype(l2_errors), NaN * zero(eltype(l2_errors))) - linf_error = convert(eltype(linf_errors), NaN * zero(eltype(linf_errors))) + + return l2_error, linf_error end - return l2_error, linf_error -end - -function calc_error_norms_per_element(func, u, t, analyzer, - mesh::ParallelTreeMesh{2}, equations, - initial_condition, - dg::DGSEM, cache, cache_analysis) - @unpack vandermonde, weights = analyzer - @unpack node_coordinates = cache.elements - @unpack u_local, u_tmp1, x_local, x_tmp1 = cache_analysis - - # Set up data structures - T = typeof(zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations))) - l2_errors = zeros(T, nelements(dg, cache)) - linf_errors = copy(l2_errors) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Interpolate solution and node locations to analysis nodes - multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1) - multiply_dimensionwise!(x_local, vandermonde, - view(node_coordinates, :, :, :, element), x_tmp1) - - # Calculate errors at each analysis node - volume_jacobian_ = volume_jacobian(element, mesh, cache) - - for j in eachnode(analyzer), i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j), - t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u_local, equations, dg, i, j), equations) - l2_errors[element] += diff .^ 2 * - (weights[i] * weights[j] * volume_jacobian_) - linf_errors[element] = @. max(linf_errors[element], abs(diff)) + function calc_error_norms_per_element( + func, u, t, analyzer, + mesh::ParallelTreeMesh{2}, equations, + initial_condition, + dg::DGSEM, cache, cache_analysis + ) + @unpack vandermonde, weights = analyzer + @unpack node_coordinates = cache.elements + @unpack u_local, u_tmp1, x_local, x_tmp1 = cache_analysis + + # Set up data structures + T = typeof(zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations))) + l2_errors = zeros(T, nelements(dg, cache)) + linf_errors = copy(l2_errors) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Interpolate solution and node locations to analysis nodes + multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1) + multiply_dimensionwise!( + x_local, vandermonde, + view(node_coordinates, :, :, :, element), x_tmp1 + ) + + # Calculate errors at each analysis node + volume_jacobian_ = volume_jacobian(element, mesh, cache) + + for j in eachnode(analyzer), i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords(x_local, equations, dg, i, j), + t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u_local, equations, dg, i, j), equations) + l2_errors[element] += diff .^ 2 * + (weights[i] * weights[j] * volume_jacobian_) + linf_errors[element] = @. max(linf_errors[element], abs(diff)) + end end + + return l2_errors, linf_errors end - return l2_errors, linf_errors -end - -function calc_error_norms(func, u, t, analyzer, - mesh::Union{ParallelP4estMesh{2}, ParallelT8codeMesh{2}}, - equations, - initial_condition, dg::DGSEM, cache, cache_analysis) - @unpack vandermonde, weights = analyzer - @unpack node_coordinates, inverse_jacobian = cache.elements - @unpack u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1 = cache_analysis - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) - linf_error = copy(l2_error) - volume = zero(real(mesh)) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Interpolate solution and node locations to analysis nodes - multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1) - multiply_dimensionwise!(x_local, vandermonde, - view(node_coordinates, :, :, :, element), x_tmp1) - multiply_scalar_dimensionwise!(jacobian_local, vandermonde, - inv.(view(inverse_jacobian, :, :, element)), - jacobian_tmp1) - - # Calculate errors at each analysis node - @. jacobian_local = abs(jacobian_local) - - for j in eachnode(analyzer), i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j), - t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u_local, equations, dg, i, j), equations) - l2_error += diff .^ 2 * (weights[i] * weights[j] * jacobian_local[i, j]) - linf_error = @. max(linf_error, abs(diff)) - volume += weights[i] * weights[j] * jacobian_local[i, j] + function calc_error_norms( + func, u, t, analyzer, + mesh::Union{ParallelP4estMesh{2}, ParallelT8codeMesh{2}}, + equations, + initial_condition, dg::DGSEM, cache, cache_analysis + ) + @unpack vandermonde, weights = analyzer + @unpack node_coordinates, inverse_jacobian = cache.elements + @unpack u_local, u_tmp1, x_local, x_tmp1, jacobian_local, jacobian_tmp1 = cache_analysis + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) + linf_error = copy(l2_error) + volume = zero(real(mesh)) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Interpolate solution and node locations to analysis nodes + multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, element), u_tmp1) + multiply_dimensionwise!( + x_local, vandermonde, + view(node_coordinates, :, :, :, element), x_tmp1 + ) + multiply_scalar_dimensionwise!( + jacobian_local, vandermonde, + inv.(view(inverse_jacobian, :, :, element)), + jacobian_tmp1 + ) + + # Calculate errors at each analysis node + @. jacobian_local = abs(jacobian_local) + + for j in eachnode(analyzer), i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords(x_local, equations, dg, i, j), + t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u_local, equations, dg, i, j), equations) + l2_error += diff .^ 2 * (weights[i] * weights[j] * jacobian_local[i, j]) + linf_error = @. max(linf_error, abs(diff)) + volume += weights[i] * weights[j] * jacobian_local[i, j] + end end - end - # Accumulate local results on root process - global_l2_error = Vector(l2_error) - global_linf_error = Vector(linf_error) - MPI.Reduce!(global_l2_error, +, mpi_root(), mpi_comm()) - MPI.Reduce!(global_linf_error, max, mpi_root(), mpi_comm()) - total_volume = MPI.Reduce(volume, +, mpi_root(), mpi_comm()) - if mpi_isroot() - l2_error = convert(typeof(l2_error), global_l2_error) - linf_error = convert(typeof(linf_error), global_linf_error) - # For L2 error, divide by total volume - l2_error = @. sqrt(l2_error / total_volume) - else - l2_error = convert(typeof(l2_error), NaN * global_l2_error) - linf_error = convert(typeof(linf_error), NaN * global_linf_error) - end + # Accumulate local results on root process + global_l2_error = Vector(l2_error) + global_linf_error = Vector(linf_error) + MPI.Reduce!(global_l2_error, +, mpi_root(), mpi_comm()) + MPI.Reduce!(global_linf_error, max, mpi_root(), mpi_comm()) + total_volume = MPI.Reduce(volume, +, mpi_root(), mpi_comm()) + if mpi_isroot() + l2_error = convert(typeof(l2_error), global_l2_error) + linf_error = convert(typeof(linf_error), global_linf_error) + # For L2 error, divide by total volume + l2_error = @. sqrt(l2_error / total_volume) + else + l2_error = convert(typeof(l2_error), NaN * global_l2_error) + linf_error = convert(typeof(linf_error), NaN * global_linf_error) + end - return l2_error, linf_error -end - -function integrate_via_indices(func::Func, u, - mesh::ParallelTreeMesh{2}, equations, dg::DGSEM, cache, - args...; normalize = true) where {Func} - # call the method accepting a general `mesh::TreeMesh{2}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - local_integral = invoke(integrate_via_indices, - Tuple{typeof(func), typeof(u), TreeMesh{2}, - typeof(equations), - typeof(dg), typeof(cache), map(typeof, args)...}, - func, u, mesh, equations, dg, cache, args..., - normalize = normalize) - - # OBS! Global results are only calculated on MPI root, all other domains receive `nothing` - global_integral = MPI.Reduce!(Ref(local_integral), +, mpi_root(), mpi_comm()) - if mpi_isroot() - integral = convert(typeof(local_integral), global_integral[]) - else - integral = convert(typeof(local_integral), NaN * local_integral) + return l2_error, linf_error end - return integral -end - -function integrate_via_indices(func::Func, u, - mesh::Union{ParallelP4estMesh{2}, ParallelT8codeMesh{2}}, - equations, - dg::DGSEM, cache, args...; normalize = true) where {Func} - @unpack weights = dg.basis - - # Initialize integral with zeros of the right shape - # Pass `zero(SVector{nvariables(equations), eltype(u))}` to `func` since `u` might be empty, if the - # current rank has no elements, see also https://github.com/trixi-framework/Trixi.jl/issues/1096. - integral = zero(func(zero(SVector{nvariables(equations), eltype(u)}), 1, 1, 1, - equations, dg, args...)) - volume = zero(real(mesh)) - - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element])) - integral += volume_jacobian * weights[i] * weights[j] * - func(u, i, j, element, equations, dg, args...) - volume += volume_jacobian * weights[i] * weights[j] + function integrate_via_indices( + func::Func, u, + mesh::ParallelTreeMesh{2}, equations, dg::DGSEM, cache, + args...; normalize = true + ) where {Func} + # call the method accepting a general `mesh::TreeMesh{2}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + local_integral = invoke( + integrate_via_indices, + Tuple{ + typeof(func), typeof(u), TreeMesh{2}, + typeof(equations), + typeof(dg), typeof(cache), map(typeof, args)..., + }, + func, u, mesh, equations, dg, cache, args..., + normalize = normalize + ) + + # OBS! Global results are only calculated on MPI root, all other domains receive `nothing` + global_integral = MPI.Reduce!(Ref(local_integral), +, mpi_root(), mpi_comm()) + if mpi_isroot() + integral = convert(typeof(local_integral), global_integral[]) + else + integral = convert(typeof(local_integral), NaN * local_integral) end - end - global_integral = MPI.Reduce!(Ref(integral), +, mpi_root(), mpi_comm()) - total_volume = MPI.Reduce(volume, +, mpi_root(), mpi_comm()) - if mpi_isroot() - integral = convert(typeof(integral), global_integral[]) - else - integral = convert(typeof(integral), NaN * integral) - total_volume = volume # non-root processes receive nothing from reduce -> overwrite + return integral end - # Normalize with total volume - if normalize - integral = integral / total_volume - end + function integrate_via_indices( + func::Func, u, + mesh::Union{ParallelP4estMesh{2}, ParallelT8codeMesh{2}}, + equations, + dg::DGSEM, cache, args...; normalize = true + ) where {Func} + @unpack weights = dg.basis + + # Initialize integral with zeros of the right shape + # Pass `zero(SVector{nvariables(equations), eltype(u))}` to `func` since `u` might be empty, if the + # current rank has no elements, see also https://github.com/trixi-framework/Trixi.jl/issues/1096. + integral = zero( + func( + zero(SVector{nvariables(equations), eltype(u)}), 1, 1, 1, + equations, dg, args... + ) + ) + volume = zero(real(mesh)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element])) + integral += volume_jacobian * weights[i] * weights[j] * + func(u, i, j, element, equations, dg, args...) + volume += volume_jacobian * weights[i] * weights[j] + end + end + + global_integral = MPI.Reduce!(Ref(integral), +, mpi_root(), mpi_comm()) + total_volume = MPI.Reduce(volume, +, mpi_root(), mpi_comm()) + if mpi_isroot() + integral = convert(typeof(integral), global_integral[]) + else + integral = convert(typeof(integral), NaN * integral) + total_volume = volume # non-root processes receive nothing from reduce -> overwrite + end - return integral -end + # Normalize with total volume + if normalize + integral = integral / total_volume + end + + return integral + end end # @muladd diff --git a/src/callbacks_step/analysis_dg3d.jl b/src/callbacks_step/analysis_dg3d.jl index 27e8a2b722..0b1f4ef221 100644 --- a/src/callbacks_step/analysis_dg3d.jl +++ b/src/callbacks_step/analysis_dg3d.jl @@ -3,373 +3,507 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function create_cache_analysis(analyzer, mesh::TreeMesh{3}, - equations, dg::DG, cache, - RealT, uEltype) - - # pre-allocate buffers - # We use `StrideArray`s here since these buffers are used in performance-critical - # places and the additional information passed to the compiler makes them faster - # than native `Array`s. - u_local = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer))) - u_tmp1 = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(dg)), StaticInt(nnodes(dg))) - u_tmp2 = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg))) - x_local = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer))) - x_tmp1 = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(dg)), StaticInt(nnodes(dg))) - x_tmp2 = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg))) - - return (; u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2) -end - -function create_cache_analysis(analyzer, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - equations, dg::DG, cache, - RealT, uEltype) - - # pre-allocate buffers - # We use `StrideArray`s here since these buffers are used in performance-critical - # places and the additional information passed to the compiler makes them faster - # than native `Array`s. - u_local = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer))) - u_tmp1 = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(dg)), StaticInt(nnodes(dg))) - u_tmp2 = StrideArray(undef, uEltype, - StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg))) - x_local = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer))) - x_tmp1 = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(dg)), StaticInt(nnodes(dg))) - x_tmp2 = StrideArray(undef, RealT, - StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg))) - jacobian_local = StrideArray(undef, RealT, - StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer))) - jacobian_tmp1 = StrideArray(undef, RealT, - StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)), - StaticInt(nnodes(dg))) - jacobian_tmp2 = StrideArray(undef, RealT, - StaticInt(nnodes(analyzer)), - StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg))) - - return (; u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2, jacobian_local, - jacobian_tmp1, jacobian_tmp2) -end - -function calc_error_norms(func, u, t, analyzer, - mesh::TreeMesh{3}, equations, initial_condition, - dg::DGSEM, cache, cache_analysis) - @unpack vandermonde, weights = analyzer - @unpack node_coordinates = cache.elements - @unpack u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2 = cache_analysis - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations)) - linf_error = copy(l2_error) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Interpolate solution and node locations to analysis nodes - multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, :, element), - u_tmp1, u_tmp2) - multiply_dimensionwise!(x_local, vandermonde, - view(node_coordinates, :, :, :, :, element), x_tmp1, - x_tmp2) - - # Calculate errors at each analysis node - volume_jacobian_ = volume_jacobian(element, mesh, cache) - - for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j, - k), t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u_local, equations, dg, i, j, k), equations) - l2_error += diff .^ 2 * - (weights[i] * weights[j] * weights[k] * volume_jacobian_) - linf_error = @. max(linf_error, abs(diff)) - end + #! format: noindent + + function create_cache_analysis( + analyzer, mesh::TreeMesh{3}, + equations, dg::DG, cache, + RealT, uEltype + ) + + # pre-allocate buffers + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + u_local = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer)) + ) + u_tmp1 = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(dg)), StaticInt(nnodes(dg)) + ) + u_tmp2 = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)) + ) + x_local = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer)) + ) + x_tmp1 = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(dg)), StaticInt(nnodes(dg)) + ) + x_tmp2 = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)) + ) + + return (; u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2) + end + + function create_cache_analysis( + analyzer, + mesh::Union{ + StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + equations, dg::DG, cache, + RealT, uEltype + ) + + # pre-allocate buffers + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + u_local = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer)) + ) + u_tmp1 = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(dg)), StaticInt(nnodes(dg)) + ) + u_tmp2 = StrideArray( + undef, uEltype, + StaticInt(nvariables(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)) + ) + x_local = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), StaticInt(nnodes(analyzer)) + ) + x_tmp1 = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(dg)), StaticInt(nnodes(dg)) + ) + x_tmp2 = StrideArray( + undef, RealT, + StaticInt(ndims(equations)), StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)) + ) + jacobian_local = StrideArray( + undef, RealT, + StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)) + ) + jacobian_tmp1 = StrideArray( + undef, RealT, + StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)) + ) + jacobian_tmp2 = StrideArray( + undef, RealT, + StaticInt(nnodes(analyzer)), + StaticInt(nnodes(analyzer)), StaticInt(nnodes(dg)) + ) + + return (; + u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2, jacobian_local, + jacobian_tmp1, jacobian_tmp2, + ) end - # For L2 error, divide by total volume - total_volume_ = total_volume(mesh) - l2_error = @. sqrt(l2_error / total_volume_) - - return l2_error, linf_error -end - -function calc_error_norms(func, u, t, analyzer, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, - equations, initial_condition, - dg::DGSEM, cache, cache_analysis) - @unpack vandermonde, weights = analyzer - @unpack node_coordinates, inverse_jacobian = cache.elements - @unpack u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2, jacobian_local, jacobian_tmp1, jacobian_tmp2 = cache_analysis - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations)) - linf_error = copy(l2_error) - total_volume = zero(real(mesh)) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Interpolate solution and node locations to analysis nodes - multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, :, element), - u_tmp1, u_tmp2) - multiply_dimensionwise!(x_local, vandermonde, - view(node_coordinates, :, :, :, :, element), x_tmp1, - x_tmp2) - multiply_scalar_dimensionwise!(jacobian_local, vandermonde, - inv.(view(inverse_jacobian, :, :, :, element)), - jacobian_tmp1, jacobian_tmp2) - - # Calculate errors at each analysis node - @. jacobian_local = abs(jacobian_local) - - for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j, - k), t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u_local, equations, dg, i, j, k), equations) - l2_error += diff .^ 2 * - (weights[i] * weights[j] * weights[k] * jacobian_local[i, j, k]) - linf_error = @. max(linf_error, abs(diff)) - total_volume += weights[i] * weights[j] * weights[k] * - jacobian_local[i, j, k] + function calc_error_norms( + func, u, t, analyzer, + mesh::TreeMesh{3}, equations, initial_condition, + dg::DGSEM, cache, cache_analysis + ) + @unpack vandermonde, weights = analyzer + @unpack node_coordinates = cache.elements + @unpack u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2 = cache_analysis + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations)) + linf_error = copy(l2_error) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Interpolate solution and node locations to analysis nodes + multiply_dimensionwise!( + u_local, vandermonde, view(u, :, :, :, :, element), + u_tmp1, u_tmp2 + ) + multiply_dimensionwise!( + x_local, vandermonde, + view(node_coordinates, :, :, :, :, element), x_tmp1, + x_tmp2 + ) + + # Calculate errors at each analysis node + volume_jacobian_ = volume_jacobian(element, mesh, cache) + + for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords( + x_local, equations, dg, i, j, + k + ), t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u_local, equations, dg, i, j, k), equations) + l2_error += diff .^ 2 * + (weights[i] * weights[j] * weights[k] * volume_jacobian_) + linf_error = @. max(linf_error, abs(diff)) + end end + + # For L2 error, divide by total volume + total_volume_ = total_volume(mesh) + l2_error = @. sqrt(l2_error / total_volume_) + + return l2_error, linf_error end - # For L2 error, divide by total volume - l2_error = @. sqrt(l2_error / total_volume) + function calc_error_norms( + func, u, t, analyzer, + mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, + equations, initial_condition, + dg::DGSEM, cache, cache_analysis + ) + @unpack vandermonde, weights = analyzer + @unpack node_coordinates, inverse_jacobian = cache.elements + @unpack u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2, jacobian_local, jacobian_tmp1, jacobian_tmp2 = cache_analysis + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations)) + linf_error = copy(l2_error) + total_volume = zero(real(mesh)) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Interpolate solution and node locations to analysis nodes + multiply_dimensionwise!( + u_local, vandermonde, view(u, :, :, :, :, element), + u_tmp1, u_tmp2 + ) + multiply_dimensionwise!( + x_local, vandermonde, + view(node_coordinates, :, :, :, :, element), x_tmp1, + x_tmp2 + ) + multiply_scalar_dimensionwise!( + jacobian_local, vandermonde, + inv.(view(inverse_jacobian, :, :, :, element)), + jacobian_tmp1, jacobian_tmp2 + ) + + # Calculate errors at each analysis node + @. jacobian_local = abs(jacobian_local) + + for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords( + x_local, equations, dg, i, j, + k + ), t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u_local, equations, dg, i, j, k), equations) + l2_error += diff .^ 2 * + (weights[i] * weights[j] * weights[k] * jacobian_local[i, j, k]) + linf_error = @. max(linf_error, abs(diff)) + total_volume += weights[i] * weights[j] * weights[k] * + jacobian_local[i, j, k] + end + end - return l2_error, linf_error -end + # For L2 error, divide by total volume + l2_error = @. sqrt(l2_error / total_volume) -function integrate_via_indices(func::Func, u, - mesh::TreeMesh{3}, equations, dg::DGSEM, cache, - args...; normalize = true) where {Func} - @unpack weights = dg.basis + return l2_error, linf_error + end - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...)) + function integrate_via_indices( + func::Func, u, + mesh::TreeMesh{3}, equations, dg::DGSEM, cache, + args...; normalize = true + ) where {Func} + @unpack weights = dg.basis + + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + volume_jacobian_ = volume_jacobian(element, mesh, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + integral += volume_jacobian_ * weights[i] * weights[j] * weights[k] * + func(u, i, j, k, element, equations, dg, args...) + end + end - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - volume_jacobian_ = volume_jacobian(element, mesh, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - integral += volume_jacobian_ * weights[i] * weights[j] * weights[k] * - func(u, i, j, k, element, equations, dg, args...) + # Normalize with total volume + if normalize + integral = integral / total_volume(mesh) end - end - # Normalize with total volume - if normalize - integral = integral / total_volume(mesh) + return integral end - return integral -end - -function integrate_via_indices(func::Func, u, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - equations, dg::DGSEM, cache, - args...; normalize = true) where {Func} - @unpack weights = dg.basis - - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...)) - total_volume = zero(real(mesh)) - - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, k, element])) - integral += volume_jacobian * weights[i] * weights[j] * weights[k] * - func(u, i, j, k, element, equations, dg, args...) - total_volume += volume_jacobian * weights[i] * weights[j] * weights[k] + function integrate_via_indices( + func::Func, u, + mesh::Union{ + StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + equations, dg::DGSEM, cache, + args...; normalize = true + ) where {Func} + @unpack weights = dg.basis + + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...)) + total_volume = zero(real(mesh)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, k, element])) + integral += volume_jacobian * weights[i] * weights[j] * weights[k] * + func(u, i, j, k, element, equations, dg, args...) + total_volume += volume_jacobian * weights[i] * weights[j] * weights[k] + end end - end - # Normalize with total volume - if normalize - integral = integral / total_volume - end + # Normalize with total volume + if normalize + integral = integral / total_volume + end - return integral -end - -function integrate(func::Func, u, - mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - equations, dg::DG, cache; normalize = true) where {Func} - integrate_via_indices(u, mesh, equations, dg, cache; - normalize = normalize) do u, i, j, k, element, equations, dg - u_local = get_node_vars(u, equations, dg, i, j, k, element) - return func(u_local, equations) + return integral end -end - -function integrate(func::Func, u, - mesh::Union{TreeMesh{3}, P4estMesh{3}}, - equations, equations_parabolic, - dg::DGSEM, - cache, cache_parabolic; normalize = true) where {Func} - gradients_x, gradients_y, gradients_z = cache_parabolic.viscous_container.gradients - integrate_via_indices(u, mesh, equations, dg, cache; - normalize = normalize) do u, i, j, k, element, equations, dg - u_local = get_node_vars(u, equations, dg, i, j, k, element) - gradients_1_local = get_node_vars(gradients_x, equations_parabolic, dg, i, j, k, - element) - gradients_2_local = get_node_vars(gradients_y, equations_parabolic, dg, i, j, k, - element) - gradients_3_local = get_node_vars(gradients_z, equations_parabolic, dg, i, j, k, - element) - return func(u_local, (gradients_1_local, gradients_2_local, gradients_3_local), - equations_parabolic) - end -end - -function analyze(::typeof(entropy_timederivative), du, u, t, - mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - equations, dg::DG, cache) - # Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ - integrate_via_indices(u, mesh, equations, dg, cache, - du) do u, i, j, k, element, equations, dg, du - u_node = get_node_vars(u, equations, dg, i, j, k, element) - du_node = get_node_vars(du, equations, dg, i, j, k, element) - dot(cons2entropy(u_node, equations), du_node) + + function integrate( + func::Func, u, + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + equations, dg::DG, cache; normalize = true + ) where {Func} + integrate_via_indices( + u, mesh, equations, dg, cache; + normalize = normalize + ) do u, i, j, k, element, equations, dg + u_local = get_node_vars(u, equations, dg, i, j, k, element) + return func(u_local, equations) + end end -end - -function analyze(::Val{:l2_divb}, du, u, t, - mesh::TreeMesh{3}, equations::IdealGlmMhdEquations3D, - dg::DGSEM, cache) - integrate_via_indices(u, mesh, equations, dg, cache, cache, - dg.basis.derivative_matrix) do u, i, j, k, element, equations, - dg, cache, derivative_matrix - divb = zero(eltype(u)) - for l in eachnode(dg) - divb += (derivative_matrix[i, l] * u[6, l, j, k, element] + - derivative_matrix[j, l] * u[7, i, l, k, element] + - derivative_matrix[k, l] * u[8, i, j, l, element]) + + function integrate( + func::Func, u, + mesh::Union{TreeMesh{3}, P4estMesh{3}}, + equations, equations_parabolic, + dg::DGSEM, + cache, cache_parabolic; normalize = true + ) where {Func} + gradients_x, gradients_y, gradients_z = cache_parabolic.viscous_container.gradients + integrate_via_indices( + u, mesh, equations, dg, cache; + normalize = normalize + ) do u, i, j, k, element, equations, dg + u_local = get_node_vars(u, equations, dg, i, j, k, element) + gradients_1_local = get_node_vars( + gradients_x, equations_parabolic, dg, i, j, k, + element + ) + gradients_2_local = get_node_vars( + gradients_y, equations_parabolic, dg, i, j, k, + element + ) + gradients_3_local = get_node_vars( + gradients_z, equations_parabolic, dg, i, j, k, + element + ) + return func( + u_local, (gradients_1_local, gradients_2_local, gradients_3_local), + equations_parabolic + ) end - divb *= cache.elements.inverse_jacobian[element] - divb^2 - end |> sqrt -end - -function analyze(::Val{:l2_divb}, du, u, t, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, - equations::IdealGlmMhdEquations3D, - dg::DGSEM, cache) - @unpack contravariant_vectors = cache.elements - integrate_via_indices(u, mesh, equations, dg, cache, cache, - dg.basis.derivative_matrix) do u, i, j, k, element, equations, - dg, cache, derivative_matrix - divb = zero(eltype(u)) - # Get the contravariant vectors Ja^1, Ja^2, and Ja^3 - Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors, i, j, k, - element) - Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors, i, j, k, - element) - Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors, i, j, k, - element) - # Compute the transformed divergence - for l in eachnode(dg) - divb += (derivative_matrix[i, l] * - (Ja11 * u[6, l, j, k, element] + Ja12 * u[7, l, j, k, element] + - Ja13 * u[8, l, j, k, element]) + - derivative_matrix[j, l] * - (Ja21 * u[6, i, l, k, element] + Ja22 * u[7, i, l, k, element] + - Ja23 * u[8, i, l, k, element]) + - derivative_matrix[k, l] * - (Ja31 * u[6, i, j, l, element] + Ja32 * u[7, i, j, l, element] + - Ja33 * u[8, i, j, l, element])) + end + + function analyze( + ::typeof(entropy_timederivative), du, u, t, + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + equations, dg::DG, cache + ) + # Calculate ∫(∂S/∂u ⋅ ∂u/∂t)dΩ + integrate_via_indices( + u, mesh, equations, dg, cache, + du + ) do u, i, j, k, element, equations, dg, du + u_node = get_node_vars(u, equations, dg, i, j, k, element) + du_node = get_node_vars(du, equations, dg, i, j, k, element) + dot(cons2entropy(u_node, equations), du_node) end - divb *= cache.elements.inverse_jacobian[i, j, k, element] - divb^2 - end |> sqrt -end - -function analyze(::Val{:linf_divb}, du, u, t, - mesh::TreeMesh{3}, equations::IdealGlmMhdEquations3D, - dg::DGSEM, cache) - @unpack derivative_matrix, weights = dg.basis - - # integrate over all elements to get the divergence-free condition errors - linf_divb = zero(eltype(u)) - for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + end + + function analyze( + ::Val{:l2_divb}, du, u, t, + mesh::TreeMesh{3}, equations::IdealGlmMhdEquations3D, + dg::DGSEM, cache + ) + integrate_via_indices( + u, mesh, equations, dg, cache, cache, + dg.basis.derivative_matrix + ) do u, i, j, k, element, equations, + dg, cache, derivative_matrix divb = zero(eltype(u)) for l in eachnode(dg) - divb += (derivative_matrix[i, l] * u[6, l, j, k, element] + - derivative_matrix[j, l] * u[7, i, l, k, element] + - derivative_matrix[k, l] * u[8, i, j, l, element]) + divb += ( + derivative_matrix[i, l] * u[6, l, j, k, element] + + derivative_matrix[j, l] * u[7, i, l, k, element] + + derivative_matrix[k, l] * u[8, i, j, l, element] + ) end divb *= cache.elements.inverse_jacobian[element] - linf_divb = max(linf_divb, abs(divb)) - end + divb^2 + end |> sqrt end - return linf_divb -end - -function analyze(::Val{:linf_divb}, du, u, t, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, - equations::IdealGlmMhdEquations3D, - dg::DGSEM, cache) - @unpack derivative_matrix, weights = dg.basis - @unpack contravariant_vectors = cache.elements - - # integrate over all elements to get the divergence-free condition errors - linf_divb = zero(eltype(u)) - for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + function analyze( + ::Val{:l2_divb}, du, u, t, + mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, + equations::IdealGlmMhdEquations3D, + dg::DGSEM, cache + ) + @unpack contravariant_vectors = cache.elements + integrate_via_indices( + u, mesh, equations, dg, cache, cache, + dg.basis.derivative_matrix + ) do u, i, j, k, element, equations, + dg, cache, derivative_matrix divb = zero(eltype(u)) # Get the contravariant vectors Ja^1, Ja^2, and Ja^3 - Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors, i, j, - k, element) - Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors, i, j, - k, element) - Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors, i, j, - k, element) + Ja11, Ja12, Ja13 = get_contravariant_vector( + 1, contravariant_vectors, i, j, k, + element + ) + Ja21, Ja22, Ja23 = get_contravariant_vector( + 2, contravariant_vectors, i, j, k, + element + ) + Ja31, Ja32, Ja33 = get_contravariant_vector( + 3, contravariant_vectors, i, j, k, + element + ) # Compute the transformed divergence for l in eachnode(dg) - divb += (derivative_matrix[i, l] * (Ja11 * u[6, l, j, k, element] + - Ja12 * u[7, l, j, k, element] + Ja13 * u[8, l, j, k, element]) + - derivative_matrix[j, l] * (Ja21 * u[6, i, l, k, element] + - Ja22 * u[7, i, l, k, element] + Ja23 * u[8, i, l, k, element]) + - derivative_matrix[k, l] * (Ja31 * u[6, i, j, l, element] + - Ja32 * u[7, i, j, l, element] + Ja33 * u[8, i, j, l, element])) + divb += ( + derivative_matrix[i, l] * + ( + Ja11 * u[6, l, j, k, element] + Ja12 * u[7, l, j, k, element] + + Ja13 * u[8, l, j, k, element] + ) + + derivative_matrix[j, l] * + ( + Ja21 * u[6, i, l, k, element] + Ja22 * u[7, i, l, k, element] + + Ja23 * u[8, i, l, k, element] + ) + + derivative_matrix[k, l] * + ( + Ja31 * u[6, i, j, l, element] + Ja32 * u[7, i, j, l, element] + + Ja33 * u[8, i, j, l, element] + ) + ) end divb *= cache.elements.inverse_jacobian[i, j, k, element] - linf_divb = max(linf_divb, abs(divb)) + divb^2 + end |> sqrt + end + + function analyze( + ::Val{:linf_divb}, du, u, t, + mesh::TreeMesh{3}, equations::IdealGlmMhdEquations3D, + dg::DGSEM, cache + ) + @unpack derivative_matrix, weights = dg.basis + + # integrate over all elements to get the divergence-free condition errors + linf_divb = zero(eltype(u)) + for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + divb = zero(eltype(u)) + for l in eachnode(dg) + divb += ( + derivative_matrix[i, l] * u[6, l, j, k, element] + + derivative_matrix[j, l] * u[7, i, l, k, element] + + derivative_matrix[k, l] * u[8, i, j, l, element] + ) + end + divb *= cache.elements.inverse_jacobian[element] + linf_divb = max(linf_divb, abs(divb)) + end end + + return linf_divb end - return linf_divb -end + function analyze( + ::Val{:linf_divb}, du, u, t, + mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, + equations::IdealGlmMhdEquations3D, + dg::DGSEM, cache + ) + @unpack derivative_matrix, weights = dg.basis + @unpack contravariant_vectors = cache.elements + + # integrate over all elements to get the divergence-free condition errors + linf_divb = zero(eltype(u)) + for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + divb = zero(eltype(u)) + # Get the contravariant vectors Ja^1, Ja^2, and Ja^3 + Ja11, Ja12, Ja13 = get_contravariant_vector( + 1, contravariant_vectors, i, j, + k, element + ) + Ja21, Ja22, Ja23 = get_contravariant_vector( + 2, contravariant_vectors, i, j, + k, element + ) + Ja31, Ja32, Ja33 = get_contravariant_vector( + 3, contravariant_vectors, i, j, + k, element + ) + # Compute the transformed divergence + for l in eachnode(dg) + divb += ( + derivative_matrix[i, l] * ( + Ja11 * u[6, l, j, k, element] + + Ja12 * u[7, l, j, k, element] + Ja13 * u[8, l, j, k, element] + ) + + derivative_matrix[j, l] * ( + Ja21 * u[6, i, l, k, element] + + Ja22 * u[7, i, l, k, element] + Ja23 * u[8, i, l, k, element] + ) + + derivative_matrix[k, l] * ( + Ja31 * u[6, i, j, l, element] + + Ja32 * u[7, i, j, l, element] + Ja33 * u[8, i, j, l, element] + ) + ) + end + divb *= cache.elements.inverse_jacobian[i, j, k, element] + linf_divb = max(linf_divb, abs(divb)) + end + end + + return linf_divb + end end # @muladd diff --git a/src/callbacks_step/analysis_dg3d_parallel.jl b/src/callbacks_step/analysis_dg3d_parallel.jl index de777be406..6427fe131c 100644 --- a/src/callbacks_step/analysis_dg3d_parallel.jl +++ b/src/callbacks_step/analysis_dg3d_parallel.jl @@ -3,104 +3,122 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function calc_error_norms(func, u, t, analyzer, - mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, - equations, - initial_condition, dg::DGSEM, cache, cache_analysis) - @unpack vandermonde, weights = analyzer - @unpack node_coordinates, inverse_jacobian = cache.elements - @unpack u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2, jacobian_local, jacobian_tmp1, jacobian_tmp2 = cache_analysis + function calc_error_norms( + func, u, t, analyzer, + mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, + equations, + initial_condition, dg::DGSEM, cache, cache_analysis + ) + @unpack vandermonde, weights = analyzer + @unpack node_coordinates, inverse_jacobian = cache.elements + @unpack u_local, u_tmp1, u_tmp2, x_local, x_tmp1, x_tmp2, jacobian_local, jacobian_tmp1, jacobian_tmp2 = cache_analysis - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations)) - linf_error = copy(l2_error) - volume = zero(real(mesh)) + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations)) + linf_error = copy(l2_error) + volume = zero(real(mesh)) - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Interpolate solution and node locations to analysis nodes - multiply_dimensionwise!(u_local, vandermonde, view(u, :, :, :, :, element), - u_tmp1, u_tmp2) - multiply_dimensionwise!(x_local, vandermonde, - view(node_coordinates, :, :, :, :, element), x_tmp1, - x_tmp2) - multiply_scalar_dimensionwise!(jacobian_local, vandermonde, - inv.(view(inverse_jacobian, :, :, :, element)), - jacobian_tmp1, jacobian_tmp2) + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Interpolate solution and node locations to analysis nodes + multiply_dimensionwise!( + u_local, vandermonde, view(u, :, :, :, :, element), + u_tmp1, u_tmp2 + ) + multiply_dimensionwise!( + x_local, vandermonde, + view(node_coordinates, :, :, :, :, element), x_tmp1, + x_tmp2 + ) + multiply_scalar_dimensionwise!( + jacobian_local, vandermonde, + inv.(view(inverse_jacobian, :, :, :, element)), + jacobian_tmp1, jacobian_tmp2 + ) - # Calculate errors at each analysis node - @. jacobian_local = abs(jacobian_local) + # Calculate errors at each analysis node + @. jacobian_local = abs(jacobian_local) - for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(x_local, equations, dg, i, j, - k), t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u_local, equations, dg, i, j, k), equations) - l2_error += diff .^ 2 * - (weights[i] * weights[j] * weights[k] * jacobian_local[i, j, k]) - linf_error = @. max(linf_error, abs(diff)) - volume += weights[i] * weights[j] * weights[k] * jacobian_local[i, j, k] + for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords( + x_local, equations, dg, i, j, + k + ), t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u_local, equations, dg, i, j, k), equations) + l2_error += diff .^ 2 * + (weights[i] * weights[j] * weights[k] * jacobian_local[i, j, k]) + linf_error = @. max(linf_error, abs(diff)) + volume += weights[i] * weights[j] * weights[k] * jacobian_local[i, j, k] + end + end + + # Accumulate local results on root process + global_l2_error = Vector(l2_error) + global_linf_error = Vector(linf_error) + MPI.Reduce!(global_l2_error, +, mpi_root(), mpi_comm()) + MPI.Reduce!(global_linf_error, max, mpi_root(), mpi_comm()) + total_volume = MPI.Reduce(volume, +, mpi_root(), mpi_comm()) + if mpi_isroot() + l2_error = convert(typeof(l2_error), global_l2_error) + linf_error = convert(typeof(linf_error), global_linf_error) + # For L2 error, divide by total volume + l2_error = @. sqrt(l2_error / total_volume) + else + l2_error = convert(typeof(l2_error), NaN * global_l2_error) + linf_error = convert(typeof(linf_error), NaN * global_linf_error) end - end - # Accumulate local results on root process - global_l2_error = Vector(l2_error) - global_linf_error = Vector(linf_error) - MPI.Reduce!(global_l2_error, +, mpi_root(), mpi_comm()) - MPI.Reduce!(global_linf_error, max, mpi_root(), mpi_comm()) - total_volume = MPI.Reduce(volume, +, mpi_root(), mpi_comm()) - if mpi_isroot() - l2_error = convert(typeof(l2_error), global_l2_error) - linf_error = convert(typeof(linf_error), global_linf_error) - # For L2 error, divide by total volume - l2_error = @. sqrt(l2_error / total_volume) - else - l2_error = convert(typeof(l2_error), NaN * global_l2_error) - linf_error = convert(typeof(linf_error), NaN * global_linf_error) + return l2_error, linf_error end - return l2_error, linf_error -end + function integrate_via_indices( + func::Func, u, + mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, + equations, + dg::DGSEM, cache, args...; normalize = true + ) where {Func} + @unpack weights = dg.basis -function integrate_via_indices(func::Func, u, - mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, - equations, - dg::DGSEM, cache, args...; normalize = true) where {Func} - @unpack weights = dg.basis + # Initialize integral with zeros of the right shape + # Pass `zero(SVector{nvariables(equations), eltype(u))}` to `func` since `u` might be empty, if the + # current rank has no elements, see also https://github.com/trixi-framework/Trixi.jl/issues/1096. + integral = zero( + func( + zero(SVector{nvariables(equations), eltype(u)}), 1, 1, 1, 1, + equations, dg, args... + ) + ) + volume = zero(real(mesh)) - # Initialize integral with zeros of the right shape - # Pass `zero(SVector{nvariables(equations), eltype(u))}` to `func` since `u` might be empty, if the - # current rank has no elements, see also https://github.com/trixi-framework/Trixi.jl/issues/1096. - integral = zero(func(zero(SVector{nvariables(equations), eltype(u)}), 1, 1, 1, 1, - equations, dg, args...)) - volume = zero(real(mesh)) + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, k, element])) + integral += volume_jacobian * weights[i] * weights[j] * weights[k] * + func(u, i, j, k, element, equations, dg, args...) + volume += volume_jacobian * weights[i] * weights[j] * weights[k] + end + end - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, k, element])) - integral += volume_jacobian * weights[i] * weights[j] * weights[k] * - func(u, i, j, k, element, equations, dg, args...) - volume += volume_jacobian * weights[i] * weights[j] * weights[k] + global_integral = MPI.Reduce!(Ref(integral), +, mpi_root(), mpi_comm()) + total_volume = MPI.Reduce(volume, +, mpi_root(), mpi_comm()) + if mpi_isroot() + integral = convert(typeof(integral), global_integral[]) + else + integral = convert(typeof(integral), NaN * integral) + total_volume = volume # non-root processes receive nothing from reduce -> overwrite end - end - global_integral = MPI.Reduce!(Ref(integral), +, mpi_root(), mpi_comm()) - total_volume = MPI.Reduce(volume, +, mpi_root(), mpi_comm()) - if mpi_isroot() - integral = convert(typeof(integral), global_integral[]) - else - integral = convert(typeof(integral), NaN * integral) - total_volume = volume # non-root processes receive nothing from reduce -> overwrite - end + # Normalize with total volume + if normalize + integral = integral / total_volume + end - # Normalize with total volume - if normalize - integral = integral / total_volume + return integral end - - return integral -end end # @muladd diff --git a/src/callbacks_step/analysis_dgmulti.jl b/src/callbacks_step/analysis_dgmulti.jl index 1f0eec2de3..5b2bc88f42 100644 --- a/src/callbacks_step/analysis_dgmulti.jl +++ b/src/callbacks_step/analysis_dgmulti.jl @@ -3,200 +3,228 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function calc_error_norms(func, u, t, analyzer, - mesh::DGMultiMesh{NDIMS}, equations, initial_condition, - dg::DGMulti{NDIMS}, cache, cache_analysis) where {NDIMS} - rd = dg.basis - md = mesh.md - @unpack u_values = cache - - # interpolate u to quadrature points - apply_to_each_field(mul_by!(rd.Vq), u_values, u) - - component_l2_errors = zero(eltype(u_values)) - component_linf_errors = zero(eltype(u_values)) - for i in each_quad_node_global(mesh, dg, cache) - u_exact = initial_condition(SVector(getindex.(md.xyzq, i)), t, equations) - error_at_node = func(u_values[i], equations) - func(u_exact, equations) - component_l2_errors += md.wJq[i] * error_at_node .^ 2 - component_linf_errors = max.(component_linf_errors, abs.(error_at_node)) - end - total_volume = sum(md.wJq) - return sqrt.(component_l2_errors ./ total_volume), component_linf_errors -end - -function integrate(func::Func, u, - mesh::DGMultiMesh, - equations, dg::DGMulti, cache; normalize = true) where {Func} - rd = dg.basis - md = mesh.md - @unpack u_values = cache - - # interpolate u to quadrature points - apply_to_each_field(mul_by!(rd.Vq), u_values, u) - - integral = sum(md.wJq .* func.(u_values, equations)) - if normalize == true - integral /= sum(md.wJq) + #! format: noindent + + function calc_error_norms( + func, u, t, analyzer, + mesh::DGMultiMesh{NDIMS}, equations, initial_condition, + dg::DGMulti{NDIMS}, cache, cache_analysis + ) where {NDIMS} + rd = dg.basis + md = mesh.md + @unpack u_values = cache + + # interpolate u to quadrature points + apply_to_each_field(mul_by!(rd.Vq), u_values, u) + + component_l2_errors = zero(eltype(u_values)) + component_linf_errors = zero(eltype(u_values)) + for i in each_quad_node_global(mesh, dg, cache) + u_exact = initial_condition(SVector(getindex.(md.xyzq, i)), t, equations) + error_at_node = func(u_values[i], equations) - func(u_exact, equations) + component_l2_errors += md.wJq[i] * error_at_node .^ 2 + component_linf_errors = max.(component_linf_errors, abs.(error_at_node)) + end + total_volume = sum(md.wJq) + return sqrt.(component_l2_errors ./ total_volume), component_linf_errors end - return integral -end - -function analyze(::typeof(entropy_timederivative), du, u, t, - mesh::DGMultiMesh, equations, dg::DGMulti, cache) - rd = dg.basis - md = mesh.md - @unpack u_values = cache - - # interpolate u, du to quadrature points - du_values = similar(u_values) # Todo: DGMulti. Can we move this to the analysis cache somehow? - apply_to_each_field(mul_by!(rd.Vq), du_values, du) - apply_to_each_field(mul_by!(rd.Vq), u_values, u) - - # compute ∫v(u) * du/dt = ∫dS/dt. We can directly compute v(u) instead of computing the entropy - # projection here, since the RHS will be projected to polynomials of degree N and testing with - # the L2 projection of v(u) would be equivalent to testing with v(u) due to the moment-preserving - # property of the L2 projection. - dS_dt = zero(eltype(first(du))) - for i in Base.OneTo(length(md.wJq)) - dS_dt += dot(cons2entropy(u_values[i], equations), du_values[i]) * md.wJq[i] + + function integrate( + func::Func, u, + mesh::DGMultiMesh, + equations, dg::DGMulti, cache; normalize = true + ) where {Func} + rd = dg.basis + md = mesh.md + @unpack u_values = cache + + # interpolate u to quadrature points + apply_to_each_field(mul_by!(rd.Vq), u_values, u) + + integral = sum(md.wJq .* func.(u_values, equations)) + if normalize == true + integral /= sum(md.wJq) + end + return integral end - return dS_dt -end - -# This function is used in `analyze(::Val{:l2_divb},...)` and `analyze(::Val{:linf_divb},...)` -function compute_local_divergence!(local_divergence, element, vector_field, - mesh, dg::DGMulti, cache) - @unpack md = mesh - rd = dg.basis - uEltype = eltype(first(vector_field)) - - fill!(local_divergence, zero(uEltype)) - - # computes dU_i/dx_i = ∑_j dxhat_j/dx_i * dU_i / dxhat_j - # dU_i/dx_i is then accumulated into local_divergence. - # TODO: DGMulti. Extend to curved elements. - for i in eachdim(mesh) - for j in eachdim(mesh) - geometric_scaling = md.rstxyzJ[i, j][1, element] - jth_ref_derivative_matrix = rd.Drst[j] - mul!(local_divergence, jth_ref_derivative_matrix, vector_field[i], - geometric_scaling, one(uEltype)) + + function analyze( + ::typeof(entropy_timederivative), du, u, t, + mesh::DGMultiMesh, equations, dg::DGMulti, cache + ) + rd = dg.basis + md = mesh.md + @unpack u_values = cache + + # interpolate u, du to quadrature points + du_values = similar(u_values) # Todo: DGMulti. Can we move this to the analysis cache somehow? + apply_to_each_field(mul_by!(rd.Vq), du_values, du) + apply_to_each_field(mul_by!(rd.Vq), u_values, u) + + # compute ∫v(u) * du/dt = ∫dS/dt. We can directly compute v(u) instead of computing the entropy + # projection here, since the RHS will be projected to polynomials of degree N and testing with + # the L2 projection of v(u) would be equivalent to testing with v(u) due to the moment-preserving + # property of the L2 projection. + dS_dt = zero(eltype(first(du))) + for i in Base.OneTo(length(md.wJq)) + dS_dt += dot(cons2entropy(u_values[i], equations), du_values[i]) * md.wJq[i] end + return dS_dt end -end - -get_component(u::StructArray, i::Int) = StructArrays.component(u, i) -get_component(u::AbstractArray{<:SVector}, i::Int) = getindex.(u, i) - -function analyze(::Val{:l2_divb}, du, u, t, - mesh::DGMultiMesh, equations::IdealGlmMhdEquations2D, - dg::DGMulti, cache) - @unpack md = mesh - rd = dg.basis - B1 = get_component(u, 6) - B2 = get_component(u, 7) - B = (B1, B2) - - uEltype = eltype(B1) - l2norm_divB = zero(uEltype) - local_divB = zeros(uEltype, size(B1, 1)) - for e in eachelement(mesh, dg, cache) - compute_local_divergence!(local_divB, e, view.(B, :, e), mesh, dg, cache) + # This function is used in `analyze(::Val{:l2_divb},...)` and `analyze(::Val{:linf_divb},...)` + function compute_local_divergence!( + local_divergence, element, vector_field, + mesh, dg::DGMulti, cache + ) + @unpack md = mesh + rd = dg.basis + uEltype = eltype(first(vector_field)) + + fill!(local_divergence, zero(uEltype)) + + # computes dU_i/dx_i = ∑_j dxhat_j/dx_i * dU_i / dxhat_j + # dU_i/dx_i is then accumulated into local_divergence. # TODO: DGMulti. Extend to curved elements. - # compute L2 norm squared via J[1, e] * u' * M * u - local_l2norm_divB = md.J[1, e] * dot(local_divB, rd.M, local_divB) - l2norm_divB += local_l2norm_divB + for i in eachdim(mesh) + for j in eachdim(mesh) + geometric_scaling = md.rstxyzJ[i, j][1, element] + jth_ref_derivative_matrix = rd.Drst[j] + mul!( + local_divergence, jth_ref_derivative_matrix, vector_field[i], + geometric_scaling, one(uEltype) + ) + end + end end - return sqrt(l2norm_divB) -end + get_component(u::StructArray, i::Int) = StructArrays.component(u, i) + get_component(u::AbstractArray{<:SVector}, i::Int) = getindex.(u, i) + + function analyze( + ::Val{:l2_divb}, du, u, t, + mesh::DGMultiMesh, equations::IdealGlmMhdEquations2D, + dg::DGMulti, cache + ) + @unpack md = mesh + rd = dg.basis + B1 = get_component(u, 6) + B2 = get_component(u, 7) + B = (B1, B2) + + uEltype = eltype(B1) + l2norm_divB = zero(uEltype) + local_divB = zeros(uEltype, size(B1, 1)) + for e in eachelement(mesh, dg, cache) + compute_local_divergence!(local_divB, e, view.(B, :, e), mesh, dg, cache) + + # TODO: DGMulti. Extend to curved elements. + # compute L2 norm squared via J[1, e] * u' * M * u + local_l2norm_divB = md.J[1, e] * dot(local_divB, rd.M, local_divB) + l2norm_divB += local_l2norm_divB + end -function analyze(::Val{:linf_divb}, du, u, t, - mesh::DGMultiMesh, equations::IdealGlmMhdEquations2D, - dg::DGMulti, cache) - B1 = get_component(u, 6) - B2 = get_component(u, 7) - B = (B1, B2) + return sqrt(l2norm_divB) + end - uEltype = eltype(B1) - linf_divB = zero(uEltype) - local_divB = zeros(uEltype, size(B1, 1)) - for e in eachelement(mesh, dg, cache) - compute_local_divergence!(local_divB, e, view.(B, :, e), mesh, dg, cache) + function analyze( + ::Val{:linf_divb}, du, u, t, + mesh::DGMultiMesh, equations::IdealGlmMhdEquations2D, + dg::DGMulti, cache + ) + B1 = get_component(u, 6) + B2 = get_component(u, 7) + B = (B1, B2) + + uEltype = eltype(B1) + linf_divB = zero(uEltype) + local_divB = zeros(uEltype, size(B1, 1)) + for e in eachelement(mesh, dg, cache) + compute_local_divergence!(local_divB, e, view.(B, :, e), mesh, dg, cache) + + # compute maximum norm + linf_divB = max(linf_divB, maximum(abs, local_divB)) + end - # compute maximum norm - linf_divB = max(linf_divB, maximum(abs, local_divB)) + return linf_divB end - return linf_divB -end - -function integrate(func::typeof(enstrophy), u, - mesh::DGMultiMesh, - equations, equations_parabolic::CompressibleNavierStokesDiffusion3D, - dg::DGMulti, - cache, cache_parabolic; normalize = true) - gradients_x, gradients_y, gradients_z = cache_parabolic.gradients - - # allocate local storage for gradients. - # TODO: can we avoid allocating here? - local_gradient_quadrature_values = ntuple(_ -> similar(cache_parabolic.local_u_values_threaded), - 3) - - integral = zero(eltype(u)) - for e in eachelement(mesh, dg) - u_quadrature_values = cache_parabolic.local_u_values_threaded[Threads.threadid()] - gradient_x_quadrature_values = local_gradient_quadrature_values[1][Threads.threadid()] - gradient_y_quadrature_values = local_gradient_quadrature_values[2][Threads.threadid()] - gradient_z_quadrature_values = local_gradient_quadrature_values[3][Threads.threadid()] - - # interpolate to quadrature on each element - apply_to_each_field(mul_by!(dg.basis.Vq), u_quadrature_values, view(u, :, e)) - apply_to_each_field(mul_by!(dg.basis.Vq), gradient_x_quadrature_values, - view(gradients_x, :, e)) - apply_to_each_field(mul_by!(dg.basis.Vq), gradient_y_quadrature_values, - view(gradients_y, :, e)) - apply_to_each_field(mul_by!(dg.basis.Vq), gradient_z_quadrature_values, - view(gradients_z, :, e)) - - # integrate over the element - for i in eachindex(u_quadrature_values) - gradients_i = SVector(gradient_x_quadrature_values[i], - gradient_y_quadrature_values[i], - gradient_z_quadrature_values[i]) - integral += mesh.md.wJq[i, e] * - func(u_quadrature_values[i], gradients_i, equations) + function integrate( + func::typeof(enstrophy), u, + mesh::DGMultiMesh, + equations, equations_parabolic::CompressibleNavierStokesDiffusion3D, + dg::DGMulti, + cache, cache_parabolic; normalize = true + ) + gradients_x, gradients_y, gradients_z = cache_parabolic.gradients + + # allocate local storage for gradients. + # TODO: can we avoid allocating here? + local_gradient_quadrature_values = ntuple( + _ -> similar(cache_parabolic.local_u_values_threaded), + 3 + ) + + integral = zero(eltype(u)) + for e in eachelement(mesh, dg) + u_quadrature_values = cache_parabolic.local_u_values_threaded[Threads.threadid()] + gradient_x_quadrature_values = local_gradient_quadrature_values[1][Threads.threadid()] + gradient_y_quadrature_values = local_gradient_quadrature_values[2][Threads.threadid()] + gradient_z_quadrature_values = local_gradient_quadrature_values[3][Threads.threadid()] + + # interpolate to quadrature on each element + apply_to_each_field(mul_by!(dg.basis.Vq), u_quadrature_values, view(u, :, e)) + apply_to_each_field( + mul_by!(dg.basis.Vq), gradient_x_quadrature_values, + view(gradients_x, :, e) + ) + apply_to_each_field( + mul_by!(dg.basis.Vq), gradient_y_quadrature_values, + view(gradients_y, :, e) + ) + apply_to_each_field( + mul_by!(dg.basis.Vq), gradient_z_quadrature_values, + view(gradients_z, :, e) + ) + + # integrate over the element + for i in eachindex(u_quadrature_values) + gradients_i = SVector( + gradient_x_quadrature_values[i], + gradient_y_quadrature_values[i], + gradient_z_quadrature_values[i] + ) + integral += mesh.md.wJq[i, e] * + func(u_quadrature_values[i], gradients_i, equations) + end end + return integral + end + + function create_cache_analysis( + analyzer, mesh::DGMultiMesh, + equations, dg::DGMulti, cache, + RealT, uEltype + ) + md = mesh.md + return (;) end - return integral -end - -function create_cache_analysis(analyzer, mesh::DGMultiMesh, - equations, dg::DGMulti, cache, - RealT, uEltype) - md = mesh.md - return (;) -end - -SolutionAnalyzer(rd::RefElemData) = rd - -nelements(mesh::DGMultiMesh, ::DGMulti, other_args...) = mesh.md.num_elements -function nelementsglobal(mesh::DGMultiMesh, solver::DGMulti, cache) - if mpi_isparallel() - error("`nelementsglobal` is not implemented for `DGMultiMesh` when used in parallel with MPI") - else - return ndofs(mesh, solver, cache) + + SolutionAnalyzer(rd::RefElemData) = rd + + nelements(mesh::DGMultiMesh, ::DGMulti, other_args...) = mesh.md.num_elements + function nelementsglobal(mesh::DGMultiMesh, solver::DGMulti, cache) + if mpi_isparallel() + error("`nelementsglobal` is not implemented for `DGMultiMesh` when used in parallel with MPI") + else + return ndofs(mesh, solver, cache) + end end -end -function ndofsglobal(mesh::DGMultiMesh, solver::DGMulti, cache) - if mpi_isparallel() - error("`ndofsglobal` is not implemented for `DGMultiMesh` when used in parallel with MPI") - else - return ndofs(mesh, solver, cache) + function ndofsglobal(mesh::DGMultiMesh, solver::DGMulti, cache) + if mpi_isparallel() + error("`ndofsglobal` is not implemented for `DGMultiMesh` when used in parallel with MPI") + else + return ndofs(mesh, solver, cache) + end end -end end # @muladd diff --git a/src/callbacks_step/analysis_surface_integral_2d.jl b/src/callbacks_step/analysis_surface_integral_2d.jl index e22d8b14e9..63cfe93288 100644 --- a/src/callbacks_step/analysis_surface_integral_2d.jl +++ b/src/callbacks_step/analysis_surface_integral_2d.jl @@ -3,400 +3,446 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This file contains callbacks that are performed on the surface like computation of -# surface forces - -""" - AnalysisSurfaceIntegral{Variable, NBoundaries}(semi, - boundary_symbols::NTuple{NBoundaries, Symbol}, - variable) - -This struct is used to compute the surface integral of a quantity of interest `variable` alongside -the boundary/boundaries associated with particular name(s) given in `boundary_symbol` -or `boundary_symbols`. -For instance, this can be used to compute the lift [`LiftCoefficientPressure`](@ref) or -drag coefficient [`DragCoefficientPressure`](@ref) of e.g. an airfoil with the boundary -name `:Airfoil` in 2D. - -- `semi::Semidiscretization`: Passed in to retrieve boundary condition information -- `boundary_symbols::NTuple{NBoundaries, Symbol}`: Name(s) of the boundary/boundaries - where the quantity of interest is computed -- `variable::Variable`: Quantity of interest, like lift or drag -""" -struct AnalysisSurfaceIntegral{Variable, NBoundaries} - variable::Variable # Quantity of interest, like lift or drag - boundary_symbols::NTuple{NBoundaries, Symbol} # Name(s) of the boundary/boundaries - - function AnalysisSurfaceIntegral(semi, - boundary_symbols::NTuple{NBoundaries, Symbol}, - variable) where {NBoundaries} - return new{typeof(variable), NBoundaries}(variable, boundary_symbols) + #! format: noindent + + # This file contains callbacks that are performed on the surface like computation of + # surface forces + + """ + AnalysisSurfaceIntegral{Variable, NBoundaries}(semi, + boundary_symbols::NTuple{NBoundaries, Symbol}, + variable) + + This struct is used to compute the surface integral of a quantity of interest `variable` alongside + the boundary/boundaries associated with particular name(s) given in `boundary_symbol` + or `boundary_symbols`. + For instance, this can be used to compute the lift [`LiftCoefficientPressure`](@ref) or + drag coefficient [`DragCoefficientPressure`](@ref) of e.g. an airfoil with the boundary + name `:Airfoil` in 2D. + + - `semi::Semidiscretization`: Passed in to retrieve boundary condition information + - `boundary_symbols::NTuple{NBoundaries, Symbol}`: Name(s) of the boundary/boundaries + where the quantity of interest is computed + - `variable::Variable`: Quantity of interest, like lift or drag + """ + struct AnalysisSurfaceIntegral{Variable, NBoundaries} + variable::Variable # Quantity of interest, like lift or drag + boundary_symbols::NTuple{NBoundaries, Symbol} # Name(s) of the boundary/boundaries + + function AnalysisSurfaceIntegral( + semi, + boundary_symbols::NTuple{NBoundaries, Symbol}, + variable + ) where {NBoundaries} + return new{typeof(variable), NBoundaries}(variable, boundary_symbols) + end + end + + struct ForceState{RealT <: Real} + psi::Tuple{RealT, RealT} # Unit vector normal or parallel to freestream + rhoinf::RealT + uinf::RealT + linf::RealT + end + + struct LiftCoefficientPressure{RealT <: Real} + force_state::ForceState{RealT} + end + + struct DragCoefficientPressure{RealT <: Real} + force_state::ForceState{RealT} + end + + # Abstract base type used for dispatch of `analyze` for quantities + # requiring gradients of the velocity field. + abstract type VariableViscous end + + struct LiftCoefficientShearStress{RealT <: Real} <: VariableViscous + force_state::ForceState{RealT} + end + + struct DragCoefficientShearStress{RealT <: Real} <: VariableViscous + force_state::ForceState{RealT} + end + + """ + LiftCoefficientPressure(aoa, rhoinf, uinf, linf) + + Compute the lift coefficient + ```math + C_{L,p} \\coloneqq \\frac{\\oint_{\\partial \\Omega} p \\boldsymbol n \\cdot \\psi_L \\, \\mathrm{d} S} + {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} + ``` + based on the pressure distribution along a boundary. + Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) + which stores the boundary information and semidiscretization. + + - `aoa::Real`: Angle of attack in radians (for airfoils etc.) + - `rhoinf::Real`: Free-stream density + - `uinf::Real`: Free-stream velocity + - `linf::Real`: Reference length of geometry (e.g. airfoil chord length) + """ + function LiftCoefficientPressure(aoa, rhoinf, uinf, linf) + # psi_lift is the normal unit vector to the freestream direction. + # Note: The choice of the normal vector psi_lift = (-sin(aoa), cos(aoa)) + # leads to positive lift coefficients for positive angles of attack for airfoils. + # One could also use psi_lift = (sin(aoa), -cos(aoa)) which results in the same + # value, but with the opposite sign. + psi_lift = (-sin(aoa), cos(aoa)) + return LiftCoefficientPressure(ForceState(psi_lift, rhoinf, uinf, linf)) + end + + """ + DragCoefficientPressure(aoa, rhoinf, uinf, linf) + + Compute the drag coefficient + ```math + C_{D,p} \\coloneqq \\frac{\\oint_{\\partial \\Omega} p \\boldsymbol n \\cdot \\psi_D \\, \\mathrm{d} S} + {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} + ``` + based on the pressure distribution along a boundary. + Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) + which stores the boundary information and semidiscretization. + + - `aoa::Real`: Angle of attack in radians (for airfoils etc.) + - `rhoinf::Real`: Free-stream density + - `uinf::Real`: Free-stream velocity + - `linf::Real`: Reference length of geometry (e.g. airfoil chord length) + """ + function DragCoefficientPressure(aoa, rhoinf, uinf, linf) + # `psi_drag` is the unit vector tangent to the freestream direction + psi_drag = (cos(aoa), sin(aoa)) + return DragCoefficientPressure(ForceState(psi_drag, rhoinf, uinf, linf)) + end + + """ + LiftCoefficientShearStress(aoa, rhoinf, uinf, linf) + + Compute the lift coefficient + ```math + C_{L,f} \\coloneqq \\frac{\\oint_{\\partial \\Omega} \\boldsymbol \\tau_w \\cdot \\psi_L \\, \\mathrm{d} S} + {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} + ``` + based on the wall shear stress vector ``\\tau_w`` along a boundary. + Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) + which stores the boundary information and semidiscretization. + + - `aoa::Real`: Angle of attack in radians (for airfoils etc.) + - `rhoinf::Real`: Free-stream density + - `uinf::Real`: Free-stream velocity + - `linf::Real`: Reference length of geometry (e.g. airfoil chord length) + """ + function LiftCoefficientShearStress(aoa, rhoinf, uinf, linf) + # psi_lift is the normal unit vector to the freestream direction. + # Note: The choice of the normal vector psi_lift = (-sin(aoa), cos(aoa)) + # leads to negative lift coefficients for airfoils. + # One could also use psi_lift = (sin(aoa), -cos(aoa)) which results in the same + # value, but with the opposite sign. + psi_lift = (-sin(aoa), cos(aoa)) + return LiftCoefficientShearStress(ForceState(psi_lift, rhoinf, uinf, linf)) + end + + """ + DragCoefficientShearStress(aoa, rhoinf, uinf, linf) + + Compute the drag coefficient + ```math + C_{D,f} \\coloneqq \\frac{\\oint_{\\partial \\Omega} \\boldsymbol \\tau_w \\cdot \\psi_D \\, \\mathrm{d} S} + {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} + ``` + based on the wall shear stress vector ``\\tau_w`` along a boundary. + Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) + which stores the boundary information and semidiscretization. + + - `aoa::Real`: Angle of attack in radians (for airfoils etc.) + - `rhoinf::Real`: Free-stream density + - `uinf::Real`: Free-stream velocity + - `linf::Real`: Reference length of geometry (e.g. airfoil chord length) + """ + function DragCoefficientShearStress(aoa, rhoinf, uinf, linf) + # `psi_drag` is the unit vector tangent to the freestream direction + psi_drag = (cos(aoa), sin(aoa)) + return DragCoefficientShearStress(ForceState(psi_drag, rhoinf, uinf, linf)) + end + + function (lift_coefficient::LiftCoefficientPressure)( + u, normal_direction, x, t, + equations + ) + p = pressure(u, equations) + @unpack psi, rhoinf, uinf, linf = lift_coefficient.force_state + # Normalize as `normal_direction` is not necessarily a unit vector + n = dot(normal_direction, psi) / norm(normal_direction) + return p * n / (0.5 * rhoinf * uinf^2 * linf) + end + + function (drag_coefficient::DragCoefficientPressure)( + u, normal_direction, x, t, + equations + ) + p = pressure(u, equations) + @unpack psi, rhoinf, uinf, linf = drag_coefficient.force_state + # Normalize as `normal_direction` is not necessarily a unit vector + n = dot(normal_direction, psi) / norm(normal_direction) + return p * n / (0.5 * rhoinf * uinf^2 * linf) + end + + # Compute the three components of the symmetric viscous stress tensor + # (tau_11, tau_12, tau_22) based on the gradients of the velocity field. + # This is required for drag and lift coefficients based on shear stress, + # as well as for the non-integrated quantities such as + # skin friction coefficient (to be added). + function viscous_stress_tensor( + u, normal_direction, equations_parabolic, + gradients_1, gradients_2 + ) + _, dv1dx, dv2dx, _ = convert_derivative_to_primitive( + u, gradients_1, + equations_parabolic + ) + _, dv1dy, dv2dy, _ = convert_derivative_to_primitive( + u, gradients_2, + equations_parabolic + ) + + # Components of viscous stress tensor + # (4/3 * (v1)_x - 2/3 * (v2)_y) + tau_11 = 4.0 / 3.0 * dv1dx - 2.0 / 3.0 * dv2dy + # ((v1)_y + (v2)_x) + # stress tensor is symmetric + tau_12 = dv1dy + dv2dx # = tau_21 + # (4/3 * (v2)_y - 2/3 * (v1)_x) + tau_22 = 4.0 / 3.0 * dv2dy - 2.0 / 3.0 * dv1dx + + mu = dynamic_viscosity(u, equations_parabolic) + + return mu .* (tau_11, tau_12, tau_22) end -end - -struct ForceState{RealT <: Real} - psi::Tuple{RealT, RealT} # Unit vector normal or parallel to freestream - rhoinf::RealT - uinf::RealT - linf::RealT -end - -struct LiftCoefficientPressure{RealT <: Real} - force_state::ForceState{RealT} -end - -struct DragCoefficientPressure{RealT <: Real} - force_state::ForceState{RealT} -end - -# Abstract base type used for dispatch of `analyze` for quantities -# requiring gradients of the velocity field. -abstract type VariableViscous end - -struct LiftCoefficientShearStress{RealT <: Real} <: VariableViscous - force_state::ForceState{RealT} -end - -struct DragCoefficientShearStress{RealT <: Real} <: VariableViscous - force_state::ForceState{RealT} -end - -""" - LiftCoefficientPressure(aoa, rhoinf, uinf, linf) - -Compute the lift coefficient -```math -C_{L,p} \\coloneqq \\frac{\\oint_{\\partial \\Omega} p \\boldsymbol n \\cdot \\psi_L \\, \\mathrm{d} S} - {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} -``` -based on the pressure distribution along a boundary. -Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) -which stores the boundary information and semidiscretization. - -- `aoa::Real`: Angle of attack in radians (for airfoils etc.) -- `rhoinf::Real`: Free-stream density -- `uinf::Real`: Free-stream velocity -- `linf::Real`: Reference length of geometry (e.g. airfoil chord length) -""" -function LiftCoefficientPressure(aoa, rhoinf, uinf, linf) - # psi_lift is the normal unit vector to the freestream direction. - # Note: The choice of the normal vector psi_lift = (-sin(aoa), cos(aoa)) - # leads to positive lift coefficients for positive angles of attack for airfoils. - # One could also use psi_lift = (sin(aoa), -cos(aoa)) which results in the same - # value, but with the opposite sign. - psi_lift = (-sin(aoa), cos(aoa)) - return LiftCoefficientPressure(ForceState(psi_lift, rhoinf, uinf, linf)) -end - -""" - DragCoefficientPressure(aoa, rhoinf, uinf, linf) - -Compute the drag coefficient -```math -C_{D,p} \\coloneqq \\frac{\\oint_{\\partial \\Omega} p \\boldsymbol n \\cdot \\psi_D \\, \\mathrm{d} S} - {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} -``` -based on the pressure distribution along a boundary. -Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) -which stores the boundary information and semidiscretization. - -- `aoa::Real`: Angle of attack in radians (for airfoils etc.) -- `rhoinf::Real`: Free-stream density -- `uinf::Real`: Free-stream velocity -- `linf::Real`: Reference length of geometry (e.g. airfoil chord length) -""" -function DragCoefficientPressure(aoa, rhoinf, uinf, linf) - # `psi_drag` is the unit vector tangent to the freestream direction - psi_drag = (cos(aoa), sin(aoa)) - return DragCoefficientPressure(ForceState(psi_drag, rhoinf, uinf, linf)) -end - -""" - LiftCoefficientShearStress(aoa, rhoinf, uinf, linf) - -Compute the lift coefficient -```math -C_{L,f} \\coloneqq \\frac{\\oint_{\\partial \\Omega} \\boldsymbol \\tau_w \\cdot \\psi_L \\, \\mathrm{d} S} - {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} -``` -based on the wall shear stress vector ``\\tau_w`` along a boundary. -Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) -which stores the boundary information and semidiscretization. - -- `aoa::Real`: Angle of attack in radians (for airfoils etc.) -- `rhoinf::Real`: Free-stream density -- `uinf::Real`: Free-stream velocity -- `linf::Real`: Reference length of geometry (e.g. airfoil chord length) -""" -function LiftCoefficientShearStress(aoa, rhoinf, uinf, linf) - # psi_lift is the normal unit vector to the freestream direction. - # Note: The choice of the normal vector psi_lift = (-sin(aoa), cos(aoa)) - # leads to negative lift coefficients for airfoils. - # One could also use psi_lift = (sin(aoa), -cos(aoa)) which results in the same - # value, but with the opposite sign. - psi_lift = (-sin(aoa), cos(aoa)) - return LiftCoefficientShearStress(ForceState(psi_lift, rhoinf, uinf, linf)) -end - -""" - DragCoefficientShearStress(aoa, rhoinf, uinf, linf) - -Compute the drag coefficient -```math -C_{D,f} \\coloneqq \\frac{\\oint_{\\partial \\Omega} \\boldsymbol \\tau_w \\cdot \\psi_D \\, \\mathrm{d} S} - {0.5 \\rho_{\\infty} U_{\\infty}^2 L_{\\infty}} -``` -based on the wall shear stress vector ``\\tau_w`` along a boundary. -Supposed to be used in conjunction with [`AnalysisSurfaceIntegral`](@ref) -which stores the boundary information and semidiscretization. - -- `aoa::Real`: Angle of attack in radians (for airfoils etc.) -- `rhoinf::Real`: Free-stream density -- `uinf::Real`: Free-stream velocity -- `linf::Real`: Reference length of geometry (e.g. airfoil chord length) -""" -function DragCoefficientShearStress(aoa, rhoinf, uinf, linf) - # `psi_drag` is the unit vector tangent to the freestream direction - psi_drag = (cos(aoa), sin(aoa)) - return DragCoefficientShearStress(ForceState(psi_drag, rhoinf, uinf, linf)) -end - -function (lift_coefficient::LiftCoefficientPressure)(u, normal_direction, x, t, - equations) - p = pressure(u, equations) - @unpack psi, rhoinf, uinf, linf = lift_coefficient.force_state - # Normalize as `normal_direction` is not necessarily a unit vector - n = dot(normal_direction, psi) / norm(normal_direction) - return p * n / (0.5 * rhoinf * uinf^2 * linf) -end - -function (drag_coefficient::DragCoefficientPressure)(u, normal_direction, x, t, - equations) - p = pressure(u, equations) - @unpack psi, rhoinf, uinf, linf = drag_coefficient.force_state - # Normalize as `normal_direction` is not necessarily a unit vector - n = dot(normal_direction, psi) / norm(normal_direction) - return p * n / (0.5 * rhoinf * uinf^2 * linf) -end - -# Compute the three components of the symmetric viscous stress tensor -# (tau_11, tau_12, tau_22) based on the gradients of the velocity field. -# This is required for drag and lift coefficients based on shear stress, -# as well as for the non-integrated quantities such as -# skin friction coefficient (to be added). -function viscous_stress_tensor(u, normal_direction, equations_parabolic, - gradients_1, gradients_2) - _, dv1dx, dv2dx, _ = convert_derivative_to_primitive(u, gradients_1, - equations_parabolic) - _, dv1dy, dv2dy, _ = convert_derivative_to_primitive(u, gradients_2, - equations_parabolic) - - # Components of viscous stress tensor - # (4/3 * (v1)_x - 2/3 * (v2)_y) - tau_11 = 4.0 / 3.0 * dv1dx - 2.0 / 3.0 * dv2dy - # ((v1)_y + (v2)_x) - # stress tensor is symmetric - tau_12 = dv1dy + dv2dx # = tau_21 - # (4/3 * (v2)_y - 2/3 * (v1)_x) - tau_22 = 4.0 / 3.0 * dv2dy - 2.0 / 3.0 * dv1dx - - mu = dynamic_viscosity(u, equations_parabolic) - - return mu .* (tau_11, tau_12, tau_22) -end - -function viscous_stress_vector(u, normal_direction, equations_parabolic, - gradients_1, gradients_2) - # Normalize normal direction, should point *into* the fluid => *(-1) - n_normal = -normal_direction / norm(normal_direction) - - tau_11, tau_12, tau_22 = viscous_stress_tensor(u, normal_direction, - equations_parabolic, - gradients_1, gradients_2) - - # Viscous stress vector: Stress tensor * normal vector - visc_stress_vector_1 = tau_11 * n_normal[1] + tau_12 * n_normal[2] - visc_stress_vector_2 = tau_12 * n_normal[1] + tau_22 * n_normal[2] - - return (visc_stress_vector_1, visc_stress_vector_2) -end - -function (lift_coefficient::LiftCoefficientShearStress)(u, normal_direction, x, t, - equations_parabolic, - gradients_1, gradients_2) - visc_stress_vector = viscous_stress_vector(u, normal_direction, equations_parabolic, - gradients_1, gradients_2) - @unpack psi, rhoinf, uinf, linf = lift_coefficient.force_state - return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) / - (0.5 * rhoinf * uinf^2 * linf) -end - -function (drag_coefficient::DragCoefficientShearStress)(u, normal_direction, x, t, - equations_parabolic, - gradients_1, gradients_2) - visc_stress_vector = viscous_stress_vector(u, normal_direction, equations_parabolic, - gradients_1, gradients_2) - @unpack psi, rhoinf, uinf, linf = drag_coefficient.force_state - return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) / - (0.5 * rhoinf * uinf^2 * linf) -end - -function get_boundary_indices(boundary_symbols, boundary_symbol_indices) - indices = Int[] - for name in boundary_symbols - append!(indices, boundary_symbol_indices[name]) + + function viscous_stress_vector( + u, normal_direction, equations_parabolic, + gradients_1, gradients_2 + ) + # Normalize normal direction, should point *into* the fluid => *(-1) + n_normal = -normal_direction / norm(normal_direction) + + tau_11, tau_12, tau_22 = viscous_stress_tensor( + u, normal_direction, + equations_parabolic, + gradients_1, gradients_2 + ) + + # Viscous stress vector: Stress tensor * normal vector + visc_stress_vector_1 = tau_11 * n_normal[1] + tau_12 * n_normal[2] + visc_stress_vector_2 = tau_12 * n_normal[1] + tau_22 * n_normal[2] + + return (visc_stress_vector_1, visc_stress_vector_2) end - sort!(indices) # Try to achieve some data locality by sorting - - return indices -end - -function analyze(surface_variable::AnalysisSurfaceIntegral, du, u, t, - mesh::P4estMesh{2}, - equations, dg::DGSEM, cache, semi) - @unpack boundaries = cache - @unpack surface_flux_values, node_coordinates, contravariant_vectors = cache.elements - @unpack weights = dg.basis - - @unpack variable, boundary_symbols = surface_variable - @unpack boundary_symbol_indices = semi.boundary_conditions - indices = get_boundary_indices(boundary_symbols, boundary_symbol_indices) - - surface_integral = zero(eltype(u)) - index_range = eachnode(dg) - for boundary in indices - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - direction = indices2direction(node_indices) - - i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) - j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) - - i_node = i_node_start - j_node = j_node_start - for node_index in index_range - u_node = Trixi.get_node_vars(cache.boundaries.u, equations, dg, node_index, - boundary) - # Extract normal direction at nodes which points from the elements outwards, - # i.e., *into* the structure. - normal_direction = get_normal_direction(direction, contravariant_vectors, - i_node, j_node, - element) - - # Coordinates at a boundary node - x = get_node_coords(node_coordinates, equations, dg, i_node, j_node, - element) - - # L2 norm of normal direction (contravariant_vector) is the surface element - dS = weights[node_index] * norm(normal_direction) - - # Integral over entire boundary surface. Note, it is assumed that the - # `normal_direction` is normalized to be a normal vector within the - # function `variable` and the division of the normal scaling factor - # `norm(normal_direction)` is then accounted for with the `dS` quantity. - surface_integral += variable(u_node, normal_direction, x, t, equations) * dS - - i_node += i_node_step - j_node += j_node_step + + function (lift_coefficient::LiftCoefficientShearStress)( + u, normal_direction, x, t, + equations_parabolic, + gradients_1, gradients_2 + ) + visc_stress_vector = viscous_stress_vector( + u, normal_direction, equations_parabolic, + gradients_1, gradients_2 + ) + @unpack psi, rhoinf, uinf, linf = lift_coefficient.force_state + return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) / + (0.5 * rhoinf * uinf^2 * linf) + end + + function (drag_coefficient::DragCoefficientShearStress)( + u, normal_direction, x, t, + equations_parabolic, + gradients_1, gradients_2 + ) + visc_stress_vector = viscous_stress_vector( + u, normal_direction, equations_parabolic, + gradients_1, gradients_2 + ) + @unpack psi, rhoinf, uinf, linf = drag_coefficient.force_state + return (visc_stress_vector[1] * psi[1] + visc_stress_vector[2] * psi[2]) / + (0.5 * rhoinf * uinf^2 * linf) + end + + function get_boundary_indices(boundary_symbols, boundary_symbol_indices) + indices = Int[] + for name in boundary_symbols + append!(indices, boundary_symbol_indices[name]) + end + sort!(indices) # Try to achieve some data locality by sorting + + return indices + end + + function analyze( + surface_variable::AnalysisSurfaceIntegral, du, u, t, + mesh::P4estMesh{2}, + equations, dg::DGSEM, cache, semi + ) + @unpack boundaries = cache + @unpack surface_flux_values, node_coordinates, contravariant_vectors = cache.elements + @unpack weights = dg.basis + + @unpack variable, boundary_symbols = surface_variable + @unpack boundary_symbol_indices = semi.boundary_conditions + indices = get_boundary_indices(boundary_symbols, boundary_symbol_indices) + + surface_integral = zero(eltype(u)) + index_range = eachnode(dg) + for boundary in indices + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) + j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) + + i_node = i_node_start + j_node = j_node_start + for node_index in index_range + u_node = Trixi.get_node_vars( + cache.boundaries.u, equations, dg, node_index, + boundary + ) + # Extract normal direction at nodes which points from the elements outwards, + # i.e., *into* the structure. + normal_direction = get_normal_direction( + direction, contravariant_vectors, + i_node, j_node, + element + ) + + # Coordinates at a boundary node + x = get_node_coords( + node_coordinates, equations, dg, i_node, j_node, + element + ) + + # L2 norm of normal direction (contravariant_vector) is the surface element + dS = weights[node_index] * norm(normal_direction) + + # Integral over entire boundary surface. Note, it is assumed that the + # `normal_direction` is normalized to be a normal vector within the + # function `variable` and the division of the normal scaling factor + # `norm(normal_direction)` is then accounted for with the `dS` quantity. + surface_integral += variable(u_node, normal_direction, x, t, equations) * dS + + i_node += i_node_step + j_node += j_node_step + end end + return surface_integral end - return surface_integral -end - -function analyze(surface_variable::AnalysisSurfaceIntegral{Variable}, - du, u, t, mesh::P4estMesh{2}, - equations, equations_parabolic, - dg::DGSEM, cache, semi, - cache_parabolic) where {Variable <: VariableViscous} - @unpack boundaries = cache - @unpack surface_flux_values, node_coordinates, contravariant_vectors = cache.elements - @unpack weights = dg.basis - - @unpack variable, boundary_symbols = surface_variable - @unpack boundary_symbol_indices = semi.boundary_conditions - indices = get_boundary_indices(boundary_symbols, boundary_symbol_indices) - - # Additions for parabolic - @unpack viscous_container = cache_parabolic - @unpack gradients = viscous_container - - gradients_x, gradients_y = gradients - - surface_integral = zero(eltype(u)) - index_range = eachnode(dg) - for boundary in indices - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - direction = indices2direction(node_indices) - - i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) - j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) - - i_node = i_node_start - j_node = j_node_start - for node_index in index_range - u_node = Trixi.get_node_vars(cache.boundaries.u, equations, dg, node_index, - boundary) - # Extract normal direction at nodes which points from the elements outwards, - # i.e., *into* the structure. - normal_direction = get_normal_direction(direction, contravariant_vectors, - i_node, j_node, - element) - - # Coordinates at a boundary node - x = get_node_coords(node_coordinates, equations, dg, i_node, j_node, - element) - - # L2 norm of normal direction (contravariant_vector) is the surface element - dS = weights[node_index] * norm(normal_direction) - - gradients_1 = get_node_vars(gradients_x, equations_parabolic, dg, i_node, - j_node, element) - gradients_2 = get_node_vars(gradients_y, equations_parabolic, dg, i_node, - j_node, element) - - # Integral over whole boundary surface. Note, it is assumed that the - # `normal_direction` is normalized to be a normal vector within the - # function `variable` and the division of the normal scaling factor - # `norm(normal_direction)` is then accounted for with the `dS` quantity. - surface_integral += variable(u_node, normal_direction, x, t, - equations_parabolic, - gradients_1, gradients_2) * dS - - i_node += i_node_step - j_node += j_node_step + + function analyze( + surface_variable::AnalysisSurfaceIntegral{Variable}, + du, u, t, mesh::P4estMesh{2}, + equations, equations_parabolic, + dg::DGSEM, cache, semi, + cache_parabolic + ) where {Variable <: VariableViscous} + @unpack boundaries = cache + @unpack surface_flux_values, node_coordinates, contravariant_vectors = cache.elements + @unpack weights = dg.basis + + @unpack variable, boundary_symbols = surface_variable + @unpack boundary_symbol_indices = semi.boundary_conditions + indices = get_boundary_indices(boundary_symbols, boundary_symbol_indices) + + # Additions for parabolic + @unpack viscous_container = cache_parabolic + @unpack gradients = viscous_container + + gradients_x, gradients_y = gradients + + surface_integral = zero(eltype(u)) + index_range = eachnode(dg) + for boundary in indices + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) + j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) + + i_node = i_node_start + j_node = j_node_start + for node_index in index_range + u_node = Trixi.get_node_vars( + cache.boundaries.u, equations, dg, node_index, + boundary + ) + # Extract normal direction at nodes which points from the elements outwards, + # i.e., *into* the structure. + normal_direction = get_normal_direction( + direction, contravariant_vectors, + i_node, j_node, + element + ) + + # Coordinates at a boundary node + x = get_node_coords( + node_coordinates, equations, dg, i_node, j_node, + element + ) + + # L2 norm of normal direction (contravariant_vector) is the surface element + dS = weights[node_index] * norm(normal_direction) + + gradients_1 = get_node_vars( + gradients_x, equations_parabolic, dg, i_node, + j_node, element + ) + gradients_2 = get_node_vars( + gradients_y, equations_parabolic, dg, i_node, + j_node, element + ) + + # Integral over whole boundary surface. Note, it is assumed that the + # `normal_direction` is normalized to be a normal vector within the + # function `variable` and the division of the normal scaling factor + # `norm(normal_direction)` is then accounted for with the `dS` quantity. + surface_integral += variable( + u_node, normal_direction, x, t, + equations_parabolic, + gradients_1, gradients_2 + ) * dS + + i_node += i_node_step + j_node += j_node_step + end end + return surface_integral + end + + function pretty_form_ascii(::AnalysisSurfaceIntegral{<:LiftCoefficientPressure{<:Any}}) + "CL_p" + end + function pretty_form_utf(::AnalysisSurfaceIntegral{<:LiftCoefficientPressure{<:Any}}) + "CL_p" + end + + function pretty_form_ascii(::AnalysisSurfaceIntegral{<:DragCoefficientPressure{<:Any}}) + "CD_p" + end + function pretty_form_utf(::AnalysisSurfaceIntegral{<:DragCoefficientPressure{<:Any}}) + "CD_p" + end + + function pretty_form_ascii(::AnalysisSurfaceIntegral{<:LiftCoefficientShearStress{<:Any}}) + "CL_f" + end + function pretty_form_utf(::AnalysisSurfaceIntegral{<:LiftCoefficientShearStress{<:Any}}) + "CL_f" + end + + function pretty_form_ascii(::AnalysisSurfaceIntegral{<:DragCoefficientShearStress{<:Any}}) + "CD_f" + end + function pretty_form_utf(::AnalysisSurfaceIntegral{<:DragCoefficientShearStress{<:Any}}) + "CD_f" end - return surface_integral -end - -function pretty_form_ascii(::AnalysisSurfaceIntegral{<:LiftCoefficientPressure{<:Any}}) - "CL_p" -end -function pretty_form_utf(::AnalysisSurfaceIntegral{<:LiftCoefficientPressure{<:Any}}) - "CL_p" -end - -function pretty_form_ascii(::AnalysisSurfaceIntegral{<:DragCoefficientPressure{<:Any}}) - "CD_p" -end -function pretty_form_utf(::AnalysisSurfaceIntegral{<:DragCoefficientPressure{<:Any}}) - "CD_p" -end - -function pretty_form_ascii(::AnalysisSurfaceIntegral{<:LiftCoefficientShearStress{<:Any}}) - "CL_f" -end -function pretty_form_utf(::AnalysisSurfaceIntegral{<:LiftCoefficientShearStress{<:Any}}) - "CL_f" -end - -function pretty_form_ascii(::AnalysisSurfaceIntegral{<:DragCoefficientShearStress{<:Any}}) - "CD_f" -end -function pretty_form_utf(::AnalysisSurfaceIntegral{<:DragCoefficientShearStress{<:Any}}) - "CD_f" -end end # muladd diff --git a/src/callbacks_step/averaging.jl b/src/callbacks_step/averaging.jl index efa71af9b9..fe7fa2329d 100644 --- a/src/callbacks_step/averaging.jl +++ b/src/callbacks_step/averaging.jl @@ -3,129 +3,145 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - AveragingCallback(semi::SemidiscretizationHyperbolic, tspan; output_directory="out", - filename="averaging.h5") - -!!! warning "Experimental code" - This callback is experimental and may change in any future release. - -A callback that averages the flow field described by `semi` which must be a semidiscretization of -the compressible Euler equations in two dimensions. The callback records the mean velocity, -mean speed of sound, mean density, and mean vorticity for each node over the time interval given by -`tspan` and stores the results in an HDF5 file `filename` in the directory `output_directory`. Note -that this callback does not support adaptive mesh refinement ([`AMRCallback`](@ref)). -""" -struct AveragingCallback{TSpan, MeanValues, Cache} - tspan::TSpan - mean_values::MeanValues - cache::Cache - output_directory::String - filename::String -end - -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:AveragingCallback}) - @nospecialize cb # reduce precompilation time - averaging_callback = cb.affect! - @unpack tspan = averaging_callback - - print(io, "AveragingCallback(tspan=", tspan, ")") -end - -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:AveragingCallback}) - @nospecialize cb # reduce precompilation time - - if get(io, :compact, false) - show(io, cb) - else + #! format: noindent + + """ + AveragingCallback(semi::SemidiscretizationHyperbolic, tspan; output_directory="out", + filename="averaging.h5") + + !!! warning "Experimental code" + This callback is experimental and may change in any future release. + + A callback that averages the flow field described by `semi` which must be a semidiscretization of + the compressible Euler equations in two dimensions. The callback records the mean velocity, + mean speed of sound, mean density, and mean vorticity for each node over the time interval given by + `tspan` and stores the results in an HDF5 file `filename` in the directory `output_directory`. Note + that this callback does not support adaptive mesh refinement ([`AMRCallback`](@ref)). + """ + struct AveragingCallback{TSpan, MeanValues, Cache} + tspan::TSpan + mean_values::MeanValues + cache::Cache + output_directory::String + filename::String + end + + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:AveragingCallback}) + @nospecialize cb # reduce precompilation time averaging_callback = cb.affect! + @unpack tspan = averaging_callback - setup = [ - "Start time" => first(averaging_callback.tspan), - "Final time" => last(averaging_callback.tspan), - ] - summary_box(io, "AveragingCallback", setup) + print(io, "AveragingCallback(tspan=", tspan, ")") end -end - -function AveragingCallback(semi::SemidiscretizationHyperbolic{<:Any, - <:CompressibleEulerEquations2D}, - tspan; output_directory = "out", filename = "averaging.h5") - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - mean_values = initialize_mean_values(mesh, equations, solver, cache) - cache = create_cache(AveragingCallback, mesh, equations, solver, cache) - - averaging_callback = AveragingCallback(tspan, mean_values, cache, output_directory, - filename) - condition = (u, t, integrator) -> first(tspan) <= t <= last(tspan) - - return DiscreteCallback(condition, averaging_callback, - save_positions = (false, false), - initialize = initialize!) -end - -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u_ode, t, - integrator) where {Condition, Affect! <: AveragingCallback} - averaging_callback = cb.affect! - semi = integrator.p - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - u = wrap_array(u_ode, mesh, equations, solver, cache) - - @trixi_timeit timer() "averaging" initialize_cache!(averaging_callback.cache, u, - mesh, equations, solver, cache) - - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) - return nothing -end - -# This function is called during time integration and updates the mean values according to the -# trapezoidal rule -function (averaging_callback::AveragingCallback)(integrator) - @unpack mean_values = averaging_callback - - u_ode = integrator.u - u_prev_ode = integrator.uprev - semi = integrator.p - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - u = wrap_array(u_ode, mesh, equations, solver, cache) - u_prev = wrap_array(u_prev_ode, mesh, equations, solver, cache) - - dt = integrator.t - integrator.tprev - tspan = averaging_callback.tspan - - integration_constant = 0.5 * dt / (tspan[2] - tspan[1]) # .5 due to trapezoidal rule - - @trixi_timeit timer() "averaging" calc_mean_values!(mean_values, - averaging_callback.cache, - u, u_prev, integration_constant, - mesh, equations, solver, cache) - - # Store mean values in a file if this is the last time step - if isfinished(integrator) - save_averaging_file(averaging_callback, semi) + + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:AveragingCallback} + ) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + averaging_callback = cb.affect! + + setup = [ + "Start time" => first(averaging_callback.tspan), + "Final time" => last(averaging_callback.tspan), + ] + summary_box(io, "AveragingCallback", setup) + end + end + + function AveragingCallback( + semi::SemidiscretizationHyperbolic{ + <:Any, + <:CompressibleEulerEquations2D, + }, + tspan; output_directory = "out", filename = "averaging.h5" + ) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + mean_values = initialize_mean_values(mesh, equations, solver, cache) + cache = create_cache(AveragingCallback, mesh, equations, solver, cache) + + averaging_callback = AveragingCallback( + tspan, mean_values, cache, output_directory, + filename + ) + condition = (u, t, integrator) -> first(tspan) <= t <= last(tspan) + + return DiscreteCallback( + condition, averaging_callback, + save_positions = (false, false), + initialize = initialize! + ) end - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u_ode, t, + integrator + ) where {Condition, Affect! <: AveragingCallback} + averaging_callback = cb.affect! + semi = integrator.p + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + u = wrap_array(u_ode, mesh, equations, solver, cache) + + @trixi_timeit timer() "averaging" initialize_cache!( + averaging_callback.cache, u, + mesh, equations, solver, cache + ) + + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) + return nothing + end + + # This function is called during time integration and updates the mean values according to the + # trapezoidal rule + function (averaging_callback::AveragingCallback)(integrator) + @unpack mean_values = averaging_callback + + u_ode = integrator.u + u_prev_ode = integrator.uprev + semi = integrator.p + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + u = wrap_array(u_ode, mesh, equations, solver, cache) + u_prev = wrap_array(u_prev_ode, mesh, equations, solver, cache) - return nothing -end + dt = integrator.t - integrator.tprev + tspan = averaging_callback.tspan -function save_averaging_file(averaging_callback, semi::AbstractSemidiscretization) - # Create output directory if it doesn't exist - mkpath(averaging_callback.output_directory) + integration_constant = 0.5 * dt / (tspan[2] - tspan[1]) # .5 due to trapezoidal rule - save_averaging_file(averaging_callback, mesh_equations_solver_cache(semi)...) -end + @trixi_timeit timer() "averaging" calc_mean_values!( + mean_values, + averaging_callback.cache, + u, u_prev, integration_constant, + mesh, equations, solver, cache + ) -function load_averaging_file(averaging_file, semi::AbstractSemidiscretization) - load_averaging_file(averaging_file, mesh_equations_solver_cache(semi)...) -end + # Store mean values in a file if this is the last time step + if isfinished(integrator) + save_averaging_file(averaging_callback, semi) + end + + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) + + return nothing + end + + function save_averaging_file(averaging_callback, semi::AbstractSemidiscretization) + # Create output directory if it doesn't exist + mkpath(averaging_callback.output_directory) + + save_averaging_file(averaging_callback, mesh_equations_solver_cache(semi)...) + end + + function load_averaging_file(averaging_file, semi::AbstractSemidiscretization) + load_averaging_file(averaging_file, mesh_equations_solver_cache(semi)...) + end -include("averaging_dg.jl") -include("averaging_dg2d.jl") + include("averaging_dg.jl") + include("averaging_dg2d.jl") end # @muladd diff --git a/src/callbacks_step/averaging_dg.jl b/src/callbacks_step/averaging_dg.jl index ca6b839f45..7b520c6b0e 100644 --- a/src/callbacks_step/averaging_dg.jl +++ b/src/callbacks_step/averaging_dg.jl @@ -3,49 +3,53 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function save_averaging_file(averaging_callback, mesh::TreeMesh, equations, dg::DGSEM, - cache) - @unpack output_directory, filename, mean_values = averaging_callback - h5open(joinpath(output_directory, filename), "w") do file - # Add context information - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["polydeg"] = polydeg(dg) - attributes(file)["n_elements"] = nelements(dg, cache) + function save_averaging_file( + averaging_callback, mesh::TreeMesh, equations, dg::DGSEM, + cache + ) + @unpack output_directory, filename, mean_values = averaging_callback + h5open(joinpath(output_directory, filename), "w") do file + # Add context information + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["polydeg"] = polydeg(dg) + attributes(file)["n_elements"] = nelements(dg, cache) - # Store all mean variables as multi-dimensional arrays - for field in fieldnames(typeof(mean_values)) - name = string(field) - data = getfield(mean_values, field) - file[name] = data + # Store all mean variables as multi-dimensional arrays + for field in fieldnames(typeof(mean_values)) + name = string(field) + data = getfield(mean_values, field) + file[name] = data + end end + + return filename end - return filename -end + function load_averaging_file( + averaging_file, mesh::TreeMesh, equations, dg::DGSEM, + cache + ) + # Read and check mesh and solver info + h5open(averaging_file, "r") do file + n_dims = read(attributes(file)["ndims"]) + n_nodes = read(attributes(file)["polydeg"]) + 1 + n_elements = read(attributes(file)["n_elements"]) -function load_averaging_file(averaging_file, mesh::TreeMesh, equations, dg::DGSEM, - cache) - # Read and check mesh and solver info - h5open(averaging_file, "r") do file - n_dims = read(attributes(file)["ndims"]) - n_nodes = read(attributes(file)["polydeg"]) + 1 - n_elements = read(attributes(file)["n_elements"]) + @assert n_dims == ndims(mesh) "ndims differs from value in averaging file" + @assert n_nodes - 1 == polydeg(dg) "polynomial degree in solver differs from value in averaging file" + @assert n_elements == nelements(dg, cache) "nelements in solver differs from value in averaging file" + end - @assert n_dims==ndims(mesh) "ndims differs from value in averaging file" - @assert n_nodes - 1==polydeg(dg) "polynomial degree in solver differs from value in averaging file" - @assert n_elements==nelements(dg, cache) "nelements in solver differs from value in averaging file" - end + # Read and return mean values + v_mean, c_mean, rho_mean, vorticity_mean = h5open(averaging_file, "r") do file + return read(file["v_mean"]), + read(file["c_mean"]), + read(file["rho_mean"]), + read(file["vorticity_mean"]) + end - # Read and return mean values - v_mean, c_mean, rho_mean, vorticity_mean = h5open(averaging_file, "r") do file - return read(file["v_mean"]), - read(file["c_mean"]), - read(file["rho_mean"]), - read(file["vorticity_mean"]) + return (; v_mean, c_mean, rho_mean, vorticity_mean) end - - return (; v_mean, c_mean, rho_mean, vorticity_mean) -end end # @muladd diff --git a/src/callbacks_step/averaging_dg2d.jl b/src/callbacks_step/averaging_dg2d.jl index 959a5655d9..902d9c59c0 100644 --- a/src/callbacks_step/averaging_dg2d.jl +++ b/src/callbacks_step/averaging_dg2d.jl @@ -3,84 +3,104 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Create arrays with DGSEM-specific structure to store the mean values and set them all to 0 -function initialize_mean_values(mesh::TreeMesh{2}, - equations::AbstractCompressibleEulerEquations{2}, - dg::DGSEM, cache) - uEltype = eltype(cache.elements) - v_mean = zeros(uEltype, - (ndims(equations), nnodes(dg), nnodes(dg), - nelements(cache.elements))) - c_mean = zeros(uEltype, (nnodes(dg), nnodes(dg), nelements(cache.elements))) - rho_mean = zeros(uEltype, size(c_mean)) - vorticity_mean = zeros(uEltype, size(c_mean)) + # Create arrays with DGSEM-specific structure to store the mean values and set them all to 0 + function initialize_mean_values( + mesh::TreeMesh{2}, + equations::AbstractCompressibleEulerEquations{2}, + dg::DGSEM, cache + ) + uEltype = eltype(cache.elements) + v_mean = zeros( + uEltype, + ( + ndims(equations), nnodes(dg), nnodes(dg), + nelements(cache.elements), + ) + ) + c_mean = zeros(uEltype, (nnodes(dg), nnodes(dg), nelements(cache.elements))) + rho_mean = zeros(uEltype, size(c_mean)) + vorticity_mean = zeros(uEltype, size(c_mean)) - return (; v_mean, c_mean, rho_mean, vorticity_mean) -end + return (; v_mean, c_mean, rho_mean, vorticity_mean) + end -# Create cache which holds the vorticity for the previous time step. This is needed due to the -# trapezoidal rule -function create_cache(::Type{AveragingCallback}, mesh::TreeMesh{2}, - equations::AbstractCompressibleEulerEquations{2}, dg::DGSEM, - cache) - # Cache vorticity from previous time step - uEltype = eltype(cache.elements) - vorticity_prev = zeros(uEltype, (nnodes(dg), nnodes(dg), nelements(cache.elements))) - return (; vorticity_prev) -end + # Create cache which holds the vorticity for the previous time step. This is needed due to the + # trapezoidal rule + function create_cache( + ::Type{AveragingCallback}, mesh::TreeMesh{2}, + equations::AbstractCompressibleEulerEquations{2}, dg::DGSEM, + cache + ) + # Cache vorticity from previous time step + uEltype = eltype(cache.elements) + vorticity_prev = zeros(uEltype, (nnodes(dg), nnodes(dg), nelements(cache.elements))) + return (; vorticity_prev) + end -# Calculate vorticity for the initial solution and store it in the cache -function initialize_cache!(averaging_callback_cache, u, - mesh::TreeMesh{2}, - equations::AbstractCompressibleEulerEquations{2}, - dg::DGSEM, cache) - @unpack vorticity_prev = averaging_callback_cache + # Calculate vorticity for the initial solution and store it in the cache + function initialize_cache!( + averaging_callback_cache, u, + mesh::TreeMesh{2}, + equations::AbstractCompressibleEulerEquations{2}, + dg::DGSEM, cache + ) + @unpack vorticity_prev = averaging_callback_cache - # Calculate vorticity for initial solution - calc_vorticity!(vorticity_prev, u, mesh, equations, dg, cache) + # Calculate vorticity for initial solution + calc_vorticity!(vorticity_prev, u, mesh, equations, dg, cache) - return nothing -end + return nothing + end -# Update mean values using the trapezoidal rule -function calc_mean_values!(mean_values, averaging_callback_cache, u, u_prev, - integration_constant, - mesh::TreeMesh{2}, - equations::AbstractCompressibleEulerEquations{2}, - dg::DGSEM, cache) - @unpack v_mean, c_mean, rho_mean, vorticity_mean = mean_values - @unpack vorticity_prev = averaging_callback_cache + # Update mean values using the trapezoidal rule + function calc_mean_values!( + mean_values, averaging_callback_cache, u, u_prev, + integration_constant, + mesh::TreeMesh{2}, + equations::AbstractCompressibleEulerEquations{2}, + dg::DGSEM, cache + ) + @unpack v_mean, c_mean, rho_mean, vorticity_mean = mean_values + @unpack vorticity_prev = averaging_callback_cache - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - vorticity = calc_vorticity_node(u, mesh, equations, dg, cache, i, j, - element) - vorticity_prev_node = vorticity_prev[i, j, element] - vorticity_prev[i, j, element] = vorticity # Cache current vorticity for the next time step + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + vorticity = calc_vorticity_node( + u, mesh, equations, dg, cache, i, j, + element + ) + vorticity_prev_node = vorticity_prev[i, j, element] + vorticity_prev[i, j, element] = vorticity # Cache current vorticity for the next time step - u_node_prim = cons2prim(get_node_vars(u, equations, dg, i, j, element), - equations) - u_prev_node_prim = cons2prim(get_node_vars(u_prev, equations, dg, i, j, - element), equations) + u_node_prim = cons2prim( + get_node_vars(u, equations, dg, i, j, element), + equations + ) + u_prev_node_prim = cons2prim( + get_node_vars( + u_prev, equations, dg, i, j, + element + ), equations + ) - rho, v1, v2, p = u_node_prim - rho_prev, v1_prev, v2_prev, p_prev = u_prev_node_prim + rho, v1, v2, p = u_node_prim + rho_prev, v1_prev, v2_prev, p_prev = u_prev_node_prim - c = sqrt(equations.gamma * p / rho) - c_prev = sqrt(equations.gamma * p_prev / rho_prev) + c = sqrt(equations.gamma * p / rho) + c_prev = sqrt(equations.gamma * p_prev / rho_prev) - # Calculate the contribution to the mean values using the trapezoidal rule - vorticity_mean[i, j, element] += integration_constant * - (vorticity_prev_node + vorticity) - v_mean[1, i, j, element] += integration_constant * (v1_prev + v1) - v_mean[2, i, j, element] += integration_constant * (v2_prev + v2) - c_mean[i, j, element] += integration_constant * (c_prev + c) - rho_mean[i, j, element] += integration_constant * (rho_prev + rho) + # Calculate the contribution to the mean values using the trapezoidal rule + vorticity_mean[i, j, element] += integration_constant * + (vorticity_prev_node + vorticity) + v_mean[1, i, j, element] += integration_constant * (v1_prev + v1) + v_mean[2, i, j, element] += integration_constant * (v2_prev + v2) + c_mean[i, j, element] += integration_constant * (c_prev + c) + rho_mean[i, j, element] += integration_constant * (rho_prev + rho) + end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_step/callbacks_step.jl b/src/callbacks_step/callbacks_step.jl index 09d197bf22..a540d1b71f 100644 --- a/src/callbacks_step/callbacks_step.jl +++ b/src/callbacks_step/callbacks_step.jl @@ -3,72 +3,78 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# overload this function for specific callbacks which use element element variables -# that should be saved -function get_element_variables!(element_variables, u, mesh, equations, solver, cache, - callback; kwargs...) - nothing -end + # overload this function for specific callbacks which use element element variables + # that should be saved + function get_element_variables!( + element_variables, u, mesh, equations, solver, cache, + callback; kwargs... + ) + nothing + end -@inline function get_element_variables!(element_variables, u_ode, - semi::AbstractSemidiscretization, - cb::DiscreteCallback; - kwargs...) - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - u = wrap_array(u_ode, mesh, equations, solver, cache) - get_element_variables!(element_variables, u, mesh, equations, solver, cache, - cb.affect!; kwargs...) -end + @inline function get_element_variables!( + element_variables, u_ode, + semi::AbstractSemidiscretization, + cb::DiscreteCallback; + kwargs... + ) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + u = wrap_array(u_ode, mesh, equations, solver, cache) + get_element_variables!( + element_variables, u, mesh, equations, solver, cache, + cb.affect!; kwargs... + ) + end -@inline function isfinished(integrator) - # Checking for floating point equality is OK here as `DifferentialEquations.jl` - # sets the time exactly to the final time in the last iteration - return integrator.t == last(integrator.sol.prob.tspan) || - isempty(integrator.opts.tstops) || - integrator.iter == integrator.opts.maxiters -end + @inline function isfinished(integrator) + # Checking for floating point equality is OK here as `DifferentialEquations.jl` + # sets the time exactly to the final time in the last iteration + return integrator.t == last(integrator.sol.prob.tspan) || + isempty(integrator.opts.tstops) || + integrator.iter == integrator.opts.maxiters + end -# `include` callback definitions in the order that we currently prefer -# when combining them into a `CallbackSet` which is called *after* a complete step -# The motivation is as follows: The first callbacks belong to the current time step iteration: -# * `SummaryCallback` controls, among other things, timers and should thus be first -# * `SteadyStateCallback` may mark a time step as the last step, which is needed by other callbacks -# * `AnalysisCallback` may also do some checks that mark a step as the last one -# * `AliveCallback` belongs to `AnalysisCallback` and should thus be nearby -# * `SaveRestartCallback`, `SaveSolutionCallback`, and `TimeSeriesCallback` should save the current -# solution state before it is potentially degraded by AMR -# * `VisualizationCallback` similarly should be called before the mesh is adapted -# -# From here on, the remaining callbacks essentially already belong to the next time step iteration: -# * `AMRCallback` really belongs to the next time step already, as it should be the "first" callback -# in a time step loop (however, callbacks are always executed *after* a step, thus it comes near -# the end here) -# * `StepsizeCallback` must come after AMR to accommodate potential changes in the minimum cell size -# * `GlmSpeedCallback` must come after computing time step size because it affects the value of c_h -# * `LBMCollisionCallback` must come after computing time step size because it is already part of -# the next time step calculation -include("summary.jl") -include("steady_state.jl") -include("analysis.jl") -include("alive.jl") -include("save_restart.jl") -include("save_solution.jl") -include("time_series.jl") -include("visualization.jl") -include("averaging.jl") + # `include` callback definitions in the order that we currently prefer + # when combining them into a `CallbackSet` which is called *after* a complete step + # The motivation is as follows: The first callbacks belong to the current time step iteration: + # * `SummaryCallback` controls, among other things, timers and should thus be first + # * `SteadyStateCallback` may mark a time step as the last step, which is needed by other callbacks + # * `AnalysisCallback` may also do some checks that mark a step as the last one + # * `AliveCallback` belongs to `AnalysisCallback` and should thus be nearby + # * `SaveRestartCallback`, `SaveSolutionCallback`, and `TimeSeriesCallback` should save the current + # solution state before it is potentially degraded by AMR + # * `VisualizationCallback` similarly should be called before the mesh is adapted + # + # From here on, the remaining callbacks essentially already belong to the next time step iteration: + # * `AMRCallback` really belongs to the next time step already, as it should be the "first" callback + # in a time step loop (however, callbacks are always executed *after* a step, thus it comes near + # the end here) + # * `StepsizeCallback` must come after AMR to accommodate potential changes in the minimum cell size + # * `GlmSpeedCallback` must come after computing time step size because it affects the value of c_h + # * `LBMCollisionCallback` must come after computing time step size because it is already part of + # the next time step calculation + include("summary.jl") + include("steady_state.jl") + include("analysis.jl") + include("alive.jl") + include("save_restart.jl") + include("save_solution.jl") + include("time_series.jl") + include("visualization.jl") + include("averaging.jl") -include("amr.jl") -include("stepsize.jl") -include("glm_speed.jl") -include("lbm_collision.jl") -include("euler_acoustics_coupling.jl") + include("amr.jl") + include("stepsize.jl") + include("glm_speed.jl") + include("lbm_collision.jl") + include("euler_acoustics_coupling.jl") -# The `TrivialCallback` purposely does nothing: It allows to quickly disable specific callbacks -# when using `trixi_include` or `test_trixi_include` -include("trivial.jl") + # The `TrivialCallback` purposely does nothing: It allows to quickly disable specific callbacks + # when using `trixi_include` or `test_trixi_include` + include("trivial.jl") -# DGMulti callbacks -include("analysis_dgmulti.jl") + # DGMulti callbacks + include("analysis_dgmulti.jl") end # @muladd diff --git a/src/callbacks_step/euler_acoustics_coupling.jl b/src/callbacks_step/euler_acoustics_coupling.jl index 52dc55befd..9c7c43fa69 100644 --- a/src/callbacks_step/euler_acoustics_coupling.jl +++ b/src/callbacks_step/euler_acoustics_coupling.jl @@ -3,217 +3,253 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - EulerAcousticsCouplingCallback - -!!! warning "Experimental code" - This callback is experimental and may change in any future release. - -A callback that couples the acoustic perturbation equations and compressible Euler equations. Must -be used in conjunction with [`SemidiscretizationEulerAcoustics`](@ref). -This callback manages the flow solver - which is always one time step ahead of the -acoustics solver - and calculates the acoustic source term after each time step. The linearized -Lamb vector is used as the source term, i.e. -```math -\mathbf{s} = -(\mathbf{\omega'} \times \bar{\mathbf{v}} - + \bar{\mathbf{\omega}} \times \mathbf{v'}), -``` -where ``\mathbf{v}`` denotes the velocity, ``\mathbf{\omega}`` denotes the vorticity, the bar -``\bar{(\cdot)}`` indicates time-averaged quantities (see [`AveragingCallback`](@ref)) and prime -``(\cdot)'`` denotes perturbed quantities defined by ``\phi' = \phi - \bar{\phi}``. Note that -the perturbed quantities here are based entirely on the pure flow solution and should not be -confused with the state variables of the acoustic perturbation equations. - -In addition, this callback manages the time step size for both solvers -and initializes the mean values of the acoustic perturbation equations using results obtained with -the [`AveragingCallback`](@ref). - -- Michael Schlottke-Lakemper (2017) - A direct-hybrid method for aeroacoustic analysis - [DOI: 10.18154/RWTH-2017-04082](https://doi.org/10.18154/RWTH-2017-04082) -""" -mutable struct EulerAcousticsCouplingCallback{RealT <: Real, MeanValues, - IntegratorEuler} - stepsize_callback_acoustics::StepsizeCallback{RealT} - stepsize_callback_euler::StepsizeCallback{RealT} - mean_values::MeanValues - integrator_euler::IntegratorEuler -end - -function Base.show(io::IO, - cb::DiscreteCallback{<:Any, <:EulerAcousticsCouplingCallback}) - @nospecialize cb # reduce precompilation time - euler_acoustics_coupling = cb.affect! - - print(io, "EulerAcousticsCouplingCallback(") - print(io, euler_acoustics_coupling.stepsize_callback_acoustics) - print(io, ", ", euler_acoustics_coupling.stepsize_callback_euler, ")") -end - -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:EulerAcousticsCouplingCallback}) - @nospecialize cb # reduce precompilation time - euler_acoustics_coupling = cb.affect! - - summary_header(io, "EulerAcousticsCouplingCallback") - summary_line(io, "acoustics StepsizeCallback", - euler_acoustics_coupling.stepsize_callback_acoustics) - summary_line(io, "Euler StepsizeCallback", - euler_acoustics_coupling.stepsize_callback_euler) - summary_footer(io) -end - -""" - EulerAcousticsCouplingCallback(ode_euler, - averaging_callback::DiscreteCallback{<:Any, <:AveragingCallback}, - alg, cfl_acoustics::Real, cfl_euler::Real; kwargs...) - -!!! warning "Experimental code" - This callback is experimental and may change in any future release. - -Creates an [`EulerAcousticsCouplingCallback`](@ref) based on the pure flow `ODEProblem` given by -`ode_euler`. Creates an integrator using the time integration method `alg` and the keyword arguments -to solve `ode_euler` (consult the [OrdinaryDiffEq documentation](https://diffeq.sciml.ai/stable/) -for further information). -Manages the step size for both solvers by using the minimum of the maximum step size obtained with -CFL numbers `cfl_acoustics` for the acoustics solver and `cfl_euler` for and flow solver, -respectively. -The mean values for the acoustic perturbation equations are read from `averaging_callback` -(see [`AveragingCallback`](@ref)). -""" -function EulerAcousticsCouplingCallback(ode_euler, - averaging_callback::DiscreteCallback{<:Any, - <:AveragingCallback}, - alg, cfl_acoustics::Real, cfl_euler::Real; - kwargs...) - @unpack mean_values = averaging_callback.affect! - - return EulerAcousticsCouplingCallback(ode_euler, mean_values, alg, cfl_acoustics, - cfl_euler; - kwargs...) -end - -""" - EulerAcousticsCouplingCallback(ode_euler, averaging_file::AbstractString, alg, - cfl_acoustics::Real, cfl_euler::Real; kwargs...) - -!!! warning "Experimental code" - This callback is experimental and may change in any future release. - -Creates an [`EulerAcousticsCouplingCallback`](@ref) based on the pure flow `ODEProblem` given by -`ode_euler`. Creates an integrator using the time integration method `alg` and the keyword arguments -to solve `ode_euler` (consult the [OrdinaryDiffEq documentation](https://diffeq.sciml.ai/stable/) -for further information). -Manages the step size for both solvers by using the minimum of the maximum step size obtained with -CFL numbers `cfl_acoustics` for the acoustics solver and `cfl_euler` for and flow solver, -respectively. -The mean values for the acoustic perturbation equations are read from `averaging_file` -(see [`AveragingCallback`](@ref)). -""" -function EulerAcousticsCouplingCallback(ode_euler, averaging_file::AbstractString, alg, - cfl_acoustics::Real, cfl_euler::Real; kwargs...) - semi_euler = ode_euler.p - mean_values = load_averaging_file(averaging_file, semi_euler) - - return EulerAcousticsCouplingCallback(ode_euler, mean_values, alg, cfl_acoustics, - cfl_euler; - kwargs...) -end - -function EulerAcousticsCouplingCallback(ode_euler, mean_values, alg, cfl_acoustics, - cfl_euler; - kwargs...) - # Set up ODE Integrator for Euler equations - integrator_euler = init(ode_euler, alg, save_everystep = false, dt = 1.0; kwargs...) # dt will be overwritten - - euler_acoustics_coupling = EulerAcousticsCouplingCallback{typeof(cfl_acoustics), - typeof(mean_values), - typeof(integrator_euler)}(StepsizeCallback(cfl_acoustics), - StepsizeCallback(cfl_euler), - mean_values, - integrator_euler) - condition = (u, t, integrator) -> true - - return DiscreteCallback(condition, euler_acoustics_coupling, - save_positions = (false, false), - initialize = initialize!) -end - -# This is called before the main loop and initializes the mean values in u_ode -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u_ode, t, - integrator_acoustics) where {Condition, - Affect! <: - EulerAcousticsCouplingCallback} - euler_acoustics_coupling = cb.affect! - semi = integrator_acoustics.p - @unpack semi_acoustics = semi - - # Initialize mean values in u_ode - u_acoustics = wrap_array(u_ode, semi_acoustics) - @unpack mean_values = euler_acoustics_coupling - @views @. u_acoustics[4:5, .., :] = mean_values.v_mean - @views @. u_acoustics[6, .., :] = mean_values.c_mean - @views @. u_acoustics[7, .., :] = mean_values.rho_mean - - # Adjust stepsize, advance the flow solver by one time step - cb.affect!(integrator_acoustics) - - return nothing -end - -# This function is called at the end of every time step and advances the Euler solution by one -# time step, manages the time stepsize for both the acoustics and Euler solvers and calculates the -# acoustic sources for the next acoustics time step -function (euler_acoustics_coupling::EulerAcousticsCouplingCallback)(integrator_acoustics) - @unpack stepsize_callback_acoustics, stepsize_callback_euler, integrator_euler = euler_acoustics_coupling - - @assert integrator_acoustics.t == integrator_euler.t - - # Use the minimum of the acoustics and Euler stepsizes for both solvers - stepsize_callback_acoustics(integrator_acoustics) - stepsize_callback_euler(integrator_euler) - dt = min(get_proposed_dt(integrator_acoustics), get_proposed_dt(integrator_euler)) - - set_proposed_dt!(integrator_acoustics, dt) - integrator_acoustics.opts.dtmax = dt - integrator_acoustics.dtcache = dt - - set_proposed_dt!(integrator_euler, dt) - integrator_euler.opts.dtmax = dt - integrator_euler.dtcache = dt - - # Advance the Euler solution by one step and check for errors - if !isfinished(integrator_euler) - @trixi_timeit timer() "Euler solver" step!(integrator_euler) - return_code = check_error(integrator_euler) - if !(SciMLBase.successful_retcode(return_code) || - return_code != SciMLBase.ReturnCode.Default) - error("Error during compressible Euler time integration. Received return code $(return_code)") - end + #! format: noindent + + @doc raw""" + EulerAcousticsCouplingCallback + + !!! warning "Experimental code" + This callback is experimental and may change in any future release. + + A callback that couples the acoustic perturbation equations and compressible Euler equations. Must + be used in conjunction with [`SemidiscretizationEulerAcoustics`](@ref). + This callback manages the flow solver - which is always one time step ahead of the + acoustics solver - and calculates the acoustic source term after each time step. The linearized + Lamb vector is used as the source term, i.e. + ```math + \mathbf{s} = -(\mathbf{\omega'} \times \bar{\mathbf{v}} + + \bar{\mathbf{\omega}} \times \mathbf{v'}), + ``` + where ``\mathbf{v}`` denotes the velocity, ``\mathbf{\omega}`` denotes the vorticity, the bar + ``\bar{(\cdot)}`` indicates time-averaged quantities (see [`AveragingCallback`](@ref)) and prime + ``(\cdot)'`` denotes perturbed quantities defined by ``\phi' = \phi - \bar{\phi}``. Note that + the perturbed quantities here are based entirely on the pure flow solution and should not be + confused with the state variables of the acoustic perturbation equations. + + In addition, this callback manages the time step size for both solvers + and initializes the mean values of the acoustic perturbation equations using results obtained with + the [`AveragingCallback`](@ref). + + - Michael Schlottke-Lakemper (2017) + A direct-hybrid method for aeroacoustic analysis + [DOI: 10.18154/RWTH-2017-04082](https://doi.org/10.18154/RWTH-2017-04082) + """ + mutable struct EulerAcousticsCouplingCallback{ + RealT <: Real, MeanValues, + IntegratorEuler, + } + stepsize_callback_acoustics::StepsizeCallback{RealT} + stepsize_callback_euler::StepsizeCallback{RealT} + mean_values::MeanValues + integrator_euler::IntegratorEuler end - # Calculate acoustic sources based on linearized lamb vector - semi = integrator_acoustics.p - semi_euler = integrator_euler.p - u_acoustics = wrap_array(integrator_acoustics.u, semi) - u_euler = wrap_array(integrator_euler.u, semi_euler) - @unpack acoustic_source_terms, coupled_element_ids = semi.cache - @unpack vorticity_mean = euler_acoustics_coupling.mean_values - - @trixi_timeit timer() "calc acoustic source terms" begin - calc_acoustic_sources!(acoustic_source_terms, u_euler, u_acoustics, - vorticity_mean, coupled_element_ids, - mesh_equations_solver_cache(semi_euler)...) + function Base.show( + io::IO, + cb::DiscreteCallback{<:Any, <:EulerAcousticsCouplingCallback} + ) + @nospecialize cb # reduce precompilation time + euler_acoustics_coupling = cb.affect! + + print(io, "EulerAcousticsCouplingCallback(") + print(io, euler_acoustics_coupling.stepsize_callback_acoustics) + print(io, ", ", euler_acoustics_coupling.stepsize_callback_euler, ")") end - # avoid re-evaluation possible FSAL stages - u_modified!(integrator_acoustics, false) - u_modified!(integrator_euler, false) + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:EulerAcousticsCouplingCallback} + ) + @nospecialize cb # reduce precompilation time + euler_acoustics_coupling = cb.affect! + + summary_header(io, "EulerAcousticsCouplingCallback") + summary_line( + io, "acoustics StepsizeCallback", + euler_acoustics_coupling.stepsize_callback_acoustics + ) + summary_line( + io, "Euler StepsizeCallback", + euler_acoustics_coupling.stepsize_callback_euler + ) + summary_footer(io) + end + + """ + EulerAcousticsCouplingCallback(ode_euler, + averaging_callback::DiscreteCallback{<:Any, <:AveragingCallback}, + alg, cfl_acoustics::Real, cfl_euler::Real; kwargs...) + + !!! warning "Experimental code" + This callback is experimental and may change in any future release. + + Creates an [`EulerAcousticsCouplingCallback`](@ref) based on the pure flow `ODEProblem` given by + `ode_euler`. Creates an integrator using the time integration method `alg` and the keyword arguments + to solve `ode_euler` (consult the [OrdinaryDiffEq documentation](https://diffeq.sciml.ai/stable/) + for further information). + Manages the step size for both solvers by using the minimum of the maximum step size obtained with + CFL numbers `cfl_acoustics` for the acoustics solver and `cfl_euler` for and flow solver, + respectively. + The mean values for the acoustic perturbation equations are read from `averaging_callback` + (see [`AveragingCallback`](@ref)). + """ + function EulerAcousticsCouplingCallback( + ode_euler, + averaging_callback::DiscreteCallback{ + <:Any, + <:AveragingCallback, + }, + alg, cfl_acoustics::Real, cfl_euler::Real; + kwargs... + ) + @unpack mean_values = averaging_callback.affect! + + return EulerAcousticsCouplingCallback( + ode_euler, mean_values, alg, cfl_acoustics, + cfl_euler; + kwargs... + ) + end + + """ + EulerAcousticsCouplingCallback(ode_euler, averaging_file::AbstractString, alg, + cfl_acoustics::Real, cfl_euler::Real; kwargs...) + + !!! warning "Experimental code" + This callback is experimental and may change in any future release. + + Creates an [`EulerAcousticsCouplingCallback`](@ref) based on the pure flow `ODEProblem` given by + `ode_euler`. Creates an integrator using the time integration method `alg` and the keyword arguments + to solve `ode_euler` (consult the [OrdinaryDiffEq documentation](https://diffeq.sciml.ai/stable/) + for further information). + Manages the step size for both solvers by using the minimum of the maximum step size obtained with + CFL numbers `cfl_acoustics` for the acoustics solver and `cfl_euler` for and flow solver, + respectively. + The mean values for the acoustic perturbation equations are read from `averaging_file` + (see [`AveragingCallback`](@ref)). + """ + function EulerAcousticsCouplingCallback( + ode_euler, averaging_file::AbstractString, alg, + cfl_acoustics::Real, cfl_euler::Real; kwargs... + ) + semi_euler = ode_euler.p + mean_values = load_averaging_file(averaging_file, semi_euler) + + return EulerAcousticsCouplingCallback( + ode_euler, mean_values, alg, cfl_acoustics, + cfl_euler; + kwargs... + ) + end + + function EulerAcousticsCouplingCallback( + ode_euler, mean_values, alg, cfl_acoustics, + cfl_euler; + kwargs... + ) + # Set up ODE Integrator for Euler equations + integrator_euler = init(ode_euler, alg, save_everystep = false, dt = 1.0; kwargs...) # dt will be overwritten + + euler_acoustics_coupling = EulerAcousticsCouplingCallback{ + typeof(cfl_acoustics), + typeof(mean_values), + typeof(integrator_euler), + }( + StepsizeCallback(cfl_acoustics), + StepsizeCallback(cfl_euler), + mean_values, + integrator_euler + ) + condition = (u, t, integrator) -> true + + return DiscreteCallback( + condition, euler_acoustics_coupling, + save_positions = (false, false), + initialize = initialize! + ) + end - return nothing -end + # This is called before the main loop and initializes the mean values in u_ode + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u_ode, t, + integrator_acoustics + ) where { + Condition, + Affect! <: + EulerAcousticsCouplingCallback, + } + euler_acoustics_coupling = cb.affect! + semi = integrator_acoustics.p + @unpack semi_acoustics = semi + + # Initialize mean values in u_ode + u_acoustics = wrap_array(u_ode, semi_acoustics) + @unpack mean_values = euler_acoustics_coupling + @views @. u_acoustics[4:5, .., :] = mean_values.v_mean + @views @. u_acoustics[6, .., :] = mean_values.c_mean + @views @. u_acoustics[7, .., :] = mean_values.rho_mean + + # Adjust stepsize, advance the flow solver by one time step + cb.affect!(integrator_acoustics) + + return nothing + end + + # This function is called at the end of every time step and advances the Euler solution by one + # time step, manages the time stepsize for both the acoustics and Euler solvers and calculates the + # acoustic sources for the next acoustics time step + function (euler_acoustics_coupling::EulerAcousticsCouplingCallback)(integrator_acoustics) + @unpack stepsize_callback_acoustics, stepsize_callback_euler, integrator_euler = euler_acoustics_coupling + + @assert integrator_acoustics.t == integrator_euler.t + + # Use the minimum of the acoustics and Euler stepsizes for both solvers + stepsize_callback_acoustics(integrator_acoustics) + stepsize_callback_euler(integrator_euler) + dt = min(get_proposed_dt(integrator_acoustics), get_proposed_dt(integrator_euler)) + + set_proposed_dt!(integrator_acoustics, dt) + integrator_acoustics.opts.dtmax = dt + integrator_acoustics.dtcache = dt + + set_proposed_dt!(integrator_euler, dt) + integrator_euler.opts.dtmax = dt + integrator_euler.dtcache = dt + + # Advance the Euler solution by one step and check for errors + if !isfinished(integrator_euler) + @trixi_timeit timer() "Euler solver" step!(integrator_euler) + return_code = check_error(integrator_euler) + if !( + SciMLBase.successful_retcode(return_code) || + return_code != SciMLBase.ReturnCode.Default + ) + error("Error during compressible Euler time integration. Received return code $(return_code)") + end + end + + # Calculate acoustic sources based on linearized lamb vector + semi = integrator_acoustics.p + semi_euler = integrator_euler.p + u_acoustics = wrap_array(integrator_acoustics.u, semi) + u_euler = wrap_array(integrator_euler.u, semi_euler) + @unpack acoustic_source_terms, coupled_element_ids = semi.cache + @unpack vorticity_mean = euler_acoustics_coupling.mean_values + + @trixi_timeit timer() "calc acoustic source terms" begin + calc_acoustic_sources!( + acoustic_source_terms, u_euler, u_acoustics, + vorticity_mean, coupled_element_ids, + mesh_equations_solver_cache(semi_euler)... + ) + end + + # avoid re-evaluation possible FSAL stages + u_modified!(integrator_acoustics, false) + u_modified!(integrator_euler, false) + + return nothing + end -include("euler_acoustics_coupling_dg2d.jl") + include("euler_acoustics_coupling_dg2d.jl") end # @muladd diff --git a/src/callbacks_step/euler_acoustics_coupling_dg2d.jl b/src/callbacks_step/euler_acoustics_coupling_dg2d.jl index 8a8bb893dc..0d23b0bc88 100644 --- a/src/callbacks_step/euler_acoustics_coupling_dg2d.jl +++ b/src/callbacks_step/euler_acoustics_coupling_dg2d.jl @@ -3,42 +3,48 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function calc_acoustic_sources!(acoustic_source_terms, u_euler, u_acoustics, - vorticity_mean, - coupled_element_ids, mesh, - equations::AbstractCompressibleEulerEquations{2}, - dg::DGSEM, cache) - acoustic_source_terms .= zero(eltype(acoustic_source_terms)) + function calc_acoustic_sources!( + acoustic_source_terms, u_euler, u_acoustics, + vorticity_mean, + coupled_element_ids, mesh, + equations::AbstractCompressibleEulerEquations{2}, + dg::DGSEM, cache + ) + acoustic_source_terms .= zero(eltype(acoustic_source_terms)) - @threaded for k in eachindex(coupled_element_ids) - element = coupled_element_ids[k] + @threaded for k in eachindex(coupled_element_ids) + element = coupled_element_ids[k] - for j in eachnode(dg), i in eachnode(dg) - vorticity = calc_vorticity_node(u_euler, mesh, equations, dg, cache, i, j, - element) + for j in eachnode(dg), i in eachnode(dg) + vorticity = calc_vorticity_node( + u_euler, mesh, equations, dg, cache, i, j, + element + ) - prim_euler = cons2prim(get_node_vars(u_euler, equations, dg, i, j, element), - equations) - v1 = prim_euler[2] - v2 = prim_euler[3] - v1_mean = u_acoustics[4, i, j, element] - v2_mean = u_acoustics[5, i, j, element] + prim_euler = cons2prim( + get_node_vars(u_euler, equations, dg, i, j, element), + equations + ) + v1 = prim_euler[2] + v2 = prim_euler[3] + v1_mean = u_acoustics[4, i, j, element] + v2_mean = u_acoustics[5, i, j, element] - vorticity_prime = vorticity - vorticity_mean[i, j, element] - v1_prime = v1 - v1_mean - v2_prime = v2 - v2_mean + vorticity_prime = vorticity - vorticity_mean[i, j, element] + v1_prime = v1 - v1_mean + v2_prime = v2 - v2_mean - acoustic_source_terms[1, i, j, k] -= -vorticity_prime * v2_mean - - vorticity_mean[i, j, element] * - v2_prime - acoustic_source_terms[2, i, j, k] -= vorticity_prime * v1_mean + - vorticity_mean[i, j, element] * - v1_prime + acoustic_source_terms[1, i, j, k] -= -vorticity_prime * v2_mean - + vorticity_mean[i, j, element] * + v2_prime + acoustic_source_terms[2, i, j, k] -= vorticity_prime * v1_mean + + vorticity_mean[i, j, element] * + v1_prime + end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_step/glm_speed.jl b/src/callbacks_step/glm_speed.jl index 8ee406af5f..4851a01c07 100644 --- a/src/callbacks_step/glm_speed.jl +++ b/src/callbacks_step/glm_speed.jl @@ -3,105 +3,113 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - GlmSpeedCallback(; glm_scale=0.5, cfl, semi_indices=()) - -Update the divergence cleaning wave speed `c_h` according to the time step -computed in [`StepsizeCallback`](@ref) for the ideal GLM-MHD equations. -The `cfl` number should be set to the same value as for the time step size calculation. The -`glm_scale` ensures that the GLM wave speed is lower than the fastest physical waves in the MHD -solution and should thus be set to a value within the interval [0,1]. Note that `glm_scale = 0` -deactivates the divergence cleaning. - -In case of coupled semidiscretizations, specify for which `semi_index`, i.e. index of the -semidiscretization, the divergence cleaning should be applied. See also -[`SemidiscretizationCoupled`](@ref). -Note: `SemidiscretizationCoupled` and all related features are considered experimental and -may change at any time. -""" -struct GlmSpeedCallback{RealT <: Real} - glm_scale::RealT - cfl::RealT - semi_indices::Vector{Int} -end - -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:GlmSpeedCallback}) - @nospecialize cb # reduce precompilation time - - glm_speed_callback = cb.affect! - @unpack glm_scale, cfl, semi_indices = glm_speed_callback - print(io, "GlmSpeedCallback(glm_scale=", glm_scale, ", cfl=", cfl, "semi_indices=", - semi_indices, ")") -end - -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:GlmSpeedCallback}) - @nospecialize cb # reduce precompilation time - - if get(io, :compact, false) - show(io, cb) - else + #! format: noindent + + """ + GlmSpeedCallback(; glm_scale=0.5, cfl, semi_indices=()) + + Update the divergence cleaning wave speed `c_h` according to the time step + computed in [`StepsizeCallback`](@ref) for the ideal GLM-MHD equations. + The `cfl` number should be set to the same value as for the time step size calculation. The + `glm_scale` ensures that the GLM wave speed is lower than the fastest physical waves in the MHD + solution and should thus be set to a value within the interval [0,1]. Note that `glm_scale = 0` + deactivates the divergence cleaning. + + In case of coupled semidiscretizations, specify for which `semi_index`, i.e. index of the + semidiscretization, the divergence cleaning should be applied. See also + [`SemidiscretizationCoupled`](@ref). + Note: `SemidiscretizationCoupled` and all related features are considered experimental and + may change at any time. + """ + struct GlmSpeedCallback{RealT <: Real} + glm_scale::RealT + cfl::RealT + semi_indices::Vector{Int} + end + + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:GlmSpeedCallback}) + @nospecialize cb # reduce precompilation time + glm_speed_callback = cb.affect! + @unpack glm_scale, cfl, semi_indices = glm_speed_callback + print( + io, "GlmSpeedCallback(glm_scale=", glm_scale, ", cfl=", cfl, "semi_indices=", + semi_indices, ")" + ) + end - setup = [ - "GLM wave speed scaling" => glm_speed_callback.glm_scale, - "Expected CFL number" => glm_speed_callback.cfl, - "Selected semidiscretizations" => glm_speed_callback.semi_indices, - ] - summary_box(io, "GlmSpeedCallback", setup) + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:GlmSpeedCallback} + ) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + glm_speed_callback = cb.affect! + + setup = [ + "GLM wave speed scaling" => glm_speed_callback.glm_scale, + "Expected CFL number" => glm_speed_callback.cfl, + "Selected semidiscretizations" => glm_speed_callback.semi_indices, + ] + summary_box(io, "GlmSpeedCallback", setup) + end end -end -function GlmSpeedCallback(; glm_scale = 0.5, cfl, semi_indices = Int[]) - @assert 0<=glm_scale<=1 "glm_scale must be between 0 and 1" + function GlmSpeedCallback(; glm_scale = 0.5, cfl, semi_indices = Int[]) + @assert 0 <= glm_scale <= 1 "glm_scale must be between 0 and 1" - glm_speed_callback = GlmSpeedCallback(glm_scale, cfl, semi_indices) + glm_speed_callback = GlmSpeedCallback(glm_scale, cfl, semi_indices) - DiscreteCallback(glm_speed_callback, glm_speed_callback, # the first one is the condition, the second the affect! - save_positions = (false, false), - initialize = initialize!) -end + DiscreteCallback( + glm_speed_callback, glm_speed_callback, # the first one is the condition, the second the affect! + save_positions = (false, false), + initialize = initialize! + ) + end -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t, - integrator) where {Condition, Affect! <: GlmSpeedCallback} - cb.affect!(integrator) -end + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u, t, + integrator + ) where {Condition, Affect! <: GlmSpeedCallback} + cb.affect!(integrator) + end -# this method is called to determine whether the callback should be activated -function (glm_speed_callback::GlmSpeedCallback)(u, t, integrator) - return true -end + # this method is called to determine whether the callback should be activated + function (glm_speed_callback::GlmSpeedCallback)(u, t, integrator) + return true + end -function update_cleaning_speed!(semi, glm_speed_callback, dt) - @unpack glm_scale, cfl = glm_speed_callback + function update_cleaning_speed!(semi, glm_speed_callback, dt) + @unpack glm_scale, cfl = glm_speed_callback - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - # compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR) - c_h_deltat = calc_dt_for_cleaning_speed(cfl, mesh, equations, solver, cache) + # compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR) + c_h_deltat = calc_dt_for_cleaning_speed(cfl, mesh, equations, solver, cache) - # c_h is proportional to its own time step divided by the complete MHD time step - equations.c_h = glm_scale * c_h_deltat / dt + # c_h is proportional to its own time step divided by the complete MHD time step + equations.c_h = glm_scale * c_h_deltat / dt - return semi -end + return semi + end -# This method is called as callback after the StepsizeCallback during the time integration. -@inline function (glm_speed_callback::GlmSpeedCallback)(integrator) - dt = get_proposed_dt(integrator) - semi = integrator.p + # This method is called as callback after the StepsizeCallback during the time integration. + @inline function (glm_speed_callback::GlmSpeedCallback)(integrator) + dt = get_proposed_dt(integrator) + semi = integrator.p - # Call the appropriate update function (this indirection allows to specialize on, - # e.g., the semidiscretization type) - update_cleaning_speed!(semi, glm_speed_callback, dt) + # Call the appropriate update function (this indirection allows to specialize on, + # e.g., the semidiscretization type) + update_cleaning_speed!(semi, glm_speed_callback, dt) - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) - return nothing -end + return nothing + end -include("glm_speed_dg.jl") + include("glm_speed_dg.jl") end # @muladd diff --git a/src/callbacks_step/glm_speed_dg.jl b/src/callbacks_step/glm_speed_dg.jl index 302aae356a..bbcf74a490 100644 --- a/src/callbacks_step/glm_speed_dg.jl +++ b/src/callbacks_step/glm_speed_dg.jl @@ -3,37 +3,45 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function calc_dt_for_cleaning_speed(cfl::Real, mesh, - equations::Union{AbstractIdealGlmMhdEquations, - AbstractIdealGlmMhdMulticomponentEquations}, - dg::DG, cache) - # compute time step for GLM linear advection equation with c_h=1 for the DG discretization on - # Cartesian meshes - max_scaled_speed_for_c_h = maximum(cache.elements.inverse_jacobian) * - ndims(equations) - # OBS! This depends on the implementation details of the StepsizeCallback and needs to be adapted - # as well if that callback changes. - return cfl * 2 / (nnodes(dg) * max_scaled_speed_for_c_h) -end + function calc_dt_for_cleaning_speed( + cfl::Real, mesh, + equations::Union{ + AbstractIdealGlmMhdEquations, + AbstractIdealGlmMhdMulticomponentEquations, + }, + dg::DG, cache + ) + # compute time step for GLM linear advection equation with c_h=1 for the DG discretization on + # Cartesian meshes + max_scaled_speed_for_c_h = maximum(cache.elements.inverse_jacobian) * + ndims(equations) + # OBS! This depends on the implementation details of the StepsizeCallback and needs to be adapted + # as well if that callback changes. + return cfl * 2 / (nnodes(dg) * max_scaled_speed_for_c_h) + end -function calc_dt_for_cleaning_speed(cfl::Real, mesh, - equations::Union{AbstractIdealGlmMhdEquations, - AbstractIdealGlmMhdMulticomponentEquations}, - dg::DGMulti, cache) - rd = dg.basis - md = mesh.md + function calc_dt_for_cleaning_speed( + cfl::Real, mesh, + equations::Union{ + AbstractIdealGlmMhdEquations, + AbstractIdealGlmMhdMulticomponentEquations, + }, + dg::DGMulti, cache + ) + rd = dg.basis + md = mesh.md - # Compute time step for GLM linear advection equation with c_h=1 for a DGMulti discretization. - # Copies implementation behavior of `calc_dt_for_cleaning_speed` for DGSEM discretizations. - max_scaled_speed_for_c_h = inv(minimum(md.J)) * ndims(equations) + # Compute time step for GLM linear advection equation with c_h=1 for a DGMulti discretization. + # Copies implementation behavior of `calc_dt_for_cleaning_speed` for DGSEM discretizations. + max_scaled_speed_for_c_h = inv(minimum(md.J)) * ndims(equations) - # This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by - # `polydeg+1`. This is because `nnodes(dg)` returns the total number of - # multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns - # the number of 1D nodes for `DGSEM` solvers. - polydeg = rd.N - return cfl * 2 / ((polydeg + 1) * max_scaled_speed_for_c_h) -end + # This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by + # `polydeg+1`. This is because `nnodes(dg)` returns the total number of + # multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns + # the number of 1D nodes for `DGSEM` solvers. + polydeg = rd.N + return cfl * 2 / ((polydeg + 1) * max_scaled_speed_for_c_h) + end end # @muladd diff --git a/src/callbacks_step/lbm_collision.jl b/src/callbacks_step/lbm_collision.jl index 33c2806d6a..a963972612 100644 --- a/src/callbacks_step/lbm_collision.jl +++ b/src/callbacks_step/lbm_collision.jl @@ -3,64 +3,76 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - LBMCollisionCallback() + """ + LBMCollisionCallback() -Apply the Lattice-Boltzmann method (LBM) collision operator before each time step. -See [`LatticeBoltzmannEquations2D`](@ref) for further details. -""" -function LBMCollisionCallback() - DiscreteCallback(lbm_collision_callback, lbm_collision_callback, - save_positions = (false, false), - initialize = initialize!) -end + Apply the Lattice-Boltzmann method (LBM) collision operator before each time step. + See [`LatticeBoltzmannEquations2D`](@ref) for further details. + """ + function LBMCollisionCallback() + DiscreteCallback( + lbm_collision_callback, lbm_collision_callback, + save_positions = (false, false), + initialize = initialize! + ) + end -# Always execute collision step after a time step, but not after the last step -lbm_collision_callback(u, t, integrator) = !isfinished(integrator) + # Always execute collision step after a time step, but not after the last step + lbm_collision_callback(u, t, integrator) = !isfinished(integrator) -function Base.show(io::IO, - cb::DiscreteCallback{<:Any, <:typeof(lbm_collision_callback)}) - @nospecialize cb # reduce precompilation time + function Base.show( + io::IO, + cb::DiscreteCallback{<:Any, <:typeof(lbm_collision_callback)} + ) + @nospecialize cb # reduce precompilation time - print(io, "LBMCollisionCallback()") -end + print(io, "LBMCollisionCallback()") + end -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:typeof(lbm_collision_callback)}) - @nospecialize cb # reduce precompilation time + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:typeof(lbm_collision_callback)} + ) + @nospecialize cb # reduce precompilation time - if get(io, :compact, false) - show(io, cb) - else - summary_box(io, "LBMCollisionCallback") + if get(io, :compact, false) + show(io, cb) + else + summary_box(io, "LBMCollisionCallback") + end end -end -# Execute collision step once in the very beginning -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t, - integrator) where {Condition, - Affect! <: typeof(lbm_collision_callback)} - cb.affect!(integrator) -end + # Execute collision step once in the very beginning + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u, t, + integrator + ) where { + Condition, + Affect! <: typeof(lbm_collision_callback), + } + cb.affect!(integrator) + end -# This method is called as callback after the StepsizeCallback during the time integration. -@inline function lbm_collision_callback(integrator) - dt = get_proposed_dt(integrator) - semi = integrator.p - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - @unpack collision_op = equations + # This method is called as callback after the StepsizeCallback during the time integration. + @inline function lbm_collision_callback(integrator) + dt = get_proposed_dt(integrator) + semi = integrator.p + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + @unpack collision_op = equations - u_ode = integrator.u - u = wrap_array(u_ode, mesh, equations, solver, cache) + u_ode = integrator.u + u = wrap_array(u_ode, mesh, equations, solver, cache) - @trixi_timeit timer() "LBM collision" apply_collision!(u, dt, collision_op, mesh, - equations, solver, cache) + @trixi_timeit timer() "LBM collision" apply_collision!( + u, dt, collision_op, mesh, + equations, solver, cache + ) - return nothing -end + return nothing + end -include("lbm_collision_dg2d.jl") -include("lbm_collision_dg3d.jl") + include("lbm_collision_dg2d.jl") + include("lbm_collision_dg3d.jl") end # @muladd diff --git a/src/callbacks_step/lbm_collision_dg2d.jl b/src/callbacks_step/lbm_collision_dg2d.jl index 932edfd61f..083a1f6884 100644 --- a/src/callbacks_step/lbm_collision_dg2d.jl +++ b/src/callbacks_step/lbm_collision_dg2d.jl @@ -3,18 +3,20 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function apply_collision!(u, dt, collision_op, - mesh::AbstractMesh{2}, equations, dg::DG, cache) - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - update = collision_op(u_node, dt, equations) - add_to_node_vars!(u, update, equations, dg, i, j, element) + function apply_collision!( + u, dt, collision_op, + mesh::AbstractMesh{2}, equations, dg::DG, cache + ) + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + update = collision_op(u_node, dt, equations) + add_to_node_vars!(u, update, equations, dg, i, j, element) + end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_step/lbm_collision_dg3d.jl b/src/callbacks_step/lbm_collision_dg3d.jl index 0620f77159..3299facacf 100644 --- a/src/callbacks_step/lbm_collision_dg3d.jl +++ b/src/callbacks_step/lbm_collision_dg3d.jl @@ -3,18 +3,20 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function apply_collision!(u, dt, collision_op, - mesh::AbstractMesh{3}, equations, dg::DG, cache) - @threaded for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - update = collision_op(u_node, dt, equations) - add_to_node_vars!(u, update, equations, dg, i, j, k, element) + function apply_collision!( + u, dt, collision_op, + mesh::AbstractMesh{3}, equations, dg::DG, cache + ) + @threaded for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + update = collision_op(u_node, dt, equations) + add_to_node_vars!(u, update, equations, dg, i, j, k, element) + end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/callbacks_step/save_restart.jl b/src/callbacks_step/save_restart.jl index 0b0d2420c7..57cb0c6c07 100644 --- a/src/callbacks_step/save_restart.jl +++ b/src/callbacks_step/save_restart.jl @@ -3,205 +3,227 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - SaveRestartCallback(; interval=0, - save_final_restart=true, - output_directory="out") - -Save the current numerical solution in a restart file every `interval` time steps. -""" -mutable struct SaveRestartCallback - interval::Int - save_final_restart::Bool - output_directory::String -end - -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:SaveRestartCallback}) - @nospecialize cb # reduce precompilation time - - restart_callback = cb.affect! - print(io, "SaveRestartCallback(interval=", restart_callback.interval, ")") -end - -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:SaveRestartCallback}) - @nospecialize cb # reduce precompilation time - - if get(io, :compact, false) - show(io, cb) - else - save_restart_callback = cb.affect! - - setup = [ - "interval" => save_restart_callback.interval, - "save final solution" => save_restart_callback.save_final_restart ? "yes" : - "no", - "output directory" => abspath(normpath(save_restart_callback.output_directory)), - ] - summary_box(io, "SaveRestartCallback", setup) + #! format: noindent + + """ + SaveRestartCallback(; interval=0, + save_final_restart=true, + output_directory="out") + + Save the current numerical solution in a restart file every `interval` time steps. + """ + mutable struct SaveRestartCallback + interval::Int + save_final_restart::Bool + output_directory::String end -end - -function SaveRestartCallback(; interval = 0, - save_final_restart = true, - output_directory = "out") - restart_callback = SaveRestartCallback(interval, save_final_restart, - output_directory) - - DiscreteCallback(restart_callback, restart_callback, # the first one is the condition, the second the affect! - save_positions = (false, false), - initialize = initialize!) -end - -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t, - integrator) where {Condition, Affect! <: SaveRestartCallback} - restart_callback = cb.affect! - - mpi_isroot() && mkpath(restart_callback.output_directory) - - semi = integrator.p - mesh, _, _, _ = mesh_equations_solver_cache(semi) - @trixi_timeit timer() "I/O" begin - if mesh.unsaved_changes - mesh.current_filename = save_mesh_file(mesh, - restart_callback.output_directory) - mesh.unsaved_changes = false + + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:SaveRestartCallback}) + @nospecialize cb # reduce precompilation time + + restart_callback = cb.affect! + print(io, "SaveRestartCallback(interval=", restart_callback.interval, ")") + end + + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:SaveRestartCallback} + ) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + save_restart_callback = cb.affect! + + setup = [ + "interval" => save_restart_callback.interval, + "save final solution" => save_restart_callback.save_final_restart ? "yes" : + "no", + "output directory" => abspath(normpath(save_restart_callback.output_directory)), + ] + summary_box(io, "SaveRestartCallback", setup) end end - return nothing -end - -# this method is called to determine whether the callback should be activated -function (restart_callback::SaveRestartCallback)(u, t, integrator) - @unpack interval, save_final_restart = restart_callback - - # With error-based step size control, some steps can be rejected. Thus, - # `integrator.iter >= integrator.stats.naccept` - # (total #steps) (#accepted steps) - # We need to check the number of accepted steps since callbacks are not - # activated after a rejected step. - return interval > 0 && (integrator.stats.naccept % interval == 0 || - (save_final_restart && isfinished(integrator))) -end - -# this method is called when the callback is activated -function (restart_callback::SaveRestartCallback)(integrator) - u_ode = integrator.u - @unpack t, dt = integrator - iter = integrator.stats.naccept - semi = integrator.p - mesh, _, _, _ = mesh_equations_solver_cache(semi) - - @trixi_timeit timer() "I/O" begin - if mesh.unsaved_changes - mesh.current_filename = save_mesh_file(mesh, - restart_callback.output_directory, - iter) - mesh.unsaved_changes = false + function SaveRestartCallback(; + interval = 0, + save_final_restart = true, + output_directory = "out" + ) + restart_callback = SaveRestartCallback( + interval, save_final_restart, + output_directory + ) + + DiscreteCallback( + restart_callback, restart_callback, # the first one is the condition, the second the affect! + save_positions = (false, false), + initialize = initialize! + ) + end + + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u, t, + integrator + ) where {Condition, Affect! <: SaveRestartCallback} + restart_callback = cb.affect! + + mpi_isroot() && mkpath(restart_callback.output_directory) + + semi = integrator.p + mesh, _, _, _ = mesh_equations_solver_cache(semi) + @trixi_timeit timer() "I/O" begin + if mesh.unsaved_changes + mesh.current_filename = save_mesh_file( + mesh, + restart_callback.output_directory + ) + mesh.unsaved_changes = false + end end - save_restart_file(u_ode, t, dt, iter, semi, restart_callback) - # If using an adaptive time stepping scheme, store controller values for restart - if integrator.opts.adaptive - save_adaptive_time_integrator(integrator, integrator.opts.controller, - restart_callback) + return nothing + end + + # this method is called to determine whether the callback should be activated + function (restart_callback::SaveRestartCallback)(u, t, integrator) + @unpack interval, save_final_restart = restart_callback + + # With error-based step size control, some steps can be rejected. Thus, + # `integrator.iter >= integrator.stats.naccept` + # (total #steps) (#accepted steps) + # We need to check the number of accepted steps since callbacks are not + # activated after a rejected step. + return interval > 0 && ( + integrator.stats.naccept % interval == 0 || + (save_final_restart && isfinished(integrator)) + ) + end + + # this method is called when the callback is activated + function (restart_callback::SaveRestartCallback)(integrator) + u_ode = integrator.u + @unpack t, dt = integrator + iter = integrator.stats.naccept + semi = integrator.p + mesh, _, _, _ = mesh_equations_solver_cache(semi) + + @trixi_timeit timer() "I/O" begin + if mesh.unsaved_changes + mesh.current_filename = save_mesh_file( + mesh, + restart_callback.output_directory, + iter + ) + mesh.unsaved_changes = false + end + + save_restart_file(u_ode, t, dt, iter, semi, restart_callback) + # If using an adaptive time stepping scheme, store controller values for restart + if integrator.opts.adaptive + save_adaptive_time_integrator( + integrator, integrator.opts.controller, + restart_callback + ) + end end + + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) + return nothing + end + + @inline function save_restart_file( + u_ode, t, dt, iter, + semi::AbstractSemidiscretization, restart_callback + ) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + u = wrap_array_native(u_ode, mesh, equations, solver, cache) + save_restart_file(u, t, dt, iter, mesh, equations, solver, cache, restart_callback) end - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) - return nothing -end - -@inline function save_restart_file(u_ode, t, dt, iter, - semi::AbstractSemidiscretization, restart_callback) - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - u = wrap_array_native(u_ode, mesh, equations, solver, cache) - save_restart_file(u, t, dt, iter, mesh, equations, solver, cache, restart_callback) -end - -""" - load_time(restart_file::AbstractString) - -Load the time saved in a `restart_file`. -""" -function load_time(restart_file::AbstractString) - h5open(restart_file, "r") do file - read(attributes(file)["time"]) + """ + load_time(restart_file::AbstractString) + + Load the time saved in a `restart_file`. + """ + function load_time(restart_file::AbstractString) + h5open(restart_file, "r") do file + read(attributes(file)["time"]) + end end -end -""" - load_timestep(restart_file::AbstractString) + """ + load_timestep(restart_file::AbstractString) -Load the time step number (`iter` in OrdinaryDiffEq.jl) saved in a `restart_file`. -""" -function load_timestep(restart_file::AbstractString) - h5open(restart_file, "r") do file - read(attributes(file)["timestep"]) + Load the time step number (`iter` in OrdinaryDiffEq.jl) saved in a `restart_file`. + """ + function load_timestep(restart_file::AbstractString) + h5open(restart_file, "r") do file + read(attributes(file)["timestep"]) + end end -end - -""" - load_timestep!(integrator, restart_file::AbstractString) - -Load the time step number saved in a `restart_file` and assign it to both the time step -number and and the number of accepted steps -(`iter` and `stats.naccept` in OrdinaryDiffEq.jl, respectively) in `integrator`. -""" -function load_timestep!(integrator, restart_file::AbstractString) - integrator.iter = load_timestep(restart_file) - integrator.stats.naccept = integrator.iter -end - -""" - load_dt(restart_file::AbstractString) - -Load the time step size (`dt` in OrdinaryDiffEq.jl) saved in a `restart_file`. -""" -function load_dt(restart_file::AbstractString) - h5open(restart_file, "r") do file - read(attributes(file)["dt"]) + + """ + load_timestep!(integrator, restart_file::AbstractString) + + Load the time step number saved in a `restart_file` and assign it to both the time step + number and and the number of accepted steps + (`iter` and `stats.naccept` in OrdinaryDiffEq.jl, respectively) in `integrator`. + """ + function load_timestep!(integrator, restart_file::AbstractString) + integrator.iter = load_timestep(restart_file) + integrator.stats.naccept = integrator.iter end -end - -function load_restart_file(semi::AbstractSemidiscretization, restart_file) - load_restart_file(mesh_equations_solver_cache(semi)..., restart_file) -end - -""" - load_adaptive_time_integrator!(integrator, restart_file::AbstractString) - -Load the context information for time integrators with error-based step size control -saved in a `restart_file`. -""" -function load_adaptive_time_integrator!(integrator, restart_file::AbstractString) - controller = integrator.opts.controller - # Read context information for controller - h5open(restart_file, "r") do file - # Ensure that the necessary information was saved - if !("time_integrator_qold" in keys(attributes(file))) || - !("time_integrator_dtpropose" in keys(attributes(file))) || - (hasproperty(controller, :err) && - !("time_integrator_controller_err" in keys(attributes(file)))) - error("Missing data in restart file: check the consistency of adaptive time controller with initial setup!") + + """ + load_dt(restart_file::AbstractString) + + Load the time step size (`dt` in OrdinaryDiffEq.jl) saved in a `restart_file`. + """ + function load_dt(restart_file::AbstractString) + h5open(restart_file, "r") do file + read(attributes(file)["dt"]) end - # Load data that is required both for PIController and PIDController - integrator.qold = read(attributes(file)["time_integrator_qold"]) - integrator.dtpropose = read(attributes(file)["time_integrator_dtpropose"]) - # Accept step to use dtpropose already in the first step - integrator.accept_step = true - # Reevaluate integrator.fsal_first on the first step - integrator.reeval_fsal = true - # Load additional parameters for PIDController - if hasproperty(controller, :err) # Distinguish PIDController from PIController - controller.err[:] = read(attributes(file)["time_integrator_controller_err"]) + end + + function load_restart_file(semi::AbstractSemidiscretization, restart_file) + load_restart_file(mesh_equations_solver_cache(semi)..., restart_file) + end + + """ + load_adaptive_time_integrator!(integrator, restart_file::AbstractString) + + Load the context information for time integrators with error-based step size control + saved in a `restart_file`. + """ + function load_adaptive_time_integrator!(integrator, restart_file::AbstractString) + controller = integrator.opts.controller + # Read context information for controller + h5open(restart_file, "r") do file + # Ensure that the necessary information was saved + if !("time_integrator_qold" in keys(attributes(file))) || + !("time_integrator_dtpropose" in keys(attributes(file))) || + ( + hasproperty(controller, :err) && + !("time_integrator_controller_err" in keys(attributes(file))) + ) + error("Missing data in restart file: check the consistency of adaptive time controller with initial setup!") + end + # Load data that is required both for PIController and PIDController + integrator.qold = read(attributes(file)["time_integrator_qold"]) + integrator.dtpropose = read(attributes(file)["time_integrator_dtpropose"]) + # Accept step to use dtpropose already in the first step + integrator.accept_step = true + # Reevaluate integrator.fsal_first on the first step + integrator.reeval_fsal = true + # Load additional parameters for PIDController + if hasproperty(controller, :err) # Distinguish PIDController from PIController + controller.err[:] = read(attributes(file)["time_integrator_controller_err"]) + end end end -end -include("save_restart_dg.jl") + include("save_restart_dg.jl") end # @muladd diff --git a/src/callbacks_step/save_restart_dg.jl b/src/callbacks_step/save_restart_dg.jl index 29b3858c13..46e53d0e98 100644 --- a/src/callbacks_step/save_restart_dg.jl +++ b/src/callbacks_step/save_restart_dg.jl @@ -3,352 +3,382 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function save_restart_file(u, time, dt, timestep, - mesh::Union{SerialTreeMesh, StructuredMesh, - UnstructuredMesh2D, SerialP4estMesh, - SerialT8codeMesh}, - equations, dg::DG, cache, - restart_callback) - @unpack output_directory = restart_callback - - # Filename based on current time step - filename = joinpath(output_directory, @sprintf("restart_%09d.h5", timestep)) - - # Restart files always store conservative variables - data = u - - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["equations"] = get_name(equations) - attributes(file)["polydeg"] = polydeg(dg) - attributes(file)["n_vars"] = nvariables(equations) - attributes(file)["n_elements"] = nelements(dg, cache) - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] - attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar - attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar - attributes(file)["timestep"] = timestep - - # Store each variable of the solution - for v in eachvariable(equations) - # Convert to 1D array - file["variables_$v"] = vec(data[v, .., :]) - - # Add variable name as attribute - var = file["variables_$v"] - attributes(var)["name"] = varnames(cons2cons, equations)[v] - end - end - - return filename -end - -function load_restart_file(mesh::Union{SerialTreeMesh, StructuredMesh, - UnstructuredMesh2D, SerialP4estMesh}, - equations, dg::DG, cache, restart_file) + #! format: noindent + + function save_restart_file( + u, time, dt, timestep, + mesh::Union{ + SerialTreeMesh, StructuredMesh, + UnstructuredMesh2D, SerialP4estMesh, + SerialT8codeMesh, + }, + equations, dg::DG, cache, + restart_callback + ) + @unpack output_directory = restart_callback - # allocate memory - u_ode = allocate_coefficients(mesh, equations, dg, cache) - u = wrap_array_native(u_ode, mesh, equations, dg, cache) + # Filename based on current time step + filename = joinpath(output_directory, @sprintf("restart_%09d.h5", timestep)) - h5open(restart_file, "r") do file - # Read attributes to perform some sanity checks - if read(attributes(file)["ndims"]) != ndims(mesh) - error("restart mismatch: ndims differs from value in restart file") - end - if read(attributes(file)["equations"]) != get_name(equations) - error("restart mismatch: equations differ from value in restart file") - end - if read(attributes(file)["polydeg"]) != polydeg(dg) - error("restart mismatch: polynomial degree in solver differs from value in restart file") - end - if read(attributes(file)["n_elements"]) != nelements(dg, cache) - error("restart mismatch: number of elements in solver differs from value in restart file") + # Restart files always store conservative variables + data = u + + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["equations"] = get_name(equations) + attributes(file)["polydeg"] = polydeg(dg) + attributes(file)["n_vars"] = nvariables(equations) + attributes(file)["n_elements"] = nelements(dg, cache) + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] + attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar + attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar + attributes(file)["timestep"] = timestep + + # Store each variable of the solution + for v in eachvariable(equations) + # Convert to 1D array + file["variables_$v"] = vec(data[v, .., :]) + + # Add variable name as attribute + var = file["variables_$v"] + attributes(var)["name"] = varnames(cons2cons, equations)[v] + end end - # Read data - for v in eachvariable(equations) - # Check if variable name matches - var = file["variables_$v"] - if (name = read(attributes(var)["name"])) != - varnames(cons2cons, equations)[v] - error("mismatch: variables_$v should be '$(varnames(cons2cons, equations)[v])', but found '$name'") + return filename + end + + function load_restart_file( + mesh::Union{ + SerialTreeMesh, StructuredMesh, + UnstructuredMesh2D, SerialP4estMesh, + }, + equations, dg::DG, cache, restart_file + ) + + # allocate memory + u_ode = allocate_coefficients(mesh, equations, dg, cache) + u = wrap_array_native(u_ode, mesh, equations, dg, cache) + + h5open(restart_file, "r") do file + # Read attributes to perform some sanity checks + if read(attributes(file)["ndims"]) != ndims(mesh) + error("restart mismatch: ndims differs from value in restart file") + end + if read(attributes(file)["equations"]) != get_name(equations) + error("restart mismatch: equations differ from value in restart file") + end + if read(attributes(file)["polydeg"]) != polydeg(dg) + error("restart mismatch: polynomial degree in solver differs from value in restart file") + end + if read(attributes(file)["n_elements"]) != nelements(dg, cache) + error("restart mismatch: number of elements in solver differs from value in restart file") end - # Read variable - u[v, .., :] = read(file["variables_$v"]) + # Read data + for v in eachvariable(equations) + # Check if variable name matches + var = file["variables_$v"] + if (name = read(attributes(var)["name"])) != + varnames(cons2cons, equations)[v] + error("mismatch: variables_$v should be '$(varnames(cons2cons, equations)[v])', but found '$name'") + end + + # Read variable + u[v, .., :] = read(file["variables_$v"]) + end end - end - return u_ode -end - -function save_restart_file(u, time, dt, timestep, - mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, equations, - dg::DG, cache, - restart_callback) - @unpack output_directory = restart_callback - # Filename based on current time step - filename = joinpath(output_directory, @sprintf("restart_%09d.h5", timestep)) - - if HDF5.has_parallel() - save_restart_file_parallel(u, time, dt, timestep, mesh, equations, dg, cache, - filename) - else - save_restart_file_on_root(u, time, dt, timestep, mesh, equations, dg, cache, - filename) + return u_ode end -end - -function save_restart_file_parallel(u, time, dt, timestep, - mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, - equations, dg::DG, cache, - filename) - - # Restart files always store conservative variables - data = u - - # Calculate element and node counts by MPI rank - element_size = nnodes(dg)^ndims(mesh) - element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) - node_counts = element_counts * Cint(element_size) - # Cumulative sum of nodes per rank starting with an additional 0 - cum_node_counts = append!(zeros(eltype(node_counts), 1), cumsum(node_counts)) - - # Open file (clobber existing content) - h5open(filename, "w", mpi_comm()) do file - # Add context information as attributes - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["equations"] = get_name(equations) - attributes(file)["polydeg"] = polydeg(dg) - attributes(file)["n_vars"] = nvariables(equations) - attributes(file)["n_elements"] = nelementsglobal(mesh, dg, cache) - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] - attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar - attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar - attributes(file)["timestep"] = timestep - - # Store each variable of the solution - for v in eachvariable(equations) - # Need to create dataset explicitly in parallel case - var = create_dataset(file, "/variables_$v", datatype(eltype(data)), - dataspace((ndofsglobal(mesh, dg, cache),))) - # Write data of each process in slices (ranks start with 0) - slice = (cum_node_counts[mpi_rank() + 1] + 1):cum_node_counts[mpi_rank() + 2] - # Convert to 1D array - var[slice] = vec(data[v, .., :]) - # Add variable name as attribute - attributes(var)["name"] = varnames(cons2cons, equations)[v] + + function save_restart_file( + u, time, dt, timestep, + mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, equations, + dg::DG, cache, + restart_callback + ) + @unpack output_directory = restart_callback + # Filename based on current time step + filename = joinpath(output_directory, @sprintf("restart_%09d.h5", timestep)) + + if HDF5.has_parallel() + save_restart_file_parallel( + u, time, dt, timestep, mesh, equations, dg, cache, + filename + ) + else + save_restart_file_on_root( + u, time, dt, timestep, mesh, equations, dg, cache, + filename + ) end end - return filename -end + function save_restart_file_parallel( + u, time, dt, timestep, + mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, + equations, dg::DG, cache, + filename + ) + + # Restart files always store conservative variables + data = u + + # Calculate element and node counts by MPI rank + element_size = nnodes(dg)^ndims(mesh) + element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) + node_counts = element_counts * Cint(element_size) + # Cumulative sum of nodes per rank starting with an additional 0 + cum_node_counts = append!(zeros(eltype(node_counts), 1), cumsum(node_counts)) + + # Open file (clobber existing content) + h5open(filename, "w", mpi_comm()) do file + # Add context information as attributes + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["equations"] = get_name(equations) + attributes(file)["polydeg"] = polydeg(dg) + attributes(file)["n_vars"] = nvariables(equations) + attributes(file)["n_elements"] = nelementsglobal(mesh, dg, cache) + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] + attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar + attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar + attributes(file)["timestep"] = timestep + + # Store each variable of the solution + for v in eachvariable(equations) + # Need to create dataset explicitly in parallel case + var = create_dataset( + file, "/variables_$v", datatype(eltype(data)), + dataspace((ndofsglobal(mesh, dg, cache),)) + ) + # Write data of each process in slices (ranks start with 0) + slice = (cum_node_counts[mpi_rank() + 1] + 1):cum_node_counts[mpi_rank() + 2] + # Convert to 1D array + var[slice] = vec(data[v, .., :]) + # Add variable name as attribute + attributes(var)["name"] = varnames(cons2cons, equations)[v] + end + end -function save_restart_file_on_root(u, time, dt, timestep, - mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, - equations, dg::DG, cache, - filename) + return filename + end - # Restart files always store conservative variables - data = u + function save_restart_file_on_root( + u, time, dt, timestep, + mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, + equations, dg::DG, cache, + filename + ) + + # Restart files always store conservative variables + data = u + + # Calculate element and node counts by MPI rank + element_size = nnodes(dg)^ndims(mesh) + element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) + node_counts = element_counts * Cint(element_size) + + # non-root ranks only send data + if !mpi_isroot() + # Send nodal data to root + for v in eachvariable(equations) + MPI.Gatherv!(vec(data[v, .., :]), nothing, mpi_root(), mpi_comm()) + end - # Calculate element and node counts by MPI rank - element_size = nnodes(dg)^ndims(mesh) - element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) - node_counts = element_counts * Cint(element_size) + return filename + end - # non-root ranks only send data - if !mpi_isroot() - # Send nodal data to root - for v in eachvariable(equations) - MPI.Gatherv!(vec(data[v, .., :]), nothing, mpi_root(), mpi_comm()) + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["equations"] = get_name(equations) + attributes(file)["polydeg"] = polydeg(dg) + attributes(file)["n_vars"] = nvariables(equations) + attributes(file)["n_elements"] = nelements(dg, cache) + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] + attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar + attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar + attributes(file)["timestep"] = timestep + + # Store each variable of the solution + for v in eachvariable(equations) + # Convert to 1D array + recv = Vector{eltype(data)}(undef, sum(node_counts)) + MPI.Gatherv!( + vec(data[v, .., :]), MPI.VBuffer(recv, node_counts), + mpi_root(), mpi_comm() + ) + file["variables_$v"] = recv + + # Add variable name as attribute + var = file["variables_$v"] + attributes(var)["name"] = varnames(cons2cons, equations)[v] + end end return filename end - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["equations"] = get_name(equations) - attributes(file)["polydeg"] = polydeg(dg) - attributes(file)["n_vars"] = nvariables(equations) - attributes(file)["n_elements"] = nelements(dg, cache) - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] - attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar - attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar - attributes(file)["timestep"] = timestep - - # Store each variable of the solution - for v in eachvariable(equations) - # Convert to 1D array - recv = Vector{eltype(data)}(undef, sum(node_counts)) - MPI.Gatherv!(vec(data[v, .., :]), MPI.VBuffer(recv, node_counts), - mpi_root(), mpi_comm()) - file["variables_$v"] = recv - - # Add variable name as attribute - var = file["variables_$v"] - attributes(var)["name"] = varnames(cons2cons, equations)[v] + function load_restart_file( + mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, equations, + dg::DG, cache, restart_file + ) + if HDF5.has_parallel() + load_restart_file_parallel(mesh, equations, dg, cache, restart_file) + else + load_restart_file_on_root(mesh, equations, dg, cache, restart_file) end end - return filename -end - -function load_restart_file(mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, equations, - dg::DG, cache, restart_file) - if HDF5.has_parallel() - load_restart_file_parallel(mesh, equations, dg, cache, restart_file) - else - load_restart_file_on_root(mesh, equations, dg, cache, restart_file) - end -end - -function load_restart_file_parallel(mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, - equations, dg::DG, cache, restart_file) - - # Calculate element and node counts by MPI rank - element_size = nnodes(dg)^ndims(mesh) - element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) - node_counts = element_counts * Cint(element_size) - # Cumulative sum of nodes per rank starting with an additional 0 - cum_node_counts = append!(zeros(eltype(node_counts), 1), cumsum(node_counts)) - - # allocate memory - u_ode = allocate_coefficients(mesh, equations, dg, cache) - u = wrap_array_native(u_ode, mesh, equations, dg, cache) - - # read in parallel - h5open(restart_file, "r", mpi_comm()) do file - # Read attributes to perform some sanity checks - if read(attributes(file)["ndims"]) != ndims(mesh) - error("restart mismatch: ndims differs from value in restart file") - end - if read(attributes(file)["equations"]) != get_name(equations) - error("restart mismatch: equations differ from value in restart file") - end - if read(attributes(file)["polydeg"]) != polydeg(dg) - error("restart mismatch: polynomial degree in solver differs from value in restart file") - end - if read(attributes(file)["n_elements"]) != nelementsglobal(mesh, dg, cache) - error("restart mismatch: number of elements in solver differs from value in restart file") - end - - # Read data - for v in eachvariable(equations) - # Check if variable name matches - var = file["variables_$v"] - if (name = read(attributes(var)["name"])) != - varnames(cons2cons, equations)[v] - error("mismatch: variables_$v should be '$(varnames(cons2cons, equations)[v])', but found '$name'") + function load_restart_file_parallel( + mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, + equations, dg::DG, cache, restart_file + ) + + # Calculate element and node counts by MPI rank + element_size = nnodes(dg)^ndims(mesh) + element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) + node_counts = element_counts * Cint(element_size) + # Cumulative sum of nodes per rank starting with an additional 0 + cum_node_counts = append!(zeros(eltype(node_counts), 1), cumsum(node_counts)) + + # allocate memory + u_ode = allocate_coefficients(mesh, equations, dg, cache) + u = wrap_array_native(u_ode, mesh, equations, dg, cache) + + # read in parallel + h5open(restart_file, "r", mpi_comm()) do file + # Read attributes to perform some sanity checks + if read(attributes(file)["ndims"]) != ndims(mesh) + error("restart mismatch: ndims differs from value in restart file") + end + if read(attributes(file)["equations"]) != get_name(equations) + error("restart mismatch: equations differ from value in restart file") + end + if read(attributes(file)["polydeg"]) != polydeg(dg) + error("restart mismatch: polynomial degree in solver differs from value in restart file") + end + if read(attributes(file)["n_elements"]) != nelementsglobal(mesh, dg, cache) + error("restart mismatch: number of elements in solver differs from value in restart file") end - # Read variable - mpi_println("Reading variables_$v ($name)...") - # Read data of each process in slices (ranks start with 0) - slice = (cum_node_counts[mpi_rank() + 1] + 1):cum_node_counts[mpi_rank() + 2] - # Convert 1D array back to actual size of `u` - u[v, .., :] = reshape(read(var)[slice], size(@view u[v, .., :])) - end - end - - return u_ode -end - -function load_restart_file_on_root(mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, - equations, dg::DG, cache, restart_file) - - # Calculate element and node counts by MPI rank - element_size = nnodes(dg)^ndims(mesh) - element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) - node_counts = element_counts * Cint(element_size) - - # allocate memory - u_ode = allocate_coefficients(mesh, equations, dg, cache) - u = wrap_array_native(u_ode, mesh, equations, dg, cache) - - # non-root ranks only receive data - if !mpi_isroot() - # Receive nodal data from root - for v in eachvariable(equations) - # put Scatterv in both blocks of the if condition to avoid type instability - if isempty(u) - data = eltype(u)[] - MPI.Scatterv!(nothing, data, mpi_root(), mpi_comm()) - else - data = @view u[v, .., :] - MPI.Scatterv!(nothing, data, mpi_root(), mpi_comm()) + # Read data + for v in eachvariable(equations) + # Check if variable name matches + var = file["variables_$v"] + if (name = read(attributes(var)["name"])) != + varnames(cons2cons, equations)[v] + error("mismatch: variables_$v should be '$(varnames(cons2cons, equations)[v])', but found '$name'") + end + + # Read variable + mpi_println("Reading variables_$v ($name)...") + # Read data of each process in slices (ranks start with 0) + slice = (cum_node_counts[mpi_rank() + 1] + 1):cum_node_counts[mpi_rank() + 2] + # Convert 1D array back to actual size of `u` + u[v, .., :] = reshape(read(var)[slice], size(@view u[v, .., :])) end end return u_ode end - # read only on MPI root - h5open(restart_file, "r") do file - # Read attributes to perform some sanity checks - if read(attributes(file)["ndims"]) != ndims(mesh) - error("restart mismatch: ndims differs from value in restart file") - end - if read(attributes(file)["equations"]) != get_name(equations) - error("restart mismatch: equations differ from value in restart file") - end - if read(attributes(file)["polydeg"]) != polydeg(dg) - error("restart mismatch: polynomial degree in solver differs from value in restart file") - end - if read(attributes(file)["n_elements"]) != nelements(dg, cache) - error("restart mismatch: number of elements in solver differs from value in restart file") - end - - # Read data - for v in eachvariable(equations) - # Check if variable name matches - var = file["variables_$v"] - if (name = read(attributes(var)["name"])) != - varnames(cons2cons, equations)[v] - error("mismatch: variables_$v should be '$(varnames(cons2cons, equations)[v])', but found '$name'") + function load_restart_file_on_root( + mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, + equations, dg::DG, cache, restart_file + ) + + # Calculate element and node counts by MPI rank + element_size = nnodes(dg)^ndims(mesh) + element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) + node_counts = element_counts * Cint(element_size) + + # allocate memory + u_ode = allocate_coefficients(mesh, equations, dg, cache) + u = wrap_array_native(u_ode, mesh, equations, dg, cache) + + # non-root ranks only receive data + if !mpi_isroot() + # Receive nodal data from root + for v in eachvariable(equations) + # put Scatterv in both blocks of the if condition to avoid type instability + if isempty(u) + data = eltype(u)[] + MPI.Scatterv!(nothing, data, mpi_root(), mpi_comm()) + else + data = @view u[v, .., :] + MPI.Scatterv!(nothing, data, mpi_root(), mpi_comm()) + end end - # Read variable - println("Reading variables_$v ($name)...") - sendbuf = MPI.VBuffer(read(file["variables_$v"]), node_counts) - MPI.Scatterv!(sendbuf, @view(u[v, .., :]), mpi_root(), mpi_comm()) + return u_ode end - end - return u_ode -end + # read only on MPI root + h5open(restart_file, "r") do file + # Read attributes to perform some sanity checks + if read(attributes(file)["ndims"]) != ndims(mesh) + error("restart mismatch: ndims differs from value in restart file") + end + if read(attributes(file)["equations"]) != get_name(equations) + error("restart mismatch: equations differ from value in restart file") + end + if read(attributes(file)["polydeg"]) != polydeg(dg) + error("restart mismatch: polynomial degree in solver differs from value in restart file") + end + if read(attributes(file)["n_elements"]) != nelements(dg, cache) + error("restart mismatch: number of elements in solver differs from value in restart file") + end -# Store controller values for an adaptive time stepping scheme -function save_adaptive_time_integrator(integrator, - controller, restart_callback) - # Save only on root - if mpi_isroot() - @unpack output_directory = restart_callback - timestep = integrator.stats.naccept + # Read data + for v in eachvariable(equations) + # Check if variable name matches + var = file["variables_$v"] + if (name = read(attributes(var)["name"])) != + varnames(cons2cons, equations)[v] + error("mismatch: variables_$v should be '$(varnames(cons2cons, equations)[v])', but found '$name'") + end + + # Read variable + println("Reading variables_$v ($name)...") + sendbuf = MPI.VBuffer(read(file["variables_$v"]), node_counts) + MPI.Scatterv!(sendbuf, @view(u[v, .., :]), mpi_root(), mpi_comm()) + end + end - # Filename based on current time step - filename = joinpath(output_directory, @sprintf("restart_%09d.h5", timestep)) + return u_ode + end - # Open file (preserve existing content) - h5open(filename, "r+") do file - # Add context information as attributes both for PIController and PIDController - attributes(file)["time_integrator_qold"] = integrator.qold - attributes(file)["time_integrator_dtpropose"] = integrator.dtpropose - # For PIDController is necessary to save additional parameters - if hasproperty(controller, :err) # Distinguish PIDController from PIController - attributes(file)["time_integrator_controller_err"] = controller.err + # Store controller values for an adaptive time stepping scheme + function save_adaptive_time_integrator( + integrator, + controller, restart_callback + ) + # Save only on root + if mpi_isroot() + @unpack output_directory = restart_callback + timestep = integrator.stats.naccept + + # Filename based on current time step + filename = joinpath(output_directory, @sprintf("restart_%09d.h5", timestep)) + + # Open file (preserve existing content) + h5open(filename, "r+") do file + # Add context information as attributes both for PIController and PIDController + attributes(file)["time_integrator_qold"] = integrator.qold + attributes(file)["time_integrator_dtpropose"] = integrator.dtpropose + # For PIDController is necessary to save additional parameters + if hasproperty(controller, :err) # Distinguish PIDController from PIController + attributes(file)["time_integrator_controller_err"] = controller.err + end end end end -end end # @muladd diff --git a/src/callbacks_step/save_solution.jl b/src/callbacks_step/save_solution.jl index 870cea0b9f..2348700885 100644 --- a/src/callbacks_step/save_solution.jl +++ b/src/callbacks_step/save_solution.jl @@ -3,248 +3,286 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - SaveSolutionCallback(; interval::Integer=0, - dt=nothing, - save_initial_solution=true, - save_final_solution=true, - output_directory="out", - solution_variables=cons2prim) - -Save the current numerical solution in regular intervals. Either pass `interval` to save -every `interval` time steps or pass `dt` to save in intervals of `dt` in terms -of integration time by adding additional (shortened) time steps where necessary (note that this may change the solution). -`solution_variables` can be any callable that converts the conservative variables -at a single point to a set of solution variables. The first parameter passed -to `solution_variables` will be the set of conservative variables -and the second parameter is the equation struct. -""" -mutable struct SaveSolutionCallback{IntervalType, SolutionVariablesType} - interval_or_dt::IntervalType - save_initial_solution::Bool - save_final_solution::Bool - output_directory::String - solution_variables::SolutionVariablesType -end - -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:SaveSolutionCallback}) - @nospecialize cb # reduce precompilation time - - save_solution_callback = cb.affect! - print(io, "SaveSolutionCallback(interval=", save_solution_callback.interval_or_dt, - ")") -end - -function Base.show(io::IO, - cb::DiscreteCallback{<:Any, - <:PeriodicCallbackAffect{<:SaveSolutionCallback}}) - @nospecialize cb # reduce precompilation time - - save_solution_callback = cb.affect!.affect! - print(io, "SaveSolutionCallback(dt=", save_solution_callback.interval_or_dt, ")") -end - -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:SaveSolutionCallback}) - @nospecialize cb # reduce precompilation time - - if get(io, :compact, false) - show(io, cb) - else - save_solution_callback = cb.affect! + #! format: noindent + + """ + SaveSolutionCallback(; interval::Integer=0, + dt=nothing, + save_initial_solution=true, + save_final_solution=true, + output_directory="out", + solution_variables=cons2prim) + + Save the current numerical solution in regular intervals. Either pass `interval` to save + every `interval` time steps or pass `dt` to save in intervals of `dt` in terms + of integration time by adding additional (shortened) time steps where necessary (note that this may change the solution). + `solution_variables` can be any callable that converts the conservative variables + at a single point to a set of solution variables. The first parameter passed + to `solution_variables` will be the set of conservative variables + and the second parameter is the equation struct. + """ + mutable struct SaveSolutionCallback{IntervalType, SolutionVariablesType} + interval_or_dt::IntervalType + save_initial_solution::Bool + save_final_solution::Bool + output_directory::String + solution_variables::SolutionVariablesType + end - setup = [ - "interval" => save_solution_callback.interval_or_dt, - "solution variables" => save_solution_callback.solution_variables, - "save initial solution" => save_solution_callback.save_initial_solution ? - "yes" : "no", - "save final solution" => save_solution_callback.save_final_solution ? - "yes" : "no", - "output directory" => abspath(normpath(save_solution_callback.output_directory)), - ] - summary_box(io, "SaveSolutionCallback", setup) + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:SaveSolutionCallback}) + @nospecialize cb # reduce precompilation time + + save_solution_callback = cb.affect! + print( + io, "SaveSolutionCallback(interval=", save_solution_callback.interval_or_dt, + ")" + ) end -end -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, - <:PeriodicCallbackAffect{<:SaveSolutionCallback}}) - @nospecialize cb # reduce precompilation time + function Base.show( + io::IO, + cb::DiscreteCallback{ + <:Any, + <:PeriodicCallbackAffect{<:SaveSolutionCallback}, + } + ) + @nospecialize cb # reduce precompilation time - if get(io, :compact, false) - show(io, cb) - else save_solution_callback = cb.affect!.affect! + print(io, "SaveSolutionCallback(dt=", save_solution_callback.interval_or_dt, ")") + end + + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:SaveSolutionCallback} + ) + @nospecialize cb # reduce precompilation time - setup = [ - "dt" => save_solution_callback.interval_or_dt, - "solution variables" => save_solution_callback.solution_variables, - "save initial solution" => save_solution_callback.save_initial_solution ? - "yes" : "no", - "save final solution" => save_solution_callback.save_final_solution ? - "yes" : "no", - "output directory" => abspath(normpath(save_solution_callback.output_directory)), - ] - summary_box(io, "SaveSolutionCallback", setup) + if get(io, :compact, false) + show(io, cb) + else + save_solution_callback = cb.affect! + + setup = [ + "interval" => save_solution_callback.interval_or_dt, + "solution variables" => save_solution_callback.solution_variables, + "save initial solution" => save_solution_callback.save_initial_solution ? + "yes" : "no", + "save final solution" => save_solution_callback.save_final_solution ? + "yes" : "no", + "output directory" => abspath(normpath(save_solution_callback.output_directory)), + ] + summary_box(io, "SaveSolutionCallback", setup) + end end -end - -function SaveSolutionCallback(; interval::Integer = 0, - dt = nothing, - save_initial_solution = true, - save_final_solution = true, - output_directory = "out", - solution_variables = cons2prim) - if !isnothing(dt) && interval > 0 - throw(ArgumentError("You can either set the number of steps between output (using `interval`) or the time between outputs (using `dt`) but not both simultaneously")) + + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{ + <:Any, + <:PeriodicCallbackAffect{<:SaveSolutionCallback}, + } + ) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + save_solution_callback = cb.affect!.affect! + + setup = [ + "dt" => save_solution_callback.interval_or_dt, + "solution variables" => save_solution_callback.solution_variables, + "save initial solution" => save_solution_callback.save_initial_solution ? + "yes" : "no", + "save final solution" => save_solution_callback.save_final_solution ? + "yes" : "no", + "output directory" => abspath(normpath(save_solution_callback.output_directory)), + ] + summary_box(io, "SaveSolutionCallback", setup) + end end - # Expected most frequent behavior comes first - if isnothing(dt) - interval_or_dt = interval - else # !isnothing(dt) - interval_or_dt = dt + function SaveSolutionCallback(; + interval::Integer = 0, + dt = nothing, + save_initial_solution = true, + save_final_solution = true, + output_directory = "out", + solution_variables = cons2prim + ) + if !isnothing(dt) && interval > 0 + throw(ArgumentError("You can either set the number of steps between output (using `interval`) or the time between outputs (using `dt`) but not both simultaneously")) + end + + # Expected most frequent behavior comes first + if isnothing(dt) + interval_or_dt = interval + else # !isnothing(dt) + interval_or_dt = dt + end + + solution_callback = SaveSolutionCallback( + interval_or_dt, + save_initial_solution, save_final_solution, + output_directory, solution_variables + ) + + # Expected most frequent behavior comes first + if isnothing(dt) + # Save every `interval` (accepted) time steps + # The first one is the condition, the second the affect! + return DiscreteCallback( + solution_callback, solution_callback, + save_positions = (false, false), + initialize = initialize_save_cb! + ) + else + # Add a `tstop` every `dt`, and save the final solution. + return PeriodicCallback( + solution_callback, dt, + save_positions = (false, false), + initialize = initialize_save_cb!, + final_affect = save_final_solution + ) + end end - solution_callback = SaveSolutionCallback(interval_or_dt, - save_initial_solution, save_final_solution, - output_directory, solution_variables) - - # Expected most frequent behavior comes first - if isnothing(dt) - # Save every `interval` (accepted) time steps - # The first one is the condition, the second the affect! - return DiscreteCallback(solution_callback, solution_callback, - save_positions = (false, false), - initialize = initialize_save_cb!) - else - # Add a `tstop` every `dt`, and save the final solution. - return PeriodicCallback(solution_callback, dt, - save_positions = (false, false), - initialize = initialize_save_cb!, - final_affect = save_final_solution) + function initialize_save_cb!(cb, u, t, integrator) + # The SaveSolutionCallback is either cb.affect! (with DiscreteCallback) + # or cb.affect!.affect! (with PeriodicCallback). + # Let recursive dispatch handle this. + initialize_save_cb!(cb.affect!, u, t, integrator) end -end -function initialize_save_cb!(cb, u, t, integrator) - # The SaveSolutionCallback is either cb.affect! (with DiscreteCallback) - # or cb.affect!.affect! (with PeriodicCallback). - # Let recursive dispatch handle this. - initialize_save_cb!(cb.affect!, u, t, integrator) -end + function initialize_save_cb!(solution_callback::SaveSolutionCallback, u, t, integrator) + mpi_isroot() && mkpath(solution_callback.output_directory) -function initialize_save_cb!(solution_callback::SaveSolutionCallback, u, t, integrator) - mpi_isroot() && mkpath(solution_callback.output_directory) + semi = integrator.p + @trixi_timeit timer() "I/O" save_mesh(semi, solution_callback.output_directory) - semi = integrator.p - @trixi_timeit timer() "I/O" save_mesh(semi, solution_callback.output_directory) + if solution_callback.save_initial_solution + solution_callback(integrator) + end - if solution_callback.save_initial_solution - solution_callback(integrator) + return nothing end - return nothing -end + # Save mesh for a general semidiscretization (default) + function save_mesh(semi::AbstractSemidiscretization, output_directory, timestep = 0) + mesh, _, _, _ = mesh_equations_solver_cache(semi) + + if mesh.unsaved_changes + # We only append the time step number to the mesh file name if it has + # changed during the simulation due to AMR. We do not append it for + # the first time step. + if timestep == 0 + mesh.current_filename = save_mesh_file(mesh, output_directory) + else + mesh.current_filename = save_mesh_file(mesh, output_directory, timestep) + end + mesh.unsaved_changes = false + end + end -# Save mesh for a general semidiscretization (default) -function save_mesh(semi::AbstractSemidiscretization, output_directory, timestep = 0) - mesh, _, _, _ = mesh_equations_solver_cache(semi) + # this method is called to determine whether the callback should be activated + function (solution_callback::SaveSolutionCallback)(u, t, integrator) + @unpack interval_or_dt, save_final_solution = solution_callback + + # With error-based step size control, some steps can be rejected. Thus, + # `integrator.iter >= integrator.stats.naccept` + # (total #steps) (#accepted steps) + # We need to check the number of accepted steps since callbacks are not + # activated after a rejected step. + return interval_or_dt > 0 && ( + integrator.stats.naccept % interval_or_dt == 0 || + (save_final_solution && isfinished(integrator)) + ) + end - if mesh.unsaved_changes - # We only append the time step number to the mesh file name if it has - # changed during the simulation due to AMR. We do not append it for - # the first time step. - if timestep == 0 - mesh.current_filename = save_mesh_file(mesh, output_directory) - else - mesh.current_filename = save_mesh_file(mesh, output_directory, timestep) + # this method is called when the callback is activated + function (solution_callback::SaveSolutionCallback)(integrator) + u_ode = integrator.u + semi = integrator.p + iter = integrator.stats.naccept + + @trixi_timeit timer() "I/O" begin + # Call high-level functions that dispatch on semidiscretization type + @trixi_timeit timer() "save mesh" save_mesh( + semi, + solution_callback.output_directory, + iter + ) + save_solution_file(semi, u_ode, solution_callback, integrator) end - mesh.unsaved_changes = false - end -end - -# this method is called to determine whether the callback should be activated -function (solution_callback::SaveSolutionCallback)(u, t, integrator) - @unpack interval_or_dt, save_final_solution = solution_callback - - # With error-based step size control, some steps can be rejected. Thus, - # `integrator.iter >= integrator.stats.naccept` - # (total #steps) (#accepted steps) - # We need to check the number of accepted steps since callbacks are not - # activated after a rejected step. - return interval_or_dt > 0 && (integrator.stats.naccept % interval_or_dt == 0 || - (save_final_solution && isfinished(integrator))) -end - -# this method is called when the callback is activated -function (solution_callback::SaveSolutionCallback)(integrator) - u_ode = integrator.u - semi = integrator.p - iter = integrator.stats.naccept - - @trixi_timeit timer() "I/O" begin - # Call high-level functions that dispatch on semidiscretization type - @trixi_timeit timer() "save mesh" save_mesh(semi, - solution_callback.output_directory, - iter) - save_solution_file(semi, u_ode, solution_callback, integrator) + + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) + return nothing end - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) - return nothing -end - -@inline function save_solution_file(semi::AbstractSemidiscretization, u_ode, - solution_callback, - integrator; system = "") - @unpack t, dt = integrator - iter = integrator.stats.naccept - - element_variables = Dict{Symbol, Any}() - @trixi_timeit timer() "get element variables" begin - get_element_variables!(element_variables, u_ode, semi) - callbacks = integrator.opts.callback - if callbacks isa CallbackSet - foreach(callbacks.continuous_callbacks) do cb - get_element_variables!(element_variables, u_ode, semi, cb; - t = integrator.t, iter = iter) - end - foreach(callbacks.discrete_callbacks) do cb - get_element_variables!(element_variables, u_ode, semi, cb; - t = integrator.t, iter = iter) + @inline function save_solution_file( + semi::AbstractSemidiscretization, u_ode, + solution_callback, + integrator; system = "" + ) + @unpack t, dt = integrator + iter = integrator.stats.naccept + + element_variables = Dict{Symbol, Any}() + @trixi_timeit timer() "get element variables" begin + get_element_variables!(element_variables, u_ode, semi) + callbacks = integrator.opts.callback + if callbacks isa CallbackSet + foreach(callbacks.continuous_callbacks) do cb + get_element_variables!( + element_variables, u_ode, semi, cb; + t = integrator.t, iter = iter + ) + end + foreach(callbacks.discrete_callbacks) do cb + get_element_variables!( + element_variables, u_ode, semi, cb; + t = integrator.t, iter = iter + ) + end end end + + node_variables = Dict{Symbol, Any}() + @trixi_timeit timer() "get node variables" get_node_variables!( + node_variables, + semi + ) + + @trixi_timeit timer() "save solution" save_solution_file( + u_ode, t, dt, iter, semi, + solution_callback, + element_variables, + node_variables, + system = system + ) end - node_variables = Dict{Symbol, Any}() - @trixi_timeit timer() "get node variables" get_node_variables!(node_variables, - semi) - - @trixi_timeit timer() "save solution" save_solution_file(u_ode, t, dt, iter, semi, - solution_callback, - element_variables, - node_variables, - system = system) -end - -@inline function save_solution_file(u_ode, t, dt, iter, - semi::AbstractSemidiscretization, solution_callback, - element_variables = Dict{Symbol, Any}(), - node_variables = Dict{Symbol, Any}(); - system = "") - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - u = wrap_array_native(u_ode, mesh, equations, solver, cache) - save_solution_file(u, t, dt, iter, mesh, equations, solver, cache, - solution_callback, - element_variables, - node_variables; system = system) -end - -# TODO: Taal refactor, move save_mesh_file? -# function save_mesh_file(mesh::TreeMesh, output_directory, timestep=-1) in io/io.jl - -include("save_solution_dg.jl") + @inline function save_solution_file( + u_ode, t, dt, iter, + semi::AbstractSemidiscretization, solution_callback, + element_variables = Dict{Symbol, Any}(), + node_variables = Dict{Symbol, Any}(); + system = "" + ) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + u = wrap_array_native(u_ode, mesh, equations, solver, cache) + save_solution_file( + u, t, dt, iter, mesh, equations, solver, cache, + solution_callback, + element_variables, + node_variables; system = system + ) + end + + # TODO: Taal refactor, move save_mesh_file? + # function save_mesh_file(mesh::TreeMesh, output_directory, timestep=-1) in io/io.jl + + include("save_solution_dg.jl") end # @muladd diff --git a/src/callbacks_step/save_solution_dg.jl b/src/callbacks_step/save_solution_dg.jl index 57cdb3ef8a..1cb6b05d9f 100644 --- a/src/callbacks_step/save_solution_dg.jl +++ b/src/callbacks_step/save_solution_dg.jl @@ -3,267 +3,315 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function save_solution_file(u, time, dt, timestep, - mesh::Union{SerialTreeMesh, StructuredMesh, - StructuredMeshView, - UnstructuredMesh2D, SerialP4estMesh, - SerialT8codeMesh}, - equations, dg::DG, cache, - solution_callback, - element_variables = Dict{Symbol, Any}(), - node_variables = Dict{Symbol, Any}(); - system = "") - @unpack output_directory, solution_variables = solution_callback - - # Filename based on current time step - if isempty(system) - filename = joinpath(output_directory, @sprintf("solution_%09d.h5", timestep)) - else - filename = joinpath(output_directory, - @sprintf("solution_%s_%09d.h5", system, timestep)) - end - - # Convert to different set of variables if requested - if solution_variables === cons2cons - data = u - n_vars = nvariables(equations) - else - # Reinterpret the solution array as an array of conservative variables, - # compute the solution variables via broadcasting, and reinterpret the - # result as a plain array of floating point numbers - data = Array(reinterpret(eltype(u), - solution_variables.(reinterpret(SVector{nvariables(equations), - eltype(u)}, u), - Ref(equations)))) - - # Find out variable count by looking at output from `solution_variables` function - n_vars = size(data, 1) - end - - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["equations"] = get_name(equations) - attributes(file)["polydeg"] = polydeg(dg) - attributes(file)["n_vars"] = n_vars - attributes(file)["n_elements"] = nelements(dg, cache) - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] - attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar - attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar - attributes(file)["timestep"] = timestep - - # Store each variable of the solution data - for v in 1:n_vars - # Convert to 1D array - file["variables_$v"] = vec(data[v, .., :]) - - # Add variable name as attribute - var = file["variables_$v"] - attributes(var)["name"] = varnames(solution_variables, equations)[v] + #! format: noindent + + function save_solution_file( + u, time, dt, timestep, + mesh::Union{ + SerialTreeMesh, StructuredMesh, + StructuredMeshView, + UnstructuredMesh2D, SerialP4estMesh, + SerialT8codeMesh, + }, + equations, dg::DG, cache, + solution_callback, + element_variables = Dict{Symbol, Any}(), + node_variables = Dict{Symbol, Any}(); + system = "" + ) + @unpack output_directory, solution_variables = solution_callback + + # Filename based on current time step + if isempty(system) + filename = joinpath(output_directory, @sprintf("solution_%09d.h5", timestep)) + else + filename = joinpath( + output_directory, + @sprintf("solution_%s_%09d.h5", system, timestep) + ) end - # Store element variables - for (v, (key, element_variable)) in enumerate(element_variables) - # Add to file - file["element_variables_$v"] = element_variable - - # Add variable name as attribute - var = file["element_variables_$v"] - attributes(var)["name"] = string(key) + # Convert to different set of variables if requested + if solution_variables === cons2cons + data = u + n_vars = nvariables(equations) + else + # Reinterpret the solution array as an array of conservative variables, + # compute the solution variables via broadcasting, and reinterpret the + # result as a plain array of floating point numbers + data = Array( + reinterpret( + eltype(u), + solution_variables.( + reinterpret( + SVector{ + nvariables(equations), + eltype(u), + }, u + ), + Ref(equations) + ) + ) + ) + + # Find out variable count by looking at output from `solution_variables` function + n_vars = size(data, 1) end - # Store node variables - for (v, (key, node_variable)) in enumerate(node_variables) - # Add to file - file["node_variables_$v"] = node_variable - - # Add variable name as attribute - var = file["node_variables_$v"] - attributes(var)["name"] = string(key) + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["equations"] = get_name(equations) + attributes(file)["polydeg"] = polydeg(dg) + attributes(file)["n_vars"] = n_vars + attributes(file)["n_elements"] = nelements(dg, cache) + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] + attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar + attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar + attributes(file)["timestep"] = timestep + + # Store each variable of the solution data + for v in 1:n_vars + # Convert to 1D array + file["variables_$v"] = vec(data[v, .., :]) + + # Add variable name as attribute + var = file["variables_$v"] + attributes(var)["name"] = varnames(solution_variables, equations)[v] + end + + # Store element variables + for (v, (key, element_variable)) in enumerate(element_variables) + # Add to file + file["element_variables_$v"] = element_variable + + # Add variable name as attribute + var = file["element_variables_$v"] + attributes(var)["name"] = string(key) + end + + # Store node variables + for (v, (key, node_variable)) in enumerate(node_variables) + # Add to file + file["node_variables_$v"] = node_variable + + # Add variable name as attribute + var = file["node_variables_$v"] + attributes(var)["name"] = string(key) + end end - end - - return filename -end - -function save_solution_file(u, time, dt, timestep, - mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, equations, - dg::DG, cache, - solution_callback, - element_variables = Dict{Symbol, Any}(), - node_variables = Dict{Symbol, Any}(); - system = "") - @unpack output_directory, solution_variables = solution_callback - - # Filename based on current time step - if isempty(system) - filename = joinpath(output_directory, @sprintf("solution_%09d.h5", timestep)) - else - filename = joinpath(output_directory, - @sprintf("solution_%s_%09d.h5", system, timestep)) - end - # Convert to different set of variables if requested - if solution_variables === cons2cons - data = u - n_vars = nvariables(equations) - else - # Reinterpret the solution array as an array of conservative variables, - # compute the solution variables via broadcasting, and reinterpret the - # result as a plain array of floating point numbers - data = Array(reinterpret(eltype(u), - solution_variables.(reinterpret(SVector{nvariables(equations), - eltype(u)}, u), - Ref(equations)))) - - # Find out variable count by looking at output from `solution_variables` function - n_vars = size(data, 1) + return filename end - if HDF5.has_parallel() - save_solution_file_parallel(data, time, dt, timestep, n_vars, mesh, equations, - dg, cache, solution_variables, filename, - element_variables) - else - save_solution_file_on_root(data, time, dt, timestep, n_vars, mesh, equations, - dg, cache, solution_variables, filename, - element_variables) - end -end - -function save_solution_file_parallel(data, time, dt, timestep, n_vars, - mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, - equations, dg::DG, cache, - solution_variables, filename, - element_variables = Dict{Symbol, Any}()) - - # Calculate element and node counts by MPI rank - element_size = nnodes(dg)^ndims(mesh) - element_counts = cache.mpi_cache.n_elements_by_rank - node_counts = element_counts * element_size - # Cumulative sum of elements per rank starting with an additional 0 - cum_element_counts = append!(zeros(eltype(element_counts), 1), - cumsum(element_counts)) - # Cumulative sum of nodes per rank starting with an additional 0 - cum_node_counts = append!(zeros(eltype(node_counts), 1), cumsum(node_counts)) - - # Open file using parallel HDF5 (clobber existing content) - h5open(filename, "w", mpi_comm()) do file - # Add context information as attributes - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["equations"] = get_name(equations) - attributes(file)["polydeg"] = polydeg(dg) - attributes(file)["n_vars"] = n_vars - attributes(file)["n_elements"] = nelementsglobal(mesh, dg, cache) - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] - attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar - attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar - attributes(file)["timestep"] = timestep - - # Store each variable of the solution data - for v in 1:n_vars - # Need to create dataset explicitly in parallel case - var = create_dataset(file, "/variables_$v", datatype(eltype(data)), - dataspace((ndofsglobal(mesh, dg, cache),))) - # Write data of each process in slices (ranks start with 0) - slice = (cum_node_counts[mpi_rank() + 1] + 1):cum_node_counts[mpi_rank() + 2] - # Convert to 1D array - var[slice] = vec(data[v, .., :]) - # Add variable name as attribute - attributes(var)["name"] = varnames(solution_variables, equations)[v] + function save_solution_file( + u, time, dt, timestep, + mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, equations, + dg::DG, cache, + solution_callback, + element_variables = Dict{Symbol, Any}(), + node_variables = Dict{Symbol, Any}(); + system = "" + ) + @unpack output_directory, solution_variables = solution_callback + + # Filename based on current time step + if isempty(system) + filename = joinpath(output_directory, @sprintf("solution_%09d.h5", timestep)) + else + filename = joinpath( + output_directory, + @sprintf("solution_%s_%09d.h5", system, timestep) + ) end - # Store element variables - for (v, (key, element_variable)) in enumerate(element_variables) - # Need to create dataset explicitly in parallel case - var = create_dataset(file, "/element_variables_$v", - datatype(eltype(element_variable)), - dataspace((nelementsglobal(mesh, dg, cache),))) - - # Write data of each process in slices (ranks start with 0) - slice = (cum_element_counts[mpi_rank() + 1] + 1):cum_element_counts[mpi_rank() + 2] - # Add to file - var[slice] = element_variable - # Add variable name as attribute - attributes(var)["name"] = string(key) + # Convert to different set of variables if requested + if solution_variables === cons2cons + data = u + n_vars = nvariables(equations) + else + # Reinterpret the solution array as an array of conservative variables, + # compute the solution variables via broadcasting, and reinterpret the + # result as a plain array of floating point numbers + data = Array( + reinterpret( + eltype(u), + solution_variables.( + reinterpret( + SVector{ + nvariables(equations), + eltype(u), + }, u + ), + Ref(equations) + ) + ) + ) + + # Find out variable count by looking at output from `solution_variables` function + n_vars = size(data, 1) end - end - return filename -end - -function save_solution_file_on_root(data, time, dt, timestep, n_vars, - mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, - equations, dg::DG, cache, - solution_variables, filename, - element_variables = Dict{Symbol, Any}()) - - # Calculate element and node counts by MPI rank - element_size = nnodes(dg)^ndims(mesh) - element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) - node_counts = element_counts * Cint(element_size) - - # non-root ranks only send data - if !mpi_isroot() - # Send nodal data to root - for v in 1:n_vars - MPI.Gatherv!(vec(data[v, .., :]), nothing, mpi_root(), mpi_comm()) + if HDF5.has_parallel() + save_solution_file_parallel( + data, time, dt, timestep, n_vars, mesh, equations, + dg, cache, solution_variables, filename, + element_variables + ) + else + save_solution_file_on_root( + data, time, dt, timestep, n_vars, mesh, equations, + dg, cache, solution_variables, filename, + element_variables + ) end + end - # Send element data to root - for (key, element_variable) in element_variables - MPI.Gatherv!(element_variable, nothing, mpi_root(), mpi_comm()) + function save_solution_file_parallel( + data, time, dt, timestep, n_vars, + mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, + equations, dg::DG, cache, + solution_variables, filename, + element_variables = Dict{Symbol, Any}() + ) + + # Calculate element and node counts by MPI rank + element_size = nnodes(dg)^ndims(mesh) + element_counts = cache.mpi_cache.n_elements_by_rank + node_counts = element_counts * element_size + # Cumulative sum of elements per rank starting with an additional 0 + cum_element_counts = append!( + zeros(eltype(element_counts), 1), + cumsum(element_counts) + ) + # Cumulative sum of nodes per rank starting with an additional 0 + cum_node_counts = append!(zeros(eltype(node_counts), 1), cumsum(node_counts)) + + # Open file using parallel HDF5 (clobber existing content) + h5open(filename, "w", mpi_comm()) do file + # Add context information as attributes + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["equations"] = get_name(equations) + attributes(file)["polydeg"] = polydeg(dg) + attributes(file)["n_vars"] = n_vars + attributes(file)["n_elements"] = nelementsglobal(mesh, dg, cache) + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] + attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar + attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar + attributes(file)["timestep"] = timestep + + # Store each variable of the solution data + for v in 1:n_vars + # Need to create dataset explicitly in parallel case + var = create_dataset( + file, "/variables_$v", datatype(eltype(data)), + dataspace((ndofsglobal(mesh, dg, cache),)) + ) + # Write data of each process in slices (ranks start with 0) + slice = (cum_node_counts[mpi_rank() + 1] + 1):cum_node_counts[mpi_rank() + 2] + # Convert to 1D array + var[slice] = vec(data[v, .., :]) + # Add variable name as attribute + attributes(var)["name"] = varnames(solution_variables, equations)[v] + end + + # Store element variables + for (v, (key, element_variable)) in enumerate(element_variables) + # Need to create dataset explicitly in parallel case + var = create_dataset( + file, "/element_variables_$v", + datatype(eltype(element_variable)), + dataspace((nelementsglobal(mesh, dg, cache),)) + ) + + # Write data of each process in slices (ranks start with 0) + slice = (cum_element_counts[mpi_rank() + 1] + 1):cum_element_counts[mpi_rank() + 2] + # Add to file + var[slice] = element_variable + # Add variable name as attribute + attributes(var)["name"] = string(key) + end end return filename end - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["equations"] = get_name(equations) - attributes(file)["polydeg"] = polydeg(dg) - attributes(file)["n_vars"] = n_vars - attributes(file)["n_elements"] = nelementsglobal(mesh, dg, cache) - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] - attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar - attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar - attributes(file)["timestep"] = timestep - - # Store each variable of the solution data - for v in 1:n_vars - # Convert to 1D array - recv = Vector{eltype(data)}(undef, sum(node_counts)) - MPI.Gatherv!(vec(data[v, .., :]), MPI.VBuffer(recv, node_counts), - mpi_root(), mpi_comm()) - file["variables_$v"] = recv - - # Add variable name as attribute - var = file["variables_$v"] - attributes(var)["name"] = varnames(solution_variables, equations)[v] + function save_solution_file_on_root( + data, time, dt, timestep, n_vars, + mesh::Union{ParallelTreeMesh, ParallelP4estMesh}, + equations, dg::DG, cache, + solution_variables, filename, + element_variables = Dict{Symbol, Any}() + ) + + # Calculate element and node counts by MPI rank + element_size = nnodes(dg)^ndims(mesh) + element_counts = convert(Vector{Cint}, collect(cache.mpi_cache.n_elements_by_rank)) + node_counts = element_counts * Cint(element_size) + + # non-root ranks only send data + if !mpi_isroot() + # Send nodal data to root + for v in 1:n_vars + MPI.Gatherv!(vec(data[v, .., :]), nothing, mpi_root(), mpi_comm()) + end + + # Send element data to root + for (key, element_variable) in element_variables + MPI.Gatherv!(element_variable, nothing, mpi_root(), mpi_comm()) + end + + return filename end - # Store element variables - for (v, (key, element_variable)) in enumerate(element_variables) - # Add to file - recv = Vector{eltype(data)}(undef, sum(element_counts)) - MPI.Gatherv!(element_variable, MPI.VBuffer(recv, element_counts), - mpi_root(), mpi_comm()) - file["element_variables_$v"] = recv - - # Add variable name as attribute - var = file["element_variables_$v"] - attributes(var)["name"] = string(key) + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["equations"] = get_name(equations) + attributes(file)["polydeg"] = polydeg(dg) + attributes(file)["n_vars"] = n_vars + attributes(file)["n_elements"] = nelementsglobal(mesh, dg, cache) + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["mesh_file"] = splitdir(mesh.current_filename)[2] + attributes(file)["time"] = convert(Float64, time) # Ensure that `time` is written as a double precision scalar + attributes(file)["dt"] = convert(Float64, dt) # Ensure that `dt` is written as a double precision scalar + attributes(file)["timestep"] = timestep + + # Store each variable of the solution data + for v in 1:n_vars + # Convert to 1D array + recv = Vector{eltype(data)}(undef, sum(node_counts)) + MPI.Gatherv!( + vec(data[v, .., :]), MPI.VBuffer(recv, node_counts), + mpi_root(), mpi_comm() + ) + file["variables_$v"] = recv + + # Add variable name as attribute + var = file["variables_$v"] + attributes(var)["name"] = varnames(solution_variables, equations)[v] + end + + # Store element variables + for (v, (key, element_variable)) in enumerate(element_variables) + # Add to file + recv = Vector{eltype(data)}(undef, sum(element_counts)) + MPI.Gatherv!( + element_variable, MPI.VBuffer(recv, element_counts), + mpi_root(), mpi_comm() + ) + file["element_variables_$v"] = recv + + # Add variable name as attribute + var = file["element_variables_$v"] + attributes(var)["name"] = string(key) + end end - end - return filename -end + return filename + end end # @muladd diff --git a/src/callbacks_step/steady_state.jl b/src/callbacks_step/steady_state.jl index 15c2e83428..886508a910 100644 --- a/src/callbacks_step/steady_state.jl +++ b/src/callbacks_step/steady_state.jl @@ -3,79 +3,87 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - SteadyStateCallback(; abstol=1.0e-8, reltol=1.0e-6) + """ + SteadyStateCallback(; abstol=1.0e-8, reltol=1.0e-6) -Terminates the integration when the [`residual_steady_state(du, equations)`](@ref) -falls below the threshold specified by `abstol, reltol`. -""" -mutable struct SteadyStateCallback{RealT <: Real} - abstol::RealT - reltol::RealT -end + Terminates the integration when the [`residual_steady_state(du, equations)`](@ref) + falls below the threshold specified by `abstol, reltol`. + """ + mutable struct SteadyStateCallback{RealT <: Real} + abstol::RealT + reltol::RealT + end -function SteadyStateCallback(; abstol = 1.0e-8, reltol = 1.0e-6) - abstol, reltol = promote(abstol, reltol) - steady_state_callback = SteadyStateCallback(abstol, reltol) + function SteadyStateCallback(; abstol = 1.0e-8, reltol = 1.0e-6) + abstol, reltol = promote(abstol, reltol) + steady_state_callback = SteadyStateCallback(abstol, reltol) - DiscreteCallback(steady_state_callback, steady_state_callback, - save_positions = (false, false)) -end + DiscreteCallback( + steady_state_callback, steady_state_callback, + save_positions = (false, false) + ) + end -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:SteadyStateCallback}) - @nospecialize cb # reduce precompilation time + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:SteadyStateCallback}) + @nospecialize cb # reduce precompilation time - steady_state_callback = cb.affect! - print(io, "SteadyStateCallback(abstol=", steady_state_callback.abstol, ", ", - "reltol=", steady_state_callback.reltol, ")") -end + steady_state_callback = cb.affect! + print( + io, "SteadyStateCallback(abstol=", steady_state_callback.abstol, ", ", + "reltol=", steady_state_callback.reltol, ")" + ) + end -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:SteadyStateCallback}) - @nospecialize cb # reduce precompilation time + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:SteadyStateCallback} + ) + @nospecialize cb # reduce precompilation time - if get(io, :compact, false) - show(io, cb) - else - steady_state_callback = cb.affect! + if get(io, :compact, false) + show(io, cb) + else + steady_state_callback = cb.affect! - setup = [ - "absolute tolerance" => steady_state_callback.abstol, - "relative tolerance" => steady_state_callback.reltol, - ] - summary_box(io, "SteadyStateCallback", setup) + setup = [ + "absolute tolerance" => steady_state_callback.abstol, + "relative tolerance" => steady_state_callback.reltol, + ] + summary_box(io, "SteadyStateCallback", setup) + end end -end -# affect! -(::SteadyStateCallback)(integrator) = terminate!(integrator) + # affect! + (::SteadyStateCallback)(integrator) = terminate!(integrator) -# the condition -function (steady_state_callback::SteadyStateCallback)(u_ode, t, integrator) - semi = integrator.p + # the condition + function (steady_state_callback::SteadyStateCallback)(u_ode, t, integrator) + semi = integrator.p - u = wrap_array(u_ode, semi) - du = wrap_array(get_du(integrator), semi) - terminate = steady_state_callback(du, u, semi) - if mpi_isparallel() - # MPI.jl doesn't seem to have MPI_C_BOOL - terminate_integer = Int(terminate) - terminate = !iszero(MPI.Allreduce!(Ref(terminate_integer), +, mpi_comm())[]) - end - if mpi_isroot() && terminate - @info " Steady state tolerance reached" steady_state_callback t + u = wrap_array(u_ode, semi) + du = wrap_array(get_du(integrator), semi) + terminate = steady_state_callback(du, u, semi) + if mpi_isparallel() + # MPI.jl doesn't seem to have MPI_C_BOOL + terminate_integer = Int(terminate) + terminate = !iszero(MPI.Allreduce!(Ref(terminate_integer), +, mpi_comm())[]) + end + if mpi_isroot() && terminate + @info " Steady state tolerance reached" steady_state_callback t + end + return terminate end - return terminate -end -function (steady_state_callback::SteadyStateCallback)(du, u, - semi::AbstractSemidiscretization) - steady_state_callback(du, u, mesh_equations_solver_cache(semi)...) -end + function (steady_state_callback::SteadyStateCallback)( + du, u, + semi::AbstractSemidiscretization + ) + steady_state_callback(du, u, mesh_equations_solver_cache(semi)...) + end -include("steady_state_dg1d.jl") -include("steady_state_dg2d.jl") -include("steady_state_dg3d.jl") + include("steady_state_dg1d.jl") + include("steady_state_dg2d.jl") + include("steady_state_dg3d.jl") end # @muladd diff --git a/src/callbacks_step/steady_state_dg1d.jl b/src/callbacks_step/steady_state_dg1d.jl index 9b895de06d..59ec54a48f 100644 --- a/src/callbacks_step/steady_state_dg1d.jl +++ b/src/callbacks_step/steady_state_dg1d.jl @@ -3,23 +3,25 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function (steady_state_callback::SteadyStateCallback)(du, u, mesh::AbstractMesh{1}, - equations, dg::DG, cache) - @unpack abstol, reltol = steady_state_callback + function (steady_state_callback::SteadyStateCallback)( + du, u, mesh::AbstractMesh{1}, + equations, dg::DG, cache + ) + @unpack abstol, reltol = steady_state_callback - terminate = true - for element in eachelement(dg, cache) - for i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, element) - du_local = get_node_vars(du, equations, dg, i, element) - threshold = abstol + reltol * residual_steady_state(u_local, equations) - terminate = terminate && - residual_steady_state(du_local, equations) <= threshold + terminate = true + for element in eachelement(dg, cache) + for i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, element) + du_local = get_node_vars(du, equations, dg, i, element) + threshold = abstol + reltol * residual_steady_state(u_local, equations) + terminate = terminate && + residual_steady_state(du_local, equations) <= threshold + end end - end - return terminate -end + return terminate + end end # @muladd diff --git a/src/callbacks_step/steady_state_dg2d.jl b/src/callbacks_step/steady_state_dg2d.jl index ebb48ce458..6721e8aece 100644 --- a/src/callbacks_step/steady_state_dg2d.jl +++ b/src/callbacks_step/steady_state_dg2d.jl @@ -3,23 +3,25 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function (steady_state_callback::SteadyStateCallback)(du, u, mesh::AbstractMesh{2}, - equations, dg::DG, cache) - @unpack abstol, reltol = steady_state_callback + function (steady_state_callback::SteadyStateCallback)( + du, u, mesh::AbstractMesh{2}, + equations, dg::DG, cache + ) + @unpack abstol, reltol = steady_state_callback - terminate = true - for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, element) - du_local = get_node_vars(du, equations, dg, i, j, element) - threshold = abstol + reltol * residual_steady_state(u_local, equations) - terminate = terminate && - residual_steady_state(du_local, equations) <= threshold + terminate = true + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, element) + du_local = get_node_vars(du, equations, dg, i, j, element) + threshold = abstol + reltol * residual_steady_state(u_local, equations) + terminate = terminate && + residual_steady_state(du_local, equations) <= threshold + end end - end - return terminate -end + return terminate + end end # @muladd diff --git a/src/callbacks_step/steady_state_dg3d.jl b/src/callbacks_step/steady_state_dg3d.jl index 69c172f963..61a3536967 100644 --- a/src/callbacks_step/steady_state_dg3d.jl +++ b/src/callbacks_step/steady_state_dg3d.jl @@ -3,23 +3,25 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function (steady_state_callback::SteadyStateCallback)(du, u, mesh::AbstractMesh{3}, - equations, dg::DG, cache) - @unpack abstol, reltol = steady_state_callback + function (steady_state_callback::SteadyStateCallback)( + du, u, mesh::AbstractMesh{3}, + equations, dg::DG, cache + ) + @unpack abstol, reltol = steady_state_callback - terminate = true - for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, k, element) - du_local = get_node_vars(du, equations, dg, i, j, k, element) - threshold = abstol + reltol * residual_steady_state(u_local, equations) - terminate = terminate && - residual_steady_state(du_local, equations) <= threshold + terminate = true + for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, k, element) + du_local = get_node_vars(du, equations, dg, i, j, k, element) + threshold = abstol + reltol * residual_steady_state(u_local, equations) + terminate = terminate && + residual_steady_state(du_local, equations) <= threshold + end end - end - return terminate -end + return terminate + end end # @muladd diff --git a/src/callbacks_step/stepsize.jl b/src/callbacks_step/stepsize.jl index 8b5cb95831..d86355dc5d 100644 --- a/src/callbacks_step/stepsize.jl +++ b/src/callbacks_step/stepsize.jl @@ -3,114 +3,125 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - StepsizeCallback(; cfl=1.0) + """ + StepsizeCallback(; cfl=1.0) -Set the time step size according to a CFL condition with CFL number `cfl` -if the time integration method isn't adaptive itself. -""" -mutable struct StepsizeCallback{RealT} - cfl_number::RealT -end + Set the time step size according to a CFL condition with CFL number `cfl` + if the time integration method isn't adaptive itself. + """ + mutable struct StepsizeCallback{RealT} + cfl_number::RealT + end -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:StepsizeCallback}) - @nospecialize cb # reduce precompilation time + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:StepsizeCallback}) + @nospecialize cb # reduce precompilation time - stepsize_callback = cb.affect! - @unpack cfl_number = stepsize_callback - print(io, "StepsizeCallback(cfl_number=", cfl_number, ")") -end + stepsize_callback = cb.affect! + @unpack cfl_number = stepsize_callback + print(io, "StepsizeCallback(cfl_number=", cfl_number, ")") + end -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:StepsizeCallback}) - @nospecialize cb # reduce precompilation time + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:StepsizeCallback} + ) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + stepsize_callback = cb.affect! + + setup = [ + "CFL number" => stepsize_callback.cfl_number, + ] + summary_box(io, "StepsizeCallback", setup) + end + end - if get(io, :compact, false) - show(io, cb) - else - stepsize_callback = cb.affect! + function StepsizeCallback(; cfl::Real = 1.0) + stepsize_callback = StepsizeCallback(cfl) - setup = [ - "CFL number" => stepsize_callback.cfl_number, - ] - summary_box(io, "StepsizeCallback", setup) + DiscreteCallback( + stepsize_callback, stepsize_callback, # the first one is the condition, the second the affect! + save_positions = (false, false), + initialize = initialize! + ) end -end - -function StepsizeCallback(; cfl::Real = 1.0) - stepsize_callback = StepsizeCallback(cfl) - - DiscreteCallback(stepsize_callback, stepsize_callback, # the first one is the condition, the second the affect! - save_positions = (false, false), - initialize = initialize!) -end - -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t, - integrator) where {Condition, Affect! <: StepsizeCallback} - cb.affect!(integrator) -end - -# this method is called to determine whether the callback should be activated -function (stepsize_callback::StepsizeCallback)(u, t, integrator) - return true -end - -# This method is called as callback during the time integration. -@inline function (stepsize_callback::StepsizeCallback)(integrator) - # TODO: Taal decide, shall we set the time step even if the integrator is adaptive? - if !integrator.opts.adaptive - t = integrator.t - u_ode = integrator.u - semi = integrator.p - @unpack cfl_number = stepsize_callback - # Dispatch based on semidiscretization - dt = @trixi_timeit timer() "calculate dt" calculate_dt(u_ode, t, cfl_number, - semi) + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u, t, + integrator + ) where {Condition, Affect! <: StepsizeCallback} + cb.affect!(integrator) + end - set_proposed_dt!(integrator, dt) - integrator.opts.dtmax = dt - integrator.dtcache = dt + # this method is called to determine whether the callback should be activated + function (stepsize_callback::StepsizeCallback)(u, t, integrator) + return true + end + + # This method is called as callback during the time integration. + @inline function (stepsize_callback::StepsizeCallback)(integrator) + # TODO: Taal decide, shall we set the time step even if the integrator is adaptive? + if !integrator.opts.adaptive + t = integrator.t + u_ode = integrator.u + semi = integrator.p + @unpack cfl_number = stepsize_callback + + # Dispatch based on semidiscretization + dt = @trixi_timeit timer() "calculate dt" calculate_dt( + u_ode, t, cfl_number, + semi + ) + + set_proposed_dt!(integrator, dt) + integrator.opts.dtmax = dt + integrator.dtcache = dt + end + + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) + return nothing + end + + # General case for a single semidiscretization + function calculate_dt(u_ode, t, cfl_number, semi::AbstractSemidiscretization) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + u = wrap_array(u_ode, mesh, equations, solver, cache) + + dt = cfl_number * max_dt( + u, t, mesh, + have_constant_speed(equations), equations, + solver, cache + ) + end + + # Time integration methods from the DiffEq ecosystem without adaptive time stepping on their own + # such as `CarpenterKennedy2N54` require passing `dt=...` in `solve(ode, ...)`. Since we don't have + # an integrator at this stage but only the ODE, this method will be used there. It's called in + # many examples in `solve(ode, ..., dt=stepsize_callback(ode), ...)`. + function (cb::DiscreteCallback{Condition, Affect!})(ode::ODEProblem) where { + Condition, + Affect! <: + StepsizeCallback, + } + stepsize_callback = cb.affect! + @unpack cfl_number = stepsize_callback + u_ode = ode.u0 + t = first(ode.tspan) + semi = ode.p + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + u = wrap_array(u_ode, mesh, equations, solver, cache) + + return cfl_number * + max_dt(u, t, mesh, have_constant_speed(equations), equations, solver, cache) end - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) - return nothing -end - -# General case for a single semidiscretization -function calculate_dt(u_ode, t, cfl_number, semi::AbstractSemidiscretization) - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - u = wrap_array(u_ode, mesh, equations, solver, cache) - - dt = cfl_number * max_dt(u, t, mesh, - have_constant_speed(equations), equations, - solver, cache) -end - -# Time integration methods from the DiffEq ecosystem without adaptive time stepping on their own -# such as `CarpenterKennedy2N54` require passing `dt=...` in `solve(ode, ...)`. Since we don't have -# an integrator at this stage but only the ODE, this method will be used there. It's called in -# many examples in `solve(ode, ..., dt=stepsize_callback(ode), ...)`. -function (cb::DiscreteCallback{Condition, Affect!})(ode::ODEProblem) where {Condition, - Affect! <: - StepsizeCallback - } - stepsize_callback = cb.affect! - @unpack cfl_number = stepsize_callback - u_ode = ode.u0 - t = first(ode.tspan) - semi = ode.p - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - u = wrap_array(u_ode, mesh, equations, solver, cache) - - return cfl_number * - max_dt(u, t, mesh, have_constant_speed(equations), equations, solver, cache) -end - -include("stepsize_dg1d.jl") -include("stepsize_dg2d.jl") -include("stepsize_dg3d.jl") + include("stepsize_dg1d.jl") + include("stepsize_dg2d.jl") + include("stepsize_dg3d.jl") end # @muladd diff --git a/src/callbacks_step/stepsize_dg1d.jl b/src/callbacks_step/stepsize_dg1d.jl index edc25ec78f..e00337c071 100644 --- a/src/callbacks_step/stepsize_dg1d.jl +++ b/src/callbacks_step/stepsize_dg1d.jl @@ -3,82 +3,90 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function max_dt(u, t, mesh::TreeMesh{1}, - constant_speed::False, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) - - for element in eachelement(dg, cache) - max_lambda1 = zero(max_scaled_speed) - for i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, element) - lambda1, = max_abs_speeds(u_node, equations) - max_lambda1 = max(max_lambda1, lambda1) + #! format: noindent + + function max_dt( + u, t, mesh::TreeMesh{1}, + constant_speed::False, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + for element in eachelement(dg, cache) + max_lambda1 = zero(max_scaled_speed) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + lambda1, = max_abs_speeds(u_node, equations) + max_lambda1 = max(max_lambda1, lambda1) + end + inv_jacobian = cache.elements.inverse_jacobian[element] + max_scaled_speed = max(max_scaled_speed, inv_jacobian * max_lambda1) end - inv_jacobian = cache.elements.inverse_jacobian[element] - max_scaled_speed = max(max_scaled_speed, inv_jacobian * max_lambda1) - end - return 2 / (nnodes(dg) * max_scaled_speed) -end + return 2 / (nnodes(dg) * max_scaled_speed) + end -function max_dt(u, t, mesh::TreeMesh{1}, - constant_speed::True, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) + function max_dt( + u, t, mesh::TreeMesh{1}, + constant_speed::True, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + for element in eachelement(dg, cache) + max_lambda1, = max_abs_speeds(equations) + inv_jacobian = cache.elements.inverse_jacobian[element] + max_scaled_speed = max(max_scaled_speed, inv_jacobian * max_lambda1) + end - for element in eachelement(dg, cache) - max_lambda1, = max_abs_speeds(equations) - inv_jacobian = cache.elements.inverse_jacobian[element] - max_scaled_speed = max(max_scaled_speed, inv_jacobian * max_lambda1) + return 2 / (nnodes(dg) * max_scaled_speed) end - return 2 / (nnodes(dg) * max_scaled_speed) -end + function max_dt( + u, t, mesh::StructuredMesh{1}, + constant_speed::False, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) -function max_dt(u, t, mesh::StructuredMesh{1}, - constant_speed::False, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) + for element in eachelement(dg, cache) + max_lambda1 = zero(max_scaled_speed) - for element in eachelement(dg, cache) - max_lambda1 = zero(max_scaled_speed) + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + lambda1, = max_abs_speeds(u_node, equations) - for i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, element) - lambda1, = max_abs_speeds(u_node, equations) + inv_jacobian = cache.elements.inverse_jacobian[i, element] - inv_jacobian = cache.elements.inverse_jacobian[i, element] + max_lambda1 = max(max_lambda1, inv_jacobian * lambda1) + end - max_lambda1 = max(max_lambda1, inv_jacobian * lambda1) + max_scaled_speed = max(max_scaled_speed, max_lambda1) end - max_scaled_speed = max(max_scaled_speed, max_lambda1) + return 2 / (nnodes(dg) * max_scaled_speed) end - return 2 / (nnodes(dg) * max_scaled_speed) -end - -function max_dt(u, t, mesh::StructuredMesh{1}, - constant_speed::True, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) - - for element in eachelement(dg, cache) - max_lambda1, = max_abs_speeds(equations) - - for i in eachnode(dg) - inv_jacobian = cache.elements.inverse_jacobian[i, element] - max_scaled_speed = max(max_scaled_speed, inv_jacobian * max_lambda1) + function max_dt( + u, t, mesh::StructuredMesh{1}, + constant_speed::True, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + for element in eachelement(dg, cache) + max_lambda1, = max_abs_speeds(equations) + + for i in eachnode(dg) + inv_jacobian = cache.elements.inverse_jacobian[i, element] + max_scaled_speed = max(max_scaled_speed, inv_jacobian * max_lambda1) + end end - end - return 2 / (nnodes(dg) * max_scaled_speed) -end + return 2 / (nnodes(dg) * max_scaled_speed) + end end # @muladd diff --git a/src/callbacks_step/stepsize_dg2d.jl b/src/callbacks_step/stepsize_dg2d.jl index 41251506a0..c41b6a5cbc 100644 --- a/src/callbacks_step/stepsize_dg2d.jl +++ b/src/callbacks_step/stepsize_dg2d.jl @@ -3,207 +3,269 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function max_dt(u, t, mesh::TreeMesh{2}, - constant_speed::False, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) - - for element in eachelement(dg, cache) - max_lambda1 = max_lambda2 = zero(max_scaled_speed) - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - lambda1, lambda2 = max_abs_speeds(u_node, equations) - max_lambda1 = max(max_lambda1, lambda1) - max_lambda2 = max(max_lambda2, lambda2) + #! format: noindent + + function max_dt( + u, t, mesh::TreeMesh{2}, + constant_speed::False, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + for element in eachelement(dg, cache) + max_lambda1 = max_lambda2 = zero(max_scaled_speed) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + lambda1, lambda2 = max_abs_speeds(u_node, equations) + max_lambda1 = max(max_lambda1, lambda1) + max_lambda2 = max(max_lambda2, lambda2) + end + inv_jacobian = cache.elements.inverse_jacobian[element] + max_scaled_speed = max( + max_scaled_speed, + inv_jacobian * (max_lambda1 + max_lambda2) + ) end - inv_jacobian = cache.elements.inverse_jacobian[element] - max_scaled_speed = max(max_scaled_speed, - inv_jacobian * (max_lambda1 + max_lambda2)) + + return 2 / (nnodes(dg) * max_scaled_speed) end - return 2 / (nnodes(dg) * max_scaled_speed) -end + function max_dt( + u, t, mesh::TreeMesh{2}, + constant_speed::True, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + for element in eachelement(dg, cache) + max_lambda1, max_lambda2 = max_abs_speeds(equations) + inv_jacobian = cache.elements.inverse_jacobian[element] + max_scaled_speed = max( + max_scaled_speed, + inv_jacobian * (max_lambda1 + max_lambda2) + ) + end -function max_dt(u, t, mesh::TreeMesh{2}, - constant_speed::True, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) + return 2 / (nnodes(dg) * max_scaled_speed) + end - for element in eachelement(dg, cache) - max_lambda1, max_lambda2 = max_abs_speeds(equations) - inv_jacobian = cache.elements.inverse_jacobian[element] - max_scaled_speed = max(max_scaled_speed, - inv_jacobian * (max_lambda1 + max_lambda2)) + function max_dt( + u, t, mesh::ParallelTreeMesh{2}, + constant_speed::False, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::TreeMesh{2}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), TreeMesh{2}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt end - return 2 / (nnodes(dg) * max_scaled_speed) -end - -function max_dt(u, t, mesh::ParallelTreeMesh{2}, - constant_speed::False, equations, dg::DG, cache) - # call the method accepting a general `mesh::TreeMesh{2}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), TreeMesh{2}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end - -function max_dt(u, t, mesh::ParallelTreeMesh{2}, - constant_speed::True, equations, dg::DG, cache) - # call the method accepting a general `mesh::TreeMesh{2}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), TreeMesh{2}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end - -function max_dt(u, t, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}, StructuredMeshView{2}}, - constant_speed::False, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) - - @unpack contravariant_vectors, inverse_jacobian = cache.elements - - for element in eachelement(dg, cache) - max_lambda1 = max_lambda2 = zero(max_scaled_speed) - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - lambda1, lambda2 = max_abs_speeds(u_node, equations) - - # Local speeds transformed to the reference element - Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, - element) - lambda1_transformed = abs(Ja11 * lambda1 + Ja12 * lambda2) - Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, - element) - lambda2_transformed = abs(Ja21 * lambda1 + Ja22 * lambda2) - - inv_jacobian = abs(inverse_jacobian[i, j, element]) - - max_lambda1 = max(max_lambda1, lambda1_transformed * inv_jacobian) - max_lambda2 = max(max_lambda2, lambda2_transformed * inv_jacobian) + function max_dt( + u, t, mesh::ParallelTreeMesh{2}, + constant_speed::True, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::TreeMesh{2}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), TreeMesh{2}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt + end + + function max_dt( + u, t, + mesh::Union{ + StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, StructuredMeshView{2}, + }, + constant_speed::False, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + @unpack contravariant_vectors, inverse_jacobian = cache.elements + + for element in eachelement(dg, cache) + max_lambda1 = max_lambda2 = zero(max_scaled_speed) + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + lambda1, lambda2 = max_abs_speeds(u_node, equations) + + # Local speeds transformed to the reference element + Ja11, Ja12 = get_contravariant_vector( + 1, contravariant_vectors, i, j, + element + ) + lambda1_transformed = abs(Ja11 * lambda1 + Ja12 * lambda2) + Ja21, Ja22 = get_contravariant_vector( + 2, contravariant_vectors, i, j, + element + ) + lambda2_transformed = abs(Ja21 * lambda1 + Ja22 * lambda2) + + inv_jacobian = abs(inverse_jacobian[i, j, element]) + + max_lambda1 = max(max_lambda1, lambda1_transformed * inv_jacobian) + max_lambda2 = max(max_lambda2, lambda2_transformed * inv_jacobian) + end + + max_scaled_speed = max(max_scaled_speed, max_lambda1 + max_lambda2) end - max_scaled_speed = max(max_scaled_speed, max_lambda1 + max_lambda2) + return 2 / (nnodes(dg) * max_scaled_speed) end - return 2 / (nnodes(dg) * max_scaled_speed) -end - -function max_dt(u, t, - mesh::Union{StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}, StructuredMeshView{2}}, - constant_speed::True, equations, dg::DG, cache) - @unpack contravariant_vectors, inverse_jacobian = cache.elements - - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) - - max_lambda1, max_lambda2 = max_abs_speeds(equations) - - for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - # Local speeds transformed to the reference element - Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, - element) - lambda1_transformed = abs(Ja11 * max_lambda1 + Ja12 * max_lambda2) - Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, - element) - lambda2_transformed = abs(Ja21 * max_lambda1 + Ja22 * max_lambda2) - - inv_jacobian = abs(inverse_jacobian[i, j, element]) - max_scaled_speed = max(max_scaled_speed, - inv_jacobian * - (lambda1_transformed + lambda2_transformed)) + function max_dt( + u, t, + mesh::Union{ + StructuredMesh{2}, UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, StructuredMeshView{2}, + }, + constant_speed::True, equations, dg::DG, cache + ) + @unpack contravariant_vectors, inverse_jacobian = cache.elements + + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + max_lambda1, max_lambda2 = max_abs_speeds(equations) + + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + # Local speeds transformed to the reference element + Ja11, Ja12 = get_contravariant_vector( + 1, contravariant_vectors, i, j, + element + ) + lambda1_transformed = abs(Ja11 * max_lambda1 + Ja12 * max_lambda2) + Ja21, Ja22 = get_contravariant_vector( + 2, contravariant_vectors, i, j, + element + ) + lambda2_transformed = abs(Ja21 * max_lambda1 + Ja22 * max_lambda2) + + inv_jacobian = abs(inverse_jacobian[i, j, element]) + max_scaled_speed = max( + max_scaled_speed, + inv_jacobian * + (lambda1_transformed + lambda2_transformed) + ) + end end + + return 2 / (nnodes(dg) * max_scaled_speed) end - return 2 / (nnodes(dg) * max_scaled_speed) -end - -function max_dt(u, t, mesh::ParallelP4estMesh{2}, - constant_speed::False, equations, dg::DG, cache) - # call the method accepting a general `mesh::P4estMesh{2}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), P4estMesh{2}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end - -function max_dt(u, t, mesh::ParallelP4estMesh{2}, - constant_speed::True, equations, dg::DG, cache) - # call the method accepting a general `mesh::P4estMesh{2}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), P4estMesh{2}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end - -function max_dt(u, t, mesh::ParallelT8codeMesh{2}, - constant_speed::False, equations, dg::DG, cache) - # call the method accepting a general `mesh::T8codeMesh{2}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), T8codeMesh{2}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end - -function max_dt(u, t, mesh::ParallelT8codeMesh{2}, - constant_speed::True, equations, dg::DG, cache) - # call the method accepting a general `mesh::T8codeMesh{2}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), T8codeMesh{2}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end + function max_dt( + u, t, mesh::ParallelP4estMesh{2}, + constant_speed::False, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::P4estMesh{2}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), P4estMesh{2}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt + end + + function max_dt( + u, t, mesh::ParallelP4estMesh{2}, + constant_speed::True, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::P4estMesh{2}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), P4estMesh{2}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt + end + + function max_dt( + u, t, mesh::ParallelT8codeMesh{2}, + constant_speed::False, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::T8codeMesh{2}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), T8codeMesh{2}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt + end + + function max_dt( + u, t, mesh::ParallelT8codeMesh{2}, + constant_speed::True, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::T8codeMesh{2}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), T8codeMesh{2}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt + end end # @muladd diff --git a/src/callbacks_step/stepsize_dg3d.jl b/src/callbacks_step/stepsize_dg3d.jl index 664596f989..4c5c232b0d 100644 --- a/src/callbacks_step/stepsize_dg3d.jl +++ b/src/callbacks_step/stepsize_dg3d.jl @@ -3,183 +3,243 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function max_dt(u, t, mesh::TreeMesh{3}, - constant_speed::False, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) - - for element in eachelement(dg, cache) - max_lambda1 = max_lambda2 = max_lambda3 = zero(max_scaled_speed) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - lambda1, lambda2, lambda3 = max_abs_speeds(u_node, equations) - max_lambda1 = max(max_lambda1, lambda1) - max_lambda2 = max(max_lambda2, lambda2) - max_lambda3 = max(max_lambda3, lambda3) + #! format: noindent + + function max_dt( + u, t, mesh::TreeMesh{3}, + constant_speed::False, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + for element in eachelement(dg, cache) + max_lambda1 = max_lambda2 = max_lambda3 = zero(max_scaled_speed) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + lambda1, lambda2, lambda3 = max_abs_speeds(u_node, equations) + max_lambda1 = max(max_lambda1, lambda1) + max_lambda2 = max(max_lambda2, lambda2) + max_lambda3 = max(max_lambda3, lambda3) + end + inv_jacobian = cache.elements.inverse_jacobian[element] + max_scaled_speed = max( + max_scaled_speed, + inv_jacobian * (max_lambda1 + max_lambda2 + max_lambda3) + ) end - inv_jacobian = cache.elements.inverse_jacobian[element] - max_scaled_speed = max(max_scaled_speed, - inv_jacobian * (max_lambda1 + max_lambda2 + max_lambda3)) - end - return 2 / (nnodes(dg) * max_scaled_speed) -end + return 2 / (nnodes(dg) * max_scaled_speed) + end -function max_dt(u, t, mesh::TreeMesh{3}, - constant_speed::True, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) + function max_dt( + u, t, mesh::TreeMesh{3}, + constant_speed::True, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + for element in eachelement(dg, cache) + max_lambda1, max_lambda2, max_lambda3 = max_abs_speeds(equations) + inv_jacobian = cache.elements.inverse_jacobian[element] + max_scaled_speed = max( + max_scaled_speed, + inv_jacobian * (max_lambda1 + max_lambda2 + max_lambda3) + ) + end - for element in eachelement(dg, cache) - max_lambda1, max_lambda2, max_lambda3 = max_abs_speeds(equations) - inv_jacobian = cache.elements.inverse_jacobian[element] - max_scaled_speed = max(max_scaled_speed, - inv_jacobian * (max_lambda1 + max_lambda2 + max_lambda3)) + return 2 / (nnodes(dg) * max_scaled_speed) end - return 2 / (nnodes(dg) * max_scaled_speed) -end - -function max_dt(u, t, mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, - constant_speed::False, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) - - @unpack contravariant_vectors = cache.elements - - for element in eachelement(dg, cache) - max_lambda1 = max_lambda2 = max_lambda3 = zero(max_scaled_speed) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - lambda1, lambda2, lambda3 = max_abs_speeds(u_node, equations) - - Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors, i, j, - k, element) - lambda1_transformed = abs(Ja11 * lambda1 + Ja12 * lambda2 + Ja13 * lambda3) - Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors, i, j, - k, element) - lambda2_transformed = abs(Ja21 * lambda1 + Ja22 * lambda2 + Ja23 * lambda3) - Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors, i, j, - k, element) - lambda3_transformed = abs(Ja31 * lambda1 + Ja32 * lambda2 + Ja33 * lambda3) - - inv_jacobian = abs(cache.elements.inverse_jacobian[i, j, k, element]) - - max_lambda1 = max(max_lambda1, inv_jacobian * lambda1_transformed) - max_lambda2 = max(max_lambda2, inv_jacobian * lambda2_transformed) - max_lambda3 = max(max_lambda3, inv_jacobian * lambda3_transformed) + function max_dt( + u, t, mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, + constant_speed::False, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + @unpack contravariant_vectors = cache.elements + + for element in eachelement(dg, cache) + max_lambda1 = max_lambda2 = max_lambda3 = zero(max_scaled_speed) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + lambda1, lambda2, lambda3 = max_abs_speeds(u_node, equations) + + Ja11, Ja12, Ja13 = get_contravariant_vector( + 1, contravariant_vectors, i, j, + k, element + ) + lambda1_transformed = abs(Ja11 * lambda1 + Ja12 * lambda2 + Ja13 * lambda3) + Ja21, Ja22, Ja23 = get_contravariant_vector( + 2, contravariant_vectors, i, j, + k, element + ) + lambda2_transformed = abs(Ja21 * lambda1 + Ja22 * lambda2 + Ja23 * lambda3) + Ja31, Ja32, Ja33 = get_contravariant_vector( + 3, contravariant_vectors, i, j, + k, element + ) + lambda3_transformed = abs(Ja31 * lambda1 + Ja32 * lambda2 + Ja33 * lambda3) + + inv_jacobian = abs(cache.elements.inverse_jacobian[i, j, k, element]) + + max_lambda1 = max(max_lambda1, inv_jacobian * lambda1_transformed) + max_lambda2 = max(max_lambda2, inv_jacobian * lambda2_transformed) + max_lambda3 = max(max_lambda3, inv_jacobian * lambda3_transformed) + end + + max_scaled_speed = max( + max_scaled_speed, + max_lambda1 + max_lambda2 + max_lambda3 + ) end - max_scaled_speed = max(max_scaled_speed, - max_lambda1 + max_lambda2 + max_lambda3) + return 2 / (nnodes(dg) * max_scaled_speed) end - return 2 / (nnodes(dg) * max_scaled_speed) -end - -function max_dt(u, t, mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, - constant_speed::True, equations, dg::DG, cache) - # to avoid a division by zero if the speed vanishes everywhere, - # e.g. for steady-state linear advection - max_scaled_speed = nextfloat(zero(t)) - - @unpack contravariant_vectors = cache.elements - - max_lambda1, max_lambda2, max_lambda3 = max_abs_speeds(equations) - - for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors, i, j, - k, element) - lambda1_transformed = abs(Ja11 * max_lambda1 + Ja12 * max_lambda2 + - Ja13 * max_lambda3) - Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors, i, j, - k, element) - lambda2_transformed = abs(Ja21 * max_lambda1 + Ja22 * max_lambda2 + - Ja23 * max_lambda3) - Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors, i, j, - k, element) - lambda3_transformed = abs(Ja31 * max_lambda1 + Ja32 * max_lambda2 + - Ja33 * max_lambda3) - - inv_jacobian = abs(cache.elements.inverse_jacobian[i, j, k, element]) - - max_scaled_speed = max(max_scaled_speed, - inv_jacobian * - (lambda1_transformed + lambda2_transformed + - lambda3_transformed)) + function max_dt( + u, t, mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, + constant_speed::True, equations, dg::DG, cache + ) + # to avoid a division by zero if the speed vanishes everywhere, + # e.g. for steady-state linear advection + max_scaled_speed = nextfloat(zero(t)) + + @unpack contravariant_vectors = cache.elements + + max_lambda1, max_lambda2, max_lambda3 = max_abs_speeds(equations) + + for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + Ja11, Ja12, Ja13 = get_contravariant_vector( + 1, contravariant_vectors, i, j, + k, element + ) + lambda1_transformed = abs( + Ja11 * max_lambda1 + Ja12 * max_lambda2 + + Ja13 * max_lambda3 + ) + Ja21, Ja22, Ja23 = get_contravariant_vector( + 2, contravariant_vectors, i, j, + k, element + ) + lambda2_transformed = abs( + Ja21 * max_lambda1 + Ja22 * max_lambda2 + + Ja23 * max_lambda3 + ) + Ja31, Ja32, Ja33 = get_contravariant_vector( + 3, contravariant_vectors, i, j, + k, element + ) + lambda3_transformed = abs( + Ja31 * max_lambda1 + Ja32 * max_lambda2 + + Ja33 * max_lambda3 + ) + + inv_jacobian = abs(cache.elements.inverse_jacobian[i, j, k, element]) + + max_scaled_speed = max( + max_scaled_speed, + inv_jacobian * + ( + lambda1_transformed + lambda2_transformed + + lambda3_transformed + ) + ) + end end + + return 2 / (nnodes(dg) * max_scaled_speed) + end + + function max_dt( + u, t, mesh::ParallelP4estMesh{3}, + constant_speed::False, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::P4estMesh{3}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), P4estMesh{3}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt + end + + function max_dt( + u, t, mesh::ParallelP4estMesh{3}, + constant_speed::True, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::P4estMesh{3}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), P4estMesh{3}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt + end + + function max_dt( + u, t, mesh::ParallelT8codeMesh{3}, + constant_speed::False, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::T8codeMesh{3}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), T8codeMesh{3}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt end - return 2 / (nnodes(dg) * max_scaled_speed) -end - -function max_dt(u, t, mesh::ParallelP4estMesh{3}, - constant_speed::False, equations, dg::DG, cache) - # call the method accepting a general `mesh::P4estMesh{3}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), P4estMesh{3}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end - -function max_dt(u, t, mesh::ParallelP4estMesh{3}, - constant_speed::True, equations, dg::DG, cache) - # call the method accepting a general `mesh::P4estMesh{3}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), P4estMesh{3}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end - -function max_dt(u, t, mesh::ParallelT8codeMesh{3}, - constant_speed::False, equations, dg::DG, cache) - # call the method accepting a general `mesh::T8codeMesh{3}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), T8codeMesh{3}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end - -function max_dt(u, t, mesh::ParallelT8codeMesh{3}, - constant_speed::True, equations, dg::DG, cache) - # call the method accepting a general `mesh::T8codeMesh{3}` - # TODO: MPI, we should improve this; maybe we should dispatch on `u` - # and create some MPI array type, overloading broadcasting and mapreduce etc. - # Then, this specific array type should also work well with DiffEq etc. - dt = invoke(max_dt, - Tuple{typeof(u), typeof(t), T8codeMesh{3}, - typeof(constant_speed), typeof(equations), typeof(dg), - typeof(cache)}, - u, t, mesh, constant_speed, equations, dg, cache) - dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] - - return dt -end + function max_dt( + u, t, mesh::ParallelT8codeMesh{3}, + constant_speed::True, equations, dg::DG, cache + ) + # call the method accepting a general `mesh::T8codeMesh{3}` + # TODO: MPI, we should improve this; maybe we should dispatch on `u` + # and create some MPI array type, overloading broadcasting and mapreduce etc. + # Then, this specific array type should also work well with DiffEq etc. + dt = invoke( + max_dt, + Tuple{ + typeof(u), typeof(t), T8codeMesh{3}, + typeof(constant_speed), typeof(equations), typeof(dg), + typeof(cache), + }, + u, t, mesh, constant_speed, equations, dg, cache + ) + dt = MPI.Allreduce!(Ref(dt), min, mpi_comm())[] + + return dt + end end # @muladd diff --git a/src/callbacks_step/summary.jl b/src/callbacks_step/summary.jl index 465cc10a31..2428bb7a92 100644 --- a/src/callbacks_step/summary.jl +++ b/src/callbacks_step/summary.jl @@ -3,247 +3,274 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -summary_callback(u, t, integrator) = false # when used as condition; never call the summary callback during the simulation -summary_callback(integrator) = u_modified!(integrator, false) # the summary callback does nothing when called accidentally - -""" - SummaryCallback() - -Create and return a callback that prints a human-readable summary of the simulation setup at the -beginning of a simulation and then resets the timer. When the returned callback is executed -directly, the current timer values are shown. -""" -function SummaryCallback(reset_threads = true) - function initialize(cb, u, t, integrator) - initialize_summary_callback(cb, u, t, integrator; - reset_threads) + #! format: noindent + + summary_callback(u, t, integrator) = false # when used as condition; never call the summary callback during the simulation + summary_callback(integrator) = u_modified!(integrator, false) # the summary callback does nothing when called accidentally + + """ + SummaryCallback() + + Create and return a callback that prints a human-readable summary of the simulation setup at the + beginning of a simulation and then resets the timer. When the returned callback is executed + directly, the current timer values are shown. + """ + function SummaryCallback(reset_threads = true) + function initialize(cb, u, t, integrator) + initialize_summary_callback( + cb, u, t, integrator; + reset_threads + ) + end + DiscreteCallback( + summary_callback, summary_callback, + save_positions = (false, false), + initialize = initialize + ) end - DiscreteCallback(summary_callback, summary_callback, - save_positions = (false, false), - initialize = initialize) -end - -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:typeof(summary_callback)}) - @nospecialize cb # reduce precompilation time - - print(io, "SummaryCallback") -end - -# Format a key/value pair for output from the SummaryCallback -function format_key_value_line(key::AbstractString, value::AbstractString, key_width, - total_width; - indentation_level = 0, guide = '…', filler = '…', - prefix = "│ ", suffix = " │") - @assert key_width < total_width - line = prefix - # Indent the key as requested (or not at all if `indentation_level == 0`) - indentation = prefix^indentation_level - reduced_key_width = key_width - length(indentation) - squeezed_key = indentation * squeeze(key, reduced_key_width, filler = filler) - line *= squeezed_key - line *= ": " - short = key_width - length(squeezed_key) - if short <= 1 - line *= " " - else - line *= guide^(short - 1) * " " + + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:typeof(summary_callback)}) + @nospecialize cb # reduce precompilation time + + print(io, "SummaryCallback") end - value_width = total_width - length(prefix) - length(suffix) - key_width - 2 - squeezed_value = squeeze(value, value_width, filler = filler) - line *= squeezed_value - short = value_width - length(squeezed_value) - line *= " "^short - line *= suffix - - @assert length(line)==total_width "should not happen: algorithm error!" - - return line -end -function format_key_value_line(key, value, args...; kwargs...) - format_key_value_line(string(key), string(value), args...; kwargs...) -end - -# Squeeze a string to fit into a maximum width by deleting characters from the center -function squeeze(message, max_width; filler::Char = '…') - @assert max_width>=3 "squeezing works only for a minimum `max_width` of 3" - - length(message) <= max_width && return message - - keep_front = div(max_width, 2) - keep_back = div(max_width, 2) - (isodd(max_width) ? 0 : 1) - remove_back = length(message) - keep_front - remove_front = length(message) - keep_back - squeezed = (chop(message, head = 0, tail = remove_back) - * filler * - chop(message, head = remove_front, tail = 0)) - @assert length(squeezed)==max_width "`$(length(squeezed)) != $max_width` should not happen: algorithm error!" + # Format a key/value pair for output from the SummaryCallback + function format_key_value_line( + key::AbstractString, value::AbstractString, key_width, + total_width; + indentation_level = 0, guide = '…', filler = '…', + prefix = "│ ", suffix = " │" + ) + @assert key_width < total_width + line = prefix + # Indent the key as requested (or not at all if `indentation_level == 0`) + indentation = prefix^indentation_level + reduced_key_width = key_width - length(indentation) + squeezed_key = indentation * squeeze(key, reduced_key_width, filler = filler) + line *= squeezed_key + line *= ": " + short = key_width - length(squeezed_key) + if short <= 1 + line *= " " + else + line *= guide^(short - 1) * " " + end + value_width = total_width - length(prefix) - length(suffix) - key_width - 2 + squeezed_value = squeeze(value, value_width, filler = filler) + line *= squeezed_value + short = value_width - length(squeezed_value) + line *= " "^short + line *= suffix - return squeezed -end + @assert length(line) == total_width "should not happen: algorithm error!" -# Print a summary with a box around it with a given heading and a setup of key=>value pairs -function summary_box(io::IO, heading, setup = []) - summary_header(io, heading) - for (key, value) in setup - summary_line(io, key, value) + return line + end + function format_key_value_line(key, value, args...; kwargs...) + format_key_value_line(string(key), string(value), args...; kwargs...) end - summary_footer(io) -end -function summary_header(io, heading; total_width = 100, indentation_level = 0) - total_width = get(io, :total_width, total_width) - indentation_level = get(io, :indentation_level, indentation_level) + # Squeeze a string to fit into a maximum width by deleting characters from the center + function squeeze(message, max_width; filler::Char = '…') + @assert max_width >= 3 "squeezing works only for a minimum `max_width` of 3" - @assert indentation_level>=0 "indentation level may not be negative" + length(message) <= max_width && return message - # If indentation level is greater than zero, we assume the header has already been printed - indentation_level > 0 && return + keep_front = div(max_width, 2) + keep_back = div(max_width, 2) - (isodd(max_width) ? 0 : 1) + remove_back = length(message) - keep_front + remove_front = length(message) - keep_back + squeezed = ( + chop(message, head = 0, tail = remove_back) + * filler * + chop(message, head = remove_front, tail = 0) + ) - # Print header - println(io, "┌" * "─"^(total_width - 2) * "┐") - println(io, "│ " * heading * " "^(total_width - length(heading) - 4) * " │") - println(io, - "│ " * "═"^length(heading) * " "^(total_width - length(heading) - 4) * " │") -end + @assert length(squeezed) == max_width "`$(length(squeezed)) != $max_width` should not happen: algorithm error!" -function summary_line(io, key, value; key_width = 30, total_width = 100, - indentation_level = 0) - # Printing is not performance-critical, so we can use `@nospecialize` to reduce latency - @nospecialize value # reduce precompilation time + return squeezed + end - key_width = get(io, :key_width, key_width) - total_width = get(io, :total_width, total_width) - indentation_level = get(io, :indentation_level, indentation_level) + # Print a summary with a box around it with a given heading and a setup of key=>value pairs + function summary_box(io::IO, heading, setup = []) + summary_header(io, heading) + for (key, value) in setup + summary_line(io, key, value) + end + summary_footer(io) + end - s = format_key_value_line(key, value, key_width, total_width, - indentation_level = indentation_level) + function summary_header(io, heading; total_width = 100, indentation_level = 0) + total_width = get(io, :total_width, total_width) + indentation_level = get(io, :indentation_level, indentation_level) - println(io, s) -end + @assert indentation_level >= 0 "indentation level may not be negative" -function summary_footer(io; total_width = 100, indentation_level = 0) - total_width = get(io, :total_width, 100) - indentation_level = get(io, :indentation_level, 0) + # If indentation level is greater than zero, we assume the header has already been printed + indentation_level > 0 && return - if indentation_level == 0 - s = "└" * "─"^(total_width - 2) * "┘" - else - s = "" + # Print header + println(io, "┌" * "─"^(total_width - 2) * "┐") + println(io, "│ " * heading * " "^(total_width - length(heading) - 4) * " │") + println( + io, + "│ " * "═"^length(heading) * " "^(total_width - length(heading) - 4) * " │" + ) end - print(io, s) -end - -@inline function increment_indent(io) - IOContext(io, :indentation_level => get(io, :indentation_level, 0) + 1) -end - -# Print information about the current simulation setup -# Note: This is called *after* all initialization is done, but *before* the first time step -function initialize_summary_callback(cb::DiscreteCallback, u, t, integrator; - reset_threads = true) - # Optionally reset Polyester.jl threads. See - # https://github.com/trixi-framework/Trixi.jl/issues/1583 - # https://github.com/JuliaSIMD/Polyester.jl/issues/30 - if reset_threads - Polyester.reset_threads!() + function summary_line( + io, key, value; key_width = 30, total_width = 100, + indentation_level = 0 + ) + # Printing is not performance-critical, so we can use `@nospecialize` to reduce latency + @nospecialize value # reduce precompilation time + + key_width = get(io, :key_width, key_width) + total_width = get(io, :total_width, total_width) + indentation_level = get(io, :indentation_level, indentation_level) + + s = format_key_value_line( + key, value, key_width, total_width, + indentation_level = indentation_level + ) + + println(io, s) end - # The summary callback should only print information on the root process. - # However, all other MPI processes should also reset the timer so that - # it can be used to diagnose performance. - if !mpi_isroot() - reset_timer!(timer()) - return nothing + function summary_footer(io; total_width = 100, indentation_level = 0) + total_width = get(io, :total_width, 100) + indentation_level = get(io, :indentation_level, 0) + + if indentation_level == 0 + s = "└" * "─"^(total_width - 2) * "┘" + else + s = "" + end + + print(io, s) end - print_startup_message() + @inline function increment_indent(io) + IOContext(io, :indentation_level => get(io, :indentation_level, 0) + 1) + end - io = stdout - io_context = IOContext(io, - :compact => false, - :key_width => 30, - :total_width => 100, - :indentation_level => 0) + # Print information about the current simulation setup + # Note: This is called *after* all initialization is done, but *before* the first time step + function initialize_summary_callback( + cb::DiscreteCallback, u, t, integrator; + reset_threads = true + ) + # Optionally reset Polyester.jl threads. See + # https://github.com/trixi-framework/Trixi.jl/issues/1583 + # https://github.com/JuliaSIMD/Polyester.jl/issues/30 + if reset_threads + Polyester.reset_threads!() + end - semi = integrator.p - print_summary_semidiscretization(io_context, semi) + # The summary callback should only print information on the root process. + # However, all other MPI processes should also reset the timer so that + # it can be used to diagnose performance. + if !mpi_isroot() + reset_timer!(timer()) + return nothing + end - callbacks = integrator.opts.callback - if callbacks isa CallbackSet - foreach(callbacks.continuous_callbacks) do cb - show(io_context, MIME"text/plain"(), cb) + print_startup_message() + + io = stdout + io_context = IOContext( + io, + :compact => false, + :key_width => 30, + :total_width => 100, + :indentation_level => 0 + ) + + semi = integrator.p + print_summary_semidiscretization(io_context, semi) + + callbacks = integrator.opts.callback + if callbacks isa CallbackSet + foreach(callbacks.continuous_callbacks) do cb + show(io_context, MIME"text/plain"(), cb) + println(io, "\n") + end + foreach(callbacks.discrete_callbacks) do cb + # Do not show ourselves + cb.affect! === summary_callback && return nothing + + show(io_context, MIME"text/plain"(), cb) + println(io, "\n") + return nothing + end + else + show(io_context, MIME"text/plain"(), callbacks) println(io, "\n") end - foreach(callbacks.discrete_callbacks) do cb - # Do not show ourselves - cb.affect! === summary_callback && return nothing - show(io_context, MIME"text/plain"(), cb) - println(io, "\n") - return nothing + # time integration + setup = Pair{String, Any}[ + "Start time" => first(integrator.sol.prob.tspan), + "Final time" => last(integrator.sol.prob.tspan), + "time integrator" => integrator.alg |> typeof |> nameof, + "adaptive" => integrator.opts.adaptive, + ] + if integrator.opts.adaptive + push!( + setup, + "abstol" => integrator.opts.abstol, + "reltol" => integrator.opts.reltol, + "controller" => integrator.opts.controller + ) end - else - show(io_context, MIME"text/plain"(), callbacks) - println(io, "\n") - end + summary_box(io, "Time integration", setup) + println() + + # technical details + setup = Pair{String, Any}["#threads" => Threads.nthreads()] + if !_PREFERENCE_POLYESTER + push!(setup, "Polyester" => "disabled") + end + if mpi_isparallel() + push!( + setup, + "#MPI ranks" => mpi_nranks() + ) + end + summary_box(io, "Environment information", setup) + println() - # time integration - setup = Pair{String, Any}["Start time" => first(integrator.sol.prob.tspan), - "Final time" => last(integrator.sol.prob.tspan), - "time integrator" => integrator.alg |> typeof |> nameof, - "adaptive" => integrator.opts.adaptive] - if integrator.opts.adaptive - push!(setup, - "abstol" => integrator.opts.abstol, - "reltol" => integrator.opts.reltol, - "controller" => integrator.opts.controller) + reset_timer!(timer()) + + return nothing end - summary_box(io, "Time integration", setup) - println() - # technical details - setup = Pair{String, Any}["#threads" => Threads.nthreads()] - if !_PREFERENCE_POLYESTER - push!(setup, "Polyester" => "disabled") + function print_summary_semidiscretization(io::IO, semi::AbstractSemidiscretization) + show(io, MIME"text/plain"(), semi) + println(io, "\n") + mesh, equations, solver, _ = mesh_equations_solver_cache(semi) + show(io, MIME"text/plain"(), mesh) + println(io, "\n") + show(io, MIME"text/plain"(), equations) + println(io, "\n") + show(io, MIME"text/plain"(), solver) + println(io, "\n") end - if mpi_isparallel() - push!(setup, - "#MPI ranks" => mpi_nranks()) + + function (cb::DiscreteCallback{Condition, Affect!})(io::IO = stdout) where { + Condition, + Affect! <: + typeof(summary_callback), + } + mpi_isroot() || return nothing + + TimerOutputs.complement!(timer()) + print_timer( + io, timer(), title = "Trixi.jl", + allocations = true, linechars = :unicode, compact = false + ) + println(io) + return nothing end - summary_box(io, "Environment information", setup) - println() - - reset_timer!(timer()) - - return nothing -end - -function print_summary_semidiscretization(io::IO, semi::AbstractSemidiscretization) - show(io, MIME"text/plain"(), semi) - println(io, "\n") - mesh, equations, solver, _ = mesh_equations_solver_cache(semi) - show(io, MIME"text/plain"(), mesh) - println(io, "\n") - show(io, MIME"text/plain"(), equations) - println(io, "\n") - show(io, MIME"text/plain"(), solver) - println(io, "\n") -end - -function (cb::DiscreteCallback{Condition, Affect!})(io::IO = stdout) where {Condition, - Affect! <: - typeof(summary_callback) - } - mpi_isroot() || return nothing - - TimerOutputs.complement!(timer()) - print_timer(io, timer(), title = "Trixi.jl", - allocations = true, linechars = :unicode, compact = false) - println(io) - return nothing -end end # @muladd diff --git a/src/callbacks_step/time_series.jl b/src/callbacks_step/time_series.jl index ae18c85700..a579f12a6b 100644 --- a/src/callbacks_step/time_series.jl +++ b/src/callbacks_step/time_series.jl @@ -3,220 +3,252 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - TimeSeriesCallback(semi, point_coordinates; - interval=1, solution_variables=cons2cons, - output_directory="out", filename="time_series.h5", - RealT=real(solver), uEltype=eltype(cache.elements)) - -Create a callback that records point-wise data at points given in `point_coordinates` every -`interval` time steps. The point coordinates are to be specified either as a vector of coordinate -tuples or as a two-dimensional array where the first dimension is the point number and the second -dimension is the coordinate dimension. By default, the conservative variables are recorded, but this -can be controlled by passing a different conversion function to `solution_variables`. - -After the last time step, the results are stored in an HDF5 file `filename` in directory -`output_directory`. - -The real data type `RealT` and data type for solution variables `uEltype` default to the respective -types used in the solver and the cache. - -Currently this callback is only implemented for [`TreeMesh`](@ref) and [`UnstructuredMesh2D`](@ref). -""" -mutable struct TimeSeriesCallback{RealT <: Real, uEltype <: Real, SolutionVariables, - VariableNames, Cache} - interval::Int - solution_variables::SolutionVariables - variable_names::VariableNames - output_directory::String - filename::String - point_coordinates::Array{RealT, 2} - # Point data is stored as a vector of vectors of the solution data type: - # * the "outer" `Vector` contains one vector for each point at which a time_series is recorded - # * the "inner" `Vector` contains the actual time series for a single point, - # with each record adding "n_vars" entries - # The reason for using this data structure is that the length of the inner vectors needs to be - # increased for each record, which can only be realized in Julia using ordinary `Vector`s. - point_data::Vector{Vector{uEltype}} - time::Vector{RealT} - step::Vector{Int} - time_series_cache::Cache -end - -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:TimeSeriesCallback}) - @nospecialize cb # reduce precompilation time - - time_series_callback = cb.affect! - @unpack interval, solution_variables, output_directory, filename = time_series_callback - print(io, "TimeSeriesCallback(", - "interval=", interval, ", ", - "solution_variables=", interval, ", ", - "output_directory=", "\"output_directory\"", ", ", - "filename=", "\"filename\"", - ")") -end - -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{<:Any, <:TimeSeriesCallback}) - @nospecialize cb # reduce precompilation time - - if get(io, :compact, false) - show(io, cb) - else - time_series_callback = cb.affect! - - setup = [ - "#points" => size(time_series_callback.point_coordinates, 2), - "interval" => time_series_callback.interval, - "solution_variables" => time_series_callback.solution_variables, - "output_directory" => time_series_callback.output_directory, - "filename" => time_series_callback.filename, - ] - summary_box(io, "TimeSeriesCallback", setup) - end -end - -# Main constructor -function TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates; - interval::Integer = 1, - solution_variables = cons2cons, - output_directory = "out", - filename = "time_series.h5", - RealT = real(solver), - uEltype = eltype(cache.elements)) - # check arguments - if !(interval isa Integer && interval >= 0) - throw(ArgumentError("`interval` must be a non-negative integer (provided `interval = $interval`)")) + #! format: noindent + + """ + TimeSeriesCallback(semi, point_coordinates; + interval=1, solution_variables=cons2cons, + output_directory="out", filename="time_series.h5", + RealT=real(solver), uEltype=eltype(cache.elements)) + + Create a callback that records point-wise data at points given in `point_coordinates` every + `interval` time steps. The point coordinates are to be specified either as a vector of coordinate + tuples or as a two-dimensional array where the first dimension is the point number and the second + dimension is the coordinate dimension. By default, the conservative variables are recorded, but this + can be controlled by passing a different conversion function to `solution_variables`. + + After the last time step, the results are stored in an HDF5 file `filename` in directory + `output_directory`. + + The real data type `RealT` and data type for solution variables `uEltype` default to the respective + types used in the solver and the cache. + + Currently this callback is only implemented for [`TreeMesh`](@ref) and [`UnstructuredMesh2D`](@ref). + """ + mutable struct TimeSeriesCallback{ + RealT <: Real, uEltype <: Real, SolutionVariables, + VariableNames, Cache, + } + interval::Int + solution_variables::SolutionVariables + variable_names::VariableNames + output_directory::String + filename::String + point_coordinates::Array{RealT, 2} + # Point data is stored as a vector of vectors of the solution data type: + # * the "outer" `Vector` contains one vector for each point at which a time_series is recorded + # * the "inner" `Vector` contains the actual time series for a single point, + # with each record adding "n_vars" entries + # The reason for using this data structure is that the length of the inner vectors needs to be + # increased for each record, which can only be realized in Julia using ordinary `Vector`s. + point_data::Vector{Vector{uEltype}} + time::Vector{RealT} + step::Vector{Int} + time_series_cache::Cache end - if ndims(point_coordinates) != 2 || size(point_coordinates, 2) != ndims(mesh) - throw(ArgumentError("`point_coordinates` must be a matrix of size n_points × ndims")) - end + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:TimeSeriesCallback}) + @nospecialize cb # reduce precompilation time - # create the output folder if it does not exist already - if mpi_isroot() && !isdir(output_directory) - mkpath(output_directory) + time_series_callback = cb.affect! + @unpack interval, solution_variables, output_directory, filename = time_series_callback + print( + io, "TimeSeriesCallback(", + "interval=", interval, ", ", + "solution_variables=", interval, ", ", + "output_directory=", "\"output_directory\"", ", ", + "filename=", "\"filename\"", + ")" + ) end - # Transpose point_coordinates to our usual format [ndims, n_points] - # Note: They are accepted in a different format to allow direct input from `readdlm` - point_coordinates_ = permutedims(point_coordinates) - - # Invoke callback every `interval` time steps or after final step (for storing the data on disk) - if interval > 0 - # With error-based step size control, some steps can be rejected. Thus, - # `integrator.iter >= integrator.stats.naccept` - # (total #steps) (#accepted steps) - # We need to check the number of accepted steps since callbacks are not - # activated after a rejected step. - condition = (u, t, integrator) -> ((integrator.stats.naccept % interval == 0 && - !(integrator.stats.naccept == 0 && - integrator.iter > 0)) || - isfinished(integrator)) - else # disable the callback for interval == 0 - condition = (u, t, integrator) -> false + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{<:Any, <:TimeSeriesCallback} + ) + @nospecialize cb # reduce precompilation time + + if get(io, :compact, false) + show(io, cb) + else + time_series_callback = cb.affect! + + setup = [ + "#points" => size(time_series_callback.point_coordinates, 2), + "interval" => time_series_callback.interval, + "solution_variables" => time_series_callback.solution_variables, + "output_directory" => time_series_callback.output_directory, + "filename" => time_series_callback.filename, + ] + summary_box(io, "TimeSeriesCallback", setup) + end end - # Create data structures that are to be filled by the callback - variable_names = varnames(solution_variables, equations) - n_points = size(point_coordinates_, 2) - point_data = Vector{uEltype}[Vector{uEltype}() for _ in 1:n_points] - time = Vector{RealT}() - step = Vector{Int}() - time_series_cache = create_cache_time_series(point_coordinates_, mesh, solver, - cache) - - time_series_callback = TimeSeriesCallback(interval, - solution_variables, - variable_names, - output_directory, - filename, - point_coordinates_, - point_data, - time, - step, - time_series_cache) - - return DiscreteCallback(condition, time_series_callback, - save_positions = (false, false)) -end - -# Convenience constructor that unpacks the semidiscretization into mesh, equations, solver, cache -function TimeSeriesCallback(semi, point_coordinates; kwargs...) - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - - return TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates; - kwargs...) -end - -# Convenience constructor that converts a vector of points into a Trixi.jl-style coordinate array -function TimeSeriesCallback(mesh, equations, solver, cache, - point_coordinates::AbstractVector; - kwargs...) - # Coordinates are usually stored in [ndims, n_points], but here as [n_points, ndims] - n_points = length(point_coordinates) - point_coordinates_ = Matrix{eltype(eltype(point_coordinates))}(undef, n_points, - ndims(mesh)) - - for p in 1:n_points - for d in 1:ndims(mesh) - point_coordinates_[p, d] = point_coordinates[p][d] + # Main constructor + function TimeSeriesCallback( + mesh, equations, solver, cache, point_coordinates; + interval::Integer = 1, + solution_variables = cons2cons, + output_directory = "out", + filename = "time_series.h5", + RealT = real(solver), + uEltype = eltype(cache.elements) + ) + # check arguments + if !(interval isa Integer && interval >= 0) + throw(ArgumentError("`interval` must be a non-negative integer (provided `interval = $interval`)")) end - end - return TimeSeriesCallback(mesh, equations, solver, cache, point_coordinates_; - kwargs...) -end + if ndims(point_coordinates) != 2 || size(point_coordinates, 2) != ndims(mesh) + throw(ArgumentError("`point_coordinates` must be a matrix of size n_points × ndims")) + end -# This method is called as callback during the time integration. -function (time_series_callback::TimeSeriesCallback)(integrator) - # Ensure this is not accidentally used with AMR enabled - if uses_amr(integrator.opts.callback) - error("the TimeSeriesCallback does not work with AMR enabled") - end + # create the output folder if it does not exist already + if mpi_isroot() && !isdir(output_directory) + mkpath(output_directory) + end - @unpack interval = time_series_callback + # Transpose point_coordinates to our usual format [ndims, n_points] + # Note: They are accepted in a different format to allow direct input from `readdlm` + point_coordinates_ = permutedims(point_coordinates) + + # Invoke callback every `interval` time steps or after final step (for storing the data on disk) + if interval > 0 + # With error-based step size control, some steps can be rejected. Thus, + # `integrator.iter >= integrator.stats.naccept` + # (total #steps) (#accepted steps) + # We need to check the number of accepted steps since callbacks are not + # activated after a rejected step. + condition = (u, t, integrator) -> ( + ( + integrator.stats.naccept % interval == 0 && + !( + integrator.stats.naccept == 0 && + integrator.iter > 0 + ) + ) || + isfinished(integrator) + ) + else # disable the callback for interval == 0 + condition = (u, t, integrator) -> false + end - # Create record if in correct interval (needs to be checked since the callback is also called - # after the final step for storing the data on disk, independent of the current interval) - if integrator.stats.naccept % interval == 0 - @trixi_timeit timer() "time series" begin - # Store time and step - push!(time_series_callback.time, integrator.t) - push!(time_series_callback.step, integrator.stats.naccept) + # Create data structures that are to be filled by the callback + variable_names = varnames(solution_variables, equations) + n_points = size(point_coordinates_, 2) + point_data = Vector{uEltype}[Vector{uEltype}() for _ in 1:n_points] + time = Vector{RealT}() + step = Vector{Int}() + time_series_cache = create_cache_time_series( + point_coordinates_, mesh, solver, + cache + ) + + time_series_callback = TimeSeriesCallback( + interval, + solution_variables, + variable_names, + output_directory, + filename, + point_coordinates_, + point_data, + time, + step, + time_series_cache + ) + + return DiscreteCallback( + condition, time_series_callback, + save_positions = (false, false) + ) + end - # Unpack data - u_ode = integrator.u - semi = integrator.p - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - u = wrap_array(u_ode, mesh, equations, solver, cache) + # Convenience constructor that unpacks the semidiscretization into mesh, equations, solver, cache + function TimeSeriesCallback(semi, point_coordinates; kwargs...) + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - @unpack (point_data, solution_variables, - variable_names, time_series_cache) = time_series_callback + return TimeSeriesCallback( + mesh, equations, solver, cache, point_coordinates; + kwargs... + ) + end - # Record state at points (solver/mesh-dependent implementation) - record_state_at_points!(point_data, u, solution_variables, - length(variable_names), mesh, - equations, solver, time_series_cache) + # Convenience constructor that converts a vector of points into a Trixi.jl-style coordinate array + function TimeSeriesCallback( + mesh, equations, solver, cache, + point_coordinates::AbstractVector; + kwargs... + ) + # Coordinates are usually stored in [ndims, n_points], but here as [n_points, ndims] + n_points = length(point_coordinates) + point_coordinates_ = Matrix{eltype(eltype(point_coordinates))}( + undef, n_points, + ndims(mesh) + ) + + for p in 1:n_points + for d in 1:ndims(mesh) + point_coordinates_[p, d] = point_coordinates[p][d] + end end - end - # Store time_series if this is the last time step - if isfinished(integrator) - semi = integrator.p - mesh, equations, solver, _ = mesh_equations_solver_cache(semi) - save_time_series_file(time_series_callback, mesh, equations, solver) + return TimeSeriesCallback( + mesh, equations, solver, cache, point_coordinates_; + kwargs... + ) end - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) + # This method is called as callback during the time integration. + function (time_series_callback::TimeSeriesCallback)(integrator) + # Ensure this is not accidentally used with AMR enabled + if uses_amr(integrator.opts.callback) + error("the TimeSeriesCallback does not work with AMR enabled") + end + + @unpack interval = time_series_callback + + # Create record if in correct interval (needs to be checked since the callback is also called + # after the final step for storing the data on disk, independent of the current interval) + if integrator.stats.naccept % interval == 0 + @trixi_timeit timer() "time series" begin + # Store time and step + push!(time_series_callback.time, integrator.t) + push!(time_series_callback.step, integrator.stats.naccept) + + # Unpack data + u_ode = integrator.u + semi = integrator.p + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + u = wrap_array(u_ode, mesh, equations, solver, cache) + + @unpack ( + point_data, solution_variables, + variable_names, time_series_cache, + ) = time_series_callback + + # Record state at points (solver/mesh-dependent implementation) + record_state_at_points!( + point_data, u, solution_variables, + length(variable_names), mesh, + equations, solver, time_series_cache + ) + end + end - return nothing -end + # Store time_series if this is the last time step + if isfinished(integrator) + semi = integrator.p + mesh, equations, solver, _ = mesh_equations_solver_cache(semi) + save_time_series_file(time_series_callback, mesh, equations, solver) + end + + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) + + return nothing + end -include("time_series_dg.jl") -include("time_series_dg_tree.jl") -include("time_series_dg_unstructured.jl") + include("time_series_dg.jl") + include("time_series_dg_tree.jl") + include("time_series_dg_unstructured.jl") end # @muladd diff --git a/src/callbacks_step/time_series_dg.jl b/src/callbacks_step/time_series_dg.jl index 5ba072bf56..d6843724e5 100644 --- a/src/callbacks_step/time_series_dg.jl +++ b/src/callbacks_step/time_series_dg.jl @@ -3,72 +3,86 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Store time series file for a DG solver -function save_time_series_file(time_series_callback, - mesh::Union{TreeMesh, UnstructuredMesh2D}, - equations, dg::DG) - @unpack (interval, variable_names, - output_directory, filename, point_coordinates, - point_data, time, step) = time_series_callback - n_points = length(point_data) + # Store time series file for a DG solver + function save_time_series_file( + time_series_callback, + mesh::Union{TreeMesh, UnstructuredMesh2D}, + equations, dg::DG + ) + @unpack ( + interval, variable_names, + output_directory, filename, point_coordinates, + point_data, time, step, + ) = time_series_callback + n_points = length(point_data) - h5open(joinpath(output_directory, filename), "w") do file - # Add context information as attributes - n_variables = length(variable_names) - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["equations"] = get_name(equations) - attributes(file)["polydeg"] = polydeg(dg) - attributes(file)["n_vars"] = n_variables - attributes(file)["n_points"] = n_points - attributes(file)["interval"] = interval - attributes(file)["variable_names"] = collect(variable_names) + h5open(joinpath(output_directory, filename), "w") do file + # Add context information as attributes + n_variables = length(variable_names) + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["equations"] = get_name(equations) + attributes(file)["polydeg"] = polydeg(dg) + attributes(file)["n_vars"] = n_variables + attributes(file)["n_points"] = n_points + attributes(file)["interval"] = interval + attributes(file)["variable_names"] = collect(variable_names) - file["time"] = time - file["timestep"] = step - file["point_coordinates"] = point_coordinates - for p in 1:n_points - # Store data as 2D array for convenience - file["point_data_$p"] = reshape(point_data[p], n_variables, length(time)) + file["time"] = time + file["timestep"] = step + file["point_coordinates"] = point_coordinates + for p in 1:n_points + # Store data as 2D array for convenience + file["point_data_$p"] = reshape(point_data[p], n_variables, length(time)) + end end end -end -# Creates cache for time series callback -function create_cache_time_series(point_coordinates, - mesh::Union{TreeMesh, UnstructuredMesh2D}, - dg, cache) - # Determine element ids for point coordinates - element_ids = get_elements_by_coordinates(point_coordinates, mesh, dg, cache) + # Creates cache for time series callback + function create_cache_time_series( + point_coordinates, + mesh::Union{TreeMesh, UnstructuredMesh2D}, + dg, cache + ) + # Determine element ids for point coordinates + element_ids = get_elements_by_coordinates(point_coordinates, mesh, dg, cache) - # Calculate & store Lagrange interpolation polynomials - interpolating_polynomials = calc_interpolating_polynomials(point_coordinates, - element_ids, mesh, - dg, cache) + # Calculate & store Lagrange interpolation polynomials + interpolating_polynomials = calc_interpolating_polynomials( + point_coordinates, + element_ids, mesh, + dg, cache + ) - time_series_cache = (; element_ids, interpolating_polynomials) + time_series_cache = (; element_ids, interpolating_polynomials) - return time_series_cache -end + return time_series_cache + end -function get_elements_by_coordinates(coordinates, mesh, dg, cache) - element_ids = Vector{Int}(undef, size(coordinates, 2)) - get_elements_by_coordinates!(element_ids, coordinates, mesh, dg, cache) + function get_elements_by_coordinates(coordinates, mesh, dg, cache) + element_ids = Vector{Int}(undef, size(coordinates, 2)) + get_elements_by_coordinates!(element_ids, coordinates, mesh, dg, cache) - return element_ids -end + return element_ids + end -function calc_interpolating_polynomials(coordinates, element_ids, - mesh::Union{TreeMesh, UnstructuredMesh2D}, - dg, cache) - interpolating_polynomials = Array{real(dg), 3}(undef, - nnodes(dg), ndims(mesh), - length(element_ids)) - calc_interpolating_polynomials!(interpolating_polynomials, coordinates, element_ids, - mesh, dg, - cache) + function calc_interpolating_polynomials( + coordinates, element_ids, + mesh::Union{TreeMesh, UnstructuredMesh2D}, + dg, cache + ) + interpolating_polynomials = Array{real(dg), 3}( + undef, + nnodes(dg), ndims(mesh), + length(element_ids) + ) + calc_interpolating_polynomials!( + interpolating_polynomials, coordinates, element_ids, + mesh, dg, + cache + ) - return interpolating_polynomials -end + return interpolating_polynomials + end end # @muladd diff --git a/src/callbacks_step/time_series_dg_tree.jl b/src/callbacks_step/time_series_dg_tree.jl index 0af1688a8e..2dee806144 100644 --- a/src/callbacks_step/time_series_dg_tree.jl +++ b/src/callbacks_step/time_series_dg_tree.jl @@ -3,183 +3,213 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent + + # Find element ids containing coordinates given as a matrix [ndims, npoints] + function get_elements_by_coordinates!( + element_ids, coordinates, mesh::TreeMesh, dg, + cache + ) + if length(element_ids) != size(coordinates, 2) + throw(DimensionMismatch("storage length for element ids does not match the number of coordinates")) + end -# Find element ids containing coordinates given as a matrix [ndims, npoints] -function get_elements_by_coordinates!(element_ids, coordinates, mesh::TreeMesh, dg, - cache) - if length(element_ids) != size(coordinates, 2) - throw(DimensionMismatch("storage length for element ids does not match the number of coordinates")) - end + @unpack cell_ids = cache.elements + @unpack tree = mesh - @unpack cell_ids = cache.elements - @unpack tree = mesh + # Reset element ids - 0 indicates "not (yet) found" + element_ids .= 0 + found_elements = 0 - # Reset element ids - 0 indicates "not (yet) found" - element_ids .= 0 - found_elements = 0 + # Iterate over all elements + for element in eachelement(dg, cache) + # Get cell id + cell_id = cell_ids[element] - # Iterate over all elements - for element in eachelement(dg, cache) - # Get cell id - cell_id = cell_ids[element] + # Iterate over coordinates + for index in eachindex(element_ids) + # Skip coordinates for which an element has already been found + if element_ids[index] > 0 + continue + end - # Iterate over coordinates - for index in eachindex(element_ids) - # Skip coordinates for which an element has already been found - if element_ids[index] > 0 - continue - end + # Construct point + x = SVector(ntuple(i -> coordinates[i, index], ndims(mesh))) - # Construct point - x = SVector(ntuple(i -> coordinates[i, index], ndims(mesh))) + # Skip if point is not in cell + if !is_point_in_cell(tree, x, cell_id) + continue + end - # Skip if point is not in cell - if !is_point_in_cell(tree, x, cell_id) - continue + # Otherwise point is in cell and thus in element + element_ids[index] = element + found_elements += 1 end - # Otherwise point is in cell and thus in element - element_ids[index] = element - found_elements += 1 + # Exit loop if all elements have already been found + if found_elements == length(element_ids) + break + end end - # Exit loop if all elements have already been found - if found_elements == length(element_ids) - break - end + return element_ids end - return element_ids -end - -# Calculate the interpolating polynomials to extract data at the given coordinates -# The coordinates are known to be located in the respective element in `element_ids` -function calc_interpolating_polynomials!(interpolating_polynomials, coordinates, - element_ids, - mesh::TreeMesh, dg::DGSEM, cache) - @unpack tree = mesh - @unpack nodes = dg.basis - - wbary = barycentric_weights(nodes) - - for index in eachindex(element_ids) - # Construct point - x = SVector(ntuple(i -> coordinates[i, index], ndims(mesh))) - - # Convert to unit coordinates - cell_id = cache.elements.cell_ids[element_ids[index]] - cell_coordinates_ = cell_coordinates(tree, cell_id) - cell_length = length_at_cell(tree, cell_id) - unit_coordinates = (x .- cell_coordinates_) * 2 / cell_length - - # Calculate interpolating polynomial for each dimension, making use of tensor product structure - for d in 1:ndims(mesh) - interpolating_polynomials[:, d, index] .= lagrange_interpolating_polynomials(unit_coordinates[d], - nodes, - wbary) + # Calculate the interpolating polynomials to extract data at the given coordinates + # The coordinates are known to be located in the respective element in `element_ids` + function calc_interpolating_polynomials!( + interpolating_polynomials, coordinates, + element_ids, + mesh::TreeMesh, dg::DGSEM, cache + ) + @unpack tree = mesh + @unpack nodes = dg.basis + + wbary = barycentric_weights(nodes) + + for index in eachindex(element_ids) + # Construct point + x = SVector(ntuple(i -> coordinates[i, index], ndims(mesh))) + + # Convert to unit coordinates + cell_id = cache.elements.cell_ids[element_ids[index]] + cell_coordinates_ = cell_coordinates(tree, cell_id) + cell_length = length_at_cell(tree, cell_id) + unit_coordinates = (x .- cell_coordinates_) * 2 / cell_length + + # Calculate interpolating polynomial for each dimension, making use of tensor product structure + for d in 1:ndims(mesh) + interpolating_polynomials[:, d, index] .= lagrange_interpolating_polynomials( + unit_coordinates[d], + nodes, + wbary + ) + end end + + return interpolating_polynomials end - return interpolating_polynomials -end - -# Record the solution variables at each given point for the 1D case -function record_state_at_points!(point_data, u, solution_variables, - n_solution_variables, - mesh::TreeMesh{1}, equations, dg::DG, - time_series_cache) - @unpack element_ids, interpolating_polynomials = time_series_cache - old_length = length(first(point_data)) - new_length = old_length + n_solution_variables - - # Loop over all points/elements that should be recorded - for index in eachindex(element_ids) - # Extract data array and element id - data = point_data[index] - element_id = element_ids[index] - - # Make room for new data to be recorded - resize!(data, new_length) - data[(old_length + 1):new_length] .= zero(eltype(data)) - - # Loop over all nodes to compute their contribution to the interpolated values - for i in eachnode(dg) - u_node = solution_variables(get_node_vars(u, equations, dg, i, - element_id), equations) - - for v in eachindex(u_node) - data[old_length + v] += (u_node[v] * - interpolating_polynomials[i, 1, index]) + # Record the solution variables at each given point for the 1D case + function record_state_at_points!( + point_data, u, solution_variables, + n_solution_variables, + mesh::TreeMesh{1}, equations, dg::DG, + time_series_cache + ) + @unpack element_ids, interpolating_polynomials = time_series_cache + old_length = length(first(point_data)) + new_length = old_length + n_solution_variables + + # Loop over all points/elements that should be recorded + for index in eachindex(element_ids) + # Extract data array and element id + data = point_data[index] + element_id = element_ids[index] + + # Make room for new data to be recorded + resize!(data, new_length) + data[(old_length + 1):new_length] .= zero(eltype(data)) + + # Loop over all nodes to compute their contribution to the interpolated values + for i in eachnode(dg) + u_node = solution_variables( + get_node_vars( + u, equations, dg, i, + element_id + ), equations + ) + + for v in eachindex(u_node) + data[old_length + v] += ( + u_node[v] * + interpolating_polynomials[i, 1, index] + ) + end end end end -end - -# Record the solution variables at each given point for the 2D case -function record_state_at_points!(point_data, u, solution_variables, - n_solution_variables, - mesh::TreeMesh{2}, - equations, dg::DG, time_series_cache) - @unpack element_ids, interpolating_polynomials = time_series_cache - old_length = length(first(point_data)) - new_length = old_length + n_solution_variables - - # Loop over all points/elements that should be recorded - for index in eachindex(element_ids) - # Extract data array and element id - data = point_data[index] - element_id = element_ids[index] - - # Make room for new data to be recorded - resize!(data, new_length) - data[(old_length + 1):new_length] .= zero(eltype(data)) - - # Loop over all nodes to compute their contribution to the interpolated values - for j in eachnode(dg), i in eachnode(dg) - u_node = solution_variables(get_node_vars(u, equations, dg, i, j, - element_id), equations) - - for v in eachindex(u_node) - data[old_length + v] += (u_node[v] - * interpolating_polynomials[i, 1, index] - * interpolating_polynomials[j, 2, index]) + + # Record the solution variables at each given point for the 2D case + function record_state_at_points!( + point_data, u, solution_variables, + n_solution_variables, + mesh::TreeMesh{2}, + equations, dg::DG, time_series_cache + ) + @unpack element_ids, interpolating_polynomials = time_series_cache + old_length = length(first(point_data)) + new_length = old_length + n_solution_variables + + # Loop over all points/elements that should be recorded + for index in eachindex(element_ids) + # Extract data array and element id + data = point_data[index] + element_id = element_ids[index] + + # Make room for new data to be recorded + resize!(data, new_length) + data[(old_length + 1):new_length] .= zero(eltype(data)) + + # Loop over all nodes to compute their contribution to the interpolated values + for j in eachnode(dg), i in eachnode(dg) + u_node = solution_variables( + get_node_vars( + u, equations, dg, i, j, + element_id + ), equations + ) + + for v in eachindex(u_node) + data[old_length + v] += ( + u_node[v] + * interpolating_polynomials[i, 1, index] + * interpolating_polynomials[j, 2, index] + ) + end end end end -end - -# Record the solution variables at each given point for the 3D case -function record_state_at_points!(point_data, u, solution_variables, - n_solution_variables, - mesh::TreeMesh{3}, equations, dg::DG, - time_series_cache) - @unpack element_ids, interpolating_polynomials = time_series_cache - old_length = length(first(point_data)) - new_length = old_length + n_solution_variables - - # Loop over all points/elements that should be recorded - for index in eachindex(element_ids) - # Extract data array and element id - data = point_data[index] - element_id = element_ids[index] - - # Make room for new data to be recorded - resize!(data, new_length) - data[(old_length + 1):new_length] .= zero(eltype(data)) - - # Loop over all nodes to compute their contribution to the interpolated values - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = solution_variables(get_node_vars(u, equations, dg, i, j, k, - element_id), equations) - - for v in eachindex(u_node) - data[old_length + v] += (u_node[v] - * interpolating_polynomials[i, 1, index] - * interpolating_polynomials[j, 2, index] - * interpolating_polynomials[k, 3, index]) + + # Record the solution variables at each given point for the 3D case + function record_state_at_points!( + point_data, u, solution_variables, + n_solution_variables, + mesh::TreeMesh{3}, equations, dg::DG, + time_series_cache + ) + @unpack element_ids, interpolating_polynomials = time_series_cache + old_length = length(first(point_data)) + new_length = old_length + n_solution_variables + + # Loop over all points/elements that should be recorded + for index in eachindex(element_ids) + # Extract data array and element id + data = point_data[index] + element_id = element_ids[index] + + # Make room for new data to be recorded + resize!(data, new_length) + data[(old_length + 1):new_length] .= zero(eltype(data)) + + # Loop over all nodes to compute their contribution to the interpolated values + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = solution_variables( + get_node_vars( + u, equations, dg, i, j, k, + element_id + ), equations + ) + + for v in eachindex(u_node) + data[old_length + v] += ( + u_node[v] + * interpolating_polynomials[i, 1, index] + * interpolating_polynomials[j, 2, index] + * interpolating_polynomials[k, 3, index] + ) + end end end end -end end # @muladd diff --git a/src/callbacks_step/time_series_dg_unstructured.jl b/src/callbacks_step/time_series_dg_unstructured.jl index 85427f1273..6568158644 100644 --- a/src/callbacks_step/time_series_dg_unstructured.jl +++ b/src/callbacks_step/time_series_dg_unstructured.jl @@ -3,303 +3,363 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Elements on an `UnstructuredMesh2D` are possibly curved. Assume that each -# element is convex, i.e., all interior angles are less than 180 degrees. -# This routine computes the shortest distance from a given point to each element -# surface in the mesh. These distances then indicate possible candidate elements. -# From these candidates we (essentially) apply a ray casting strategy and identify -# the element in which the point lies by comparing the ray formed by the point to -# the nearest boundary to the rays cast by the candidate element barycenters to the -# boundary. If these rays point in the same direction, then we have identified the -# desired element location. -function get_elements_by_coordinates!(element_ids, coordinates, - mesh::UnstructuredMesh2D, - dg, cache) - if length(element_ids) != size(coordinates, 2) - throw(DimensionMismatch("storage length for element ids does not match the number of coordinates")) - end - - # Reset element ids - 0 indicates "not (yet) found" - element_ids .= 0 - - # Compute and save the barycentric coordinate on each element - bary_centers = zeros(eltype(mesh.corners), 2, mesh.n_elements) - calc_bary_centers!(bary_centers, dg, cache) - - # Iterate over coordinates - distances = zeros(eltype(mesh.corners), mesh.n_elements) - indices = zeros(Int, mesh.n_elements, 2) - for index in eachindex(element_ids) - # Grab the current point for which the element needs found - point = SVector(coordinates[1, index], - coordinates[2, index]) - - # Compute the minimum distance between the `point` and all the element surfaces - # saved into `distances`. The point in `node_coordinates` that gives said minimum - # distance on each element is saved in `indices` - distances, indices = calc_minimum_surface_distance(point, - cache.elements.node_coordinates, - dg, mesh) - - # Get the candidate elements where the `point` might live - candidates = findall(abs.(minimum(distances) .- distances) .< - 500 * eps(eltype(point))) - - # The minimal surface point is on a boundary so it plays no role which candidate - # we use to grab it. So just use the first one - surface_point = SVector(cache.elements.node_coordinates[1, - indices[candidates[1], - 1], - indices[candidates[1], - 2], - candidates[1]], - cache.elements.node_coordinates[2, - indices[candidates[1], - 1], - indices[candidates[1], - 2], - candidates[1]]) - - # Compute the vector pointing from the current `point` toward the surface - P = surface_point - point - - # If the vector `P` is the zero vector then this `point` is at an element corner or - # on a surface. In this case the choice of a candidate element is ambiguous and - # we just use the first candidate. However, solutions might differ at discontinuous - # interfaces such that this choice may influence the result. - if sum(P .* P) < 500 * eps(eltype(point)) - element_ids[index] = candidates[1] - continue + #! format: noindent + + # Elements on an `UnstructuredMesh2D` are possibly curved. Assume that each + # element is convex, i.e., all interior angles are less than 180 degrees. + # This routine computes the shortest distance from a given point to each element + # surface in the mesh. These distances then indicate possible candidate elements. + # From these candidates we (essentially) apply a ray casting strategy and identify + # the element in which the point lies by comparing the ray formed by the point to + # the nearest boundary to the rays cast by the candidate element barycenters to the + # boundary. If these rays point in the same direction, then we have identified the + # desired element location. + function get_elements_by_coordinates!( + element_ids, coordinates, + mesh::UnstructuredMesh2D, + dg, cache + ) + if length(element_ids) != size(coordinates, 2) + throw(DimensionMismatch("storage length for element ids does not match the number of coordinates")) end - # Loop through all the element candidates until we find a vector from the barycenter - # to the surface that points in the same direction as the current `point` vector. - # This then gives us the correct element. - for element in eachindex(candidates) - bary_center = SVector(bary_centers[1, candidates[element]], - bary_centers[2, candidates[element]]) - # Vector pointing from the barycenter toward the minimal `surface_point` - B = surface_point - bary_center - if sum(P .* B) > zero(eltype(bary_center)) - element_ids[index] = candidates[element] - break + # Reset element ids - 0 indicates "not (yet) found" + element_ids .= 0 + + # Compute and save the barycentric coordinate on each element + bary_centers = zeros(eltype(mesh.corners), 2, mesh.n_elements) + calc_bary_centers!(bary_centers, dg, cache) + + # Iterate over coordinates + distances = zeros(eltype(mesh.corners), mesh.n_elements) + indices = zeros(Int, mesh.n_elements, 2) + for index in eachindex(element_ids) + # Grab the current point for which the element needs found + point = SVector( + coordinates[1, index], + coordinates[2, index] + ) + + # Compute the minimum distance between the `point` and all the element surfaces + # saved into `distances`. The point in `node_coordinates` that gives said minimum + # distance on each element is saved in `indices` + distances, indices = calc_minimum_surface_distance( + point, + cache.elements.node_coordinates, + dg, mesh + ) + + # Get the candidate elements where the `point` might live + candidates = findall( + abs.(minimum(distances) .- distances) .< + 500 * eps(eltype(point)) + ) + + # The minimal surface point is on a boundary so it plays no role which candidate + # we use to grab it. So just use the first one + surface_point = SVector( + cache.elements.node_coordinates[ + 1, + indices[ + candidates[1], + 1, + ], + indices[ + candidates[1], + 2, + ], + candidates[1], + ], + cache.elements.node_coordinates[ + 2, + indices[ + candidates[1], + 1, + ], + indices[ + candidates[1], + 2, + ], + candidates[1], + ] + ) + + # Compute the vector pointing from the current `point` toward the surface + P = surface_point - point + + # If the vector `P` is the zero vector then this `point` is at an element corner or + # on a surface. In this case the choice of a candidate element is ambiguous and + # we just use the first candidate. However, solutions might differ at discontinuous + # interfaces such that this choice may influence the result. + if sum(P .* P) < 500 * eps(eltype(point)) + element_ids[index] = candidates[1] + continue end - end - end - return element_ids -end - -# Use the available `node_coordinates` on each element to compute and save the barycenter. -# In essence, the barycenter is like an average where all the x and y node coordinates are -# summed and then we divide by the total number of degrees of freedom on the element, i.e., -# the value of `n^2` in two spatial dimensions. -@inline function calc_bary_centers!(bary_centers, dg, cache) - n = nnodes(dg) - @views for element in eachelement(dg, cache) - bary_centers[1, element] = sum(cache.elements.node_coordinates[1, :, :, - element]) / n^2 - bary_centers[2, element] = sum(cache.elements.node_coordinates[2, :, :, - element]) / n^2 - end - return nothing -end - -# Compute the shortest distance from a `point` to the surface of each element -# using the available `node_coordinates`. Also return the index pair of this -# minimum surface point location. We compute and store in `min_distance` -# the squared norm to avoid computing computationally more expensive square roots. -# Note! Could be made more accurate if the `node_coordinates` were super-sampled -# and reinterpolated onto a higher polynomial degree before this computation. -function calc_minimum_surface_distance(point, node_coordinates, - dg, mesh::UnstructuredMesh2D) - n = nnodes(dg) - min_distance2 = Inf * ones(eltype(mesh.corners), length(mesh)) - indices = zeros(Int, length(mesh), 2) - for k in 1:length(mesh) - # used to ensure that only boundary points are used - on_surface = MVector(false, false) - for j in 1:n - on_surface[2] = (j == 1) || (j == n) - for i in 1:n - on_surface[1] = (i == 1) || (i == n) - if !any(on_surface) - continue - end - node = SVector(node_coordinates[1, i, j, k], - node_coordinates[2, i, j, k]) - distance2 = sum(abs2, node - point) - if distance2 < min_distance2[k] - min_distance2[k] = distance2 - indices[k, 1] = i - indices[k, 2] = j + # Loop through all the element candidates until we find a vector from the barycenter + # to the surface that points in the same direction as the current `point` vector. + # This then gives us the correct element. + for element in eachindex(candidates) + bary_center = SVector( + bary_centers[1, candidates[element]], + bary_centers[2, candidates[element]] + ) + # Vector pointing from the barycenter toward the minimal `surface_point` + B = surface_point - bary_center + if sum(P .* B) > zero(eltype(bary_center)) + element_ids[index] = candidates[element] + break end end end - end - return min_distance2, indices -end - -function calc_interpolating_polynomials!(interpolating_polynomials, coordinates, - element_ids, - mesh::UnstructuredMesh2D, dg::DGSEM, cache) - @unpack nodes = dg.basis + return element_ids + end - wbary = barycentric_weights(nodes) + # Use the available `node_coordinates` on each element to compute and save the barycenter. + # In essence, the barycenter is like an average where all the x and y node coordinates are + # summed and then we divide by the total number of degrees of freedom on the element, i.e., + # the value of `n^2` in two spatial dimensions. + @inline function calc_bary_centers!(bary_centers, dg, cache) + n = nnodes(dg) + @views for element in eachelement(dg, cache) + bary_centers[1, element] = sum( + cache.elements.node_coordinates[ + 1, :, :, + element, + ] + ) / n^2 + bary_centers[2, element] = sum( + cache.elements.node_coordinates[ + 2, :, :, + element, + ] + ) / n^2 + end + return nothing + end - # Helper array for a straight-sided quadrilateral element - corners = zeros(eltype(mesh.corners), 4, 2) + # Compute the shortest distance from a `point` to the surface of each element + # using the available `node_coordinates`. Also return the index pair of this + # minimum surface point location. We compute and store in `min_distance` + # the squared norm to avoid computing computationally more expensive square roots. + # Note! Could be made more accurate if the `node_coordinates` were super-sampled + # and reinterpolated onto a higher polynomial degree before this computation. + function calc_minimum_surface_distance( + point, node_coordinates, + dg, mesh::UnstructuredMesh2D + ) + n = nnodes(dg) + min_distance2 = Inf * ones(eltype(mesh.corners), length(mesh)) + indices = zeros(Int, length(mesh), 2) + for k in 1:length(mesh) + # used to ensure that only boundary points are used + on_surface = MVector(false, false) + for j in 1:n + on_surface[2] = (j == 1) || (j == n) + for i in 1:n + on_surface[1] = (i == 1) || (i == n) + if !any(on_surface) + continue + end + node = SVector( + node_coordinates[1, i, j, k], + node_coordinates[2, i, j, k] + ) + distance2 = sum(abs2, node - point) + if distance2 < min_distance2[k] + min_distance2[k] = distance2 + indices[k, 1] = i + indices[k, 2] = j + end + end + end + end - for index in eachindex(element_ids) - # Construct point - x = SVector(ntuple(i -> coordinates[i, index], ndims(mesh))) + return min_distance2, indices + end - # Convert to unit coordinates; procedure differs for straight-sided - # versus curvilinear elements - element = element_ids[index] - if !mesh.element_is_curved[element] - for j in 1:2, i in 1:4 - # Pull the (x,y) values of the element corners from the global corners array - corners[i, j] = mesh.corners[j, mesh.element_node_ids[i, element]] - end - # Compute coordinates in reference system - unit_coordinates = invert_bilinear_interpolation(mesh, x, corners) - - # Sanity check that the computed `unit_coordinates` indeed recover the desired point `x` - x_check = straight_side_quad_map(unit_coordinates[1], unit_coordinates[2], - corners) - if !isapprox(x[1], x_check[1]) || !isapprox(x[2], x_check[2]) - error("failed to compute computational coordinates for the time series point $(x), closet candidate was $(x_check)") + function calc_interpolating_polynomials!( + interpolating_polynomials, coordinates, + element_ids, + mesh::UnstructuredMesh2D, dg::DGSEM, cache + ) + @unpack nodes = dg.basis + + wbary = barycentric_weights(nodes) + + # Helper array for a straight-sided quadrilateral element + corners = zeros(eltype(mesh.corners), 4, 2) + + for index in eachindex(element_ids) + # Construct point + x = SVector(ntuple(i -> coordinates[i, index], ndims(mesh))) + + # Convert to unit coordinates; procedure differs for straight-sided + # versus curvilinear elements + element = element_ids[index] + if !mesh.element_is_curved[element] + for j in 1:2, i in 1:4 + # Pull the (x,y) values of the element corners from the global corners array + corners[i, j] = mesh.corners[j, mesh.element_node_ids[i, element]] + end + # Compute coordinates in reference system + unit_coordinates = invert_bilinear_interpolation(mesh, x, corners) + + # Sanity check that the computed `unit_coordinates` indeed recover the desired point `x` + x_check = straight_side_quad_map( + unit_coordinates[1], unit_coordinates[2], + corners + ) + if !isapprox(x[1], x_check[1]) || !isapprox(x[2], x_check[2]) + error("failed to compute computational coordinates for the time series point $(x), closet candidate was $(x_check)") + end + else # mesh.element_is_curved[element] + unit_coordinates = invert_transfinite_interpolation( + mesh, x, + view( + mesh.surface_curves, + :, element + ) + ) + + # Sanity check that the computed `unit_coordinates` indeed recover the desired point `x` + x_check = transfinite_quad_map( + unit_coordinates[1], unit_coordinates[2], + view(mesh.surface_curves, :, element) + ) + if !isapprox(x[1], x_check[1]) || !isapprox(x[2], x_check[2]) + error("failed to compute computational coordinates for the time series point $(x), closet candidate was $(x_check)") + end end - else # mesh.element_is_curved[element] - unit_coordinates = invert_transfinite_interpolation(mesh, x, - view(mesh.surface_curves, - :, element)) - - # Sanity check that the computed `unit_coordinates` indeed recover the desired point `x` - x_check = transfinite_quad_map(unit_coordinates[1], unit_coordinates[2], - view(mesh.surface_curves, :, element)) - if !isapprox(x[1], x_check[1]) || !isapprox(x[2], x_check[2]) - error("failed to compute computational coordinates for the time series point $(x), closet candidate was $(x_check)") + + # Calculate interpolating polynomial for each dimension, making use of tensor product structure + for d in 1:ndims(mesh) + interpolating_polynomials[:, d, index] .= lagrange_interpolating_polynomials( + unit_coordinates[d], + nodes, + wbary + ) end end - # Calculate interpolating polynomial for each dimension, making use of tensor product structure - for d in 1:ndims(mesh) - interpolating_polynomials[:, d, index] .= lagrange_interpolating_polynomials(unit_coordinates[d], - nodes, - wbary) - end + return interpolating_polynomials end - return interpolating_polynomials -end - -# Use a Newton iteration to determine the computational coordinates -# (xi, eta) of an (x,y) `point` that is given in physical coordinates -# by inverting the transformation. For straight-sided elements this -# amounts to inverting a bi-linear interpolation. For curved -# elements we invert the transfinite interpolation with linear blending. -# The residual function for the Newton iteration is -# r(xi, eta) = X(xi, eta) - point -# and the Jacobian entries are computed accordingly from either -# `straight_side_quad_map_metrics` or `transfinite_quad_map_metrics`. -# We exploit the 2x2 nature of the problem and directly compute the matrix -# inverse to make things faster. The implementations below are inspired by -# an answer on Stack Overflow (https://stackoverflow.com/a/18332009) where -# the author explicitly states that their code is released to the public domain. -@inline function invert_bilinear_interpolation(mesh::UnstructuredMesh2D, point, - element_corners) - # Initial guess for the point (center of the reference element) - xi = zero(eltype(point)) - eta = zero(eltype(point)) - for k in 1:5 # Newton's method should converge quickly - # Compute current x and y coordinate and the Jacobian matrix - # J = (X_xi, X_eta; Y_xi, Y_eta) - x, y = straight_side_quad_map(xi, eta, element_corners) - J11, J12, J21, J22 = straight_side_quad_map_metrics(xi, eta, element_corners) - - # Compute residuals for the Newton teration for the current (x, y) coordinate - r1 = x - point[1] - r2 = y - point[2] - - # Newton update that directly applies the inverse of the 2x2 Jacobian matrix - inv_detJ = inv(J11 * J22 - J12 * J21) - - # Update with explicitly inverted Jacobian - xi = xi - inv_detJ * (J22 * r1 - J12 * r2) - eta = eta - inv_detJ * (-J21 * r1 + J11 * r2) - - # Ensure updated point is in the reference element - xi = min(max(xi, -1), 1) - eta = min(max(eta, -1), 1) + # Use a Newton iteration to determine the computational coordinates + # (xi, eta) of an (x,y) `point` that is given in physical coordinates + # by inverting the transformation. For straight-sided elements this + # amounts to inverting a bi-linear interpolation. For curved + # elements we invert the transfinite interpolation with linear blending. + # The residual function for the Newton iteration is + # r(xi, eta) = X(xi, eta) - point + # and the Jacobian entries are computed accordingly from either + # `straight_side_quad_map_metrics` or `transfinite_quad_map_metrics`. + # We exploit the 2x2 nature of the problem and directly compute the matrix + # inverse to make things faster. The implementations below are inspired by + # an answer on Stack Overflow (https://stackoverflow.com/a/18332009) where + # the author explicitly states that their code is released to the public domain. + @inline function invert_bilinear_interpolation( + mesh::UnstructuredMesh2D, point, + element_corners + ) + # Initial guess for the point (center of the reference element) + xi = zero(eltype(point)) + eta = zero(eltype(point)) + for k in 1:5 # Newton's method should converge quickly + # Compute current x and y coordinate and the Jacobian matrix + # J = (X_xi, X_eta; Y_xi, Y_eta) + x, y = straight_side_quad_map(xi, eta, element_corners) + J11, J12, J21, J22 = straight_side_quad_map_metrics(xi, eta, element_corners) + + # Compute residuals for the Newton teration for the current (x, y) coordinate + r1 = x - point[1] + r2 = y - point[2] + + # Newton update that directly applies the inverse of the 2x2 Jacobian matrix + inv_detJ = inv(J11 * J22 - J12 * J21) + + # Update with explicitly inverted Jacobian + xi = xi - inv_detJ * (J22 * r1 - J12 * r2) + eta = eta - inv_detJ * (-J21 * r1 + J11 * r2) + + # Ensure updated point is in the reference element + xi = min(max(xi, -1), 1) + eta = min(max(eta, -1), 1) + end + + return SVector(xi, eta) end - return SVector(xi, eta) -end - -@inline function invert_transfinite_interpolation(mesh::UnstructuredMesh2D, point, - surface_curves::AbstractVector{<:CurvedSurface}) - # Initial guess for the point (center of the reference element) - xi = zero(eltype(point)) - eta = zero(eltype(point)) - for k in 1:5 # Newton's method should converge quickly - # Compute current x and y coordinate and the Jacobian matrix - # J = (X_xi, X_eta; Y_xi, Y_eta) - x, y = transfinite_quad_map(xi, eta, surface_curves) - J11, J12, J21, J22 = transfinite_quad_map_metrics(xi, eta, surface_curves) - - # Compute residuals for the Newton teration for the current (x,y) coordinate - r1 = x - point[1] - r2 = y - point[2] - - # Newton update that directly applies the inverse of the 2x2 Jacobian matrix - inv_detJ = inv(J11 * J22 - J12 * J21) - - # Update with explicitly inverted Jacobian - xi = xi - inv_detJ * (J22 * r1 - J12 * r2) - eta = eta - inv_detJ * (-J21 * r1 + J11 * r2) - - # Ensure updated point is in the reference element - xi = min(max(xi, -1), 1) - eta = min(max(eta, -1), 1) + @inline function invert_transfinite_interpolation( + mesh::UnstructuredMesh2D, point, + surface_curves::AbstractVector{<:CurvedSurface} + ) + # Initial guess for the point (center of the reference element) + xi = zero(eltype(point)) + eta = zero(eltype(point)) + for k in 1:5 # Newton's method should converge quickly + # Compute current x and y coordinate and the Jacobian matrix + # J = (X_xi, X_eta; Y_xi, Y_eta) + x, y = transfinite_quad_map(xi, eta, surface_curves) + J11, J12, J21, J22 = transfinite_quad_map_metrics(xi, eta, surface_curves) + + # Compute residuals for the Newton teration for the current (x,y) coordinate + r1 = x - point[1] + r2 = y - point[2] + + # Newton update that directly applies the inverse of the 2x2 Jacobian matrix + inv_detJ = inv(J11 * J22 - J12 * J21) + + # Update with explicitly inverted Jacobian + xi = xi - inv_detJ * (J22 * r1 - J12 * r2) + eta = eta - inv_detJ * (-J21 * r1 + J11 * r2) + + # Ensure updated point is in the reference element + xi = min(max(xi, -1), 1) + eta = min(max(eta, -1), 1) + end + + return SVector(xi, eta) end - return SVector(xi, eta) -end - -function record_state_at_points!(point_data, u, solution_variables, - n_solution_variables, - mesh::UnstructuredMesh2D, - equations, dg::DG, time_series_cache) - @unpack element_ids, interpolating_polynomials = time_series_cache - old_length = length(first(point_data)) - new_length = old_length + n_solution_variables - - # Loop over all points/elements that should be recorded - for index in eachindex(element_ids) - # Extract data array and element id - data = point_data[index] - element_id = element_ids[index] - - # Make room for new data to be recorded - resize!(data, new_length) - data[(old_length + 1):new_length] .= zero(eltype(data)) - - # Loop over all nodes to compute their contribution to the interpolated values - for j in eachnode(dg), i in eachnode(dg) - u_node = solution_variables(get_node_vars(u, equations, dg, i, j, - element_id), equations) - - for v in eachindex(u_node) - data[old_length + v] += (u_node[v] - * interpolating_polynomials[i, 1, index] - * interpolating_polynomials[j, 2, index]) + function record_state_at_points!( + point_data, u, solution_variables, + n_solution_variables, + mesh::UnstructuredMesh2D, + equations, dg::DG, time_series_cache + ) + @unpack element_ids, interpolating_polynomials = time_series_cache + old_length = length(first(point_data)) + new_length = old_length + n_solution_variables + + # Loop over all points/elements that should be recorded + for index in eachindex(element_ids) + # Extract data array and element id + data = point_data[index] + element_id = element_ids[index] + + # Make room for new data to be recorded + resize!(data, new_length) + data[(old_length + 1):new_length] .= zero(eltype(data)) + + # Loop over all nodes to compute their contribution to the interpolated values + for j in eachnode(dg), i in eachnode(dg) + u_node = solution_variables( + get_node_vars( + u, equations, dg, i, j, + element_id + ), equations + ) + + for v in eachindex(u_node) + data[old_length + v] += ( + u_node[v] + * interpolating_polynomials[i, 1, index] + * interpolating_polynomials[j, 2, index] + ) + end end end end -end end # @muladd diff --git a/src/callbacks_step/trivial.jl b/src/callbacks_step/trivial.jl index fb93cf96c0..f15a98df49 100644 --- a/src/callbacks_step/trivial.jl +++ b/src/callbacks_step/trivial.jl @@ -3,34 +3,37 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - TrivialCallback() + """ + TrivialCallback() -A callback that does nothing. This can be useful to disable some callbacks easily via -[`trixi_include`](@ref). -""" -function TrivialCallback() - DiscreteCallback(trivial_callback, trivial_callback, - save_positions = (false, false)) -end + A callback that does nothing. This can be useful to disable some callbacks easily via + [`trixi_include`](@ref). + """ + function TrivialCallback() + DiscreteCallback( + trivial_callback, trivial_callback, + save_positions = (false, false) + ) + end -trivial_callback(u, t, integrator) = false -trivial_callback(integrator) = u_modified!(integrator, false) + trivial_callback(u, t, integrator) = false + trivial_callback(integrator) = u_modified!(integrator, false) -function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:typeof(trivial_callback)}) - @nospecialize cb # reduce precompilation time + function Base.show(io::IO, cb::DiscreteCallback{<:Any, <:typeof(trivial_callback)}) + @nospecialize cb # reduce precompilation time - print(io, "TrivialCallback()") -end + print(io, "TrivialCallback()") + end -# This allows to set `summary_callback = TrivialCallback()` in elixirs to suppress -# output, e.g. in `convergence_test`. -function (cb::DiscreteCallback{Condition, Affect!})(io::IO = stdout) where {Condition, - Affect! <: - typeof(trivial_callback) - } - return nothing -end + # This allows to set `summary_callback = TrivialCallback()` in elixirs to suppress + # output, e.g. in `convergence_test`. + function (cb::DiscreteCallback{Condition, Affect!})(io::IO = stdout) where { + Condition, + Affect! <: + typeof(trivial_callback), + } + return nothing + end end # @muladd diff --git a/src/callbacks_step/visualization.jl b/src/callbacks_step/visualization.jl index f91fe27bd3..9c699ddde3 100644 --- a/src/callbacks_step/visualization.jl +++ b/src/callbacks_step/visualization.jl @@ -3,259 +3,285 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -mutable struct VisualizationCallback{SolutionVariables, VariableNames, PlotDataCreator, - PlotCreator} - interval::Int - solution_variables::SolutionVariables - variable_names::VariableNames - show_mesh::Bool - plot_data_creator::PlotDataCreator - plot_creator::PlotCreator - plot_arguments::Dict{Symbol, Any} -end - -function Base.show(io::IO, - cb::DiscreteCallback{Condition, Affect!}) where {Condition, - Affect! <: - VisualizationCallback - } - visualization_callback = cb.affect! - @unpack interval, plot_arguments, solution_variables, variable_names, show_mesh, plot_creator, plot_data_creator = visualization_callback - print(io, "VisualizationCallback(", - "interval=", interval, ", ", - "solution_variables=", solution_variables, ", ", - "variable_names=", variable_names, ", ", - "show_mesh=", show_mesh, ", ", - "plot_data_creator=", plot_data_creator, ", ", - "plot_creator=", plot_creator, ", ", - "plot_arguments=", plot_arguments, ")") -end - -function Base.show(io::IO, ::MIME"text/plain", - cb::DiscreteCallback{Condition, Affect!}) where {Condition, - Affect! <: - VisualizationCallback - } - if get(io, :compact, false) - show(io, cb) - else - visualization_callback = cb.affect! - - setup = [ - "interval" => visualization_callback.interval, - "plot arguments" => visualization_callback.plot_arguments, - "solution variables" => visualization_callback.solution_variables, - "variable names" => visualization_callback.variable_names, - "show mesh" => visualization_callback.show_mesh, - "plot creator" => visualization_callback.plot_creator, - "plot data creator" => visualization_callback.plot_data_creator, - ] - summary_box(io, "VisualizationCallback", setup) - end -end - -""" - VisualizationCallback(; interval=0, - solution_variables=cons2prim, - variable_names=[], - show_mesh=false, - plot_data_creator=PlotData2D, - plot_creator=show_plot, - plot_arguments...) - -Create a callback that visualizes results during a simulation, also known as *in-situ -visualization*. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in any future releases. - -The `interval` specifies the number of time step iterations after which a new plot is generated. The -available variables to plot are configured with the `solution_variables` parameter, which acts the -same way as for the [`SaveSolutionCallback`](@ref). The variables to be actually plotted can be -selected by providing a single string or a list of strings to `variable_names`, and if `show_mesh` -is `true`, an additional plot with the mesh will be generated. - -To customize the generated figure, `plot_data_creator` allows to use different plot data types. With -`plot_creator` you can further specify an own function to visualize results, which must support the -same interface as the default implementation [`show_plot`](@ref). All remaining -keyword arguments are collected and passed as additional arguments to the plotting command. -""" -function VisualizationCallback(; interval = 0, - solution_variables = cons2prim, - variable_names = [], - show_mesh = false, - plot_data_creator = PlotData2D, - plot_creator = show_plot, - plot_arguments...) - mpi_isparallel() && error("this callback does not work in parallel yet") - - if variable_names isa String - variable_names = String[variable_names] + #! format: noindent + + mutable struct VisualizationCallback{ + SolutionVariables, VariableNames, PlotDataCreator, + PlotCreator, + } + interval::Int + solution_variables::SolutionVariables + variable_names::VariableNames + show_mesh::Bool + plot_data_creator::PlotDataCreator + plot_creator::PlotCreator + plot_arguments::Dict{Symbol, Any} end - visualization_callback = VisualizationCallback(interval, - solution_variables, variable_names, - show_mesh, - plot_data_creator, plot_creator, - Dict{Symbol, Any}(plot_arguments)) - - # Warn users if they create a visualization callback without having loaded the Plots package - # - # Note: This warning is added for convenience, as Plots is the only "officially" supported - # visualization package right now. However, in general nothing prevents anyone from using - # other packages such as Makie, Gadfly etc., given that appropriate `plot_creator`s are - # passed. This is also the reason why the visualization callback is not included via - # Requires.jl only when Plots is present. - # In the future, we should update/remove this warning if other plotting packages are - # starting to be used. - if !(:Plots in names(@__MODULE__, all = true)) - @warn "Package `Plots` not loaded but required by `VisualizationCallback` to visualize results" + function Base.show( + io::IO, + cb::DiscreteCallback{Condition, Affect!} + ) where { + Condition, + Affect! <: + VisualizationCallback, + } + visualization_callback = cb.affect! + @unpack interval, plot_arguments, solution_variables, variable_names, show_mesh, plot_creator, plot_data_creator = visualization_callback + print( + io, "VisualizationCallback(", + "interval=", interval, ", ", + "solution_variables=", solution_variables, ", ", + "variable_names=", variable_names, ", ", + "show_mesh=", show_mesh, ", ", + "plot_data_creator=", plot_data_creator, ", ", + "plot_creator=", plot_creator, ", ", + "plot_arguments=", plot_arguments, ")" + ) end - DiscreteCallback(visualization_callback, visualization_callback, # the first one is the condition, the second the affect! - save_positions = (false, false), - initialize = initialize!) -end - -function initialize!(cb::DiscreteCallback{Condition, Affect!}, u, t, - integrator) where {Condition, Affect! <: VisualizationCallback} - visualization_callback = cb.affect! - - visualization_callback(integrator) - - return nothing -end - -# this method is called to determine whether the callback should be activated -function (visualization_callback::VisualizationCallback)(u, t, integrator) - @unpack interval = visualization_callback - - # With error-based step size control, some steps can be rejected. Thus, - # `integrator.iter >= integrator.stats.naccept` - # (total #steps) (#accepted steps) - # We need to check the number of accepted steps since callbacks are not - # activated after a rejected step. - return interval > 0 && (integrator.stats.naccept % interval == 0 || - isfinished(integrator)) -end - -# this method is called when the callback is activated -function (visualization_callback::VisualizationCallback)(integrator) - u_ode = integrator.u - semi = integrator.p - @unpack plot_arguments, solution_variables, variable_names, show_mesh, plot_data_creator, plot_creator = visualization_callback - - # Extract plot data - plot_data = plot_data_creator(u_ode, semi, solution_variables = solution_variables) - - # If variable names were not specified, plot everything - if isempty(variable_names) - variable_names = String[keys(plot_data)...] + function Base.show( + io::IO, ::MIME"text/plain", + cb::DiscreteCallback{Condition, Affect!} + ) where { + Condition, + Affect! <: + VisualizationCallback, + } + if get(io, :compact, false) + show(io, cb) + else + visualization_callback = cb.affect! + + setup = [ + "interval" => visualization_callback.interval, + "plot arguments" => visualization_callback.plot_arguments, + "solution variables" => visualization_callback.solution_variables, + "variable names" => visualization_callback.variable_names, + "show mesh" => visualization_callback.show_mesh, + "plot creator" => visualization_callback.plot_creator, + "plot data creator" => visualization_callback.plot_data_creator, + ] + summary_box(io, "VisualizationCallback", setup) + end end - # Create plot - plot_creator(plot_data, variable_names; - show_mesh = show_mesh, plot_arguments = plot_arguments, - time = integrator.t, timestep = integrator.stats.naccept) - - # avoid re-evaluating possible FSAL stages - u_modified!(integrator, false) - return nothing -end - -""" - show_plot(plot_data, variable_names; - show_mesh=true, plot_arguments=Dict{Symbol,Any}(), - time=nothing, timestep=nothing) - -Visualize the plot data object provided in `plot_data` and display result, plotting only the -variables in `variable_names` and, optionally, the mesh (if `show_mesh` is `true`). Additionally, -`plot_arguments` will be unpacked and passed as keyword arguments to the `Plots.plot` command. - -This function is the default `plot_creator` argument for the [`VisualizationCallback`](@ref). -`time` and `timestep` are currently unused by this function. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. - -See also: [`VisualizationCallback`](@ref), [`save_plot`](@ref) -""" -function show_plot(plot_data, variable_names; - show_mesh = true, plot_arguments = Dict{Symbol, Any}(), - time = nothing, timestep = nothing) - # Gather subplots - plots = [] - for v in variable_names - push!(plots, Plots.plot(plot_data[v]; plot_arguments...)) - end - if show_mesh - push!(plots, Plots.plot(getmesh(plot_data); plot_arguments...)) + """ + VisualizationCallback(; interval=0, + solution_variables=cons2prim, + variable_names=[], + show_mesh=false, + plot_data_creator=PlotData2D, + plot_creator=show_plot, + plot_arguments...) + + Create a callback that visualizes results during a simulation, also known as *in-situ + visualization*. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in any future releases. + + The `interval` specifies the number of time step iterations after which a new plot is generated. The + available variables to plot are configured with the `solution_variables` parameter, which acts the + same way as for the [`SaveSolutionCallback`](@ref). The variables to be actually plotted can be + selected by providing a single string or a list of strings to `variable_names`, and if `show_mesh` + is `true`, an additional plot with the mesh will be generated. + + To customize the generated figure, `plot_data_creator` allows to use different plot data types. With + `plot_creator` you can further specify an own function to visualize results, which must support the + same interface as the default implementation [`show_plot`](@ref). All remaining + keyword arguments are collected and passed as additional arguments to the plotting command. + """ + function VisualizationCallback(; + interval = 0, + solution_variables = cons2prim, + variable_names = [], + show_mesh = false, + plot_data_creator = PlotData2D, + plot_creator = show_plot, + plot_arguments... + ) + mpi_isparallel() && error("this callback does not work in parallel yet") + + if variable_names isa String + variable_names = String[variable_names] + end + + visualization_callback = VisualizationCallback( + interval, + solution_variables, variable_names, + show_mesh, + plot_data_creator, plot_creator, + Dict{Symbol, Any}(plot_arguments) + ) + + # Warn users if they create a visualization callback without having loaded the Plots package + # + # Note: This warning is added for convenience, as Plots is the only "officially" supported + # visualization package right now. However, in general nothing prevents anyone from using + # other packages such as Makie, Gadfly etc., given that appropriate `plot_creator`s are + # passed. This is also the reason why the visualization callback is not included via + # Requires.jl only when Plots is present. + # In the future, we should update/remove this warning if other plotting packages are + # starting to be used. + if !(:Plots in names(@__MODULE__, all = true)) + @warn "Package `Plots` not loaded but required by `VisualizationCallback` to visualize results" + end + + DiscreteCallback( + visualization_callback, visualization_callback, # the first one is the condition, the second the affect! + save_positions = (false, false), + initialize = initialize! + ) end - # Note, for the visualization callback to work for general equation systems - # this layout construction would need to use the if-logic below. - # Currently, there is no use case for this so it is left here as a note. - # - # Determine layout - # if length(plots) <= 3 - # cols = length(plots) - # rows = 1 - # else - # cols = ceil(Int, sqrt(length(plots))) - # rows = div(length(plots), cols, RoundUp) - # end - # layout = (rows, cols) - - # Determine layout - cols = ceil(Int, sqrt(length(plots))) - rows = div(length(plots), cols, RoundUp) - layout = (rows, cols) - - # Show plot - display(Plots.plot(plots..., layout = layout)) -end - -""" - save_plot(plot_data, variable_names; - show_mesh=true, plot_arguments=Dict{Symbol,Any}(), - time=nothing, timestep=nothing) - -Visualize the plot data object provided in `plot_data` and save result as a PNG file in the `out` -directory, plotting only the variables in `variable_names` and, optionally, the mesh (if `show_mesh` -is `true`). Additionally, `plot_arguments` will be unpacked and passed as keyword arguments to the -`Plots.plot` command. - -The `timestep` is used in the filename. `time` is currently unused by this function. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. - -See also: [`VisualizationCallback`](@ref), [`show_plot`](@ref) -""" -function save_plot(plot_data, variable_names; - show_mesh = true, plot_arguments = Dict{Symbol, Any}(), - time = nothing, timestep = nothing) - # Gather subplots - plots = [] - for v in variable_names - push!(plots, Plots.plot(plot_data[v]; plot_arguments...)) + function initialize!( + cb::DiscreteCallback{Condition, Affect!}, u, t, + integrator + ) where {Condition, Affect! <: VisualizationCallback} + visualization_callback = cb.affect! + + visualization_callback(integrator) + + return nothing end - if show_mesh - push!(plots, Plots.plot(getmesh(plot_data); plot_arguments...)) + + # this method is called to determine whether the callback should be activated + function (visualization_callback::VisualizationCallback)(u, t, integrator) + @unpack interval = visualization_callback + + # With error-based step size control, some steps can be rejected. Thus, + # `integrator.iter >= integrator.stats.naccept` + # (total #steps) (#accepted steps) + # We need to check the number of accepted steps since callbacks are not + # activated after a rejected step. + return interval > 0 && ( + integrator.stats.naccept % interval == 0 || + isfinished(integrator) + ) end - # Determine layout - cols = ceil(Int, sqrt(length(plots))) - rows = div(length(plots), cols, RoundUp) - layout = (rows, cols) + # this method is called when the callback is activated + function (visualization_callback::VisualizationCallback)(integrator) + u_ode = integrator.u + semi = integrator.p + @unpack plot_arguments, solution_variables, variable_names, show_mesh, plot_data_creator, plot_creator = visualization_callback + + # Extract plot data + plot_data = plot_data_creator(u_ode, semi, solution_variables = solution_variables) + + # If variable names were not specified, plot everything + if isempty(variable_names) + variable_names = String[keys(plot_data)...] + end + + # Create plot + plot_creator( + plot_data, variable_names; + show_mesh = show_mesh, plot_arguments = plot_arguments, + time = integrator.t, timestep = integrator.stats.naccept + ) + + # avoid re-evaluating possible FSAL stages + u_modified!(integrator, false) + return nothing + end - # Create plot - Plots.plot(plots..., layout = layout) + """ + show_plot(plot_data, variable_names; + show_mesh=true, plot_arguments=Dict{Symbol,Any}(), + time=nothing, timestep=nothing) + + Visualize the plot data object provided in `plot_data` and display result, plotting only the + variables in `variable_names` and, optionally, the mesh (if `show_mesh` is `true`). Additionally, + `plot_arguments` will be unpacked and passed as keyword arguments to the `Plots.plot` command. + + This function is the default `plot_creator` argument for the [`VisualizationCallback`](@ref). + `time` and `timestep` are currently unused by this function. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + + See also: [`VisualizationCallback`](@ref), [`save_plot`](@ref) + """ + function show_plot( + plot_data, variable_names; + show_mesh = true, plot_arguments = Dict{Symbol, Any}(), + time = nothing, timestep = nothing + ) + # Gather subplots + plots = [] + for v in variable_names + push!(plots, Plots.plot(plot_data[v]; plot_arguments...)) + end + if show_mesh + push!(plots, Plots.plot(getmesh(plot_data); plot_arguments...)) + end + + # Note, for the visualization callback to work for general equation systems + # this layout construction would need to use the if-logic below. + # Currently, there is no use case for this so it is left here as a note. + # + # Determine layout + # if length(plots) <= 3 + # cols = length(plots) + # rows = 1 + # else + # cols = ceil(Int, sqrt(length(plots))) + # rows = div(length(plots), cols, RoundUp) + # end + # layout = (rows, cols) + + # Determine layout + cols = ceil(Int, sqrt(length(plots))) + rows = div(length(plots), cols, RoundUp) + layout = (rows, cols) + + # Show plot + display(Plots.plot(plots..., layout = layout)) + end - # Determine filename and save plot - filename = joinpath("out", @sprintf("solution_%09d.png", timestep)) - Plots.savefig(filename) -end + """ + save_plot(plot_data, variable_names; + show_mesh=true, plot_arguments=Dict{Symbol,Any}(), + time=nothing, timestep=nothing) + + Visualize the plot data object provided in `plot_data` and save result as a PNG file in the `out` + directory, plotting only the variables in `variable_names` and, optionally, the mesh (if `show_mesh` + is `true`). Additionally, `plot_arguments` will be unpacked and passed as keyword arguments to the + `Plots.plot` command. + + The `timestep` is used in the filename. `time` is currently unused by this function. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + + See also: [`VisualizationCallback`](@ref), [`show_plot`](@ref) + """ + function save_plot( + plot_data, variable_names; + show_mesh = true, plot_arguments = Dict{Symbol, Any}(), + time = nothing, timestep = nothing + ) + # Gather subplots + plots = [] + for v in variable_names + push!(plots, Plots.plot(plot_data[v]; plot_arguments...)) + end + if show_mesh + push!(plots, Plots.plot(getmesh(plot_data); plot_arguments...)) + end + + # Determine layout + cols = ceil(Int, sqrt(length(plots))) + rows = div(length(plots), cols, RoundUp) + layout = (rows, cols) + + # Create plot + Plots.plot(plots..., layout = layout) + + # Determine filename and save plot + filename = joinpath("out", @sprintf("solution_%09d.png", timestep)) + Plots.savefig(filename) + end end # @muladd diff --git a/src/equations/acoustic_perturbation_2d.jl b/src/equations/acoustic_perturbation_2d.jl index 1bde94a648..d4f6fe7232 100644 --- a/src/equations/acoustic_perturbation_2d.jl +++ b/src/equations/acoustic_perturbation_2d.jl @@ -3,384 +3,428 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - AcousticPerturbationEquations2D(v_mean_global, c_mean_global, rho_mean_global) - -Acoustic perturbation equations (APE) in two space dimensions. The equations are given by -```math -\begin{aligned} - \frac{\partial\mathbf{v'}}{\partial t} + \nabla (\bar{\mathbf{v}}\cdot\mathbf{v'}) - + \nabla\left( \frac{\bar{c}^2 \tilde{p}'}{\bar{\rho}} \right) &= 0 \\ - \frac{\partial \tilde{p}'}{\partial t} + - \nabla\cdot (\bar{\rho} \mathbf{v'} + \bar{\mathbf{v}} \tilde{p}') &= 0. -\end{aligned} -``` -The bar ``\bar{(\cdot)}`` indicates time-averaged quantities. The unknowns of the APE are the -perturbed velocities ``\mathbf{v'} = (v_1', v_2')^T`` and the scaled perturbed pressure -``\tilde{p}' = \frac{p'}{\bar{c}^2}``, where ``p'`` denotes the perturbed pressure and the -perturbed variables are defined by ``\phi' = \phi - \bar{\phi}``. - -In addition to the unknowns, Trixi.jl currently stores the mean values in the state vector, -i.e. the state vector used internally is given by -```math -\mathbf{u} = - \begin{pmatrix} - v_1' \\ v_2' \\ \tilde{p}' \\ \bar{v}_1 \\ \bar{v}_2 \\ \bar{c} \\ \bar{\rho} - \end{pmatrix}. -``` -This affects the implementation and use of these equations in various ways: -* The flux values corresponding to the mean values must be zero. -* The mean values have to be considered when defining initial conditions, boundary conditions or - source terms. -* [`AnalysisCallback`](@ref) analyzes these variables too. -* Trixi.jl's visualization tools will visualize the mean values by default. - -The constructor accepts a 2-tuple `v_mean_global` and scalars `c_mean_global` and `rho_mean_global` -which can be used to make the definition of initial conditions for problems with constant mean flow -more flexible. These values are ignored if the mean values are defined internally in an initial -condition. - -The equations are based on the APE-4 system introduced in the following paper: -- Roland Ewert and Wolfgang Schröder (2003) - Acoustic perturbation equations based on flow decomposition via source filtering - [DOI: 10.1016/S0021-9991(03)00168-2](https://doi.org/10.1016/S0021-9991(03)00168-2) -""" -struct AcousticPerturbationEquations2D{RealT <: Real} <: - AbstractAcousticPerturbationEquations{2, 7} - v_mean_global::SVector{2, RealT} - c_mean_global::RealT - rho_mean_global::RealT -end - -function AcousticPerturbationEquations2D(v_mean_global::NTuple{2, <:Real}, - c_mean_global::Real, - rho_mean_global::Real) - return AcousticPerturbationEquations2D(SVector(v_mean_global), c_mean_global, - rho_mean_global) -end - -function AcousticPerturbationEquations2D(; v_mean_global::NTuple{2, <:Real}, - c_mean_global::Real, - rho_mean_global::Real) - return AcousticPerturbationEquations2D(SVector(v_mean_global), c_mean_global, - rho_mean_global) -end - -function varnames(::typeof(cons2cons), ::AcousticPerturbationEquations2D) - ("v1_prime", "v2_prime", "p_prime_scaled", - "v1_mean", "v2_mean", "c_mean", "rho_mean") -end -function varnames(::typeof(cons2prim), ::AcousticPerturbationEquations2D) - ("v1_prime", "v2_prime", "p_prime", - "v1_mean", "v2_mean", "c_mean", "rho_mean") -end - -# Convenience functions for retrieving state variables and mean variables -function cons2state(u, equations::AcousticPerturbationEquations2D) - return SVector(u[1], u[2], u[3]) -end - -function cons2mean(u, equations::AcousticPerturbationEquations2D) - return SVector(u[4], u[5], u[6], u[7]) -end - -function varnames(::typeof(cons2state), ::AcousticPerturbationEquations2D) - ("v1_prime", "v2_prime", "p_prime_scaled") -end -function varnames(::typeof(cons2mean), ::AcousticPerturbationEquations2D) - ("v1_mean", "v2_mean", "c_mean", "rho_mean") -end - -""" - global_mean_vars(equations::AcousticPerturbationEquations2D) - -Returns the global mean variables stored in `equations`. This makes it easier -to define flexible initial conditions for problems with constant mean flow. -""" -function global_mean_vars(equations::AcousticPerturbationEquations2D) - return equations.v_mean_global[1], equations.v_mean_global[2], - equations.c_mean_global, - equations.rho_mean_global -end - -""" - initial_condition_constant(x, t, equations::AcousticPerturbationEquations2D) - -A constant initial condition where the state variables are zero and the mean flow is constant. -Uses the global mean values from `equations`. -""" -function initial_condition_constant(x, t, equations::AcousticPerturbationEquations2D) - v1_prime = 0 - v2_prime = 0 - p_prime_scaled = 0 - - return SVector(v1_prime, v2_prime, p_prime_scaled, global_mean_vars(equations)...) -end - -""" - initial_condition_convergence_test(x, t, equations::AcousticPerturbationEquations2D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref). Uses the global mean values from `equations`. -""" -function initial_condition_convergence_test(x, t, - equations::AcousticPerturbationEquations2D) - RealT = eltype(x) - a = 1 - c = 2 - L = 2 - f = 2.0f0 / L - A = convert(RealT, 0.2) - omega = 2 * convert(RealT, pi) * f - init = c + A * sin(omega * (x[1] + x[2] - a * t)) - - v1_prime = init - v2_prime = init - p_prime = init^2 - - prim = SVector(v1_prime, v2_prime, p_prime, global_mean_vars(equations)...) - - return prim2cons(prim, equations) -end - -""" - source_terms_convergence_test(u, x, t, equations::AcousticPerturbationEquations2D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref). -""" -function source_terms_convergence_test(u, x, t, - equations::AcousticPerturbationEquations2D) - v1_mean, v2_mean, c_mean, rho_mean = cons2mean(u, equations) - - RealT = eltype(u) - a = 1 - c = 2 - L = 2 - f = 2.0f0 / L - A = convert(RealT, 0.2) - omega = 2 * convert(RealT, pi) * f - - si, co = sincos(omega * (x[1] + x[2] - a * t)) - tmp = v1_mean + v2_mean - a - - du1 = du2 = A * omega * co * (2 * c / rho_mean + tmp + 2 / rho_mean * A * si) - du3 = A * omega * co * (2 * c_mean^2 * rho_mean + 2 * c * tmp + 2 * A * tmp * si) / - c_mean^2 - - du4 = du5 = du6 = du7 = 0 - - return SVector(du1, du2, du3, du4, du5, du6, du7) -end - -""" - initial_condition_gauss(x, t, equations::AcousticPerturbationEquations2D) - -A Gaussian pulse in a constant mean flow. Uses the global mean values from `equations`. -""" -function initial_condition_gauss(x, t, equations::AcousticPerturbationEquations2D) - v1_prime = 0 - v2_prime = 0 - p_prime = exp(-4 * (x[1]^2 + x[2]^2)) - - prim = SVector(v1_prime, v2_prime, p_prime, global_mean_vars(equations)...) - - return prim2cons(prim, equations) -end - -""" - boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, - equations::AcousticPerturbationEquations2D) - -Boundary conditions for a solid wall. -""" -function boundary_condition_wall(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::AcousticPerturbationEquations2D) - # Boundary state is equal to the inner state except for the perturbed velocity. For boundaries - # in the -x/+x direction, we multiply the perturbed velocity in the x direction by -1. - # Similarly, for boundaries in the -y/+y direction, we multiply the perturbed velocity in the - # y direction by -1 - if direction in (1, 2) # x direction - u_boundary = SVector(-u_inner[1], u_inner[2], u_inner[3], - cons2mean(u_inner, equations)...) - else # y direction - u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3], - cons2mean(u_inner, equations)...) + #! format: noindent + + @doc raw""" + AcousticPerturbationEquations2D(v_mean_global, c_mean_global, rho_mean_global) + + Acoustic perturbation equations (APE) in two space dimensions. The equations are given by + ```math + \begin{aligned} + \frac{\partial\mathbf{v'}}{\partial t} + \nabla (\bar{\mathbf{v}}\cdot\mathbf{v'}) + + \nabla\left( \frac{\bar{c}^2 \tilde{p}'}{\bar{\rho}} \right) &= 0 \\ + \frac{\partial \tilde{p}'}{\partial t} + + \nabla\cdot (\bar{\rho} \mathbf{v'} + \bar{\mathbf{v}} \tilde{p}') &= 0. + \end{aligned} + ``` + The bar ``\bar{(\cdot)}`` indicates time-averaged quantities. The unknowns of the APE are the + perturbed velocities ``\mathbf{v'} = (v_1', v_2')^T`` and the scaled perturbed pressure + ``\tilde{p}' = \frac{p'}{\bar{c}^2}``, where ``p'`` denotes the perturbed pressure and the + perturbed variables are defined by ``\phi' = \phi - \bar{\phi}``. + + In addition to the unknowns, Trixi.jl currently stores the mean values in the state vector, + i.e. the state vector used internally is given by + ```math + \mathbf{u} = + \begin{pmatrix} + v_1' \\ v_2' \\ \tilde{p}' \\ \bar{v}_1 \\ \bar{v}_2 \\ \bar{c} \\ \bar{\rho} + \end{pmatrix}. + ``` + This affects the implementation and use of these equations in various ways: + * The flux values corresponding to the mean values must be zero. + * The mean values have to be considered when defining initial conditions, boundary conditions or + source terms. + * [`AnalysisCallback`](@ref) analyzes these variables too. + * Trixi.jl's visualization tools will visualize the mean values by default. + + The constructor accepts a 2-tuple `v_mean_global` and scalars `c_mean_global` and `rho_mean_global` + which can be used to make the definition of initial conditions for problems with constant mean flow + more flexible. These values are ignored if the mean values are defined internally in an initial + condition. + + The equations are based on the APE-4 system introduced in the following paper: + - Roland Ewert and Wolfgang Schröder (2003) + Acoustic perturbation equations based on flow decomposition via source filtering + [DOI: 10.1016/S0021-9991(03)00168-2](https://doi.org/10.1016/S0021-9991(03)00168-2) + """ + struct AcousticPerturbationEquations2D{RealT <: Real} <: + AbstractAcousticPerturbationEquations{2, 7} + v_mean_global::SVector{2, RealT} + c_mean_global::RealT + rho_mean_global::RealT end - # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + function AcousticPerturbationEquations2D( + v_mean_global::NTuple{2, <:Real}, + c_mean_global::Real, + rho_mean_global::Real + ) + return AcousticPerturbationEquations2D( + SVector(v_mean_global), c_mean_global, + rho_mean_global + ) end - return flux -end - -""" - boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, - equations::AcousticPerturbationEquations2D) - -Use an orthogonal projection of the perturbed velocities to zero out the normal velocity -while retaining the possibility of a tangential velocity in the boundary state. -Further details are available in the paper: -- Marcus Bauer, Jürgen Dierke and Roland Ewert (2011) - Application of a discontinuous Galerkin method to discretize acoustic perturbation equations - [DOI: 10.2514/1.J050333](https://doi.org/10.2514/1.J050333) -""" -function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, x, t, - surface_flux_function, - equations::AcousticPerturbationEquations2D) - # normalize the outward pointing direction - normal = normal_direction / norm(normal_direction) - - # compute the normal perturbed velocity - u_normal = normal[1] * u_inner[1] + normal[2] * u_inner[2] - - # create the "external" boundary solution state - u_boundary = SVector(u_inner[1] - 2 * u_normal * normal[1], - u_inner[2] - 2 * u_normal * normal[2], - u_inner[3], cons2mean(u_inner, equations)...) - - # calculate the boundary flux - flux = surface_flux_function(u_inner, u_boundary, normal_direction, equations) - - return flux -end - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, - equations::AcousticPerturbationEquations2D) - v1_prime, v2_prime, p_prime_scaled = cons2state(u, equations) - v1_mean, v2_mean, c_mean, rho_mean = cons2mean(u, equations) - - # Calculate flux for conservative state variables - RealT = eltype(u) - if orientation == 1 - f1 = v1_mean * v1_prime + v2_mean * v2_prime + - c_mean^2 * p_prime_scaled / rho_mean - f2 = zero(RealT) - f3 = rho_mean * v1_prime + v1_mean * p_prime_scaled - else - f1 = zero(RealT) - f2 = v1_mean * v1_prime + v2_mean * v2_prime + - c_mean^2 * p_prime_scaled / rho_mean - f3 = rho_mean * v2_prime + v2_mean * p_prime_scaled + function AcousticPerturbationEquations2D(; + v_mean_global::NTuple{2, <:Real}, + c_mean_global::Real, + rho_mean_global::Real + ) + return AcousticPerturbationEquations2D( + SVector(v_mean_global), c_mean_global, + rho_mean_global + ) end - # The rest of the state variables are actually variable coefficients, hence the flux should be - # zero. See https://github.com/trixi-framework/Trixi.jl/issues/358#issuecomment-784828762 - # for details. - f4 = f5 = f6 = f7 = 0 + function varnames(::typeof(cons2cons), ::AcousticPerturbationEquations2D) + ( + "v1_prime", "v2_prime", "p_prime_scaled", + "v1_mean", "v2_mean", "c_mean", "rho_mean", + ) + end + function varnames(::typeof(cons2prim), ::AcousticPerturbationEquations2D) + ( + "v1_prime", "v2_prime", "p_prime", + "v1_mean", "v2_mean", "c_mean", "rho_mean", + ) + end - return SVector(f1, f2, f3, f4, f5, f6, f7) -end + # Convenience functions for retrieving state variables and mean variables + function cons2state(u, equations::AcousticPerturbationEquations2D) + return SVector(u[1], u[2], u[3]) + end -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::AcousticPerturbationEquations2D) - # Calculate v = v_prime + v_mean - v_prime_ll = u_ll[orientation] - v_prime_rr = u_rr[orientation] - v_mean_ll = u_ll[orientation + 3] - v_mean_rr = u_rr[orientation + 3] - - v_ll = v_prime_ll + v_mean_ll - v_rr = v_prime_rr + v_mean_rr - - c_mean_ll = u_ll[6] - c_mean_rr = u_rr[6] - - λ_max = max(abs(v_ll), abs(v_rr)) + max(c_mean_ll, c_mean_rr) -end - -# Calculate 1D flux for a single point in the normal direction -# Note, this directional vector is not normalized -@inline function flux(u, normal_direction::AbstractVector, - equations::AcousticPerturbationEquations2D) - v1_prime, v2_prime, p_prime_scaled = cons2state(u, equations) - v1_mean, v2_mean, c_mean, rho_mean = cons2mean(u, equations) - - f1 = normal_direction[1] * (v1_mean * v1_prime + v2_mean * v2_prime + - c_mean^2 * p_prime_scaled / rho_mean) - f2 = normal_direction[2] * (v1_mean * v1_prime + v2_mean * v2_prime + - c_mean^2 * p_prime_scaled / rho_mean) - f3 = (normal_direction[1] * (rho_mean * v1_prime + v1_mean * p_prime_scaled) - + - normal_direction[2] * (rho_mean * v2_prime + v2_mean * p_prime_scaled)) - - # The rest of the state variables are actually variable coefficients, hence the flux should be - # zero. See https://github.com/trixi-framework/Trixi.jl/issues/358#issuecomment-784828762 - # for details. - f4 = f5 = f6 = f7 = 0 - - return SVector(f1, f2, f3, f4, f5, f6, f7) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, + function cons2mean(u, equations::AcousticPerturbationEquations2D) + return SVector(u[4], u[5], u[6], u[7]) + end + + function varnames(::typeof(cons2state), ::AcousticPerturbationEquations2D) + ("v1_prime", "v2_prime", "p_prime_scaled") + end + function varnames(::typeof(cons2mean), ::AcousticPerturbationEquations2D) + ("v1_mean", "v2_mean", "c_mean", "rho_mean") + end + + """ + global_mean_vars(equations::AcousticPerturbationEquations2D) + + Returns the global mean variables stored in `equations`. This makes it easier + to define flexible initial conditions for problems with constant mean flow. + """ + function global_mean_vars(equations::AcousticPerturbationEquations2D) + return equations.v_mean_global[1], equations.v_mean_global[2], + equations.c_mean_global, + equations.rho_mean_global + end + + """ + initial_condition_constant(x, t, equations::AcousticPerturbationEquations2D) + + A constant initial condition where the state variables are zero and the mean flow is constant. + Uses the global mean values from `equations`. + """ + function initial_condition_constant(x, t, equations::AcousticPerturbationEquations2D) + v1_prime = 0 + v2_prime = 0 + p_prime_scaled = 0 + + return SVector(v1_prime, v2_prime, p_prime_scaled, global_mean_vars(equations)...) + end + + """ + initial_condition_convergence_test(x, t, equations::AcousticPerturbationEquations2D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref). Uses the global mean values from `equations`. + """ + function initial_condition_convergence_test( + x, t, + equations::AcousticPerturbationEquations2D + ) + RealT = eltype(x) + a = 1 + c = 2 + L = 2 + f = 2.0f0 / L + A = convert(RealT, 0.2) + omega = 2 * convert(RealT, pi) * f + init = c + A * sin(omega * (x[1] + x[2] - a * t)) + + v1_prime = init + v2_prime = init + p_prime = init^2 + + prim = SVector(v1_prime, v2_prime, p_prime, global_mean_vars(equations)...) + + return prim2cons(prim, equations) + end + + """ + source_terms_convergence_test(u, x, t, equations::AcousticPerturbationEquations2D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref). + """ + function source_terms_convergence_test( + u, x, t, + equations::AcousticPerturbationEquations2D + ) + v1_mean, v2_mean, c_mean, rho_mean = cons2mean(u, equations) + + RealT = eltype(u) + a = 1 + c = 2 + L = 2 + f = 2.0f0 / L + A = convert(RealT, 0.2) + omega = 2 * convert(RealT, pi) * f + + si, co = sincos(omega * (x[1] + x[2] - a * t)) + tmp = v1_mean + v2_mean - a + + du1 = du2 = A * omega * co * (2 * c / rho_mean + tmp + 2 / rho_mean * A * si) + du3 = A * omega * co * (2 * c_mean^2 * rho_mean + 2 * c * tmp + 2 * A * tmp * si) / + c_mean^2 + + du4 = du5 = du6 = du7 = 0 + + return SVector(du1, du2, du3, du4, du5, du6, du7) + end + + """ + initial_condition_gauss(x, t, equations::AcousticPerturbationEquations2D) + + A Gaussian pulse in a constant mean flow. Uses the global mean values from `equations`. + """ + function initial_condition_gauss(x, t, equations::AcousticPerturbationEquations2D) + v1_prime = 0 + v2_prime = 0 + p_prime = exp(-4 * (x[1]^2 + x[2]^2)) + + prim = SVector(v1_prime, v2_prime, p_prime, global_mean_vars(equations)...) + + return prim2cons(prim, equations) + end + + """ + boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, + equations::AcousticPerturbationEquations2D) + + Boundary conditions for a solid wall. + """ + function boundary_condition_wall( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::AcousticPerturbationEquations2D + ) + # Boundary state is equal to the inner state except for the perturbed velocity. For boundaries + # in the -x/+x direction, we multiply the perturbed velocity in the x direction by -1. + # Similarly, for boundaries in the -y/+y direction, we multiply the perturbed velocity in the + # y direction by -1 + if direction in (1, 2) # x direction + u_boundary = SVector( + -u_inner[1], u_inner[2], u_inner[3], + cons2mean(u_inner, equations)... + ) + else # y direction + u_boundary = SVector( + u_inner[1], -u_inner[2], u_inner[3], + cons2mean(u_inner, equations)... + ) + end + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end + + """ + boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, equations::AcousticPerturbationEquations2D) - # Calculate v = v_prime + v_mean - v_prime_ll = normal_direction[1] * u_ll[1] + normal_direction[2] * u_ll[2] - v_prime_rr = normal_direction[1] * u_rr[1] + normal_direction[2] * u_rr[2] - v_mean_ll = normal_direction[1] * u_ll[4] + normal_direction[2] * u_ll[5] - v_mean_rr = normal_direction[1] * u_rr[4] + normal_direction[2] * u_rr[5] - v_ll = v_prime_ll + v_mean_ll - v_rr = v_prime_rr + v_mean_rr + Use an orthogonal projection of the perturbed velocities to zero out the normal velocity + while retaining the possibility of a tangential velocity in the boundary state. + Further details are available in the paper: + - Marcus Bauer, Jürgen Dierke and Roland Ewert (2011) + Application of a discontinuous Galerkin method to discretize acoustic perturbation equations + [DOI: 10.2514/1.J050333](https://doi.org/10.2514/1.J050333) + """ + function boundary_condition_slip_wall( + u_inner, normal_direction::AbstractVector, x, t, + surface_flux_function, + equations::AcousticPerturbationEquations2D + ) + # normalize the outward pointing direction + normal = normal_direction / norm(normal_direction) + + # compute the normal perturbed velocity + u_normal = normal[1] * u_inner[1] + normal[2] * u_inner[2] + + # create the "external" boundary solution state + u_boundary = SVector( + u_inner[1] - 2 * u_normal * normal[1], + u_inner[2] - 2 * u_normal * normal[2], + u_inner[3], cons2mean(u_inner, equations)... + ) + + # calculate the boundary flux + flux = surface_flux_function(u_inner, u_boundary, normal_direction, equations) + + return flux + end + + # Calculate 1D flux for a single point + @inline function flux( + u, orientation::Integer, + equations::AcousticPerturbationEquations2D + ) + v1_prime, v2_prime, p_prime_scaled = cons2state(u, equations) + v1_mean, v2_mean, c_mean, rho_mean = cons2mean(u, equations) + + # Calculate flux for conservative state variables + RealT = eltype(u) + if orientation == 1 + f1 = v1_mean * v1_prime + v2_mean * v2_prime + + c_mean^2 * p_prime_scaled / rho_mean + f2 = zero(RealT) + f3 = rho_mean * v1_prime + v1_mean * p_prime_scaled + else + f1 = zero(RealT) + f2 = v1_mean * v1_prime + v2_mean * v2_prime + + c_mean^2 * p_prime_scaled / rho_mean + f3 = rho_mean * v2_prime + v2_mean * p_prime_scaled + end + + # The rest of the state variables are actually variable coefficients, hence the flux should be + # zero. See https://github.com/trixi-framework/Trixi.jl/issues/358#issuecomment-784828762 + # for details. + f4 = f5 = f6 = f7 = 0 + + return SVector(f1, f2, f3, f4, f5, f6, f7) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::AcousticPerturbationEquations2D + ) + # Calculate v = v_prime + v_mean + v_prime_ll = u_ll[orientation] + v_prime_rr = u_rr[orientation] + v_mean_ll = u_ll[orientation + 3] + v_mean_rr = u_rr[orientation + 3] + + v_ll = v_prime_ll + v_mean_ll + v_rr = v_prime_rr + v_mean_rr + + c_mean_ll = u_ll[6] + c_mean_rr = u_rr[6] - c_mean_ll = u_ll[6] - c_mean_rr = u_rr[6] + λ_max = max(abs(v_ll), abs(v_rr)) + max(c_mean_ll, c_mean_rr) + end + + # Calculate 1D flux for a single point in the normal direction + # Note, this directional vector is not normalized + @inline function flux( + u, normal_direction::AbstractVector, + equations::AcousticPerturbationEquations2D + ) + v1_prime, v2_prime, p_prime_scaled = cons2state(u, equations) + v1_mean, v2_mean, c_mean, rho_mean = cons2mean(u, equations) + + f1 = normal_direction[1] * ( + v1_mean * v1_prime + v2_mean * v2_prime + + c_mean^2 * p_prime_scaled / rho_mean + ) + f2 = normal_direction[2] * ( + v1_mean * v1_prime + v2_mean * v2_prime + + c_mean^2 * p_prime_scaled / rho_mean + ) + f3 = ( + normal_direction[1] * (rho_mean * v1_prime + v1_mean * p_prime_scaled) + + + normal_direction[2] * (rho_mean * v2_prime + v2_mean * p_prime_scaled) + ) + + # The rest of the state variables are actually variable coefficients, hence the flux should be + # zero. See https://github.com/trixi-framework/Trixi.jl/issues/358#issuecomment-784828762 + # for details. + f4 = f5 = f6 = f7 = 0 + + return SVector(f1, f2, f3, f4, f5, f6, f7) + end - # The v_normals are already scaled by the norm - λ_max = max(abs(v_ll), abs(v_rr)) + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::AcousticPerturbationEquations2D + ) + # Calculate v = v_prime + v_mean + v_prime_ll = normal_direction[1] * u_ll[1] + normal_direction[2] * u_ll[2] + v_prime_rr = normal_direction[1] * u_rr[1] + normal_direction[2] * u_rr[2] + v_mean_ll = normal_direction[1] * u_ll[4] + normal_direction[2] * u_ll[5] + v_mean_rr = normal_direction[1] * u_rr[4] + normal_direction[2] * u_rr[5] + + v_ll = v_prime_ll + v_mean_ll + v_rr = v_prime_rr + v_mean_rr + + c_mean_ll = u_ll[6] + c_mean_rr = u_rr[6] + + # The v_normals are already scaled by the norm + λ_max = max(abs(v_ll), abs(v_rr)) + max(c_mean_ll, c_mean_rr) * norm(normal_direction) -end - -# Specialized `DissipationLocalLaxFriedrichs` to avoid spurious dissipation in the mean values -@inline function (dissipation::DissipationLocalLaxFriedrichs)(u_ll, u_rr, - orientation_or_normal_direction, - equations::AcousticPerturbationEquations2D) - λ = dissipation.max_abs_speed(u_ll, u_rr, orientation_or_normal_direction, - equations) - diss = -0.5f0 * λ * (u_rr - u_ll) - z = 0 - - return SVector(diss[1], diss[2], diss[3], z, z, z, z) -end - -@inline have_constant_speed(::AcousticPerturbationEquations2D) = False() - -@inline function max_abs_speeds(u, equations::AcousticPerturbationEquations2D) - v1_mean = u[4] - v2_mean = u[5] - c_mean = u[6] - - return abs(v1_mean) + c_mean, abs(v2_mean) + c_mean -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::AcousticPerturbationEquations2D) - p_prime_scaled = u[3] - c_mean = u[6] - p_prime = p_prime_scaled * c_mean^2 - - return SVector(u[1], u[2], p_prime, u[4], u[5], u[6], u[7]) -end - -# Convert primitive variables to conservative -@inline function prim2cons(u, equations::AcousticPerturbationEquations2D) - p_prime = u[3] - c_mean = u[6] - p_prime_scaled = p_prime / c_mean^2 - - return SVector(u[1], u[2], p_prime_scaled, u[4], u[5], u[6], u[7]) -end - -# Convert conservative variables to entropy variables -@inline cons2entropy(u, equations::AcousticPerturbationEquations2D) = u + end + + # Specialized `DissipationLocalLaxFriedrichs` to avoid spurious dissipation in the mean values + @inline function (dissipation::DissipationLocalLaxFriedrichs)( + u_ll, u_rr, + orientation_or_normal_direction, + equations::AcousticPerturbationEquations2D + ) + λ = dissipation.max_abs_speed( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + diss = -0.5f0 * λ * (u_rr - u_ll) + z = 0 + + return SVector(diss[1], diss[2], diss[3], z, z, z, z) + end + + @inline have_constant_speed(::AcousticPerturbationEquations2D) = False() + + @inline function max_abs_speeds(u, equations::AcousticPerturbationEquations2D) + v1_mean = u[4] + v2_mean = u[5] + c_mean = u[6] + + return abs(v1_mean) + c_mean, abs(v2_mean) + c_mean + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::AcousticPerturbationEquations2D) + p_prime_scaled = u[3] + c_mean = u[6] + p_prime = p_prime_scaled * c_mean^2 + + return SVector(u[1], u[2], p_prime, u[4], u[5], u[6], u[7]) + end + + # Convert primitive variables to conservative + @inline function prim2cons(u, equations::AcousticPerturbationEquations2D) + p_prime = u[3] + c_mean = u[6] + p_prime_scaled = p_prime / c_mean^2 + + return SVector(u[1], u[2], p_prime_scaled, u[4], u[5], u[6], u[7]) + end + + # Convert conservative variables to entropy variables + @inline cons2entropy(u, equations::AcousticPerturbationEquations2D) = u end # @muladd diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index a71750ff98..c03a8319d9 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -3,993 +3,1047 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - CompressibleEulerEquations1D(gamma) - -The compressible Euler equations -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -\rho \\ \rho v_1 \\ \rho e -\end{pmatrix} -+ -\frac{\partial}{\partial x} -\begin{pmatrix} -\rho v_1 \\ \rho v_1^2 + p \\ (\rho e +p) v_1 -\end{pmatrix} -= -\begin{pmatrix} -0 \\ 0 \\ 0 -\end{pmatrix} -``` -for an ideal gas with ratio of specific heats `gamma` in one space dimension. -Here, ``\rho`` is the density, ``v_1`` the velocity, ``e`` the specific total energy **rather than** specific internal energy, and -```math -p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho v_1^2 \right) -``` -the pressure. -""" -struct CompressibleEulerEquations1D{RealT <: Real} <: - AbstractCompressibleEulerEquations{1, 3} - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - - function CompressibleEulerEquations1D(gamma) - γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) - new{typeof(γ)}(γ, inv_gamma_minus_one) - end -end - -function varnames(::typeof(cons2cons), ::CompressibleEulerEquations1D) - ("rho", "rho_v1", "rho_e") -end -varnames(::typeof(cons2prim), ::CompressibleEulerEquations1D) = ("rho", "v1", "p") - -""" - initial_condition_constant(x, t, equations::CompressibleEulerEquations1D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equations::CompressibleEulerEquations1D) - RealT = eltype(x) - rho = 1 - rho_v1 = convert(RealT, 0.1) - rho_e = 10 - return SVector(rho, rho_v1, rho_e) -end - -""" - initial_condition_convergence_test(x, t, equations::CompressibleEulerEquations1D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -function initial_condition_convergence_test(x, t, - equations::CompressibleEulerEquations1D) - RealT = eltype(x) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - ω = 2 * convert(RealT, pi) * f - ini = c + A * sin(ω * (x[1] - t)) - - rho = ini - rho_v1 = ini - rho_e = ini^2 - - return SVector(rho, rho_v1, rho_e) -end - -""" - source_terms_convergence_test(u, x, t, equations::CompressibleEulerEquations1D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::CompressibleEulerEquations1D) - # Same settings as in `initial_condition` - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - ω = 2 * convert(RealT, pi) * f - γ = equations.gamma - - x1, = x - - si, co = sincos(ω * (x1 - t)) - rho = c + A * si - rho_x = ω * A * co - - # Note that d/dt rho = -d/dx rho. - # This yields du2 = du3 = d/dx p (derivative of pressure). - # Other terms vanish because of v = 1. - du1 = 0 - du2 = rho_x * (2 * rho - 0.5f0) * (γ - 1) - du3 = du2 - - return SVector(du1, du2, du3) -end - -""" - initial_condition_density_wave(x, t, equations::CompressibleEulerEquations1D) - -A sine wave in the density with constant velocity and pressure; reduces the -compressible Euler equations to the linear advection equations. -This setup is the test case for stability of EC fluxes from paper -- Gregor J. Gassner, Magnus Svärd, Florian J. Hindenlang (2020) - Stability issues of entropy-stable and/or split-form high-order schemes - [arXiv: 2007.09026](https://arxiv.org/abs/2007.09026) -with the following parameters -- domain [-1, 1] -- mesh = 4x4 -- polydeg = 5 -""" -function initial_condition_density_wave(x, t, equations::CompressibleEulerEquations1D) - RealT = eltype(x) - v1 = convert(RealT, 0.1) - rho = 1 + convert(RealT, 0.98) * sinpi(2 * (x[1] - t * v1)) - rho_v1 = rho * v1 - p = 20 - rho_e = p / (equations.gamma - 1) + 0.5f0 * rho * v1^2 - return SVector(rho, rho_v1, rho_e) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerEquations1D) - -A weak blast wave taken from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, - equations::CompressibleEulerEquations1D) - # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Set up polar coordinates - RealT = eltype(x) - inicenter = SVector(0) - x_norm = x[1] - inicenter[1] - r = abs(x_norm) - # The following code is equivalent to - # phi = atan(0.0, x_norm) - # cos_phi = cos(phi) - # in 1D but faster - cos_phi = x_norm > 0 ? 1 : -1 - - # Calculate primitive variables - rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - return prim2cons(SVector(rho, v1, p), equations) -end - -""" - initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::CompressibleEulerEquations1D) - -One dimensional variant of the setup used for convergence tests of the Euler equations -with self-gravity from -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -!!! note - There is no additional source term necessary for the manufactured solution in one - spatial dimension. Thus, [`source_terms_eoc_test_coupled_euler_gravity`](@ref) is not - present there. -""" -function initial_condition_eoc_test_coupled_euler_gravity(x, t, - equations::CompressibleEulerEquations1D) - # OBS! this assumes that γ = 2 other manufactured source terms are incorrect - if equations.gamma != 2 - error("adiabatic constant must be 2 for the coupling convergence test") - end - RealT = eltype(x) - c = 2 - A = convert(RealT, 0.1) - ini = c + A * sinpi(x[1] - t) - G = 1 # gravitational constant - - rho = ini - v1 = 1 - p = 2 * ini^2 * G / convert(RealT, pi) # * 2 / ndims, but ndims==1 here - - return prim2cons(SVector(rho, v1, p), equations) -end - -""" - boundary_condition_slip_wall(u_inner, orientation, direction, x, t, - surface_flux_function, equations::CompressibleEulerEquations1D) -Determine the boundary numerical surface flux for a slip wall condition. -Imposes a zero normal velocity at the wall. -Density is taken from the internal solution state and pressure is computed as an -exact solution of a 1D Riemann problem. Further details about this boundary state -are available in the paper: -- J. J. W. van der Vegt and H. van der Ven (2002) - Slip flow boundary conditions in discontinuous Galerkin discretizations of - the Euler equations of gas dynamics - [PDF](https://reports.nlr.nl/bitstream/handle/10921/692/TP-2002-300.pdf?sequence=1) - - Should be used together with [`TreeMesh`](@ref). -""" -@inline function boundary_condition_slip_wall(u_inner, orientation, - direction, x, t, - surface_flux_function, - equations::CompressibleEulerEquations1D) - # compute the primitive variables - rho_local, v_normal, p_local = cons2prim(u_inner, equations) - - if isodd(direction) # flip sign of normal to make it outward pointing - v_normal *= -1 - end - - # Get the solution of the pressure Riemann problem - # See Section 6.3.3 of - # Eleuterio F. Toro (2009) - # Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction - # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) - if v_normal <= 0 - sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed - p_star = p_local * - (1 + 0.5f0 * (equations.gamma - 1) * v_normal / sound_speed)^(2 * - equations.gamma * - equations.inv_gamma_minus_one) - else # v_normal > 0 - A = 2 / ((equations.gamma + 1) * rho_local) - B = p_local * (equations.gamma - 1) / (equations.gamma + 1) - p_star = p_local + - 0.5f0 * v_normal / A * - (v_normal + sqrt(v_normal^2 + 4 * A * (p_local + B))) - end - - # For the slip wall we directly set the flux as the normal velocity is zero - return SVector(0, p_star, 0) -end - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - v1 = rho_v1 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) - # Ignore orientation since it is always "1" in 1D - f1 = rho_v1 - f2 = rho_v1 * v1 + p - f3 = (rho_e + p) * v1 - return SVector(f1, f2, f3) -end - -""" - flux_shima_etal(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) - -This flux is is a modification of the original kinetic energy preserving two-point flux by -- Yuichi Kuya, Kosuke Totani and Soshi Kawai (2018) - Kinetic energy and entropy preserving schemes for compressible flows - by split convective forms - [DOI: 10.1016/j.jcp.2018.08.058](https://doi.org/10.1016/j.jcp.2018.08.058) - -The modification is in the energy flux to guarantee pressure equilibrium and was developed by -- Nao Shima, Yuichi Kuya, Yoshiharu Tamaki, Soshi Kawai (JCP 2020) - Preventing spurious pressure oscillations in split convective form discretizations for - compressible flows - [DOI: 10.1016/j.jcp.2020.110060](https://doi.org/10.1016/j.jcp.2020.110060) -""" -@inline function flux_shima_etal(u_ll, u_rr, orientation::Integer, + #! format: noindent + + @doc raw""" + CompressibleEulerEquations1D(gamma) + + The compressible Euler equations + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + \rho \\ \rho v_1 \\ \rho e + \end{pmatrix} + + + \frac{\partial}{\partial x} + \begin{pmatrix} + \rho v_1 \\ \rho v_1^2 + p \\ (\rho e +p) v_1 + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 \\ 0 + \end{pmatrix} + ``` + for an ideal gas with ratio of specific heats `gamma` in one space dimension. + Here, ``\rho`` is the density, ``v_1`` the velocity, ``e`` the specific total energy **rather than** specific internal energy, and + ```math + p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho v_1^2 \right) + ``` + the pressure. + """ + struct CompressibleEulerEquations1D{RealT <: Real} <: + AbstractCompressibleEulerEquations{1, 3} + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + + function CompressibleEulerEquations1D(gamma) + γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) + new{typeof(γ)}(γ, inv_gamma_minus_one) + end + end + + function varnames(::typeof(cons2cons), ::CompressibleEulerEquations1D) + ("rho", "rho_v1", "rho_e") + end + varnames(::typeof(cons2prim), ::CompressibleEulerEquations1D) = ("rho", "v1", "p") + + """ + initial_condition_constant(x, t, equations::CompressibleEulerEquations1D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equations::CompressibleEulerEquations1D) + RealT = eltype(x) + rho = 1 + rho_v1 = convert(RealT, 0.1) + rho_e = 10 + return SVector(rho, rho_v1, rho_e) + end + + """ + initial_condition_convergence_test(x, t, equations::CompressibleEulerEquations1D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + function initial_condition_convergence_test( + x, t, + equations::CompressibleEulerEquations1D + ) + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + ini = c + A * sin(ω * (x[1] - t)) + + rho = ini + rho_v1 = ini + rho_e = ini^2 + + return SVector(rho, rho_v1, rho_e) + end + + """ + source_terms_convergence_test(u, x, t, equations::CompressibleEulerEquations1D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::CompressibleEulerEquations1D + ) + # Same settings as in `initial_condition` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + γ = equations.gamma + + x1, = x + + si, co = sincos(ω * (x1 - t)) + rho = c + A * si + rho_x = ω * A * co + + # Note that d/dt rho = -d/dx rho. + # This yields du2 = du3 = d/dx p (derivative of pressure). + # Other terms vanish because of v = 1. + du1 = 0 + du2 = rho_x * (2 * rho - 0.5f0) * (γ - 1) + du3 = du2 + + return SVector(du1, du2, du3) + end + + """ + initial_condition_density_wave(x, t, equations::CompressibleEulerEquations1D) + + A sine wave in the density with constant velocity and pressure; reduces the + compressible Euler equations to the linear advection equations. + This setup is the test case for stability of EC fluxes from paper + - Gregor J. Gassner, Magnus Svärd, Florian J. Hindenlang (2020) + Stability issues of entropy-stable and/or split-form high-order schemes + [arXiv: 2007.09026](https://arxiv.org/abs/2007.09026) + with the following parameters + - domain [-1, 1] + - mesh = 4x4 + - polydeg = 5 + """ + function initial_condition_density_wave(x, t, equations::CompressibleEulerEquations1D) + RealT = eltype(x) + v1 = convert(RealT, 0.1) + rho = 1 + convert(RealT, 0.98) * sinpi(2 * (x[1] - t * v1)) + rho_v1 = rho * v1 + p = 20 + rho_e = p / (equations.gamma - 1) + 0.5f0 * rho * v1^2 + return SVector(rho, rho_v1, rho_e) + end + + """ + initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerEquations1D) + + A weak blast wave taken from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave( + x, t, + equations::CompressibleEulerEquations1D + ) + # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Set up polar coordinates + RealT = eltype(x) + inicenter = SVector(0) + x_norm = x[1] - inicenter[1] + r = abs(x_norm) + # The following code is equivalent to + # phi = atan(0.0, x_norm) + # cos_phi = cos(phi) + # in 1D but faster + cos_phi = x_norm > 0 ? 1 : -1 + + # Calculate primitive variables + rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + return prim2cons(SVector(rho, v1, p), equations) + end + + """ + initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::CompressibleEulerEquations1D) + + One dimensional variant of the setup used for convergence tests of the Euler equations + with self-gravity from + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + !!! note + There is no additional source term necessary for the manufactured solution in one + spatial dimension. Thus, [`source_terms_eoc_test_coupled_euler_gravity`](@ref) is not + present there. + """ + function initial_condition_eoc_test_coupled_euler_gravity( + x, t, + equations::CompressibleEulerEquations1D + ) + # OBS! this assumes that γ = 2 other manufactured source terms are incorrect + if equations.gamma != 2 + error("adiabatic constant must be 2 for the coupling convergence test") + end + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + ini = c + A * sinpi(x[1] - t) + G = 1 # gravitational constant + + rho = ini + v1 = 1 + p = 2 * ini^2 * G / convert(RealT, pi) # * 2 / ndims, but ndims==1 here + + return prim2cons(SVector(rho, v1, p), equations) + end + + """ + boundary_condition_slip_wall(u_inner, orientation, direction, x, t, + surface_flux_function, equations::CompressibleEulerEquations1D) + Determine the boundary numerical surface flux for a slip wall condition. + Imposes a zero normal velocity at the wall. + Density is taken from the internal solution state and pressure is computed as an + exact solution of a 1D Riemann problem. Further details about this boundary state + are available in the paper: + - J. J. W. van der Vegt and H. van der Ven (2002) + Slip flow boundary conditions in discontinuous Galerkin discretizations of + the Euler equations of gas dynamics + [PDF](https://reports.nlr.nl/bitstream/handle/10921/692/TP-2002-300.pdf?sequence=1) + + Should be used together with [`TreeMesh`](@ref). + """ + @inline function boundary_condition_slip_wall( + u_inner, orientation, + direction, x, t, + surface_flux_function, + equations::CompressibleEulerEquations1D + ) + # compute the primitive variables + rho_local, v_normal, p_local = cons2prim(u_inner, equations) + + if isodd(direction) # flip sign of normal to make it outward pointing + v_normal *= -1 + end + + # Get the solution of the pressure Riemann problem + # See Section 6.3.3 of + # Eleuterio F. Toro (2009) + # Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + if v_normal <= 0 + sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed + p_star = p_local * + (1 + 0.5f0 * (equations.gamma - 1) * v_normal / sound_speed)^( + 2 * + equations.gamma * + equations.inv_gamma_minus_one + ) + else # v_normal > 0 + A = 2 / ((equations.gamma + 1) * rho_local) + B = p_local * (equations.gamma - 1) / (equations.gamma + 1) + p_star = p_local + + 0.5f0 * v_normal / A * + (v_normal + sqrt(v_normal^2 + 4 * A * (p_local + B))) + end + + # For the slip wall we directly set the flux as the normal velocity is zero + return SVector(0, p_star, 0) + end + + # Calculate 1D flux for a single point + @inline function flux(u, orientation::Integer, equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) + # Ignore orientation since it is always "1" in 1D + f1 = rho_v1 + f2 = rho_v1 * v1 + p + f3 = (rho_e + p) * v1 + return SVector(f1, f2, f3) + end + + """ + flux_shima_etal(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) + + This flux is is a modification of the original kinetic energy preserving two-point flux by + - Yuichi Kuya, Kosuke Totani and Soshi Kawai (2018) + Kinetic energy and entropy preserving schemes for compressible flows + by split convective forms + [DOI: 10.1016/j.jcp.2018.08.058](https://doi.org/10.1016/j.jcp.2018.08.058) + + The modification is in the energy flux to guarantee pressure equilibrium and was developed by + - Nao Shima, Yuichi Kuya, Yoshiharu Tamaki, Soshi Kawai (JCP 2020) + Preventing spurious pressure oscillations in split convective form discretizations for + compressible flows + [DOI: 10.1016/j.jcp.2020.110060](https://doi.org/10.1016/j.jcp.2020.110060) + """ + @inline function flux_shima_etal( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + # Unpack left and right state + rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + kin_avg = 0.5f0 * (v1_ll * v1_rr) + + # Calculate fluxes + # Ignore orientation since it is always "1" in 1D + pv1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + f1 = rho_avg * v1_avg + f2 = f1 * v1_avg + p_avg + f3 = p_avg * v1_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv1_avg + + return SVector(f1, f2, f3) + end + + """ + flux_kennedy_gruber(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) + + Kinetic energy preserving two-point flux by + - Kennedy and Gruber (2008) + Reduced aliasing formulations of the convective terms within the + Navier-Stokes equations for a compressible fluid + [DOI: 10.1016/j.jcp.2007.09.020](https://doi.org/10.1016/j.jcp.2007.09.020) + """ + @inline function flux_kennedy_gruber( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + # Unpack left and right state + rho_e_ll = last(u_ll) + rho_e_rr = last(u_rr) + rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) + + # Ignore orientation since it is always "1" in 1D + f1 = rho_avg * v1_avg + f2 = rho_avg * v1_avg * v1_avg + p_avg + f3 = (rho_avg * e_avg + p_avg) * v1_avg + + return SVector(f1, f2, f3) + end + + """ + flux_chandrashekar(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) + + Entropy conserving two-point flux by + - Chandrashekar (2013) + Kinetic Energy Preserving and Entropy Stable Finite Volume Schemes + for Compressible Euler and Navier-Stokes Equations + [DOI: 10.4208/cicp.170712.010313a](https://doi.org/10.4208/cicp.170712.010313a) + """ + @inline function flux_chandrashekar( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + # Unpack left and right state + rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) + beta_ll = 0.5f0 * rho_ll / p_ll + beta_rr = 0.5f0 * rho_rr / p_rr + specific_kin_ll = 0.5f0 * (v1_ll^2) + specific_kin_rr = 0.5f0 * (v1_rr^2) + + # Compute the necessary mean values + rho_avg = 0.5f0 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5f0 * (beta_ll + beta_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + p_mean = 0.5f0 * rho_avg / beta_avg + velocity_square_avg = specific_kin_ll + specific_kin_rr + + # Calculate fluxes + # Ignore orientation since it is always "1" in 1D + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_mean + f3 = f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + + return SVector(f1, f2, f3) + end + + """ + flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations1D) + + Entropy conserving and kinetic energy preserving two-point flux by + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + See also + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_ranocha( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + # Unpack left and right state + rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) + + # Compute the necessary mean values + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr) + + # Calculate fluxes + # Ignore orientation since it is always "1" in 1D + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_avg + f3 = f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + + return SVector(f1, f2, f3) + end + + # While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that + # the normal component is incorporated into the numerical flux. + # + # See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a + # similar implementation. + @inline function flux_ranocha( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations1D + ) + return normal_direction[1] * flux_ranocha(u_ll, u_rr, 1, equations) + end + + """ + splitting_steger_warming(u, orientation::Integer, equations::CompressibleEulerEquations1D) - # Unpack left and right state - rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - kin_avg = 0.5f0 * (v1_ll * v1_rr) - - # Calculate fluxes - # Ignore orientation since it is always "1" in 1D - pv1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - f1 = rho_avg * v1_avg - f2 = f1 * v1_avg + p_avg - f3 = p_avg * v1_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv1_avg - - return SVector(f1, f2, f3) -end - -""" - flux_kennedy_gruber(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) - -Kinetic energy preserving two-point flux by -- Kennedy and Gruber (2008) - Reduced aliasing formulations of the convective terms within the - Navier-Stokes equations for a compressible fluid - [DOI: 10.1016/j.jcp.2007.09.020](https://doi.org/10.1016/j.jcp.2007.09.020) -""" -@inline function flux_kennedy_gruber(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations1D) - # Unpack left and right state - rho_e_ll = last(u_ll) - rho_e_rr = last(u_rr) - rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) - - # Ignore orientation since it is always "1" in 1D - f1 = rho_avg * v1_avg - f2 = rho_avg * v1_avg * v1_avg + p_avg - f3 = (rho_avg * e_avg + p_avg) * v1_avg - - return SVector(f1, f2, f3) -end - -""" - flux_chandrashekar(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) - -Entropy conserving two-point flux by -- Chandrashekar (2013) - Kinetic Energy Preserving and Entropy Stable Finite Volume Schemes - for Compressible Euler and Navier-Stokes Equations - [DOI: 10.4208/cicp.170712.010313a](https://doi.org/10.4208/cicp.170712.010313a) -""" -@inline function flux_chandrashekar(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations1D) - # Unpack left and right state - rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) - beta_ll = 0.5f0 * rho_ll / p_ll - beta_rr = 0.5f0 * rho_rr / p_rr - specific_kin_ll = 0.5f0 * (v1_ll^2) - specific_kin_rr = 0.5f0 * (v1_rr^2) - - # Compute the necessary mean values - rho_avg = 0.5f0 * (rho_ll + rho_rr) - rho_mean = ln_mean(rho_ll, rho_rr) - beta_mean = ln_mean(beta_ll, beta_rr) - beta_avg = 0.5f0 * (beta_ll + beta_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - p_mean = 0.5f0 * rho_avg / beta_avg - velocity_square_avg = specific_kin_ll + specific_kin_rr - - # Calculate fluxes - # Ignore orientation since it is always "1" in 1D - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_mean - f3 = f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + - f2 * v1_avg - - return SVector(f1, f2, f3) -end - -""" - flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations1D) - -Entropy conserving and kinetic energy preserving two-point flux by -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -See also -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_ranocha(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations1D) - # Unpack left and right state - rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) - - # Compute the necessary mean values - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr) - - # Calculate fluxes - # Ignore orientation since it is always "1" in 1D - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_avg - f3 = f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - - return SVector(f1, f2, f3) -end - -# While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that -# the normal component is incorporated into the numerical flux. -# -# See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a -# similar implementation. -@inline function flux_ranocha(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations1D) - return normal_direction[1] * flux_ranocha(u_ll, u_rr, 1, equations) -end - -""" - splitting_steger_warming(u, orientation::Integer, - equations::CompressibleEulerEquations1D) - splitting_steger_warming(u, which::Union{Val{:minus}, Val{:plus}} - orientation::Integer, - equations::CompressibleEulerEquations1D) - -Splitting of the compressible Euler flux of Steger and Warming. - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. - -## References - -- Joseph L. Steger and R. F. Warming (1979) - Flux Vector Splitting of the Inviscid Gasdynamic Equations - With Application to Finite Difference Methods - [NASA Technical Memorandum](https://ntrs.nasa.gov/api/citations/19790020779/downloads/19790020779.pdf) -""" -@inline function splitting_steger_warming(u, orientation::Integer, - equations::CompressibleEulerEquations1D) - fm = splitting_steger_warming(u, Val{:minus}(), orientation, equations) - fp = splitting_steger_warming(u, Val{:plus}(), orientation, equations) - return fm, fp -end - -@inline function splitting_steger_warming(u, ::Val{:plus}, orientation::Integer, - equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - v1 = rho_v1 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) - a = sqrt(equations.gamma * p / rho) - - lambda1 = v1 - lambda2 = v1 + a - lambda3 = v1 - a - - lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) - lambda2_p = positive_part(lambda2) - lambda3_p = positive_part(lambda3) - - alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1p = rho_2gamma * alpha_p - f2p = rho_2gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) - f3p = rho_2gamma * (alpha_p * 0.5f0 * v1^2 + a * v1 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) - - return SVector(f1p, f2p, f3p) -end - -@inline function splitting_steger_warming(u, ::Val{:minus}, orientation::Integer, - equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - v1 = rho_v1 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) - a = sqrt(equations.gamma * p / rho) - - lambda1 = v1 - lambda2 = v1 + a - lambda3 = v1 - a - - lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) - lambda2_m = negative_part(lambda2) - lambda3_m = negative_part(lambda3) - - alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1m = rho_2gamma * alpha_m - f2m = rho_2gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) - f3m = rho_2gamma * (alpha_m * 0.5f0 * v1^2 + a * v1 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) - - return SVector(f1m, f2m, f3m) -end - -""" - splitting_vanleer_haenel(u, orientation::Integer, - equations::CompressibleEulerEquations1D) - splitting_vanleer_haenel(u, which::Union{Val{:minus}, Val{:plus}} - orientation::Integer, - equations::CompressibleEulerEquations1D) - -Splitting of the compressible Euler flux from van Leer. This splitting further -contains a reformulation due to Hänel et al. where the energy flux uses the -enthalpy. The pressure splitting is independent from the splitting of the -convective terms. As such there are many pressure splittings suggested across -the literature. We implement the 'p4' variant suggested by Liou and Steffen as -it proved the most robust in practice. - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. - -## References - -- Bram van Leer (1982) - Flux-Vector Splitting for the Euler Equation - [DOI: 10.1007/978-3-642-60543-7_5](https://doi.org/10.1007/978-3-642-60543-7_5) -- D. Hänel, R. Schwane and G. Seider (1987) - On the accuracy of upwind schemes for the solution of the Navier-Stokes equations - [DOI: 10.2514/6.1987-1105](https://doi.org/10.2514/6.1987-1105) -- Meng-Sing Liou and Chris J. Steffen, Jr. (1991) - High-Order Polynomial Expansions (HOPE) for Flux-Vector Splitting - [NASA Technical Memorandum](https://ntrs.nasa.gov/citations/19910016425) -""" -@inline function splitting_vanleer_haenel(u, orientation::Integer, - equations::CompressibleEulerEquations1D) - fm = splitting_vanleer_haenel(u, Val{:minus}(), orientation, equations) - fp = splitting_vanleer_haenel(u, Val{:plus}(), orientation, equations) - return fm, fp -end - -@inline function splitting_vanleer_haenel(u, ::Val{:plus}, orientation::Integer, - equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - v1 = rho_v1 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) - - # sound speed and enthalpy - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - # signed Mach number - M = v1 / a - - p_plus = 0.5f0 * (1 + equations.gamma * M) * p - - f1p = 0.25f0 * rho * a * (M + 1)^2 - f2p = f1p * v1 + p_plus - f3p = f1p * H - - return SVector(f1p, f2p, f3p) -end - -@inline function splitting_vanleer_haenel(u, ::Val{:minus}, orientation::Integer, - equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - v1 = rho_v1 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) - - # sound speed and enthalpy - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - # signed Mach number - M = v1 / a - - p_minus = 0.5f0 * (1 - equations.gamma * M) * p - - f1m = -0.25f0 * rho * a * (M - 1)^2 - f2m = f1m * v1 + p_minus - f3m = f1m * H - - return SVector(f1m, f2m, f3m) -end - -# TODO: FD -# This splitting is interesting because it can handle the "el diablo" wave -# for long time runs. Computing the eigenvalues of the operator we see -# J = jacobian_ad_forward(semi); -# lamb = eigvals(J); -# maximum(real.(lamb)) -# 2.1411031631522748e-6 -# So the instability of this splitting is very weak. However, the 2D variant -# of this splitting on "el diablo" still crashes early. Can we learn anything -# from the design of this splitting? -""" - splitting_coirier_vanleer(u, orientation::Integer, - equations::CompressibleEulerEquations1D) - splitting_coirier_vanleer(u, which::Union{Val{:minus}, Val{:plus}} - orientation::Integer, - equations::CompressibleEulerEquations1D) - -Splitting of the compressible Euler flux from Coirier and van Leer. -The splitting has correction terms in the pressure splitting as well as -the mass and energy flux components. The motivation for these corrections -are to handle flows at the low Mach number limit. - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. - -## References - -- William Coirier and Bram van Leer (1991) - Numerical flux formulas for the Euler and Navier-Stokes equations. - II - Progress in flux-vector splitting - [DOI: 10.2514/6.1991-1566](https://doi.org/10.2514/6.1991-1566) -""" -@inline function splitting_coirier_vanleer(u, orientation::Integer, - equations::CompressibleEulerEquations1D) - fm = splitting_coirier_vanleer(u, Val{:minus}(), orientation, equations) - fp = splitting_coirier_vanleer(u, Val{:plus}(), orientation, equations) - return fm, fp -end - -@inline function splitting_coirier_vanleer(u, ::Val{:plus}, orientation::Integer, - equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - v1 = rho_v1 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) - - # sound speed and enthalpy - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - # signed Mach number - M = v1 / a - - P = 2 - mu = 1 - nu = 0.75f0 - omega = 2 # adjusted from suggested value of 1.5 - - p_plus = 0.25f0 * ((M + 1)^2 * (2 - M) - nu * M * (M^2 - 1)^P) * p - - f1p = 0.25f0 * rho * a * ((M + 1)^2 - mu * (M^2 - 1)^P) - f2p = f1p * v1 + p_plus - f3p = f1p * H - omega * rho * a^3 * M^2 * (M^2 - 1)^2 - - return SVector(f1p, f2p, f3p) -end - -@inline function splitting_coirier_vanleer(u, ::Val{:minus}, orientation::Integer, - equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - v1 = rho_v1 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) - - # sound speed and enthalpy - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - # signed Mach number - M = v1 / a - - P = 2 - mu = 1 - nu = 0.75f0 - omega = 2 # adjusted from suggested value of 1.5 - - p_minus = 0.25f0 * ((M - 1)^2 * (2 + M) + nu * M * (M^2 - 1)^P) * p - - f1m = -0.25f0 * rho * a * ((M - 1)^2 - mu * (M^2 - 1)^P) - f2m = f1m * v1 + p_minus - f3m = f1m * H + omega * rho * a^3 * M^2 * (M^2 - 1)^2 - - return SVector(f1m, f2m, f3m) -end - -# Calculate estimates for maximum wave speed for local Lax-Friedrichs-type dissipation as the -# maximum velocity magnitude plus the maximum speed of sound -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations1D) - rho_ll, rho_v1_ll, rho_e_ll = u_ll - rho_rr, rho_v1_rr, rho_e_rr = u_rr - - # Calculate primitive variables and speed of sound - v1_ll = rho_v1_ll / rho_ll - v_mag_ll = abs(v1_ll) - p_ll = (equations.gamma - 1) * (rho_e_ll - 0.5f0 * rho_ll * v_mag_ll^2) - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - v1_rr = rho_v1_rr / rho_rr - v_mag_rr = abs(v1_rr) - p_rr = (equations.gamma - 1) * (rho_e_rr - 0.5f0 * rho_rr * v_mag_rr^2) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - λ_max = max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) -end - -# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations1D) - rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) - - λ_min = v1_ll - sqrt(equations.gamma * p_ll / rho_ll) - λ_max = v1_rr + sqrt(equations.gamma * p_rr / rho_rr) - - return λ_min, λ_max -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations1D) - rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) - - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - λ_min = min(v1_ll - c_ll, v1_rr - c_rr) - λ_max = max(v1_ll + c_ll, v1_rr + c_rr) - - return λ_min, λ_max -end - -""" - flux_hllc(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) - -Computes the HLLC flux (HLL with Contact) for compressible Euler equations developed by E.F. Toro -[Lecture slides](http://www.prague-sum.com/download/2012/Toro_2-HLLC-RiemannSolver.pdf) -Signal speeds: [DOI: 10.1137/S1064827593260140](https://doi.org/10.1137/S1064827593260140) -""" -function flux_hllc(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations1D) - # Calculate primitive variables and speed of sound - rho_ll, rho_v1_ll, rho_e_ll = u_ll - rho_rr, rho_v1_rr, rho_e_rr = u_rr - - v1_ll = rho_v1_ll / rho_ll - e_ll = rho_e_ll / rho_ll - p_ll = (equations.gamma - 1) * (rho_e_ll - 0.5f0 * rho_ll * v1_ll^2) - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - - v1_rr = rho_v1_rr / rho_rr - e_rr = rho_e_rr / rho_rr - p_rr = (equations.gamma - 1) * (rho_e_rr - 0.5f0 * rho_rr * v1_rr^2) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - # Obtain left and right fluxes - f_ll = flux(u_ll, orientation, equations) - f_rr = flux(u_rr, orientation, equations) - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr - vel_L = v1_ll - vel_R = v1_rr - vel_roe = (sqrt_rho_ll * vel_L + sqrt_rho_rr * vel_R) / sum_sqrt_rho - ekin_roe = 0.5f0 * vel_roe^2 - H_ll = (rho_e_ll + p_ll) / rho_ll - H_rr = (rho_e_rr + p_rr) / rho_rr - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - ekin_roe)) - - Ssl = min(vel_L - c_ll, vel_roe - c_roe) - Ssr = max(vel_R + c_rr, vel_roe + c_roe) - sMu_L = Ssl - vel_L - sMu_R = Ssr - vel_R - if Ssl >= 0 - f1 = f_ll[1] - f2 = f_ll[2] - f3 = f_ll[3] - elseif Ssr <= 0 - f1 = f_rr[1] - f2 = f_rr[2] - f3 = f_rr[3] - else - SStar = (p_rr - p_ll + rho_ll * vel_L * sMu_L - rho_rr * vel_R * sMu_R) / - (rho_ll * sMu_L - rho_rr * sMu_R) - if Ssl <= 0 <= SStar - densStar = rho_ll * sMu_L / (Ssl - SStar) - enerStar = e_ll + (SStar - vel_L) * (SStar + p_ll / (rho_ll * sMu_L)) - UStar1 = densStar - UStar2 = densStar * SStar - UStar3 = densStar * enerStar - - f1 = f_ll[1] + Ssl * (UStar1 - rho_ll) - f2 = f_ll[2] + Ssl * (UStar2 - rho_v1_ll) - f3 = f_ll[3] + Ssl * (UStar3 - rho_e_ll) + splitting_steger_warming(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, + equations::CompressibleEulerEquations1D) + + Splitting of the compressible Euler flux of Steger and Warming. + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + + ## References + + - Joseph L. Steger and R. F. Warming (1979) + Flux Vector Splitting of the Inviscid Gasdynamic Equations + With Application to Finite Difference Methods + [NASA Technical Memorandum](https://ntrs.nasa.gov/api/citations/19790020779/downloads/19790020779.pdf) + """ + @inline function splitting_steger_warming( + u, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + fm = splitting_steger_warming(u, Val{:minus}(), orientation, equations) + fp = splitting_steger_warming(u, Val{:plus}(), orientation, equations) + return fm, fp + end + + @inline function splitting_steger_warming( + u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) + a = sqrt(equations.gamma * p / rho) + + lambda1 = v1 + lambda2 = v1 + a + lambda3 = v1 - a + + lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1p = rho_2gamma * alpha_p + f2p = rho_2gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) + f3p = rho_2gamma * ( + alpha_p * 0.5f0 * v1^2 + a * v1 * (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one + ) + + return SVector(f1p, f2p, f3p) + end + + @inline function splitting_steger_warming( + u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) + a = sqrt(equations.gamma * p / rho) + + lambda1 = v1 + lambda2 = v1 + a + lambda3 = v1 - a + + lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1m = rho_2gamma * alpha_m + f2m = rho_2gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) + f3m = rho_2gamma * ( + alpha_m * 0.5f0 * v1^2 + a * v1 * (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one + ) + + return SVector(f1m, f2m, f3m) + end + + """ + splitting_vanleer_haenel(u, orientation::Integer, + equations::CompressibleEulerEquations1D) + splitting_vanleer_haenel(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, + equations::CompressibleEulerEquations1D) + + Splitting of the compressible Euler flux from van Leer. This splitting further + contains a reformulation due to Hänel et al. where the energy flux uses the + enthalpy. The pressure splitting is independent from the splitting of the + convective terms. As such there are many pressure splittings suggested across + the literature. We implement the 'p4' variant suggested by Liou and Steffen as + it proved the most robust in practice. + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + + ## References + + - Bram van Leer (1982) + Flux-Vector Splitting for the Euler Equation + [DOI: 10.1007/978-3-642-60543-7_5](https://doi.org/10.1007/978-3-642-60543-7_5) + - D. Hänel, R. Schwane and G. Seider (1987) + On the accuracy of upwind schemes for the solution of the Navier-Stokes equations + [DOI: 10.2514/6.1987-1105](https://doi.org/10.2514/6.1987-1105) + - Meng-Sing Liou and Chris J. Steffen, Jr. (1991) + High-Order Polynomial Expansions (HOPE) for Flux-Vector Splitting + [NASA Technical Memorandum](https://ntrs.nasa.gov/citations/19910016425) + """ + @inline function splitting_vanleer_haenel( + u, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + fm = splitting_vanleer_haenel(u, Val{:minus}(), orientation, equations) + fp = splitting_vanleer_haenel(u, Val{:plus}(), orientation, equations) + return fm, fp + end + + @inline function splitting_vanleer_haenel( + u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) + + # sound speed and enthalpy + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + # signed Mach number + M = v1 / a + + p_plus = 0.5f0 * (1 + equations.gamma * M) * p + + f1p = 0.25f0 * rho * a * (M + 1)^2 + f2p = f1p * v1 + p_plus + f3p = f1p * H + + return SVector(f1p, f2p, f3p) + end + + @inline function splitting_vanleer_haenel( + u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) + + # sound speed and enthalpy + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + # signed Mach number + M = v1 / a + + p_minus = 0.5f0 * (1 - equations.gamma * M) * p + + f1m = -0.25f0 * rho * a * (M - 1)^2 + f2m = f1m * v1 + p_minus + f3m = f1m * H + + return SVector(f1m, f2m, f3m) + end + + # TODO: FD + # This splitting is interesting because it can handle the "el diablo" wave + # for long time runs. Computing the eigenvalues of the operator we see + # J = jacobian_ad_forward(semi); + # lamb = eigvals(J); + # maximum(real.(lamb)) + # 2.1411031631522748e-6 + # So the instability of this splitting is very weak. However, the 2D variant + # of this splitting on "el diablo" still crashes early. Can we learn anything + # from the design of this splitting? + """ + splitting_coirier_vanleer(u, orientation::Integer, + equations::CompressibleEulerEquations1D) + splitting_coirier_vanleer(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, + equations::CompressibleEulerEquations1D) + + Splitting of the compressible Euler flux from Coirier and van Leer. + The splitting has correction terms in the pressure splitting as well as + the mass and energy flux components. The motivation for these corrections + are to handle flows at the low Mach number limit. + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + + ## References + + - William Coirier and Bram van Leer (1991) + Numerical flux formulas for the Euler and Navier-Stokes equations. + II - Progress in flux-vector splitting + [DOI: 10.2514/6.1991-1566](https://doi.org/10.2514/6.1991-1566) + """ + @inline function splitting_coirier_vanleer( + u, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + fm = splitting_coirier_vanleer(u, Val{:minus}(), orientation, equations) + fp = splitting_coirier_vanleer(u, Val{:plus}(), orientation, equations) + return fm, fp + end + + @inline function splitting_coirier_vanleer( + u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) + + # sound speed and enthalpy + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + # signed Mach number + M = v1 / a + + P = 2 + mu = 1 + nu = 0.75f0 + omega = 2 # adjusted from suggested value of 1.5 + + p_plus = 0.25f0 * ((M + 1)^2 * (2 - M) - nu * M * (M^2 - 1)^P) * p + + f1p = 0.25f0 * rho * a * ((M + 1)^2 - mu * (M^2 - 1)^P) + f2p = f1p * v1 + p_plus + f3p = f1p * H - omega * rho * a^3 * M^2 * (M^2 - 1)^2 + + return SVector(f1p, f2p, f3p) + end + + @inline function splitting_coirier_vanleer( + u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) + + # sound speed and enthalpy + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + # signed Mach number + M = v1 / a + + P = 2 + mu = 1 + nu = 0.75f0 + omega = 2 # adjusted from suggested value of 1.5 + + p_minus = 0.25f0 * ((M - 1)^2 * (2 + M) + nu * M * (M^2 - 1)^P) * p + + f1m = -0.25f0 * rho * a * ((M - 1)^2 - mu * (M^2 - 1)^P) + f2m = f1m * v1 + p_minus + f3m = f1m * H + omega * rho * a^3 * M^2 * (M^2 - 1)^2 + + return SVector(f1m, f2m, f3m) + end + + # Calculate estimates for maximum wave speed for local Lax-Friedrichs-type dissipation as the + # maximum velocity magnitude plus the maximum speed of sound + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + rho_ll, rho_v1_ll, rho_e_ll = u_ll + rho_rr, rho_v1_rr, rho_e_rr = u_rr + + # Calculate primitive variables and speed of sound + v1_ll = rho_v1_ll / rho_ll + v_mag_ll = abs(v1_ll) + p_ll = (equations.gamma - 1) * (rho_e_ll - 0.5f0 * rho_ll * v_mag_ll^2) + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + v1_rr = rho_v1_rr / rho_rr + v_mag_rr = abs(v1_rr) + p_rr = (equations.gamma - 1) * (rho_e_rr - 0.5f0 * rho_rr * v_mag_rr^2) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + λ_max = max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) + end + + # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) + + λ_min = v1_ll - sqrt(equations.gamma * p_ll / rho_ll) + λ_max = v1_rr + sqrt(equations.gamma * p_rr / rho_rr) + + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + rho_ll, v1_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, p_rr = cons2prim(u_rr, equations) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + + return λ_min, λ_max + end + + """ + flux_hllc(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) + + Computes the HLLC flux (HLL with Contact) for compressible Euler equations developed by E.F. Toro + [Lecture slides](http://www.prague-sum.com/download/2012/Toro_2-HLLC-RiemannSolver.pdf) + Signal speeds: [DOI: 10.1137/S1064827593260140](https://doi.org/10.1137/S1064827593260140) + """ + function flux_hllc( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + # Calculate primitive variables and speed of sound + rho_ll, rho_v1_ll, rho_e_ll = u_ll + rho_rr, rho_v1_rr, rho_e_rr = u_rr + + v1_ll = rho_v1_ll / rho_ll + e_ll = rho_e_ll / rho_ll + p_ll = (equations.gamma - 1) * (rho_e_ll - 0.5f0 * rho_ll * v1_ll^2) + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + + v1_rr = rho_v1_rr / rho_rr + e_rr = rho_e_rr / rho_rr + p_rr = (equations.gamma - 1) * (rho_e_rr - 0.5f0 * rho_rr * v1_rr^2) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + # Obtain left and right fluxes + f_ll = flux(u_ll, orientation, equations) + f_rr = flux(u_rr, orientation, equations) + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr + vel_L = v1_ll + vel_R = v1_rr + vel_roe = (sqrt_rho_ll * vel_L + sqrt_rho_rr * vel_R) / sum_sqrt_rho + ekin_roe = 0.5f0 * vel_roe^2 + H_ll = (rho_e_ll + p_ll) / rho_ll + H_rr = (rho_e_rr + p_rr) / rho_rr + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - ekin_roe)) + + Ssl = min(vel_L - c_ll, vel_roe - c_roe) + Ssr = max(vel_R + c_rr, vel_roe + c_roe) + sMu_L = Ssl - vel_L + sMu_R = Ssr - vel_R + if Ssl >= 0 + f1 = f_ll[1] + f2 = f_ll[2] + f3 = f_ll[3] + elseif Ssr <= 0 + f1 = f_rr[1] + f2 = f_rr[2] + f3 = f_rr[3] else - densStar = rho_rr * sMu_R / (Ssr - SStar) - enerStar = e_rr + (SStar - vel_R) * (SStar + p_rr / (rho_rr * sMu_R)) - UStar1 = densStar - UStar2 = densStar * SStar - UStar3 = densStar * enerStar - - #end - f1 = f_rr[1] + Ssr * (UStar1 - rho_rr) - f2 = f_rr[2] + Ssr * (UStar2 - rho_v1_rr) - f3 = f_rr[3] + Ssr * (UStar3 - rho_e_rr) + SStar = (p_rr - p_ll + rho_ll * vel_L * sMu_L - rho_rr * vel_R * sMu_R) / + (rho_ll * sMu_L - rho_rr * sMu_R) + if Ssl <= 0 <= SStar + densStar = rho_ll * sMu_L / (Ssl - SStar) + enerStar = e_ll + (SStar - vel_L) * (SStar + p_ll / (rho_ll * sMu_L)) + UStar1 = densStar + UStar2 = densStar * SStar + UStar3 = densStar * enerStar + + f1 = f_ll[1] + Ssl * (UStar1 - rho_ll) + f2 = f_ll[2] + Ssl * (UStar2 - rho_v1_ll) + f3 = f_ll[3] + Ssl * (UStar3 - rho_e_ll) + else + densStar = rho_rr * sMu_R / (Ssr - SStar) + enerStar = e_rr + (SStar - vel_R) * (SStar + p_rr / (rho_rr * sMu_R)) + UStar1 = densStar + UStar2 = densStar * SStar + UStar3 = densStar * enerStar + + #end + f1 = f_rr[1] + Ssr * (UStar1 - rho_rr) + f2 = f_rr[2] + Ssr * (UStar2 - rho_v1_rr) + f3 = f_rr[3] + Ssr * (UStar3 - rho_e_rr) + end end + return SVector(f1, f2, f3) + end + + """ + min_max_speed_einfeldt(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) + + Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. + Special estimates of the signal velocites and linearization of the Riemann problem developed + by Einfeldt to ensure that the internal energy and density remain positive during the computation + of the numerical flux. + + Original publication: + - Bernd Einfeldt (1988) + On Godunov-type methods for gas dynamics. + [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) + + Compactly summarized: + - Siddhartha Mishra, Ulrik Skre Fjordholm and Rémi Abgrall + Numerical methods for conservation laws and related equations. + [Link](https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf) + """ + @inline function min_max_speed_einfeldt( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations1D + ) + # Calculate primitive variables, enthalpy and speed of sound + rho_ll, v_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v_rr, p_rr = cons2prim(u_rr, equations) + + # `u_ll[3]` is total energy `rho_e_ll` on the left + H_ll = (u_ll[3] + p_ll) / rho_ll + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + + # `u_rr[3]` is total energy `rho_e_rr` on the right + H_rr = (u_rr[3] + p_rr) / rho_rr + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) + + v_roe = (sqrt_rho_ll * v_ll + sqrt_rho_rr * v_rr) * inv_sum_sqrt_rho + v_roe_mag = v_roe^2 + + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) + + # Compute convenience constant for positivity preservation, see + # https://doi.org/10.1016/0021-9991(91)90211-3 + beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) + + # Estimate the edges of the Riemann fan (with positivity conservation) + SsL = min(v_roe - c_roe, v_ll - beta * c_ll, 0) + SsR = max(v_roe + c_roe, v_rr + beta * c_rr, 0) + + return SsL, SsR + end + + @inline function max_abs_speeds(u, equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho * v1^2) + c = sqrt(equations.gamma * p / rho) + + return (abs(v1) + c,) + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) + + return SVector(rho, v1, p) + end + + # Convert conservative variables to entropy + @inline function cons2entropy(u, equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + + v1 = rho_v1 / rho + v_square = v1^2 + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho * v_square) + s = log(p) - equations.gamma * log(rho) + rho_p = rho / p + + w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - + 0.5f0 * rho_p * v_square + w2 = rho_p * v1 + w3 = -rho_p + + return SVector(w1, w2, w3) + end + + @inline function entropy2cons(w, equations::CompressibleEulerEquations1D) + # See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD + # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) + @unpack gamma = equations + + # convert to entropy `-rho * s` used by Hughes, France, Mallet (1986) + # instead of `-rho * s / (gamma - 1)` + V1, V2, V5 = w .* (gamma - 1) + + # specific entropy, eq. (53) + s = gamma - V1 + 0.5f0 * (V2^2) / V5 + + # eq. (52) + energy_internal = ((gamma - 1) / (-V5)^gamma)^(equations.inv_gamma_minus_one) * + exp(-s * equations.inv_gamma_minus_one) + + # eq. (51) + rho = -V5 * energy_internal + rho_v1 = V2 * energy_internal + rho_e = (1 - 0.5f0 * (V2^2) / V5) * energy_internal + return SVector(rho, rho_v1, rho_e) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::CompressibleEulerEquations1D) + rho, v1, p = prim + rho_v1 = rho * v1 + rho_e = p * equations.inv_gamma_minus_one + 0.5f0 * (rho_v1 * v1) + return SVector(rho, rho_v1, rho_e) + end + + @inline function density(u, equations::CompressibleEulerEquations1D) + rho = u[1] + return rho end - return SVector(f1, f2, f3) -end - -""" - min_max_speed_einfeldt(u_ll, u_rr, orientation, equations::CompressibleEulerEquations1D) -Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. -Special estimates of the signal velocites and linearization of the Riemann problem developed -by Einfeldt to ensure that the internal energy and density remain positive during the computation -of the numerical flux. - -Original publication: -- Bernd Einfeldt (1988) - On Godunov-type methods for gas dynamics. - [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) - -Compactly summarized: -- Siddhartha Mishra, Ulrik Skre Fjordholm and Rémi Abgrall - Numerical methods for conservation laws and related equations. - [Link](https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf) -""" -@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations1D) - # Calculate primitive variables, enthalpy and speed of sound - rho_ll, v_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v_rr, p_rr = cons2prim(u_rr, equations) - - # `u_ll[3]` is total energy `rho_e_ll` on the left - H_ll = (u_ll[3] + p_ll) / rho_ll - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - - # `u_rr[3]` is total energy `rho_e_rr` on the right - H_rr = (u_rr[3] + p_rr) / rho_rr - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) - - v_roe = (sqrt_rho_ll * v_ll + sqrt_rho_rr * v_rr) * inv_sum_sqrt_rho - v_roe_mag = v_roe^2 - - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) - - # Compute convenience constant for positivity preservation, see - # https://doi.org/10.1016/0021-9991(91)90211-3 - beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) - - # Estimate the edges of the Riemann fan (with positivity conservation) - SsL = min(v_roe - c_roe, v_ll - beta * c_ll, 0) - SsR = max(v_roe + c_roe, v_rr + beta * c_rr, 0) - - return SsL, SsR -end - -@inline function max_abs_speeds(u, equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - v1 = rho_v1 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho * v1^2) - c = sqrt(equations.gamma * p / rho) - - return (abs(v1) + c,) -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - - v1 = rho_v1 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1 * v1) - - return SVector(rho, v1, p) -end - -# Convert conservative variables to entropy -@inline function cons2entropy(u, equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - - v1 = rho_v1 / rho - v_square = v1^2 - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho * v_square) - s = log(p) - equations.gamma * log(rho) - rho_p = rho / p - - w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - - 0.5f0 * rho_p * v_square - w2 = rho_p * v1 - w3 = -rho_p - - return SVector(w1, w2, w3) -end - -@inline function entropy2cons(w, equations::CompressibleEulerEquations1D) - # See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD - # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) - @unpack gamma = equations - - # convert to entropy `-rho * s` used by Hughes, France, Mallet (1986) - # instead of `-rho * s / (gamma - 1)` - V1, V2, V5 = w .* (gamma - 1) - - # specific entropy, eq. (53) - s = gamma - V1 + 0.5f0 * (V2^2) / V5 - - # eq. (52) - energy_internal = ((gamma - 1) / (-V5)^gamma)^(equations.inv_gamma_minus_one) * - exp(-s * equations.inv_gamma_minus_one) - - # eq. (51) - rho = -V5 * energy_internal - rho_v1 = V2 * energy_internal - rho_e = (1 - 0.5f0 * (V2^2) / V5) * energy_internal - return SVector(rho, rho_v1, rho_e) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::CompressibleEulerEquations1D) - rho, v1, p = prim - rho_v1 = rho * v1 - rho_e = p * equations.inv_gamma_minus_one + 0.5f0 * (rho_v1 * v1) - return SVector(rho, rho_v1, rho_e) -end - -@inline function density(u, equations::CompressibleEulerEquations1D) - rho = u[1] - return rho -end - -@inline function pressure(u, equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2) / rho) - return p -end - -@inline function density_pressure(u, equations::CompressibleEulerEquations1D) - rho, rho_v1, rho_e = u - rho_times_p = (equations.gamma - 1) * (rho * rho_e - 0.5f0 * (rho_v1^2)) - return rho_times_p -end - -# Calculate thermodynamic entropy for a conservative state `cons` -@inline function entropy_thermodynamic(cons, equations::CompressibleEulerEquations1D) - # Pressure - p = (equations.gamma - 1) * (cons[3] - 0.5f0 * (cons[2]^2) / cons[1]) - - # Thermodynamic entropy - s = log(p) - equations.gamma * log(cons[1]) - - return s -end - -# Calculate mathematical entropy for a conservative state `cons` -@inline function entropy_math(cons, equations::CompressibleEulerEquations1D) - # Mathematical entropy - S = -entropy_thermodynamic(cons, equations) * cons[1] * - equations.inv_gamma_minus_one - - return S -end - -# Default entropy is the mathematical entropy -@inline function entropy(cons, equations::CompressibleEulerEquations1D) - entropy_math(cons, equations) -end - -# Calculate total energy for a conservative state `cons` -@inline energy_total(cons, ::CompressibleEulerEquations1D) = cons[3] - -# Calculate kinetic energy for a conservative state `cons` -@inline function energy_kinetic(cons, equations::CompressibleEulerEquations1D) - return 0.5f0 * (cons[2]^2) / cons[1] -end - -# Calculate internal energy for a conservative state `cons` -@inline function energy_internal(cons, equations::CompressibleEulerEquations1D) - return energy_total(cons, equations) - energy_kinetic(cons, equations) -end + @inline function pressure(u, equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2) / rho) + return p + end + + @inline function density_pressure(u, equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + rho_times_p = (equations.gamma - 1) * (rho * rho_e - 0.5f0 * (rho_v1^2)) + return rho_times_p + end + + # Calculate thermodynamic entropy for a conservative state `cons` + @inline function entropy_thermodynamic(cons, equations::CompressibleEulerEquations1D) + # Pressure + p = (equations.gamma - 1) * (cons[3] - 0.5f0 * (cons[2]^2) / cons[1]) + + # Thermodynamic entropy + s = log(p) - equations.gamma * log(cons[1]) + + return s + end + + # Calculate mathematical entropy for a conservative state `cons` + @inline function entropy_math(cons, equations::CompressibleEulerEquations1D) + # Mathematical entropy + S = -entropy_thermodynamic(cons, equations) * cons[1] * + equations.inv_gamma_minus_one + + return S + end + + # Default entropy is the mathematical entropy + @inline function entropy(cons, equations::CompressibleEulerEquations1D) + entropy_math(cons, equations) + end + + # Calculate total energy for a conservative state `cons` + @inline energy_total(cons, ::CompressibleEulerEquations1D) = cons[3] + + # Calculate kinetic energy for a conservative state `cons` + @inline function energy_kinetic(cons, equations::CompressibleEulerEquations1D) + return 0.5f0 * (cons[2]^2) / cons[1] + end + + # Calculate internal energy for a conservative state `cons` + @inline function energy_internal(cons, equations::CompressibleEulerEquations1D) + return energy_total(cons, equations) - energy_kinetic(cons, equations) + end end # @muladd diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index e964d9c2b2..15d2d5191d 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -3,2085 +3,2235 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - CompressibleEulerEquations2D(gamma) - -The compressible Euler equations -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -\rho \\ \rho v_1 \\ \rho v_2 \\ \rho e -\end{pmatrix} -+ -\frac{\partial}{\partial x} -\begin{pmatrix} - \rho v_1 \\ \rho v_1^2 + p \\ \rho v_1 v_2 \\ (\rho e +p) v_1 -\end{pmatrix} -+ -\frac{\partial}{\partial y} -\begin{pmatrix} -\rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + p \\ (\rho e +p) v_2 -\end{pmatrix} -= -\begin{pmatrix} -0 \\ 0 \\ 0 \\ 0 -\end{pmatrix} -``` -for an ideal gas with ratio of specific heats `gamma` -in two space dimensions. -Here, ``\rho`` is the density, ``v_1``, ``v_2`` the velocities, ``e`` the specific total energy **rather than** specific internal energy, and -```math -p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2+v_2^2) \right) -``` -the pressure. -""" -struct CompressibleEulerEquations2D{RealT <: Real} <: - AbstractCompressibleEulerEquations{2, 4} - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - - function CompressibleEulerEquations2D(gamma) - γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) - new{typeof(γ)}(γ, inv_gamma_minus_one) - end -end - -function varnames(::typeof(cons2cons), ::CompressibleEulerEquations2D) - ("rho", "rho_v1", "rho_v2", "rho_e") -end -varnames(::typeof(cons2prim), ::CompressibleEulerEquations2D) = ("rho", "v1", "v2", "p") - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::CompressibleEulerEquations2D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equations::CompressibleEulerEquations2D) - RealT = eltype(x) - rho = 1 - rho_v1 = convert(RealT, 0.1) - rho_v2 = convert(RealT, -0.2) - rho_e = 10 - return SVector(rho, rho_v1, rho_v2, rho_e) -end - -""" - initial_condition_convergence_test(x, t, equations::CompressibleEulerEquations2D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -function initial_condition_convergence_test(x, t, - equations::CompressibleEulerEquations2D) - RealT = eltype(x) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - ω = 2 * convert(RealT, pi) * f - ini = c + A * sin(ω * (x[1] + x[2] - t)) - - rho = ini - rho_v1 = ini - rho_v2 = ini - rho_e = ini^2 - - return SVector(rho, rho_v1, rho_v2, rho_e) -end - -""" - source_terms_convergence_test(u, x, t, equations::CompressibleEulerEquations2D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::CompressibleEulerEquations2D) - # Same settings as in `initial_condition` - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - ω = 2 * convert(RealT, pi) * f - γ = equations.gamma - - x1, x2 = x - si, co = sincos(ω * (x1 + x2 - t)) - rho = c + A * si - rho_x = ω * A * co - # Note that d/dt rho = -d/dx rho = -d/dy rho. - - tmp = (2 * rho - 1) * (γ - 1) - - du1 = rho_x - du2 = rho_x * (1 + tmp) - du3 = du2 - du4 = 2 * rho_x * (rho + tmp) - - return SVector(du1, du2, du3, du4) -end - -""" - initial_condition_density_wave(x, t, equations::CompressibleEulerEquations2D) - -A sine wave in the density with constant velocity and pressure; reduces the -compressible Euler equations to the linear advection equations. -This setup is the test case for stability of EC fluxes from paper -- Gregor J. Gassner, Magnus Svärd, Florian J. Hindenlang (2020) - Stability issues of entropy-stable and/or split-form high-order schemes - [arXiv: 2007.09026](https://arxiv.org/abs/2007.09026) -with the following parameters -- domain [-1, 1] -- mesh = 4x4 -- polydeg = 5 -""" -function initial_condition_density_wave(x, t, equations::CompressibleEulerEquations2D) - RealT = eltype(x) - v1 = convert(RealT, 0.1) - v2 = convert(RealT, 0.2) - rho = 1 + convert(RealT, 0.98) * sinpi(2 * (x[1] + x[2] - t * (v1 + v2))) - rho_v1 = rho * v1 - rho_v2 = rho * v2 - p = 20 - rho_e = p / (equations.gamma - 1) + 0.5f0 * rho * (v1^2 + v2^2) - return SVector(rho, rho_v1, rho_v2, rho_e) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerEquations2D) - -A weak blast wave taken from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, - equations::CompressibleEulerEquations2D) - # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Set up polar coordinates - inicenter = SVector(0, 0) - x_norm = x[1] - inicenter[1] - y_norm = x[2] - inicenter[2] - r = sqrt(x_norm^2 + y_norm^2) - phi = atan(y_norm, x_norm) - sin_phi, cos_phi = sincos(phi) - - # Calculate primitive variables - RealT = eltype(x) - rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi - v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin_phi - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - return prim2cons(SVector(rho, v1, v2, p), equations) -end - -""" - initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::CompressibleEulerEquations2D) - -Setup used for convergence tests of the Euler equations with self-gravity used in -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -in combination with [`source_terms_eoc_test_coupled_euler_gravity`](@ref) -or [`source_terms_eoc_test_euler`](@ref). -""" -function initial_condition_eoc_test_coupled_euler_gravity(x, t, - equations::CompressibleEulerEquations2D) - # OBS! this assumes that γ = 2 other manufactured source terms are incorrect - if equations.gamma != 2 - error("adiabatic constant must be 2 for the coupling convergence test") - end - RealT = eltype(x) - c = 2 - A = convert(RealT, 0.1) - ini = c + A * sin(convert(RealT, pi) * (x[1] + x[2] - t)) - G = 1 # gravitational constant - - rho = ini - v1 = 1 - v2 = 1 - p = ini^2 * G / convert(RealT, pi) # * 2 / ndims, but ndims==2 here - - return prim2cons(SVector(rho, v1, v2, p), equations) -end - -""" - source_terms_eoc_test_coupled_euler_gravity(u, x, t, equations::CompressibleEulerEquations2D) - -Setup used for convergence tests of the Euler equations with self-gravity used in -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -in combination with [`initial_condition_eoc_test_coupled_euler_gravity`](@ref). -""" -@inline function source_terms_eoc_test_coupled_euler_gravity(u, x, t, - equations::CompressibleEulerEquations2D) - # Same settings as in `initial_condition_eoc_test_coupled_euler_gravity` - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - G = 1 # gravitational constant, must match coupling solver - C_grav = -2 * G / convert(RealT, pi) # 2 == 4 / ndims - - x1, x2 = x - si, co = sincos(convert(RealT, pi) * (x1 + x2 - t)) - rhox = A * convert(RealT, pi) * co - rho = c + A * si - - du1 = rhox - du2 = rhox - du3 = rhox - du4 = (1 - C_grav * rho) * rhox - - return SVector(du1, du2, du3, du4) -end - -""" - source_terms_eoc_test_euler(u, x, t, equations::CompressibleEulerEquations2D) - -Setup used for convergence tests of the Euler equations with self-gravity used in -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -in combination with [`initial_condition_eoc_test_coupled_euler_gravity`](@ref). -""" -@inline function source_terms_eoc_test_euler(u, x, t, - equations::CompressibleEulerEquations2D) - # Same settings as in `initial_condition_eoc_test_coupled_euler_gravity` - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - G = 1 - C_grav = -2 * G / convert(RealT, pi) # 2 == 4 / ndims - - x1, x2 = x - si, co = sincos(convert(RealT, pi) * (x1 + x2 - t)) - rhox = A * convert(RealT, pi) * co - rho = c + A * si - - du1 = rhox - du2 = rhox * (1 - C_grav * rho) - du3 = rhox * (1 - C_grav * rho) - du4 = rhox * (1 - 3 * C_grav * rho) - - return SVector(du1, du2, du3, du4) -end - -""" - boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, - equations::CompressibleEulerEquations2D) + #! format: noindent + + @doc raw""" + CompressibleEulerEquations2D(gamma) + + The compressible Euler equations + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + \rho \\ \rho v_1 \\ \rho v_2 \\ \rho e + \end{pmatrix} + + + \frac{\partial}{\partial x} + \begin{pmatrix} + \rho v_1 \\ \rho v_1^2 + p \\ \rho v_1 v_2 \\ (\rho e +p) v_1 + \end{pmatrix} + + + \frac{\partial}{\partial y} + \begin{pmatrix} + \rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + p \\ (\rho e +p) v_2 + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 \\ 0 \\ 0 + \end{pmatrix} + ``` + for an ideal gas with ratio of specific heats `gamma` + in two space dimensions. + Here, ``\rho`` is the density, ``v_1``, ``v_2`` the velocities, ``e`` the specific total energy **rather than** specific internal energy, and + ```math + p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2+v_2^2) \right) + ``` + the pressure. + """ + struct CompressibleEulerEquations2D{RealT <: Real} <: + AbstractCompressibleEulerEquations{2, 4} + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + + function CompressibleEulerEquations2D(gamma) + γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) + new{typeof(γ)}(γ, inv_gamma_minus_one) + end + end -Determine the boundary numerical surface flux for a slip wall condition. -Imposes a zero normal velocity at the wall. -Density is taken from the internal solution state and pressure is computed as an -exact solution of a 1D Riemann problem. Further details about this boundary state -are available in the paper: -- J. J. W. van der Vegt and H. van der Ven (2002) - Slip flow boundary conditions in discontinuous Galerkin discretizations of - the Euler equations of gas dynamics - [PDF](https://reports.nlr.nl/bitstream/handle/10921/692/TP-2002-300.pdf?sequence=1) - -Details about the 1D pressure Riemann solution can be found in Section 6.3.3 of the book -- Eleuterio F. Toro (2009) - Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction - 3rd edition - [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) - -Should be used together with [`UnstructuredMesh2D`](@ref). -""" -@inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, - x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) - norm_ = norm(normal_direction) - # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later - normal = normal_direction / norm_ - - # rotate the internal solution state - u_local = rotate_to_x(u_inner, normal, equations) - - # compute the primitive variables - rho_local, v_normal, v_tangent, p_local = cons2prim(u_local, equations) - - # Get the solution of the pressure Riemann problem - # See Section 6.3.3 of - # Eleuterio F. Toro (2009) - # Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction - # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) - if v_normal <= 0 - sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed - p_star = p_local * - (1 + 0.5f0 * (equations.gamma - 1) * v_normal / sound_speed)^(2 * - equations.gamma * - equations.inv_gamma_minus_one) - else # v_normal > 0 - A = 2 / ((equations.gamma + 1) * rho_local) - B = p_local * (equations.gamma - 1) / (equations.gamma + 1) - p_star = p_local + - 0.5f0 * v_normal / A * - (v_normal + sqrt(v_normal^2 + 4 * A * (p_local + B))) - end - - # For the slip wall we directly set the flux as the normal velocity is zero - return SVector(0, - p_star * normal[1], - p_star * normal[2], - 0) * norm_ -end - -""" - boundary_condition_slip_wall(u_inner, orientation, direction, x, t, - surface_flux_function, equations::CompressibleEulerEquations2D) - -Should be used together with [`TreeMesh`](@ref). -""" -@inline function boundary_condition_slip_wall(u_inner, orientation, - direction, x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) - # get the appropriate normal vector from the orientation - RealT = eltype(u_inner) - if orientation == 1 - normal_direction = SVector(one(RealT), zero(RealT)) - else # orientation == 2 - normal_direction = SVector(zero(RealT), one(RealT)) - end - - # compute and return the flux using `boundary_condition_slip_wall` routine above - return boundary_condition_slip_wall(u_inner, normal_direction, direction, - x, t, surface_flux_function, equations) -end - -""" - boundary_condition_slip_wall(u_inner, normal_direction, direction, x, t, - surface_flux_function, equations::CompressibleEulerEquations2D) - -Should be used together with [`StructuredMesh`](@ref). -""" -@inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, - direction, x, t, - surface_flux_function, - equations::CompressibleEulerEquations2D) - # flip sign of normal to make it outward pointing, then flip the sign of the normal flux back - # to be inward pointing on the -x and -y sides due to the orientation convention used by StructuredMesh - if isodd(direction) - boundary_flux = -boundary_condition_slip_wall(u_inner, -normal_direction, - x, t, surface_flux_function, - equations) - else - boundary_flux = boundary_condition_slip_wall(u_inner, normal_direction, - x, t, surface_flux_function, - equations) - end - - return boundary_flux -end - -# Calculate 2D flux for a single point -@inline function flux(u, orientation::Integer, equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - if orientation == 1 - f1 = rho_v1 - f2 = rho_v1 * v1 + p - f3 = rho_v1 * v2 - f4 = (rho_e + p) * v1 - else - f1 = rho_v2 - f2 = rho_v2 * v1 - f3 = rho_v2 * v2 + p - f4 = (rho_e + p) * v2 - end - return SVector(f1, f2, f3, f4) -end - -# Calculate 2D flux for a single point in the normal direction -# Note, this directional vector is not normalized -@inline function flux(u, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho_e = last(u) - rho, v1, v2, p = cons2prim(u, equations) - - v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] - rho_v_normal = rho * v_normal - f1 = rho_v_normal - f2 = rho_v_normal * v1 + p * normal_direction[1] - f3 = rho_v_normal * v2 + p * normal_direction[2] - f4 = (rho_e + p) * v_normal - return SVector(f1, f2, f3, f4) -end - -""" - flux_shima_etal(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - -This flux is is a modification of the original kinetic energy preserving two-point flux by -- Yuichi Kuya, Kosuke Totani and Soshi Kawai (2018) - Kinetic energy and entropy preserving schemes for compressible flows - by split convective forms - [DOI: 10.1016/j.jcp.2018.08.058](https://doi.org/10.1016/j.jcp.2018.08.058) - -The modification is in the energy flux to guarantee pressure equilibrium and was developed by -- Nao Shima, Yuichi Kuya, Yoshiharu Tamaki, Soshi Kawai (JCP 2020) - Preventing spurious pressure oscillations in split convective form discretizations for - compressible flows - [DOI: 10.1016/j.jcp.2020.110060](https://doi.org/10.1016/j.jcp.2020.110060) -""" -@inline function flux_shima_etal(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - kin_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) - - # Calculate fluxes depending on orientation - if orientation == 1 - pv1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - f1 = rho_avg * v1_avg - f2 = f1 * v1_avg + p_avg - f3 = f1 * v2_avg - f4 = p_avg * v1_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv1_avg - else - pv2_avg = 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) - f1 = rho_avg * v2_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_avg - f4 = p_avg * v2_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv2_avg - end - - return SVector(f1, f2, f3, f4) -end - -@inline function flux_shima_etal(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v_dot_n_avg = 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) - - # Calculate fluxes depending on normal_direction - f1 = rho_avg * v_dot_n_avg - f2 = f1 * v1_avg + p_avg * normal_direction[1] - f3 = f1 * v2_avg + p_avg * normal_direction[2] - f4 = (f1 * velocity_square_avg + - p_avg * v_dot_n_avg * equations.inv_gamma_minus_one - + 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) - - return SVector(f1, f2, f3, f4) -end - -""" - flux_kennedy_gruber(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) + function varnames(::typeof(cons2cons), ::CompressibleEulerEquations2D) + ("rho", "rho_v1", "rho_v2", "rho_e") + end + varnames(::typeof(cons2prim), ::CompressibleEulerEquations2D) = ("rho", "v1", "v2", "p") + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::CompressibleEulerEquations2D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equations::CompressibleEulerEquations2D) + RealT = eltype(x) + rho = 1 + rho_v1 = convert(RealT, 0.1) + rho_v2 = convert(RealT, -0.2) + rho_e = 10 + return SVector(rho, rho_v1, rho_v2, rho_e) + end -Kinetic energy preserving two-point flux by -- Kennedy and Gruber (2008) - Reduced aliasing formulations of the convective terms within the - Navier-Stokes equations for a compressible fluid - [DOI: 10.1016/j.jcp.2007.09.020](https://doi.org/10.1016/j.jcp.2007.09.020) -""" -@inline function flux_kennedy_gruber(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - # Unpack left and right state - rho_e_ll = last(u_ll) - rho_e_rr = last(u_rr) - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) - - # Calculate fluxes depending on orientation - if orientation == 1 - f1 = rho_avg * v1_avg - f2 = rho_avg * v1_avg * v1_avg + p_avg - f3 = rho_avg * v1_avg * v2_avg - f4 = (rho_avg * e_avg + p_avg) * v1_avg - else - f1 = rho_avg * v2_avg - f2 = rho_avg * v2_avg * v1_avg - f3 = rho_avg * v2_avg * v2_avg + p_avg - f4 = (rho_avg * e_avg + p_avg) * v2_avg - end - - return SVector(f1, f2, f3, f4) -end - -@inline function flux_kennedy_gruber(u_ll, u_rr, normal_direction::AbstractVector, + """ + initial_condition_convergence_test(x, t, equations::CompressibleEulerEquations2D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + function initial_condition_convergence_test( + x, t, + equations::CompressibleEulerEquations2D + ) + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + ini = c + A * sin(ω * (x[1] + x[2] - t)) + + rho = ini + rho_v1 = ini + rho_v2 = ini + rho_e = ini^2 + + return SVector(rho, rho_v1, rho_v2, rho_e) + end + + """ + source_terms_convergence_test(u, x, t, equations::CompressibleEulerEquations2D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::CompressibleEulerEquations2D + ) + # Same settings as in `initial_condition` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + γ = equations.gamma + + x1, x2 = x + si, co = sincos(ω * (x1 + x2 - t)) + rho = c + A * si + rho_x = ω * A * co + # Note that d/dt rho = -d/dx rho = -d/dy rho. + + tmp = (2 * rho - 1) * (γ - 1) + + du1 = rho_x + du2 = rho_x * (1 + tmp) + du3 = du2 + du4 = 2 * rho_x * (rho + tmp) + + return SVector(du1, du2, du3, du4) + end + + """ + initial_condition_density_wave(x, t, equations::CompressibleEulerEquations2D) + + A sine wave in the density with constant velocity and pressure; reduces the + compressible Euler equations to the linear advection equations. + This setup is the test case for stability of EC fluxes from paper + - Gregor J. Gassner, Magnus Svärd, Florian J. Hindenlang (2020) + Stability issues of entropy-stable and/or split-form high-order schemes + [arXiv: 2007.09026](https://arxiv.org/abs/2007.09026) + with the following parameters + - domain [-1, 1] + - mesh = 4x4 + - polydeg = 5 + """ + function initial_condition_density_wave(x, t, equations::CompressibleEulerEquations2D) + RealT = eltype(x) + v1 = convert(RealT, 0.1) + v2 = convert(RealT, 0.2) + rho = 1 + convert(RealT, 0.98) * sinpi(2 * (x[1] + x[2] - t * (v1 + v2))) + rho_v1 = rho * v1 + rho_v2 = rho * v2 + p = 20 + rho_e = p / (equations.gamma - 1) + 0.5f0 * rho * (v1^2 + v2^2) + return SVector(rho, rho_v1, rho_v2, rho_e) + end + + """ + initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerEquations2D) + + A weak blast wave taken from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave( + x, t, + equations::CompressibleEulerEquations2D + ) + # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Set up polar coordinates + inicenter = SVector(0, 0) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + phi = atan(y_norm, x_norm) + sin_phi, cos_phi = sincos(phi) + + # Calculate primitive variables + RealT = eltype(x) + rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi + v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin_phi + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + return prim2cons(SVector(rho, v1, v2, p), equations) + end + + """ + initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::CompressibleEulerEquations2D) + + Setup used for convergence tests of the Euler equations with self-gravity used in + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + in combination with [`source_terms_eoc_test_coupled_euler_gravity`](@ref) + or [`source_terms_eoc_test_euler`](@ref). + """ + function initial_condition_eoc_test_coupled_euler_gravity( + x, t, + equations::CompressibleEulerEquations2D + ) + # OBS! this assumes that γ = 2 other manufactured source terms are incorrect + if equations.gamma != 2 + error("adiabatic constant must be 2 for the coupling convergence test") + end + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + ini = c + A * sin(convert(RealT, pi) * (x[1] + x[2] - t)) + G = 1 # gravitational constant + + rho = ini + v1 = 1 + v2 = 1 + p = ini^2 * G / convert(RealT, pi) # * 2 / ndims, but ndims==2 here + + return prim2cons(SVector(rho, v1, v2, p), equations) + end + + """ + source_terms_eoc_test_coupled_euler_gravity(u, x, t, equations::CompressibleEulerEquations2D) + + Setup used for convergence tests of the Euler equations with self-gravity used in + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + in combination with [`initial_condition_eoc_test_coupled_euler_gravity`](@ref). + """ + @inline function source_terms_eoc_test_coupled_euler_gravity( + u, x, t, + equations::CompressibleEulerEquations2D + ) + # Same settings as in `initial_condition_eoc_test_coupled_euler_gravity` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + G = 1 # gravitational constant, must match coupling solver + C_grav = -2 * G / convert(RealT, pi) # 2 == 4 / ndims + + x1, x2 = x + si, co = sincos(convert(RealT, pi) * (x1 + x2 - t)) + rhox = A * convert(RealT, pi) * co + rho = c + A * si + + du1 = rhox + du2 = rhox + du3 = rhox + du4 = (1 - C_grav * rho) * rhox + + return SVector(du1, du2, du3, du4) + end + + """ + source_terms_eoc_test_euler(u, x, t, equations::CompressibleEulerEquations2D) + + Setup used for convergence tests of the Euler equations with self-gravity used in + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + in combination with [`initial_condition_eoc_test_coupled_euler_gravity`](@ref). + """ + @inline function source_terms_eoc_test_euler( + u, x, t, + equations::CompressibleEulerEquations2D + ) + # Same settings as in `initial_condition_eoc_test_coupled_euler_gravity` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + G = 1 + C_grav = -2 * G / convert(RealT, pi) # 2 == 4 / ndims + + x1, x2 = x + si, co = sincos(convert(RealT, pi) * (x1 + x2 - t)) + rhox = A * convert(RealT, pi) * co + rho = c + A * si + + du1 = rhox + du2 = rhox * (1 - C_grav * rho) + du3 = rhox * (1 - C_grav * rho) + du4 = rhox * (1 - 3 * C_grav * rho) + + return SVector(du1, du2, du3, du4) + end + + """ + boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, equations::CompressibleEulerEquations2D) - # Unpack left and right state - rho_e_ll = last(u_ll) - rho_e_rr = last(u_rr) - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v_dot_n_avg = v1_avg * normal_direction[1] + v2_avg * normal_direction[2] - p_avg = 0.5f0 * (p_ll + p_rr) - e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) - - # Calculate fluxes depending on normal_direction - f1 = rho_avg * v_dot_n_avg - f2 = f1 * v1_avg + p_avg * normal_direction[1] - f3 = f1 * v2_avg + p_avg * normal_direction[2] - f4 = f1 * e_avg + p_avg * v_dot_n_avg - - return SVector(f1, f2, f3, f4) -end - -""" - flux_chandrashekar(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations2D) - -Entropy conserving two-point flux by -- Chandrashekar (2013) - Kinetic Energy Preserving and Entropy Stable Finite Volume Schemes - for Compressible Euler and Navier-Stokes Equations - [DOI: 10.4208/cicp.170712.010313a](https://doi.org/10.4208/cicp.170712.010313a) -""" -@inline function flux_chandrashekar(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - beta_ll = 0.5f0 * rho_ll / p_ll - beta_rr = 0.5f0 * rho_rr / p_rr - specific_kin_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) - specific_kin_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) - - # Compute the necessary mean values - rho_avg = 0.5f0 * (rho_ll + rho_rr) - rho_mean = ln_mean(rho_ll, rho_rr) - beta_mean = ln_mean(beta_ll, beta_rr) - beta_avg = 0.5f0 * (beta_ll + beta_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_mean = 0.5f0 * rho_avg / beta_avg - velocity_square_avg = specific_kin_ll + specific_kin_rr - - # Calculate fluxes depending on orientation - if orientation == 1 - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_mean - f3 = f1 * v2_avg - f4 = f1 * 0.5f0 * - (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + - f2 * v1_avg + f3 * v2_avg - else - f1 = rho_mean * v2_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_mean - f4 = f1 * 0.5f0 * - (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + - f2 * v1_avg + f3 * v2_avg - end - - return SVector(f1, f2, f3, f4) -end - -@inline function flux_chandrashekar(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - beta_ll = 0.5f0 * rho_ll / p_ll - beta_rr = 0.5f0 * rho_rr / p_rr - specific_kin_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) - specific_kin_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) - - # Compute the necessary mean values - rho_avg = 0.5f0 * (rho_ll + rho_rr) - rho_mean = ln_mean(rho_ll, rho_rr) - beta_mean = ln_mean(beta_ll, beta_rr) - beta_avg = 0.5f0 * (beta_ll + beta_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_mean = 0.5f0 * rho_avg / beta_avg - velocity_square_avg = specific_kin_ll + specific_kin_rr - - # Multiply with average of normal velocities - f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - f2 = f1 * v1_avg + p_mean * normal_direction[1] - f3 = f1 * v2_avg + p_mean * normal_direction[2] - f4 = f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + - f2 * v1_avg + f3 * v2_avg - - return SVector(f1, f2, f3, f4) -end - -""" - flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - -Entropy conserving and kinetic energy preserving two-point flux by -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -See also -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_ranocha(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - # Compute the necessary mean values - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) - - # Calculate fluxes depending on orientation - if orientation == 1 - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_avg - f3 = f1 * v2_avg - f4 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - else - f1 = rho_mean * v2_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_avg - f4 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) - end - - return SVector(f1, f2, f3, f4) -end - -@inline function flux_ranocha(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # Compute the necessary mean values - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) - - # Calculate fluxes depending on normal_direction - f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - f2 = f1 * v1_avg + p_avg * normal_direction[1] - f3 = f1 * v2_avg + p_avg * normal_direction[2] - f4 = (f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) - - return SVector(f1, f2, f3, f4) -end - -""" - splitting_steger_warming(u, orientation::Integer, - equations::CompressibleEulerEquations2D) - splitting_steger_warming(u, which::Union{Val{:minus}, Val{:plus}} - orientation::Integer, - equations::CompressibleEulerEquations2D) - -Splitting of the compressible Euler flux of Steger and Warming. For -curvilinear coordinates use the improved Steger-Warming-type splitting -[`splitting_drikakis_tsangaris`](@ref). - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. - -## References - -- Joseph L. Steger and R. F. Warming (1979) - Flux Vector Splitting of the Inviscid Gasdynamic Equations - With Application to Finite Difference Methods - [NASA Technical Memorandum](https://ntrs.nasa.gov/api/citations/19790020779/downloads/19790020779.pdf) -""" -@inline function splitting_steger_warming(u, orientation::Integer, - equations::CompressibleEulerEquations2D) - fm = splitting_steger_warming(u, Val{:minus}(), orientation, equations) - fp = splitting_steger_warming(u, Val{:plus}(), orientation, equations) - return fm, fp -end - -@inline function splitting_steger_warming(u, ::Val{:plus}, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - a = sqrt(equations.gamma * p / rho) - - if orientation == 1 - lambda1 = v1 - lambda2 = v1 + a - lambda3 = v1 - a - lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) - lambda2_p = positive_part(lambda2) - lambda3_p = positive_part(lambda3) - - alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1p = rho_2gamma * alpha_p - f2p = rho_2gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) - f3p = rho_2gamma * alpha_p * v2 - f4p = rho_2gamma * - (alpha_p * 0.5f0 * (v1^2 + v2^2) + a * v1 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) - else # orientation == 2 - lambda1 = v2 - lambda2 = v2 + a - lambda3 = v2 - a + Determine the boundary numerical surface flux for a slip wall condition. + Imposes a zero normal velocity at the wall. + Density is taken from the internal solution state and pressure is computed as an + exact solution of a 1D Riemann problem. Further details about this boundary state + are available in the paper: + - J. J. W. van der Vegt and H. van der Ven (2002) + Slip flow boundary conditions in discontinuous Galerkin discretizations of + the Euler equations of gas dynamics + [PDF](https://reports.nlr.nl/bitstream/handle/10921/692/TP-2002-300.pdf?sequence=1) + + Details about the 1D pressure Riemann solution can be found in Section 6.3.3 of the book + - Eleuterio F. Toro (2009) + Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + 3rd edition + [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + + Should be used together with [`UnstructuredMesh2D`](@ref). + """ + @inline function boundary_condition_slip_wall( + u_inner, normal_direction::AbstractVector, + x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) + norm_ = norm(normal_direction) + # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later + normal = normal_direction / norm_ + + # rotate the internal solution state + u_local = rotate_to_x(u_inner, normal, equations) + + # compute the primitive variables + rho_local, v_normal, v_tangent, p_local = cons2prim(u_local, equations) + + # Get the solution of the pressure Riemann problem + # See Section 6.3.3 of + # Eleuterio F. Toro (2009) + # Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + if v_normal <= 0 + sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed + p_star = p_local * + (1 + 0.5f0 * (equations.gamma - 1) * v_normal / sound_speed)^( + 2 * + equations.gamma * + equations.inv_gamma_minus_one + ) + else # v_normal > 0 + A = 2 / ((equations.gamma + 1) * rho_local) + B = p_local * (equations.gamma - 1) / (equations.gamma + 1) + p_star = p_local + + 0.5f0 * v_normal / A * + (v_normal + sqrt(v_normal^2 + 4 * A * (p_local + B))) + end - lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) - lambda2_p = positive_part(lambda2) - lambda3_p = positive_part(lambda3) - - alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1p = rho_2gamma * alpha_p - f2p = rho_2gamma * alpha_p * v1 - f3p = rho_2gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) - f4p = rho_2gamma * - (alpha_p * 0.5f0 * (v1^2 + v2^2) + a * v2 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) - end - return SVector(f1p, f2p, f3p, f4p) -end - -@inline function splitting_steger_warming(u, ::Val{:minus}, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - a = sqrt(equations.gamma * p / rho) - - if orientation == 1 - lambda1 = v1 - lambda2 = v1 + a - lambda3 = v1 - a + # For the slip wall we directly set the flux as the normal velocity is zero + return SVector( + 0, + p_star * normal[1], + p_star * normal[2], + 0 + ) * norm_ + end - lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) - lambda2_m = negative_part(lambda2) - lambda3_m = negative_part(lambda3) - - alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1m = rho_2gamma * alpha_m - f2m = rho_2gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) - f3m = rho_2gamma * alpha_m * v2 - f4m = rho_2gamma * - (alpha_m * 0.5f0 * (v1^2 + v2^2) + a * v1 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) - else # orientation == 2 - lambda1 = v2 - lambda2 = v2 + a - lambda3 = v2 - a + """ + boundary_condition_slip_wall(u_inner, orientation, direction, x, t, + surface_flux_function, equations::CompressibleEulerEquations2D) + + Should be used together with [`TreeMesh`](@ref). + """ + @inline function boundary_condition_slip_wall( + u_inner, orientation, + direction, x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) + # get the appropriate normal vector from the orientation + RealT = eltype(u_inner) + if orientation == 1 + normal_direction = SVector(one(RealT), zero(RealT)) + else # orientation == 2 + normal_direction = SVector(zero(RealT), one(RealT)) + end - lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) - lambda2_m = negative_part(lambda2) - lambda3_m = negative_part(lambda3) + # compute and return the flux using `boundary_condition_slip_wall` routine above + return boundary_condition_slip_wall( + u_inner, normal_direction, direction, + x, t, surface_flux_function, equations + ) + end + + """ + boundary_condition_slip_wall(u_inner, normal_direction, direction, x, t, + surface_flux_function, equations::CompressibleEulerEquations2D) + + Should be used together with [`StructuredMesh`](@ref). + """ + @inline function boundary_condition_slip_wall( + u_inner, normal_direction::AbstractVector, + direction, x, t, + surface_flux_function, + equations::CompressibleEulerEquations2D + ) + # flip sign of normal to make it outward pointing, then flip the sign of the normal flux back + # to be inward pointing on the -x and -y sides due to the orientation convention used by StructuredMesh + if isodd(direction) + boundary_flux = -boundary_condition_slip_wall( + u_inner, -normal_direction, + x, t, surface_flux_function, + equations + ) + else + boundary_flux = boundary_condition_slip_wall( + u_inner, normal_direction, + x, t, surface_flux_function, + equations + ) + end + + return boundary_flux + end + + # Calculate 2D flux for a single point + @inline function flux(u, orientation::Integer, equations::CompressibleEulerEquations2D) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + if orientation == 1 + f1 = rho_v1 + f2 = rho_v1 * v1 + p + f3 = rho_v1 * v2 + f4 = (rho_e + p) * v1 + else + f1 = rho_v2 + f2 = rho_v2 * v1 + f3 = rho_v2 * v2 + p + f4 = (rho_e + p) * v2 + end + return SVector(f1, f2, f3, f4) + end + + # Calculate 2D flux for a single point in the normal direction + # Note, this directional vector is not normalized + @inline function flux( + u, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho_e = last(u) + rho, v1, v2, p = cons2prim(u, equations) + + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + rho_v_normal = rho * v_normal + f1 = rho_v_normal + f2 = rho_v_normal * v1 + p * normal_direction[1] + f3 = rho_v_normal * v2 + p * normal_direction[2] + f4 = (rho_e + p) * v_normal + return SVector(f1, f2, f3, f4) + end + + """ + flux_shima_etal(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + + This flux is is a modification of the original kinetic energy preserving two-point flux by + - Yuichi Kuya, Kosuke Totani and Soshi Kawai (2018) + Kinetic energy and entropy preserving schemes for compressible flows + by split convective forms + [DOI: 10.1016/j.jcp.2018.08.058](https://doi.org/10.1016/j.jcp.2018.08.058) + + The modification is in the energy flux to guarantee pressure equilibrium and was developed by + - Nao Shima, Yuichi Kuya, Yoshiharu Tamaki, Soshi Kawai (JCP 2020) + Preventing spurious pressure oscillations in split convective form discretizations for + compressible flows + [DOI: 10.1016/j.jcp.2020.110060](https://doi.org/10.1016/j.jcp.2020.110060) + """ + @inline function flux_shima_etal( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + kin_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) + + # Calculate fluxes depending on orientation + if orientation == 1 + pv1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + f1 = rho_avg * v1_avg + f2 = f1 * v1_avg + p_avg + f3 = f1 * v2_avg + f4 = p_avg * v1_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv1_avg + else + pv2_avg = 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) + f1 = rho_avg * v2_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_avg + f4 = p_avg * v2_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv2_avg + end + + return SVector(f1, f2, f3, f4) + end + + @inline function flux_shima_etal( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v_dot_n_avg = 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) + + # Calculate fluxes depending on normal_direction + f1 = rho_avg * v_dot_n_avg + f2 = f1 * v1_avg + p_avg * normal_direction[1] + f3 = f1 * v2_avg + p_avg * normal_direction[2] + f4 = ( + f1 * velocity_square_avg + + p_avg * v_dot_n_avg * equations.inv_gamma_minus_one + + 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) + + return SVector(f1, f2, f3, f4) + end + + """ + flux_kennedy_gruber(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + + Kinetic energy preserving two-point flux by + - Kennedy and Gruber (2008) + Reduced aliasing formulations of the convective terms within the + Navier-Stokes equations for a compressible fluid + [DOI: 10.1016/j.jcp.2007.09.020](https://doi.org/10.1016/j.jcp.2007.09.020) + """ + @inline function flux_kennedy_gruber( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + # Unpack left and right state + rho_e_ll = last(u_ll) + rho_e_rr = last(u_rr) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) + + # Calculate fluxes depending on orientation + if orientation == 1 + f1 = rho_avg * v1_avg + f2 = rho_avg * v1_avg * v1_avg + p_avg + f3 = rho_avg * v1_avg * v2_avg + f4 = (rho_avg * e_avg + p_avg) * v1_avg + else + f1 = rho_avg * v2_avg + f2 = rho_avg * v2_avg * v1_avg + f3 = rho_avg * v2_avg * v2_avg + p_avg + f4 = (rho_avg * e_avg + p_avg) * v2_avg + end + + return SVector(f1, f2, f3, f4) + end + + @inline function flux_kennedy_gruber( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + # Unpack left and right state + rho_e_ll = last(u_ll) + rho_e_rr = last(u_rr) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v_dot_n_avg = v1_avg * normal_direction[1] + v2_avg * normal_direction[2] + p_avg = 0.5f0 * (p_ll + p_rr) + e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) + + # Calculate fluxes depending on normal_direction + f1 = rho_avg * v_dot_n_avg + f2 = f1 * v1_avg + p_avg * normal_direction[1] + f3 = f1 * v2_avg + p_avg * normal_direction[2] + f4 = f1 * e_avg + p_avg * v_dot_n_avg + + return SVector(f1, f2, f3, f4) + end + + """ + flux_chandrashekar(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations2D) + + Entropy conserving two-point flux by + - Chandrashekar (2013) + Kinetic Energy Preserving and Entropy Stable Finite Volume Schemes + for Compressible Euler and Navier-Stokes Equations + [DOI: 10.4208/cicp.170712.010313a](https://doi.org/10.4208/cicp.170712.010313a) + """ + @inline function flux_chandrashekar( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + beta_ll = 0.5f0 * rho_ll / p_ll + beta_rr = 0.5f0 * rho_rr / p_rr + specific_kin_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) + specific_kin_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) + + # Compute the necessary mean values + rho_avg = 0.5f0 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5f0 * (beta_ll + beta_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_mean = 0.5f0 * rho_avg / beta_avg + velocity_square_avg = specific_kin_ll + specific_kin_rr + + # Calculate fluxes depending on orientation + if orientation == 1 + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_mean + f3 = f1 * v2_avg + f4 = f1 * 0.5f0 * + (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + f3 * v2_avg + else + f1 = rho_mean * v2_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_mean + f4 = f1 * 0.5f0 * + (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + f3 * v2_avg + end + + return SVector(f1, f2, f3, f4) + end + + @inline function flux_chandrashekar( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + beta_ll = 0.5f0 * rho_ll / p_ll + beta_rr = 0.5f0 * rho_rr / p_rr + specific_kin_ll = 0.5f0 * (v1_ll^2 + v2_ll^2) + specific_kin_rr = 0.5f0 * (v1_rr^2 + v2_rr^2) + + # Compute the necessary mean values + rho_avg = 0.5f0 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5f0 * (beta_ll + beta_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_mean = 0.5f0 * rho_avg / beta_avg + velocity_square_avg = specific_kin_ll + specific_kin_rr + + # Multiply with average of normal velocities + f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + f2 = f1 * v1_avg + p_mean * normal_direction[1] + f3 = f1 * v2_avg + p_mean * normal_direction[2] + f4 = f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + f3 * v2_avg + + return SVector(f1, f2, f3, f4) + end + + """ + flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + + Entropy conserving and kinetic energy preserving two-point flux by + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + See also + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_ranocha( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + # Compute the necessary mean values + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) + + # Calculate fluxes depending on orientation + if orientation == 1 + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_avg + f3 = f1 * v2_avg + f4 = f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + else + f1 = rho_mean * v2_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_avg + f4 = f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) + end - alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + return SVector(f1, f2, f3, f4) + end - rho_2gamma = 0.5f0 * rho / equations.gamma - f1m = rho_2gamma * alpha_m - f2m = rho_2gamma * alpha_m * v1 - f3m = rho_2gamma * (alpha_m * v2 + a * (lambda2_m - lambda3_m)) - f4m = rho_2gamma * - (alpha_m * 0.5f0 * (v1^2 + v2^2) + a * v2 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) + @inline function flux_ranocha( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # Compute the necessary mean values + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) + + # Calculate fluxes depending on normal_direction + f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + f2 = f1 * v1_avg + p_avg * normal_direction[1] + f3 = f1 * v2_avg + p_avg * normal_direction[2] + f4 = ( + f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) + + return SVector(f1, f2, f3, f4) end - return SVector(f1m, f2m, f3m, f4m) -end -""" - splitting_drikakis_tsangaris(u, orientation_or_normal_direction, + """ + splitting_steger_warming(u, orientation::Integer, equations::CompressibleEulerEquations2D) - splitting_drikakis_tsangaris(u, which::Union{Val{:minus}, Val{:plus}} - orientation_or_normal_direction, + splitting_steger_warming(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, equations::CompressibleEulerEquations2D) -Improved variant of the Steger-Warming flux vector splitting -[`splitting_steger_warming`](@ref) for generalized coordinates. -This splitting also reformulates the energy -flux as in Hänel et al. to obtain conservation of the total temperature -for inviscid flows. - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. - -## References - -- D. Drikakis and S. Tsangaris (1993) - On the solution of the compressible Navier-Stokes equations using - improved flux vector splitting methods - [DOI: 10.1016/0307-904X(93)90054-K](https://doi.org/10.1016/0307-904X(93)90054-K) -- D. Hänel, R. Schwane and G. Seider (1987) - On the accuracy of upwind schemes for the solution of the Navier-Stokes equations - [DOI: 10.2514/6.1987-1105](https://doi.org/10.2514/6.1987-1105) -""" -@inline function splitting_drikakis_tsangaris(u, orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - fm = splitting_drikakis_tsangaris(u, Val{:minus}(), orientation_or_normal_direction, - equations) - fp = splitting_drikakis_tsangaris(u, Val{:plus}(), orientation_or_normal_direction, - equations) - return fm, fp -end - -@inline function splitting_drikakis_tsangaris(u, ::Val{:plus}, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - if orientation == 1 - lambda1 = v1 + a - lambda2 = v1 - a + Splitting of the compressible Euler flux of Steger and Warming. For + curvilinear coordinates use the improved Steger-Warming-type splitting + [`splitting_drikakis_tsangaris`](@ref). + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + + ## References + + - Joseph L. Steger and R. F. Warming (1979) + Flux Vector Splitting of the Inviscid Gasdynamic Equations + With Application to Finite Difference Methods + [NASA Technical Memorandum](https://ntrs.nasa.gov/api/citations/19790020779/downloads/19790020779.pdf) + """ + @inline function splitting_steger_warming( + u, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + fm = splitting_steger_warming(u, Val{:minus}(), orientation, equations) + fp = splitting_steger_warming(u, Val{:plus}(), orientation, equations) + return fm, fp + end - lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) - lambda2_p = positive_part(lambda2) + @inline function splitting_steger_warming( + u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + a = sqrt(equations.gamma * p / rho) + + if orientation == 1 + lambda1 = v1 + lambda2 = v1 + a + lambda3 = v1 - a + + lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1p = rho_2gamma * alpha_p + f2p = rho_2gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) + f3p = rho_2gamma * alpha_p * v2 + f4p = rho_2gamma * + ( + alpha_p * 0.5f0 * (v1^2 + v2^2) + a * v1 * (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one + ) + else # orientation == 2 + lambda1 = v2 + lambda2 = v2 + a + lambda3 = v2 - a + + lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1p = rho_2gamma * alpha_p + f2p = rho_2gamma * alpha_p * v1 + f3p = rho_2gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) + f4p = rho_2gamma * + ( + alpha_p * 0.5f0 * (v1^2 + v2^2) + a * v2 * (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one + ) + end + return SVector(f1p, f2p, f3p, f4p) + end - rhoa_2gamma = 0.5f0 * rho * a / equations.gamma - f1p = 0.5f0 * rho * (lambda1_p + lambda2_p) - f2p = f1p * v1 + rhoa_2gamma * (lambda1_p - lambda2_p) - f3p = f1p * v2 - f4p = f1p * H - else # orientation == 2 - lambda1 = v2 + a - lambda2 = v2 - a + @inline function splitting_steger_warming( + u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + a = sqrt(equations.gamma * p / rho) + + if orientation == 1 + lambda1 = v1 + lambda2 = v1 + a + lambda3 = v1 - a + + lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1m = rho_2gamma * alpha_m + f2m = rho_2gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) + f3m = rho_2gamma * alpha_m * v2 + f4m = rho_2gamma * + ( + alpha_m * 0.5f0 * (v1^2 + v2^2) + a * v1 * (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one + ) + else # orientation == 2 + lambda1 = v2 + lambda2 = v2 + a + lambda3 = v2 - a + + lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1m = rho_2gamma * alpha_m + f2m = rho_2gamma * alpha_m * v1 + f3m = rho_2gamma * (alpha_m * v2 + a * (lambda2_m - lambda3_m)) + f4m = rho_2gamma * + ( + alpha_m * 0.5f0 * (v1^2 + v2^2) + a * v2 * (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one + ) + end + return SVector(f1m, f2m, f3m, f4m) + end + + """ + splitting_drikakis_tsangaris(u, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + splitting_drikakis_tsangaris(u, which::Union{Val{:minus}, Val{:plus}} + orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + + Improved variant of the Steger-Warming flux vector splitting + [`splitting_steger_warming`](@ref) for generalized coordinates. + This splitting also reformulates the energy + flux as in Hänel et al. to obtain conservation of the total temperature + for inviscid flows. + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + + ## References + + - D. Drikakis and S. Tsangaris (1993) + On the solution of the compressible Navier-Stokes equations using + improved flux vector splitting methods + [DOI: 10.1016/0307-904X(93)90054-K](https://doi.org/10.1016/0307-904X(93)90054-K) + - D. Hänel, R. Schwane and G. Seider (1987) + On the accuracy of upwind schemes for the solution of the Navier-Stokes equations + [DOI: 10.2514/6.1987-1105](https://doi.org/10.2514/6.1987-1105) + """ + @inline function splitting_drikakis_tsangaris( + u, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D + ) + fm = splitting_drikakis_tsangaris( + u, Val{:minus}(), orientation_or_normal_direction, + equations + ) + fp = splitting_drikakis_tsangaris( + u, Val{:plus}(), orientation_or_normal_direction, + equations + ) + return fm, fp + end + + @inline function splitting_drikakis_tsangaris( + u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + if orientation == 1 + lambda1 = v1 + a + lambda2 = v1 - a + + lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) + lambda2_p = positive_part(lambda2) + + rhoa_2gamma = 0.5f0 * rho * a / equations.gamma + f1p = 0.5f0 * rho * (lambda1_p + lambda2_p) + f2p = f1p * v1 + rhoa_2gamma * (lambda1_p - lambda2_p) + f3p = f1p * v2 + f4p = f1p * H + else # orientation == 2 + lambda1 = v2 + a + lambda2 = v2 - a + + lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) + lambda2_p = positive_part(lambda2) + + rhoa_2gamma = 0.5f0 * rho * a / equations.gamma + f1p = 0.5f0 * rho * (lambda1_p + lambda2_p) + f2p = f1p * v1 + f3p = f1p * v2 + rhoa_2gamma * (lambda1_p - lambda2_p) + f4p = f1p * H + end + return SVector(f1p, f2p, f3p, f4p) + end + + @inline function splitting_drikakis_tsangaris( + u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + if orientation == 1 + lambda1 = v1 + a + lambda2 = v1 - a + + lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) + lambda2_m = negative_part(lambda2) + + rhoa_2gamma = 0.5f0 * rho * a / equations.gamma + f1m = 0.5f0 * rho * (lambda1_m + lambda2_m) + f2m = f1m * v1 + rhoa_2gamma * (lambda1_m - lambda2_m) + f3m = f1m * v2 + f4m = f1m * H + else # orientation == 2 + lambda1 = v2 + a + lambda2 = v2 - a + + lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) + lambda2_m = negative_part(lambda2) + + rhoa_2gamma = 0.5f0 * rho * a / equations.gamma + f1m = 0.5f0 * rho * (lambda1_m + lambda2_m) + f2m = f1m * v1 + f3m = f1m * v2 + rhoa_2gamma * (lambda1_m - lambda2_m) + f4m = f1m * H + end + return SVector(f1m, f2m, f3m, f4m) + end + + @inline function splitting_drikakis_tsangaris( + u, ::Val{:plus}, + normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + v_n = normal_direction[1] * v1 + normal_direction[2] * v2 + + lambda1 = v_n + a + lambda2 = v_n - a lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) lambda2_p = positive_part(lambda2) rhoa_2gamma = 0.5f0 * rho * a / equations.gamma f1p = 0.5f0 * rho * (lambda1_p + lambda2_p) - f2p = f1p * v1 - f3p = f1p * v2 + rhoa_2gamma * (lambda1_p - lambda2_p) + f2p = f1p * v1 + rhoa_2gamma * normal_direction[1] * (lambda1_p - lambda2_p) + f3p = f1p * v2 + rhoa_2gamma * normal_direction[2] * (lambda1_p - lambda2_p) f4p = f1p * H + + return SVector(f1p, f2p, f3p, f4p) end - return SVector(f1p, f2p, f3p, f4p) -end -@inline function splitting_drikakis_tsangaris(u, ::Val{:minus}, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho + @inline function splitting_drikakis_tsangaris( + u, ::Val{:minus}, + normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho - if orientation == 1 - lambda1 = v1 + a - lambda2 = v1 - a + v_n = normal_direction[1] * v1 + normal_direction[2] * v2 + + lambda1 = v_n + a + lambda2 = v_n - a lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) lambda2_m = negative_part(lambda2) rhoa_2gamma = 0.5f0 * rho * a / equations.gamma f1m = 0.5f0 * rho * (lambda1_m + lambda2_m) - f2m = f1m * v1 + rhoa_2gamma * (lambda1_m - lambda2_m) - f3m = f1m * v2 + f2m = f1m * v1 + rhoa_2gamma * normal_direction[1] * (lambda1_m - lambda2_m) + f3m = f1m * v2 + rhoa_2gamma * normal_direction[2] * (lambda1_m - lambda2_m) f4m = f1m * H - else # orientation == 2 - lambda1 = v2 + a - lambda2 = v2 - a - lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) - lambda2_m = negative_part(lambda2) + return SVector(f1m, f2m, f3m, f4m) + end - rhoa_2gamma = 0.5f0 * rho * a / equations.gamma - f1m = 0.5f0 * rho * (lambda1_m + lambda2_m) - f2m = f1m * v1 - f3m = f1m * v2 + rhoa_2gamma * (lambda1_m - lambda2_m) - f4m = f1m * H + """ + FluxLMARS(c)(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + + Low Mach number approximate Riemann solver (LMARS) for atmospheric flows using + an estimate `c` of the speed of sound. + + References: + - Xi Chen et al. (2013) + A Control-Volume Model of the Compressible Euler Equations with a Vertical + Lagrangian Coordinate + [DOI: 10.1175/MWR-D-12-00129.1](https://doi.org/10.1175/mwr-d-12-00129.1) + """ + struct FluxLMARS{SpeedOfSound} + # Estimate for the speed of sound + speed_of_sound::SpeedOfSound end - return SVector(f1m, f2m, f3m, f4m) -end - -@inline function splitting_drikakis_tsangaris(u, ::Val{:plus}, - normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - v_n = normal_direction[1] * v1 + normal_direction[2] * v2 - - lambda1 = v_n + a - lambda2 = v_n - a - - lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) - lambda2_p = positive_part(lambda2) - - rhoa_2gamma = 0.5f0 * rho * a / equations.gamma - f1p = 0.5f0 * rho * (lambda1_p + lambda2_p) - f2p = f1p * v1 + rhoa_2gamma * normal_direction[1] * (lambda1_p - lambda2_p) - f3p = f1p * v2 + rhoa_2gamma * normal_direction[2] * (lambda1_p - lambda2_p) - f4p = f1p * H - - return SVector(f1p, f2p, f3p, f4p) -end - -@inline function splitting_drikakis_tsangaris(u, ::Val{:minus}, - normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - v_n = normal_direction[1] * v1 + normal_direction[2] * v2 - - lambda1 = v_n + a - lambda2 = v_n - a - - lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) - lambda2_m = negative_part(lambda2) - - rhoa_2gamma = 0.5f0 * rho * a / equations.gamma - f1m = 0.5f0 * rho * (lambda1_m + lambda2_m) - f2m = f1m * v1 + rhoa_2gamma * normal_direction[1] * (lambda1_m - lambda2_m) - f3m = f1m * v2 + rhoa_2gamma * normal_direction[2] * (lambda1_m - lambda2_m) - f4m = f1m * H - - return SVector(f1m, f2m, f3m, f4m) -end - -""" - FluxLMARS(c)(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - -Low Mach number approximate Riemann solver (LMARS) for atmospheric flows using -an estimate `c` of the speed of sound. - -References: -- Xi Chen et al. (2013) - A Control-Volume Model of the Compressible Euler Equations with a Vertical - Lagrangian Coordinate - [DOI: 10.1175/MWR-D-12-00129.1](https://doi.org/10.1175/mwr-d-12-00129.1) -""" -struct FluxLMARS{SpeedOfSound} - # Estimate for the speed of sound - speed_of_sound::SpeedOfSound -end - -@inline function (flux_lmars::FluxLMARS)(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - c = flux_lmars.speed_of_sound - - # Unpack left and right state - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - if orientation == 1 - v_ll = v1_ll - v_rr = v1_rr - else # orientation == 2 - v_ll = v2_ll - v_rr = v2_rr - end - - rho = 0.5f0 * (rho_ll + rho_rr) - p = 0.5f0 * (p_ll + p_rr) - 0.5f0 * c * rho * (v_rr - v_ll) - v = 0.5f0 * (v_ll + v_rr) - 1 / (2 * c * rho) * (p_rr - p_ll) - - # We treat the energy term analogous to the potential temperature term in the paper by - # Chen et al., i.e. we use p_ll and p_rr, and not p - if v >= 0 - f1, f2, f3, f4 = v * u_ll - f4 = f4 + p_ll * v - else - f1, f2, f3, f4 = v * u_rr - f4 = f4 + p_rr * v - end - - if orientation == 1 - f2 = f2 + p - else # orientation == 2 - f3 = f3 + p - end - - return SVector(f1, f2, f3, f4) -end - -@inline function (flux_lmars::FluxLMARS)(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - c = flux_lmars.speed_of_sound - - # Unpack left and right state - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - v_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # Note that this is the same as computing v_ll and v_rr with a normalized normal vector - # and then multiplying v by `norm_` again, but this version is slightly faster. - norm_ = norm(normal_direction) - - rho = 0.5f0 * (rho_ll + rho_rr) - p = 0.5f0 * (p_ll + p_rr) - 0.5f0 * c * rho * (v_rr - v_ll) / norm_ - v = 0.5f0 * (v_ll + v_rr) - 1 / (2 * c * rho) * (p_rr - p_ll) * norm_ - - # We treat the energy term analogous to the potential temperature term in the paper by - # Chen et al., i.e. we use p_ll and p_rr, and not p - if v >= 0 - f1, f2, f3, f4 = u_ll * v - f4 = f4 + p_ll * v - else - f1, f2, f3, f4 = u_rr * v - f4 = f4 + p_rr * v - end - - return SVector(f1, - f2 + p * normal_direction[1], - f3 + p * normal_direction[2], - f4) -end - -""" - splitting_vanleer_haenel(u, orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - splitting_vanleer_haenel(u, which::Union{Val{:minus}, Val{:plus}} - orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - -Splitting of the compressible Euler flux from van Leer. This splitting further -contains a reformulation due to Hänel et al. where the energy flux uses the -enthalpy. The pressure splitting is independent from the splitting of the -convective terms. As such there are many pressure splittings suggested across -the literature. We implement the 'p4' variant suggested by Liou and Steffen as -it proved the most robust in practice. For details on the curvilinear variant -of this flux vector splitting see Anderson et al. - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. - -## References - -- Bram van Leer (1982) - Flux-Vector Splitting for the Euler Equation - [DOI: 10.1007/978-3-642-60543-7_5](https://doi.org/10.1007/978-3-642-60543-7_5) -- D. Hänel, R. Schwane and G. Seider (1987) - On the accuracy of upwind schemes for the solution of the Navier-Stokes equations - [DOI: 10.2514/6.1987-1105](https://doi.org/10.2514/6.1987-1105) -- Meng-Sing Liou and Chris J. Steffen, Jr. (1991) - High-Order Polynomial Expansions (HOPE) for Flux-Vector Splitting - [NASA Technical Memorandum](https://ntrs.nasa.gov/citations/19910016425) -- W. Kyle Anderson, James L. Thomas, and Bram van Leer (1986) - Comparison of Finite Volume Flux Vector Splittings for the Euler Equations - [DOI: 10.2514/3.9465](https://doi.org/10.2514/3.9465) -""" -@inline function splitting_vanleer_haenel(u, orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - fm = splitting_vanleer_haenel(u, Val{:minus}(), orientation_or_normal_direction, - equations) - fp = splitting_vanleer_haenel(u, Val{:plus}(), orientation_or_normal_direction, - equations) - return fm, fp -end - -@inline function splitting_vanleer_haenel(u, ::Val{:plus}, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - if orientation == 1 - M = v1 / a - p_plus = 0.5f0 * (1 + equations.gamma * M) * p - f1p = 0.25f0 * rho * a * (M + 1)^2 - f2p = f1p * v1 + p_plus - f3p = f1p * v2 - f4p = f1p * H - else # orientation == 2 - M = v2 / a + @inline function (flux_lmars::FluxLMARS)( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + c = flux_lmars.speed_of_sound + + # Unpack left and right state + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + if orientation == 1 + v_ll = v1_ll + v_rr = v1_rr + else # orientation == 2 + v_ll = v2_ll + v_rr = v2_rr + end + + rho = 0.5f0 * (rho_ll + rho_rr) + p = 0.5f0 * (p_ll + p_rr) - 0.5f0 * c * rho * (v_rr - v_ll) + v = 0.5f0 * (v_ll + v_rr) - 1 / (2 * c * rho) * (p_rr - p_ll) + + # We treat the energy term analogous to the potential temperature term in the paper by + # Chen et al., i.e. we use p_ll and p_rr, and not p + if v >= 0 + f1, f2, f3, f4 = v * u_ll + f4 = f4 + p_ll * v + else + f1, f2, f3, f4 = v * u_rr + f4 = f4 + p_rr * v + end + + if orientation == 1 + f2 = f2 + p + else # orientation == 2 + f3 = f3 + p + end + + return SVector(f1, f2, f3, f4) + end + + @inline function (flux_lmars::FluxLMARS)( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + c = flux_lmars.speed_of_sound + + # Unpack left and right state + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + v_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # Note that this is the same as computing v_ll and v_rr with a normalized normal vector + # and then multiplying v by `norm_` again, but this version is slightly faster. + norm_ = norm(normal_direction) + + rho = 0.5f0 * (rho_ll + rho_rr) + p = 0.5f0 * (p_ll + p_rr) - 0.5f0 * c * rho * (v_rr - v_ll) / norm_ + v = 0.5f0 * (v_ll + v_rr) - 1 / (2 * c * rho) * (p_rr - p_ll) * norm_ + + # We treat the energy term analogous to the potential temperature term in the paper by + # Chen et al., i.e. we use p_ll and p_rr, and not p + if v >= 0 + f1, f2, f3, f4 = u_ll * v + f4 = f4 + p_ll * v + else + f1, f2, f3, f4 = u_rr * v + f4 = f4 + p_rr * v + end + + return SVector( + f1, + f2 + p * normal_direction[1], + f3 + p * normal_direction[2], + f4 + ) + end + + """ + splitting_vanleer_haenel(u, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + splitting_vanleer_haenel(u, which::Union{Val{:minus}, Val{:plus}} + orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + + Splitting of the compressible Euler flux from van Leer. This splitting further + contains a reformulation due to Hänel et al. where the energy flux uses the + enthalpy. The pressure splitting is independent from the splitting of the + convective terms. As such there are many pressure splittings suggested across + the literature. We implement the 'p4' variant suggested by Liou and Steffen as + it proved the most robust in practice. For details on the curvilinear variant + of this flux vector splitting see Anderson et al. + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + + ## References + + - Bram van Leer (1982) + Flux-Vector Splitting for the Euler Equation + [DOI: 10.1007/978-3-642-60543-7_5](https://doi.org/10.1007/978-3-642-60543-7_5) + - D. Hänel, R. Schwane and G. Seider (1987) + On the accuracy of upwind schemes for the solution of the Navier-Stokes equations + [DOI: 10.2514/6.1987-1105](https://doi.org/10.2514/6.1987-1105) + - Meng-Sing Liou and Chris J. Steffen, Jr. (1991) + High-Order Polynomial Expansions (HOPE) for Flux-Vector Splitting + [NASA Technical Memorandum](https://ntrs.nasa.gov/citations/19910016425) + - W. Kyle Anderson, James L. Thomas, and Bram van Leer (1986) + Comparison of Finite Volume Flux Vector Splittings for the Euler Equations + [DOI: 10.2514/3.9465](https://doi.org/10.2514/3.9465) + """ + @inline function splitting_vanleer_haenel( + u, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D + ) + fm = splitting_vanleer_haenel( + u, Val{:minus}(), orientation_or_normal_direction, + equations + ) + fp = splitting_vanleer_haenel( + u, Val{:plus}(), orientation_or_normal_direction, + equations + ) + return fm, fp + end + + @inline function splitting_vanleer_haenel( + u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + if orientation == 1 + M = v1 / a + p_plus = 0.5f0 * (1 + equations.gamma * M) * p + + f1p = 0.25f0 * rho * a * (M + 1)^2 + f2p = f1p * v1 + p_plus + f3p = f1p * v2 + f4p = f1p * H + else # orientation == 2 + M = v2 / a + p_plus = 0.5f0 * (1 + equations.gamma * M) * p + + f1p = 0.25f0 * rho * a * (M + 1)^2 + f2p = f1p * v1 + f3p = f1p * v2 + p_plus + f4p = f1p * H + end + return SVector(f1p, f2p, f3p, f4p) + end + + @inline function splitting_vanleer_haenel( + u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + if orientation == 1 + M = v1 / a + p_minus = 0.5f0 * (1 - equations.gamma * M) * p + + f1m = -0.25f0 * rho * a * (M - 1)^2 + f2m = f1m * v1 + p_minus + f3m = f1m * v2 + f4m = f1m * H + else # orientation == 2 + M = v2 / a + p_minus = 0.5f0 * (1 - equations.gamma * M) * p + + f1m = -0.25f0 * rho * a * (M - 1)^2 + f2m = f1m * v1 + f3m = f1m * v2 + p_minus + f4m = f1m * H + end + return SVector(f1m, f2m, f3m, f4m) + end + + @inline function splitting_vanleer_haenel( + u, ::Val{:plus}, + normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + v_n = normal_direction[1] * v1 + normal_direction[2] * v2 + M = v_n / a p_plus = 0.5f0 * (1 + equations.gamma * M) * p f1p = 0.25f0 * rho * a * (M + 1)^2 - f2p = f1p * v1 - f3p = f1p * v2 + p_plus + f2p = f1p * v1 + normal_direction[1] * p_plus + f3p = f1p * v2 + normal_direction[2] * p_plus f4p = f1p * H - end - return SVector(f1p, f2p, f3p, f4p) -end - -@inline function splitting_vanleer_haenel(u, ::Val{:minus}, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho + return SVector(f1p, f2p, f3p, f4p) + end - if orientation == 1 - M = v1 / a + @inline function splitting_vanleer_haenel( + u, ::Val{:minus}, + normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + + v_n = normal_direction[1] * v1 + normal_direction[2] * v2 + M = v_n / a p_minus = 0.5f0 * (1 - equations.gamma * M) * p f1m = -0.25f0 * rho * a * (M - 1)^2 - f2m = f1m * v1 + p_minus - f3m = f1m * v2 + f2m = f1m * v1 + normal_direction[1] * p_minus + f3m = f1m * v2 + normal_direction[2] * p_minus f4m = f1m * H - else # orientation == 2 - M = v2 / a - p_minus = 0.5f0 * (1 - equations.gamma * M) * p - f1m = -0.25f0 * rho * a * (M - 1)^2 - f2m = f1m * v1 - f3m = f1m * v2 + p_minus - f4m = f1m * H + return SVector(f1m, f2m, f3m, f4m) end - return SVector(f1m, f2m, f3m, f4m) -end - -@inline function splitting_vanleer_haenel(u, ::Val{:plus}, - normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - v_n = normal_direction[1] * v1 + normal_direction[2] * v2 - M = v_n / a - p_plus = 0.5f0 * (1 + equations.gamma * M) * p - - f1p = 0.25f0 * rho * a * (M + 1)^2 - f2p = f1p * v1 + normal_direction[1] * p_plus - f3p = f1p * v2 + normal_direction[2] * p_plus - f4p = f1p * H - - return SVector(f1p, f2p, f3p, f4p) -end - -@inline function splitting_vanleer_haenel(u, ::Val{:minus}, - normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - - v_n = normal_direction[1] * v1 + normal_direction[2] * v2 - M = v_n / a - p_minus = 0.5f0 * (1 - equations.gamma * M) * p - - f1m = -0.25f0 * rho * a * (M - 1)^2 - f2m = f1m * v1 + normal_direction[1] * p_minus - f3m = f1m * v2 + normal_direction[2] * p_minus - f4m = f1m * H - - return SVector(f1m, f2m, f3m, f4m) -end - -""" - splitting_lax_friedrichs(u, orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} - orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - -Naive local Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ u)` -and `f⁻ = 0.5 (f - λ u)` similar to a flux splitting one would apply, e.g., -to Burgers' equation. - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. -""" -@inline function splitting_lax_friedrichs(u, orientation_or_normal_direction, - equations::CompressibleEulerEquations2D) - fm = splitting_lax_friedrichs(u, Val{:minus}(), orientation_or_normal_direction, - equations) - fp = splitting_lax_friedrichs(u, Val{:plus}(), orientation_or_normal_direction, - equations) - return fm, fp -end - -@inline function splitting_lax_friedrichs(u, ::Val{:plus}, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - lambda = 0.5f0 * (sqrt(v1^2 + v2^2) + a) - - if orientation == 1 - #lambda = 0.5 * (abs(v1) + a) - f1p = 0.5f0 * rho * v1 + lambda * u[1] - f2p = 0.5f0 * rho * v1 * v1 + 0.5f0 * p + lambda * u[2] - f3p = 0.5f0 * rho * v1 * v2 + lambda * u[3] - f4p = 0.5f0 * rho * v1 * H + lambda * u[4] - else # orientation == 2 - #lambda = 0.5 * (abs(v2) + a) - f1p = 0.5f0 * rho * v2 + lambda * u[1] - f2p = 0.5f0 * rho * v2 * v1 + lambda * u[2] - f3p = 0.5f0 * rho * v2 * v2 + 0.5f0 * p + lambda * u[3] - f4p = 0.5f0 * rho * v2 * H + lambda * u[4] - end - return SVector(f1p, f2p, f3p, f4p) -end - -@inline function splitting_lax_friedrichs(u, ::Val{:minus}, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - lambda = 0.5f0 * (sqrt(v1^2 + v2^2) + a) - - if orientation == 1 - #lambda = 0.5 * (abs(v1) + a) - f1m = 0.5f0 * rho * v1 - lambda * u[1] - f2m = 0.5f0 * rho * v1 * v1 + 0.5f0 * p - lambda * u[2] - f3m = 0.5f0 * rho * v1 * v2 - lambda * u[3] - f4m = 0.5f0 * rho * v1 * H - lambda * u[4] - else # orientation == 2 - #lambda = 0.5 * (abs(v2) + a) - f1m = 0.5f0 * rho * v2 - lambda * u[1] - f2m = 0.5f0 * rho * v2 * v1 - lambda * u[2] - f3m = 0.5f0 * rho * v2 * v2 + 0.5f0 * p - lambda * u[3] - f4m = 0.5f0 * rho * v2 * H - lambda * u[4] - end - return SVector(f1m, f2m, f3m, f4m) -end - -@inline function splitting_lax_friedrichs(u, ::Val{:plus}, - normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho_e = last(u) - rho, v1, v2, p = cons2prim(u, equations) - - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - lambda = 0.5f0 * (sqrt(v1^2 + v2^2) + a) - - v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] - rho_v_normal = rho * v_normal - - f1p = 0.5f0 * rho_v_normal + lambda * u[1] - f2p = 0.5f0 * rho_v_normal * v1 + 0.5f0 * p * normal_direction[1] + lambda * u[2] - f3p = 0.5f0 * rho_v_normal * v2 + 0.5f0 * p * normal_direction[2] + lambda * u[3] - f4p = 0.5f0 * rho_v_normal * H + lambda * u[4] - - return SVector(f1p, f2p, f3p, f4p) -end - -@inline function splitting_lax_friedrichs(u, ::Val{:minus}, - normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho_e = last(u) - rho, v1, v2, p = cons2prim(u, equations) - - a = sqrt(equations.gamma * p / rho) - H = (rho_e + p) / rho - lambda = 0.5f0 * (sqrt(v1^2 + v2^2) + a) - - v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] - rho_v_normal = rho * v_normal - - f1m = 0.5f0 * rho_v_normal - lambda * u[1] - f2m = 0.5f0 * rho_v_normal * v1 + 0.5f0 * p * normal_direction[1] - lambda * u[2] - f3m = 0.5f0 * rho_v_normal * v2 + 0.5f0 * p * normal_direction[2] - lambda * u[3] - f4m = 0.5f0 * rho_v_normal * H - lambda * u[4] - - return SVector(f1m, f2m, f3m, f4m) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the -# maximum velocity magnitude plus the maximum speed of sound -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - # Get the velocity value in the appropriate direction - if orientation == 1 - v_ll = v1_ll - v_rr = v1_rr - else # orientation == 2 - v_ll = v2_ll - v_rr = v2_rr + """ + splitting_lax_friedrichs(u, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} + orientation_or_normal_direction, + equations::CompressibleEulerEquations2D) + + Naive local Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ u)` + and `f⁻ = 0.5 (f - λ u)` similar to a flux splitting one would apply, e.g., + to Burgers' equation. + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + """ + @inline function splitting_lax_friedrichs( + u, orientation_or_normal_direction, + equations::CompressibleEulerEquations2D + ) + fm = splitting_lax_friedrichs( + u, Val{:minus}(), orientation_or_normal_direction, + equations + ) + fp = splitting_lax_friedrichs( + u, Val{:plus}(), orientation_or_normal_direction, + equations + ) + return fm, fp end - # Calculate sound speeds - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) -end + @inline function splitting_lax_friedrichs( + u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + lambda = 0.5f0 * (sqrt(v1^2 + v2^2) + a) + + if orientation == 1 + #lambda = 0.5 * (abs(v1) + a) + f1p = 0.5f0 * rho * v1 + lambda * u[1] + f2p = 0.5f0 * rho * v1 * v1 + 0.5f0 * p + lambda * u[2] + f3p = 0.5f0 * rho * v1 * v2 + lambda * u[3] + f4p = 0.5f0 * rho * v1 * H + lambda * u[4] + else # orientation == 2 + #lambda = 0.5 * (abs(v2) + a) + f1p = 0.5f0 * rho * v2 + lambda * u[1] + f2p = 0.5f0 * rho * v2 * v1 + lambda * u[2] + f3p = 0.5f0 * rho * v2 * v2 + 0.5f0 * p + lambda * u[3] + f4p = 0.5f0 * rho * v2 * H + lambda * u[4] + end + return SVector(f1p, f2p, f3p, f4p) + end -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - # Calculate normal velocities and sound speed - # left - v_ll = (v1_ll * normal_direction[1] - + - v2_ll * normal_direction[2]) - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - # right - v_rr = (v1_rr * normal_direction[1] - + - v2_rr * normal_direction[2]) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) -end - -# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + @inline function splitting_lax_friedrichs( + u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + lambda = 0.5f0 * (sqrt(v1^2 + v2^2) + a) + + if orientation == 1 + #lambda = 0.5 * (abs(v1) + a) + f1m = 0.5f0 * rho * v1 - lambda * u[1] + f2m = 0.5f0 * rho * v1 * v1 + 0.5f0 * p - lambda * u[2] + f3m = 0.5f0 * rho * v1 * v2 - lambda * u[3] + f4m = 0.5f0 * rho * v1 * H - lambda * u[4] + else # orientation == 2 + #lambda = 0.5 * (abs(v2) + a) + f1m = 0.5f0 * rho * v2 - lambda * u[1] + f2m = 0.5f0 * rho * v2 * v1 - lambda * u[2] + f3m = 0.5f0 * rho * v2 * v2 + 0.5f0 * p - lambda * u[3] + f4m = 0.5f0 * rho * v2 * H - lambda * u[4] + end + return SVector(f1m, f2m, f3m, f4m) + end + + @inline function splitting_lax_friedrichs( + u, ::Val{:plus}, + normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho_e = last(u) + rho, v1, v2, p = cons2prim(u, equations) - if orientation == 1 # x-direction - λ_min = v1_ll - sqrt(equations.gamma * p_ll / rho_ll) - λ_max = v1_rr + sqrt(equations.gamma * p_rr / rho_rr) - else # y-direction - λ_min = v2_ll - sqrt(equations.gamma * p_ll / rho_ll) - λ_max = v2_rr + sqrt(equations.gamma * p_rr / rho_rr) + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + lambda = 0.5f0 * (sqrt(v1^2 + v2^2) + a) + + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + rho_v_normal = rho * v_normal + + f1p = 0.5f0 * rho_v_normal + lambda * u[1] + f2p = 0.5f0 * rho_v_normal * v1 + 0.5f0 * p * normal_direction[1] + lambda * u[2] + f3p = 0.5f0 * rho_v_normal * v2 + 0.5f0 * p * normal_direction[2] + lambda * u[3] + f4p = 0.5f0 * rho_v_normal * H + lambda * u[4] + + return SVector(f1p, f2p, f3p, f4p) end - return λ_min, λ_max -end + @inline function splitting_lax_friedrichs( + u, ::Val{:minus}, + normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho_e = last(u) + rho, v1, v2, p = cons2prim(u, equations) -@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + lambda = 0.5f0 * (sqrt(v1^2 + v2^2) + a) - v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + rho_v_normal = rho * v_normal - norm_ = norm(normal_direction) - # The v_normals are already scaled by the norm - λ_min = v_normal_ll - sqrt(equations.gamma * p_ll / rho_ll) * norm_ - λ_max = v_normal_rr + sqrt(equations.gamma * p_rr / rho_rr) * norm_ + f1m = 0.5f0 * rho_v_normal - lambda * u[1] + f2m = 0.5f0 * rho_v_normal * v1 + 0.5f0 * p * normal_direction[1] - lambda * u[2] + f3m = 0.5f0 * rho_v_normal * v2 + 0.5f0 * p * normal_direction[2] - lambda * u[3] + f4m = 0.5f0 * rho_v_normal * H - lambda * u[4] - return λ_min, λ_max -end + return SVector(f1m, f2m, f3m, f4m) + end -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the + # maximum velocity magnitude plus the maximum speed of sound + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + # Get the velocity value in the appropriate direction + if orientation == 1 + v_ll = v1_ll + v_rr = v1_rr + else # orientation == 2 + v_ll = v2_ll + v_rr = v2_rr + end + # Calculate sound speeds + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + # Calculate normal velocities and sound speed + # left + v_ll = ( + v1_ll * normal_direction[1] + + + v2_ll * normal_direction[2] + ) + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + # right + v_rr = ( + v1_rr * normal_direction[1] + + + v2_rr * normal_direction[2] + ) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) + end - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) + # Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + if orientation == 1 # x-direction + λ_min = v1_ll - sqrt(equations.gamma * p_ll / rho_ll) + λ_max = v1_rr + sqrt(equations.gamma * p_rr / rho_rr) + else # y-direction + λ_min = v2_ll - sqrt(equations.gamma * p_ll / rho_ll) + λ_max = v2_rr + sqrt(equations.gamma * p_rr / rho_rr) + end - if orientation == 1 # x-direction - λ_min = min(v1_ll - c_ll, v1_rr - c_rr) - λ_max = max(v1_ll + c_ll, v1_rr + c_rr) - else # y-direction - λ_min = min(v2_ll - c_ll, v2_rr - c_rr) - λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + return λ_min, λ_max end - return λ_min, λ_max -end + @inline function min_max_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - norm_ = norm(normal_direction) - - c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ - c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ - - v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # The v_normals are already scaled by the norm - λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) - λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) - - return λ_min, λ_max -end - -# Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction -# has been normalized prior to this rotation of the state vector -@inline function rotate_to_x(u, normal_vector, equations::CompressibleEulerEquations2D) - # cos and sin of the angle between the x-axis and the normalized normal_vector are - # the normalized vector's x and y coordinates respectively (see unit circle). - c = normal_vector[1] - s = normal_vector[2] - - # Apply the 2D rotation matrix with normal and tangent directions of the form - # [ 1 0 0 0; - # 0 n_1 n_2 0; - # 0 t_1 t_2 0; - # 0 0 0 1 ] - # where t_1 = -n_2 and t_2 = n_1 - - return SVector(u[1], - c * u[2] + s * u[3], - -s * u[2] + c * u[3], - u[4]) -end - -# Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction -# has been normalized prior to this back-rotation of the state vector -@inline function rotate_from_x(u, normal_vector, - equations::CompressibleEulerEquations2D) - # cos and sin of the angle between the x-axis and the normalized normal_vector are - # the normalized vector's x and y coordinates respectively (see unit circle). - c = normal_vector[1] - s = normal_vector[2] - - # Apply the 2D back-rotation matrix with normal and tangent directions of the form - # [ 1 0 0 0; - # 0 n_1 t_1 0; - # 0 n_2 t_2 0; - # 0 0 0 1 ] - # where t_1 = -n_2 and t_2 = n_1 - - return SVector(u[1], - c * u[2] - s * u[3], - s * u[2] + c * u[3], - u[4]) -end - -""" - flux_hllc(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations2D) - -Computes the HLLC flux (HLL with Contact) for compressible Euler equations developed by E.F. Toro -[Lecture slides](http://www.prague-sum.com/download/2012/Toro_2-HLLC-RiemannSolver.pdf) -Signal speeds: [DOI: 10.1137/S1064827593260140](https://doi.org/10.1137/S1064827593260140) -""" -function flux_hllc(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - # Calculate primitive variables and speed of sound - rho_ll, rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr - - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - e_ll = rho_e_ll / rho_ll - p_ll = (equations.gamma - 1) * (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2)) - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - e_rr = rho_e_rr / rho_rr - p_rr = (equations.gamma - 1) * (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2)) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - # Obtain left and right fluxes - f_ll = flux(u_ll, orientation, equations) - f_rr = flux(u_rr, orientation, equations) - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr - if orientation == 1 # x-direction - vel_L = v1_ll - vel_R = v1_rr - elseif orientation == 2 # y-direction - vel_L = v2_ll - vel_R = v2_rr - end - vel_roe = (sqrt_rho_ll * vel_L + sqrt_rho_rr * vel_R) / sum_sqrt_rho - v1_roe = sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr - v2_roe = sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr - vel_roe_mag = (v1_roe^2 + v2_roe^2) / sum_sqrt_rho^2 - H_ll = (rho_e_ll + p_ll) / rho_ll - H_rr = (rho_e_rr + p_rr) / rho_rr - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * vel_roe_mag)) - Ssl = min(vel_L - c_ll, vel_roe - c_roe) - Ssr = max(vel_R + c_rr, vel_roe + c_roe) - sMu_L = Ssl - vel_L - sMu_R = Ssr - vel_R - - if Ssl >= 0 - f1 = f_ll[1] - f2 = f_ll[2] - f3 = f_ll[3] - f4 = f_ll[4] - elseif Ssr <= 0 - f1 = f_rr[1] - f2 = f_rr[2] - f3 = f_rr[3] - f4 = f_rr[4] - else - SStar = (p_rr - p_ll + rho_ll * vel_L * sMu_L - rho_rr * vel_R * sMu_R) / + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + norm_ = norm(normal_direction) + # The v_normals are already scaled by the norm + λ_min = v_normal_ll - sqrt(equations.gamma * p_ll / rho_ll) * norm_ + λ_max = v_normal_rr + sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + if orientation == 1 # x-direction + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + else # y-direction + λ_min = min(v2_ll - c_ll, v2_rr - c_rr) + λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + end + + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + norm_ = norm(normal_direction) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ + c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # The v_normals are already scaled by the norm + λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) + λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) + + return λ_min, λ_max + end + + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction + # has been normalized prior to this rotation of the state vector + @inline function rotate_to_x(u, normal_vector, equations::CompressibleEulerEquations2D) + # cos and sin of the angle between the x-axis and the normalized normal_vector are + # the normalized vector's x and y coordinates respectively (see unit circle). + c = normal_vector[1] + s = normal_vector[2] + + # Apply the 2D rotation matrix with normal and tangent directions of the form + # [ 1 0 0 0; + # 0 n_1 n_2 0; + # 0 t_1 t_2 0; + # 0 0 0 1 ] + # where t_1 = -n_2 and t_2 = n_1 + + return SVector( + u[1], + c * u[2] + s * u[3], + -s * u[2] + c * u[3], + u[4] + ) + end + + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction + # has been normalized prior to this back-rotation of the state vector + @inline function rotate_from_x( + u, normal_vector, + equations::CompressibleEulerEquations2D + ) + # cos and sin of the angle between the x-axis and the normalized normal_vector are + # the normalized vector's x and y coordinates respectively (see unit circle). + c = normal_vector[1] + s = normal_vector[2] + + # Apply the 2D back-rotation matrix with normal and tangent directions of the form + # [ 1 0 0 0; + # 0 n_1 t_1 0; + # 0 n_2 t_2 0; + # 0 0 0 1 ] + # where t_1 = -n_2 and t_2 = n_1 + + return SVector( + u[1], + c * u[2] - s * u[3], + s * u[2] + c * u[3], + u[4] + ) + end + + """ + flux_hllc(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations2D) + + Computes the HLLC flux (HLL with Contact) for compressible Euler equations developed by E.F. Toro + [Lecture slides](http://www.prague-sum.com/download/2012/Toro_2-HLLC-RiemannSolver.pdf) + Signal speeds: [DOI: 10.1137/S1064827593260140](https://doi.org/10.1137/S1064827593260140) + """ + function flux_hllc( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + # Calculate primitive variables and speed of sound + rho_ll, rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr + + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + e_ll = rho_e_ll / rho_ll + p_ll = (equations.gamma - 1) * (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2)) + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + e_rr = rho_e_rr / rho_rr + p_rr = (equations.gamma - 1) * (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2)) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + # Obtain left and right fluxes + f_ll = flux(u_ll, orientation, equations) + f_rr = flux(u_rr, orientation, equations) + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr + if orientation == 1 # x-direction + vel_L = v1_ll + vel_R = v1_rr + elseif orientation == 2 # y-direction + vel_L = v2_ll + vel_R = v2_rr + end + vel_roe = (sqrt_rho_ll * vel_L + sqrt_rho_rr * vel_R) / sum_sqrt_rho + v1_roe = sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr + v2_roe = sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr + vel_roe_mag = (v1_roe^2 + v2_roe^2) / sum_sqrt_rho^2 + H_ll = (rho_e_ll + p_ll) / rho_ll + H_rr = (rho_e_rr + p_rr) / rho_rr + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * vel_roe_mag)) + Ssl = min(vel_L - c_ll, vel_roe - c_roe) + Ssr = max(vel_R + c_rr, vel_roe + c_roe) + sMu_L = Ssl - vel_L + sMu_R = Ssr - vel_R + + if Ssl >= 0 + f1 = f_ll[1] + f2 = f_ll[2] + f3 = f_ll[3] + f4 = f_ll[4] + elseif Ssr <= 0 + f1 = f_rr[1] + f2 = f_rr[2] + f3 = f_rr[3] + f4 = f_rr[4] + else + SStar = (p_rr - p_ll + rho_ll * vel_L * sMu_L - rho_rr * vel_R * sMu_R) / (rho_ll * sMu_L - rho_rr * sMu_R) - if Ssl <= 0 <= SStar - densStar = rho_ll * sMu_L / (Ssl - SStar) - enerStar = e_ll + (SStar - vel_L) * (SStar + p_ll / (rho_ll * sMu_L)) - UStar1 = densStar - UStar4 = densStar * enerStar - if orientation == 1 # x-direction - UStar2 = densStar * SStar - UStar3 = densStar * v2_ll - elseif orientation == 2 # y-direction - UStar2 = densStar * v1_ll - UStar3 = densStar * SStar + if Ssl <= 0 <= SStar + densStar = rho_ll * sMu_L / (Ssl - SStar) + enerStar = e_ll + (SStar - vel_L) * (SStar + p_ll / (rho_ll * sMu_L)) + UStar1 = densStar + UStar4 = densStar * enerStar + if orientation == 1 # x-direction + UStar2 = densStar * SStar + UStar3 = densStar * v2_ll + elseif orientation == 2 # y-direction + UStar2 = densStar * v1_ll + UStar3 = densStar * SStar + end + f1 = f_ll[1] + Ssl * (UStar1 - rho_ll) + f2 = f_ll[2] + Ssl * (UStar2 - rho_v1_ll) + f3 = f_ll[3] + Ssl * (UStar3 - rho_v2_ll) + f4 = f_ll[4] + Ssl * (UStar4 - rho_e_ll) + else + densStar = rho_rr * sMu_R / (Ssr - SStar) + enerStar = e_rr + (SStar - vel_R) * (SStar + p_rr / (rho_rr * sMu_R)) + UStar1 = densStar + UStar4 = densStar * enerStar + if orientation == 1 # x-direction + UStar2 = densStar * SStar + UStar3 = densStar * v2_rr + elseif orientation == 2 # y-direction + UStar2 = densStar * v1_rr + UStar3 = densStar * SStar + end + f1 = f_rr[1] + Ssr * (UStar1 - rho_rr) + f2 = f_rr[2] + Ssr * (UStar2 - rho_v1_rr) + f3 = f_rr[3] + Ssr * (UStar3 - rho_v2_rr) + f4 = f_rr[4] + Ssr * (UStar4 - rho_e_rr) end - f1 = f_ll[1] + Ssl * (UStar1 - rho_ll) - f2 = f_ll[2] + Ssl * (UStar2 - rho_v1_ll) - f3 = f_ll[3] + Ssl * (UStar3 - rho_v2_ll) - f4 = f_ll[4] + Ssl * (UStar4 - rho_e_ll) + end + return SVector(f1, f2, f3, f4) + end + + function flux_hllc( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + # Calculate primitive variables and speed of sound + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + norm_ = norm(normal_direction) + norm_sq = norm_ * norm_ + inv_norm_sq = inv(norm_sq) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ + c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + # Obtain left and right fluxes + f_ll = flux(u_ll, normal_direction, equations) + f_rr = flux(u_rr, normal_direction, equations) + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr + + v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) / sum_sqrt_rho + v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) / sum_sqrt_rho + vel_roe = v1_roe * normal_direction[1] + v2_roe * normal_direction[2] + vel_roe_mag = v1_roe^2 + v2_roe^2 + + e_ll = u_ll[4] / rho_ll + e_rr = u_rr[4] / rho_rr + + H_ll = (u_ll[4] + p_ll) / rho_ll + H_rr = (u_rr[4] + p_rr) / rho_rr + + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * vel_roe_mag)) * norm_ + + Ssl = min(v_dot_n_ll - c_ll, vel_roe - c_roe) + Ssr = max(v_dot_n_rr + c_rr, vel_roe + c_roe) + sMu_L = Ssl - v_dot_n_ll + sMu_R = Ssr - v_dot_n_rr + + if Ssl >= 0 + f1 = f_ll[1] + f2 = f_ll[2] + f3 = f_ll[3] + f4 = f_ll[4] + elseif Ssr <= 0 + f1 = f_rr[1] + f2 = f_rr[2] + f3 = f_rr[3] + f4 = f_rr[4] else - densStar = rho_rr * sMu_R / (Ssr - SStar) - enerStar = e_rr + (SStar - vel_R) * (SStar + p_rr / (rho_rr * sMu_R)) - UStar1 = densStar - UStar4 = densStar * enerStar - if orientation == 1 # x-direction - UStar2 = densStar * SStar - UStar3 = densStar * v2_rr - elseif orientation == 2 # y-direction - UStar2 = densStar * v1_rr - UStar3 = densStar * SStar + SStar = ( + rho_ll * v_dot_n_ll * sMu_L - rho_rr * v_dot_n_rr * sMu_R + + (p_rr - p_ll) * norm_sq + ) / (rho_ll * sMu_L - rho_rr * sMu_R) + if Ssl <= 0 <= SStar + densStar = rho_ll * sMu_L / (Ssl - SStar) + enerStar = e_ll + + (SStar - v_dot_n_ll) * + (SStar * inv_norm_sq + p_ll / (rho_ll * sMu_L)) + UStar1 = densStar + UStar2 = densStar * + (v1_ll + (SStar - v_dot_n_ll) * normal_direction[1] * inv_norm_sq) + UStar3 = densStar * + (v2_ll + (SStar - v_dot_n_ll) * normal_direction[2] * inv_norm_sq) + UStar4 = densStar * enerStar + f1 = f_ll[1] + Ssl * (UStar1 - u_ll[1]) + f2 = f_ll[2] + Ssl * (UStar2 - u_ll[2]) + f3 = f_ll[3] + Ssl * (UStar3 - u_ll[3]) + f4 = f_ll[4] + Ssl * (UStar4 - u_ll[4]) + else + densStar = rho_rr * sMu_R / (Ssr - SStar) + enerStar = e_rr + + (SStar - v_dot_n_rr) * + (SStar * inv_norm_sq + p_rr / (rho_rr * sMu_R)) + UStar1 = densStar + UStar2 = densStar * + (v1_rr + (SStar - v_dot_n_rr) * normal_direction[1] * inv_norm_sq) + UStar3 = densStar * + (v2_rr + (SStar - v_dot_n_rr) * normal_direction[2] * inv_norm_sq) + UStar4 = densStar * enerStar + f1 = f_rr[1] + Ssr * (UStar1 - u_rr[1]) + f2 = f_rr[2] + Ssr * (UStar2 - u_rr[2]) + f3 = f_rr[3] + Ssr * (UStar3 - u_rr[3]) + f4 = f_rr[4] + Ssr * (UStar4 - u_rr[4]) end - f1 = f_rr[1] + Ssr * (UStar1 - rho_rr) - f2 = f_rr[2] + Ssr * (UStar2 - rho_v1_rr) - f3 = f_rr[3] + Ssr * (UStar3 - rho_v2_rr) - f4 = f_rr[4] + Ssr * (UStar4 - rho_e_rr) end + return SVector(f1, f2, f3, f4) end - return SVector(f1, f2, f3, f4) -end - -function flux_hllc(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - # Calculate primitive variables and speed of sound - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - norm_ = norm(normal_direction) - norm_sq = norm_ * norm_ - inv_norm_sq = inv(norm_sq) - - c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ - c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ - - # Obtain left and right fluxes - f_ll = flux(u_ll, normal_direction, equations) - f_rr = flux(u_rr, normal_direction, equations) - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr - - v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) / sum_sqrt_rho - v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) / sum_sqrt_rho - vel_roe = v1_roe * normal_direction[1] + v2_roe * normal_direction[2] - vel_roe_mag = v1_roe^2 + v2_roe^2 - - e_ll = u_ll[4] / rho_ll - e_rr = u_rr[4] / rho_rr - - H_ll = (u_ll[4] + p_ll) / rho_ll - H_rr = (u_rr[4] + p_rr) / rho_rr - - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * vel_roe_mag)) * norm_ - - Ssl = min(v_dot_n_ll - c_ll, vel_roe - c_roe) - Ssr = max(v_dot_n_rr + c_rr, vel_roe + c_roe) - sMu_L = Ssl - v_dot_n_ll - sMu_R = Ssr - v_dot_n_rr - - if Ssl >= 0 - f1 = f_ll[1] - f2 = f_ll[2] - f3 = f_ll[3] - f4 = f_ll[4] - elseif Ssr <= 0 - f1 = f_rr[1] - f2 = f_rr[2] - f3 = f_rr[3] - f4 = f_rr[4] - else - SStar = (rho_ll * v_dot_n_ll * sMu_L - rho_rr * v_dot_n_rr * sMu_R + - (p_rr - p_ll) * norm_sq) / (rho_ll * sMu_L - rho_rr * sMu_R) - if Ssl <= 0 <= SStar - densStar = rho_ll * sMu_L / (Ssl - SStar) - enerStar = e_ll + - (SStar - v_dot_n_ll) * - (SStar * inv_norm_sq + p_ll / (rho_ll * sMu_L)) - UStar1 = densStar - UStar2 = densStar * - (v1_ll + (SStar - v_dot_n_ll) * normal_direction[1] * inv_norm_sq) - UStar3 = densStar * - (v2_ll + (SStar - v_dot_n_ll) * normal_direction[2] * inv_norm_sq) - UStar4 = densStar * enerStar - f1 = f_ll[1] + Ssl * (UStar1 - u_ll[1]) - f2 = f_ll[2] + Ssl * (UStar2 - u_ll[2]) - f3 = f_ll[3] + Ssl * (UStar3 - u_ll[3]) - f4 = f_ll[4] + Ssl * (UStar4 - u_ll[4]) - else - densStar = rho_rr * sMu_R / (Ssr - SStar) - enerStar = e_rr + - (SStar - v_dot_n_rr) * - (SStar * inv_norm_sq + p_rr / (rho_rr * sMu_R)) - UStar1 = densStar - UStar2 = densStar * - (v1_rr + (SStar - v_dot_n_rr) * normal_direction[1] * inv_norm_sq) - UStar3 = densStar * - (v2_rr + (SStar - v_dot_n_rr) * normal_direction[2] * inv_norm_sq) - UStar4 = densStar * enerStar - f1 = f_rr[1] + Ssr * (UStar1 - u_rr[1]) - f2 = f_rr[2] + Ssr * (UStar2 - u_rr[2]) - f3 = f_rr[3] + Ssr * (UStar3 - u_rr[3]) - f4 = f_rr[4] + Ssr * (UStar4 - u_rr[4]) + + """ + min_max_speed_einfeldt(u_ll, u_rr, orientation, equations::CompressibleEulerEquations2D) + + Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. + Special estimates of the signal velocites and linearization of the Riemann problem developed + by Einfeldt to ensure that the internal energy and density remain positive during the computation + of the numerical flux. + + - Bernd Einfeldt (1988) + On Godunov-type methods for gas dynamics. + [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) + - Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) + On Godunov-type methods near low densities. + [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) + """ + @inline function min_max_speed_einfeldt( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations2D + ) + # Calculate primitive variables, enthalpy and speed of sound + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + # `u_ll[4]` is total energy `rho_e_ll` on the left + H_ll = (u_ll[4] + p_ll) / rho_ll + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + + # `u_rr[4]` is total energy `rho_e_rr` on the right + H_rr = (u_rr[4] + p_rr) / rho_rr + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) + + v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) * inv_sum_sqrt_rho + v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) * inv_sum_sqrt_rho + v_roe_mag = v1_roe^2 + v2_roe^2 + + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) + + # Compute convenience constant for positivity preservation, see + # https://doi.org/10.1016/0021-9991(91)90211-3 + beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) + + # Estimate the edges of the Riemann fan (with positivity conservation) + if orientation == 1 # x-direction + SsL = min(v1_roe - c_roe, v1_ll - beta * c_ll, 0) + SsR = max(v1_roe + c_roe, v1_rr + beta * c_rr, 0) + elseif orientation == 2 # y-direction + SsL = min(v2_roe - c_roe, v2_ll - beta * c_ll, 0) + SsR = max(v2_roe + c_roe, v2_rr + beta * c_rr, 0) + end + + return SsL, SsR + end + + """ + min_max_speed_einfeldt(u_ll, u_rr, normal_direction, equations::CompressibleEulerEquations2D) + + Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. + Special estimates of the signal velocites and linearization of the Riemann problem developed + by Einfeldt to ensure that the internal energy and density remain positive during the computation + of the numerical flux. + + - Bernd Einfeldt (1988) + On Godunov-type methods for gas dynamics. + [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) + - Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) + On Godunov-type methods near low densities. + [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) + """ + @inline function min_max_speed_einfeldt( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations2D + ) + # Calculate primitive variables, enthalpy and speed of sound + rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) + + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + norm_ = norm(normal_direction) + + # `u_ll[4]` is total energy `rho_e_ll` on the left + H_ll = (u_ll[4] + p_ll) / rho_ll + c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ + + # `u_rr[4]` is total energy `rho_e_rr` on the right + H_rr = (u_rr[4] + p_rr) / rho_rr + c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) + + v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) * inv_sum_sqrt_rho + v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) * inv_sum_sqrt_rho + v_roe = v1_roe * normal_direction[1] + v2_roe * normal_direction[2] + v_roe_mag = v1_roe^2 + v2_roe^2 + + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) * norm_ + + # Compute convenience constant for positivity preservation, see + # https://doi.org/10.1016/0021-9991(91)90211-3 + beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) + + # Estimate the edges of the Riemann fan (with positivity conservation) + SsL = min(v_roe - c_roe, v_dot_n_ll - beta * c_ll, 0) + SsR = max(v_roe + c_roe, v_dot_n_rr + beta * c_rr, 0) + + return SsL, SsR + end + + @inline function max_abs_speeds(u, equations::CompressibleEulerEquations2D) + rho, v1, v2, p = cons2prim(u, equations) + c = sqrt(equations.gamma * p / rho) + + return abs(v1) + c, abs(v2) + c + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::CompressibleEulerEquations2D) + rho, rho_v1, rho_v2, rho_e = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) + + return SVector(rho, v1, v2, p) + end + + # Convert conservative variables to entropy + @inline function cons2entropy(u, equations::CompressibleEulerEquations2D) + rho, rho_v1, rho_v2, rho_e = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v_square = v1^2 + v2^2 + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho * v_square) + s = log(p) - equations.gamma * log(rho) + rho_p = rho / p + + w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - + 0.5f0 * rho_p * v_square + w2 = rho_p * v1 + w3 = rho_p * v2 + w4 = -rho_p + + return SVector(w1, w2, w3, w4) + end + + # Transformation from conservative variables u to entropy vector ds_0/du, + # using the modified specific entropy of Guermond et al. (2019): s_0 = p * rho^(-gamma) / (gamma-1). + # Note: This is *not* the "conventional" specific entropy s = ln(p / rho^(gamma)). + @inline function cons2entropy_guermond_etal(u, equations::CompressibleEulerEquations2D) + rho, rho_v1, rho_v2, rho_e = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v_square = v1^2 + v2^2 + inv_rho_gammap1 = (1 / rho)^(equations.gamma + 1) + + # The derivative vector for the modified specific entropy of Guermond et al. + w1 = inv_rho_gammap1 * + (0.5f0 * rho * (equations.gamma + 1) * v_square - equations.gamma * rho_e) + w2 = -rho_v1 * inv_rho_gammap1 + w3 = -rho_v2 * inv_rho_gammap1 + w4 = (1 / rho)^equations.gamma + + return SVector(w1, w2, w3, w4) + end + + @inline function entropy2cons(w, equations::CompressibleEulerEquations2D) + # See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD + # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) + @unpack gamma = equations + + # convert to entropy `-rho * s` used by Hughes, France, Mallet (1986) + # instead of `-rho * s / (gamma - 1)` + V1, V2, V3, V5 = w .* (gamma - 1) + + # s = specific entropy, eq. (53) + s = gamma - V1 + (V2^2 + V3^2) / (2 * V5) + + # eq. (52) + rho_iota = ((gamma - 1) / (-V5)^gamma)^(equations.inv_gamma_minus_one) * + exp(-s * equations.inv_gamma_minus_one) + + # eq. (51) + rho = -rho_iota * V5 + rho_v1 = rho_iota * V2 + rho_v2 = rho_iota * V3 + rho_e = rho_iota * (1 - (V2^2 + V3^2) / (2 * V5)) + return SVector(rho, rho_v1, rho_v2, rho_e) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::CompressibleEulerEquations2D) + rho, v1, v2, p = prim + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_e = p * equations.inv_gamma_minus_one + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2) + return SVector(rho, rho_v1, rho_v2, rho_e) + end + + @inline function density(u, equations::CompressibleEulerEquations2D) + rho = u[1] + return rho + end + + @inline function pressure(u, equations::CompressibleEulerEquations2D) + rho, rho_v1, rho_v2, rho_e = u + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho) + return p + end + + # Transformation from conservative variables u to d(p)/d(u) + @inline function gradient_conservative( + ::typeof(pressure), + u, equations::CompressibleEulerEquations2D + ) + rho, rho_v1, rho_v2, rho_e = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v_square = v1^2 + v2^2 + + return (equations.gamma - 1) * SVector(0.5f0 * v_square, -v1, -v2, 1) + end + + @inline function density_pressure(u, equations::CompressibleEulerEquations2D) + rho, rho_v1, rho_v2, rho_e = u + rho_times_p = (equations.gamma - 1) * (rho * rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2)) + return rho_times_p + end + + # Calculates the entropy flux in direction "orientation" and the entropy variables for a state cons + # NOTE: This method seems to work currently (b82534e) but is never used anywhere. Thus it is + # commented here until someone uses it or writes a test for it. + # @inline function cons2entropyvars_and_flux(gamma::Float64, cons, orientation::Int) + # entropy = MVector{4, Float64}(undef) + # v = (cons[2] / cons[1] , cons[3] / cons[1]) + # v_square= v[1]*v[1]+v[2]*v[2] + # p = (gamma - 1) * (cons[4] - 1/2 * (cons[2] * v[1] + cons[3] * v[2])) + # rho_p = cons[1] / p + # # thermodynamic entropy + # s = log(p) - gamma*log(cons[1]) + # # mathematical entropy + # S = - s*cons[1]/(gamma-1) + # # entropy variables + # entropy[1] = (gamma - s)/(gamma-1) - 0.5*rho_p*v_square + # entropy[2] = rho_p*v[1] + # entropy[3] = rho_p*v[2] + # entropy[4] = -rho_p + # # entropy flux + # entropy_flux = S*v[orientation] + # return entropy, entropy_flux + # end + + # Calculate thermodynamic entropy for a conservative state `cons` + @inline function entropy_thermodynamic(cons, equations::CompressibleEulerEquations2D) + # Pressure + p = (equations.gamma - 1) * (cons[4] - 0.5f0 * (cons[2]^2 + cons[3]^2) / cons[1]) + + # Thermodynamic entropy + s = log(p) - equations.gamma * log(cons[1]) + + return s + end + + # Calculate mathematical entropy for a conservative state `cons` + @inline function entropy_math(cons, equations::CompressibleEulerEquations2D) + # Mathematical entropy + S = -entropy_thermodynamic(cons, equations) * cons[1] * + equations.inv_gamma_minus_one + + return S + end + + # Transformation from conservative variables u to d(s)/d(u) + @inline function gradient_conservative( + ::typeof(entropy_math), + u, equations::CompressibleEulerEquations2D + ) + return cons2entropy(u, equations) + end + + @doc raw""" + entropy_guermond_etal(u, equations::CompressibleEulerEquations2D) + + Calculate the modified specific entropy of Guermond et al. (2019): + ```math + s_0 = p * \rho^{-\gamma} / (\gamma-1). + ``` + Note: This is *not* the "conventional" specific entropy ``s = ln(p / \rho^\gamma)``. + - Guermond at al. (2019) + Invariant domain preserving discretization-independent schemes and convex limiting for hyperbolic systems. + [DOI: 10.1016/j.cma.2018.11.036](https://doi.org/10.1016/j.cma.2018.11.036) + """ + @inline function entropy_guermond_etal(u, equations::CompressibleEulerEquations2D) + rho, rho_v1, rho_v2, rho_e = u + + # Modified specific entropy from Guermond et al. (2019) + s = (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho) * (1 / rho)^equations.gamma + + return s + end + + # Transformation from conservative variables u to d(s)/d(u) + @inline function gradient_conservative( + ::typeof(entropy_guermond_etal), + u, equations::CompressibleEulerEquations2D + ) + return cons2entropy_guermond_etal(u, equations) + end + + # Default entropy is the mathematical entropy + @inline function entropy(cons, equations::CompressibleEulerEquations2D) + entropy_math(cons, equations) + end + + # Calculate total energy for a conservative state `cons` + @inline energy_total(cons, ::CompressibleEulerEquations2D) = cons[4] + + # Calculate kinetic energy for a conservative state `cons` + @inline function energy_kinetic(u, equations::CompressibleEulerEquations2D) + rho, rho_v1, rho_v2, rho_e = u + return (rho_v1^2 + rho_v2^2) / (2 * rho) + end + + # Calculate internal energy for a conservative state `cons` + @inline function energy_internal(cons, equations::CompressibleEulerEquations2D) + return energy_total(cons, equations) - energy_kinetic(cons, equations) + end + + # State validation for Newton-bisection method of subcell IDP limiting + @inline function Base.isvalid(u, equations::CompressibleEulerEquations2D) + p = pressure(u, equations) + if u[1] <= 0 || p <= 0 + return false end + return true end - return SVector(f1, f2, f3, f4) -end - -""" - min_max_speed_einfeldt(u_ll, u_rr, orientation, equations::CompressibleEulerEquations2D) - -Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. -Special estimates of the signal velocites and linearization of the Riemann problem developed -by Einfeldt to ensure that the internal energy and density remain positive during the computation -of the numerical flux. - -- Bernd Einfeldt (1988) - On Godunov-type methods for gas dynamics. - [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) -- Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) - On Godunov-type methods near low densities. - [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) -""" -@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations2D) - # Calculate primitive variables, enthalpy and speed of sound - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - # `u_ll[4]` is total energy `rho_e_ll` on the left - H_ll = (u_ll[4] + p_ll) / rho_ll - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - - # `u_rr[4]` is total energy `rho_e_rr` on the right - H_rr = (u_rr[4] + p_rr) / rho_rr - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) - - v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) * inv_sum_sqrt_rho - v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) * inv_sum_sqrt_rho - v_roe_mag = v1_roe^2 + v2_roe^2 - - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) - - # Compute convenience constant for positivity preservation, see - # https://doi.org/10.1016/0021-9991(91)90211-3 - beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) - - # Estimate the edges of the Riemann fan (with positivity conservation) - if orientation == 1 # x-direction - SsL = min(v1_roe - c_roe, v1_ll - beta * c_ll, 0) - SsR = max(v1_roe + c_roe, v1_rr + beta * c_rr, 0) - elseif orientation == 2 # y-direction - SsL = min(v2_roe - c_roe, v2_ll - beta * c_ll, 0) - SsR = max(v2_roe + c_roe, v2_rr + beta * c_rr, 0) - end - - return SsL, SsR -end - -""" - min_max_speed_einfeldt(u_ll, u_rr, normal_direction, equations::CompressibleEulerEquations2D) - -Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. -Special estimates of the signal velocites and linearization of the Riemann problem developed -by Einfeldt to ensure that the internal energy and density remain positive during the computation -of the numerical flux. - -- Bernd Einfeldt (1988) - On Godunov-type methods for gas dynamics. - [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) -- Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) - On Godunov-type methods near low densities. - [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) -""" -@inline function min_max_speed_einfeldt(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations2D) - # Calculate primitive variables, enthalpy and speed of sound - rho_ll, v1_ll, v2_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, p_rr = cons2prim(u_rr, equations) - - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - norm_ = norm(normal_direction) - - # `u_ll[4]` is total energy `rho_e_ll` on the left - H_ll = (u_ll[4] + p_ll) / rho_ll - c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ - - # `u_rr[4]` is total energy `rho_e_rr` on the right - H_rr = (u_rr[4] + p_rr) / rho_rr - c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) - - v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) * inv_sum_sqrt_rho - v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) * inv_sum_sqrt_rho - v_roe = v1_roe * normal_direction[1] + v2_roe * normal_direction[2] - v_roe_mag = v1_roe^2 + v2_roe^2 - - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) * norm_ - - # Compute convenience constant for positivity preservation, see - # https://doi.org/10.1016/0021-9991(91)90211-3 - beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) - - # Estimate the edges of the Riemann fan (with positivity conservation) - SsL = min(v_roe - c_roe, v_dot_n_ll - beta * c_ll, 0) - SsR = max(v_roe + c_roe, v_dot_n_rr + beta * c_rr, 0) - - return SsL, SsR -end - -@inline function max_abs_speeds(u, equations::CompressibleEulerEquations2D) - rho, v1, v2, p = cons2prim(u, equations) - c = sqrt(equations.gamma * p / rho) - - return abs(v1) + c, abs(v2) + c -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2)) - - return SVector(rho, v1, v2, p) -end - -# Convert conservative variables to entropy -@inline function cons2entropy(u, equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v_square = v1^2 + v2^2 - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho * v_square) - s = log(p) - equations.gamma * log(rho) - rho_p = rho / p - - w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - - 0.5f0 * rho_p * v_square - w2 = rho_p * v1 - w3 = rho_p * v2 - w4 = -rho_p - - return SVector(w1, w2, w3, w4) -end - -# Transformation from conservative variables u to entropy vector ds_0/du, -# using the modified specific entropy of Guermond et al. (2019): s_0 = p * rho^(-gamma) / (gamma-1). -# Note: This is *not* the "conventional" specific entropy s = ln(p / rho^(gamma)). -@inline function cons2entropy_guermond_etal(u, equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v_square = v1^2 + v2^2 - inv_rho_gammap1 = (1 / rho)^(equations.gamma + 1) - - # The derivative vector for the modified specific entropy of Guermond et al. - w1 = inv_rho_gammap1 * - (0.5f0 * rho * (equations.gamma + 1) * v_square - equations.gamma * rho_e) - w2 = -rho_v1 * inv_rho_gammap1 - w3 = -rho_v2 * inv_rho_gammap1 - w4 = (1 / rho)^equations.gamma - - return SVector(w1, w2, w3, w4) -end - -@inline function entropy2cons(w, equations::CompressibleEulerEquations2D) - # See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD - # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) - @unpack gamma = equations - - # convert to entropy `-rho * s` used by Hughes, France, Mallet (1986) - # instead of `-rho * s / (gamma - 1)` - V1, V2, V3, V5 = w .* (gamma - 1) - - # s = specific entropy, eq. (53) - s = gamma - V1 + (V2^2 + V3^2) / (2 * V5) - - # eq. (52) - rho_iota = ((gamma - 1) / (-V5)^gamma)^(equations.inv_gamma_minus_one) * - exp(-s * equations.inv_gamma_minus_one) - - # eq. (51) - rho = -rho_iota * V5 - rho_v1 = rho_iota * V2 - rho_v2 = rho_iota * V3 - rho_e = rho_iota * (1 - (V2^2 + V3^2) / (2 * V5)) - return SVector(rho, rho_v1, rho_v2, rho_e) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::CompressibleEulerEquations2D) - rho, v1, v2, p = prim - rho_v1 = rho * v1 - rho_v2 = rho * v2 - rho_e = p * equations.inv_gamma_minus_one + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2) - return SVector(rho, rho_v1, rho_v2, rho_e) -end - -@inline function density(u, equations::CompressibleEulerEquations2D) - rho = u[1] - return rho -end - -@inline function pressure(u, equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho) - return p -end - -# Transformation from conservative variables u to d(p)/d(u) -@inline function gradient_conservative(::typeof(pressure), - u, equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v_square = v1^2 + v2^2 - - return (equations.gamma - 1) * SVector(0.5f0 * v_square, -v1, -v2, 1) -end - -@inline function density_pressure(u, equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - rho_times_p = (equations.gamma - 1) * (rho * rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2)) - return rho_times_p -end - -# Calculates the entropy flux in direction "orientation" and the entropy variables for a state cons -# NOTE: This method seems to work currently (b82534e) but is never used anywhere. Thus it is -# commented here until someone uses it or writes a test for it. -# @inline function cons2entropyvars_and_flux(gamma::Float64, cons, orientation::Int) -# entropy = MVector{4, Float64}(undef) -# v = (cons[2] / cons[1] , cons[3] / cons[1]) -# v_square= v[1]*v[1]+v[2]*v[2] -# p = (gamma - 1) * (cons[4] - 1/2 * (cons[2] * v[1] + cons[3] * v[2])) -# rho_p = cons[1] / p -# # thermodynamic entropy -# s = log(p) - gamma*log(cons[1]) -# # mathematical entropy -# S = - s*cons[1]/(gamma-1) -# # entropy variables -# entropy[1] = (gamma - s)/(gamma-1) - 0.5*rho_p*v_square -# entropy[2] = rho_p*v[1] -# entropy[3] = rho_p*v[2] -# entropy[4] = -rho_p -# # entropy flux -# entropy_flux = S*v[orientation] -# return entropy, entropy_flux -# end - -# Calculate thermodynamic entropy for a conservative state `cons` -@inline function entropy_thermodynamic(cons, equations::CompressibleEulerEquations2D) - # Pressure - p = (equations.gamma - 1) * (cons[4] - 0.5f0 * (cons[2]^2 + cons[3]^2) / cons[1]) - - # Thermodynamic entropy - s = log(p) - equations.gamma * log(cons[1]) - - return s -end - -# Calculate mathematical entropy for a conservative state `cons` -@inline function entropy_math(cons, equations::CompressibleEulerEquations2D) - # Mathematical entropy - S = -entropy_thermodynamic(cons, equations) * cons[1] * - equations.inv_gamma_minus_one - - return S -end - -# Transformation from conservative variables u to d(s)/d(u) -@inline function gradient_conservative(::typeof(entropy_math), - u, equations::CompressibleEulerEquations2D) - return cons2entropy(u, equations) -end - -@doc raw""" - entropy_guermond_etal(u, equations::CompressibleEulerEquations2D) - -Calculate the modified specific entropy of Guermond et al. (2019): -```math -s_0 = p * \rho^{-\gamma} / (\gamma-1). -``` -Note: This is *not* the "conventional" specific entropy ``s = ln(p / \rho^\gamma)``. -- Guermond at al. (2019) - Invariant domain preserving discretization-independent schemes and convex limiting for hyperbolic systems. - [DOI: 10.1016/j.cma.2018.11.036](https://doi.org/10.1016/j.cma.2018.11.036) -""" -@inline function entropy_guermond_etal(u, equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - - # Modified specific entropy from Guermond et al. (2019) - s = (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho) * (1 / rho)^equations.gamma - - return s -end - -# Transformation from conservative variables u to d(s)/d(u) -@inline function gradient_conservative(::typeof(entropy_guermond_etal), - u, equations::CompressibleEulerEquations2D) - return cons2entropy_guermond_etal(u, equations) -end - -# Default entropy is the mathematical entropy -@inline function entropy(cons, equations::CompressibleEulerEquations2D) - entropy_math(cons, equations) -end - -# Calculate total energy for a conservative state `cons` -@inline energy_total(cons, ::CompressibleEulerEquations2D) = cons[4] - -# Calculate kinetic energy for a conservative state `cons` -@inline function energy_kinetic(u, equations::CompressibleEulerEquations2D) - rho, rho_v1, rho_v2, rho_e = u - return (rho_v1^2 + rho_v2^2) / (2 * rho) -end - -# Calculate internal energy for a conservative state `cons` -@inline function energy_internal(cons, equations::CompressibleEulerEquations2D) - return energy_total(cons, equations) - energy_kinetic(cons, equations) -end - -# State validation for Newton-bisection method of subcell IDP limiting -@inline function Base.isvalid(u, equations::CompressibleEulerEquations2D) - p = pressure(u, equations) - if u[1] <= 0 || p <= 0 - return false - end - return true -end end # @muladd diff --git a/src/equations/compressible_euler_3d.jl b/src/equations/compressible_euler_3d.jl index 4f4d4553a8..17af220db2 100644 --- a/src/equations/compressible_euler_3d.jl +++ b/src/equations/compressible_euler_3d.jl @@ -3,1744 +3,1852 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - CompressibleEulerEquations3D(gamma) - -The compressible Euler equations -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -\rho \\ \rho v_1 \\ \rho v_2 \\ \rho v_3 \\ \rho e -\end{pmatrix} -+ -\frac{\partial}{\partial x} -\begin{pmatrix} - \rho v_1 \\ \rho v_1^2 + p \\ \rho v_1 v_2 \\ \rho v_1 v_3 \\ ( \rho e +p) v_1 -\end{pmatrix} -+ -\frac{\partial}{\partial y} -\begin{pmatrix} -\rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + p \\ \rho v_1 v_3 \\ ( \rho e +p) v_2 -\end{pmatrix} -+ -\frac{\partial}{\partial z} -\begin{pmatrix} -\rho v_3 \\ \rho v_1 v_3 \\ \rho v_2 v_3 \\ \rho v_3^2 + p \\ ( \rho e +p) v_3 -\end{pmatrix} -= -\begin{pmatrix} -0 \\ 0 \\ 0 \\ 0 \\ 0 -\end{pmatrix} -``` -for an ideal gas with ratio of specific heats `gamma` -in three space dimensions. -Here, ``\rho`` is the density, ``v_1``, ``v_2``, ``v_3`` the velocities, ``e`` the specific total energy **rather than** specific internal energy, and -```math -p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2+v_2^2+v_3^2) \right) -``` -the pressure. -""" -struct CompressibleEulerEquations3D{RealT <: Real} <: - AbstractCompressibleEulerEquations{3, 5} - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - - function CompressibleEulerEquations3D(gamma) - γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) - new{typeof(γ)}(γ, inv_gamma_minus_one) + #! format: noindent + + @doc raw""" + CompressibleEulerEquations3D(gamma) + + The compressible Euler equations + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + \rho \\ \rho v_1 \\ \rho v_2 \\ \rho v_3 \\ \rho e + \end{pmatrix} + + + \frac{\partial}{\partial x} + \begin{pmatrix} + \rho v_1 \\ \rho v_1^2 + p \\ \rho v_1 v_2 \\ \rho v_1 v_3 \\ ( \rho e +p) v_1 + \end{pmatrix} + + + \frac{\partial}{\partial y} + \begin{pmatrix} + \rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + p \\ \rho v_1 v_3 \\ ( \rho e +p) v_2 + \end{pmatrix} + + + \frac{\partial}{\partial z} + \begin{pmatrix} + \rho v_3 \\ \rho v_1 v_3 \\ \rho v_2 v_3 \\ \rho v_3^2 + p \\ ( \rho e +p) v_3 + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 \\ 0 \\ 0 \\ 0 + \end{pmatrix} + ``` + for an ideal gas with ratio of specific heats `gamma` + in three space dimensions. + Here, ``\rho`` is the density, ``v_1``, ``v_2``, ``v_3`` the velocities, ``e`` the specific total energy **rather than** specific internal energy, and + ```math + p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2+v_2^2+v_3^2) \right) + ``` + the pressure. + """ + struct CompressibleEulerEquations3D{RealT <: Real} <: + AbstractCompressibleEulerEquations{3, 5} + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + + function CompressibleEulerEquations3D(gamma) + γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) + new{typeof(γ)}(γ, inv_gamma_minus_one) + end end -end - -function varnames(::typeof(cons2cons), ::CompressibleEulerEquations3D) - ("rho", "rho_v1", "rho_v2", "rho_v3", "rho_e") -end -function varnames(::typeof(cons2prim), ::CompressibleEulerEquations3D) - ("rho", "v1", "v2", "v3", "p") -end - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::CompressibleEulerEquations3D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equations::CompressibleEulerEquations3D) - RealT = eltype(x) - rho = 1 - rho_v1 = convert(RealT, 0.1) - rho_v2 = convert(RealT, -0.2) - rho_v3 = convert(RealT, 0.7) - rho_e = 10 - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e) -end - -""" - initial_condition_convergence_test(x, t, equations::CompressibleEulerEquations3D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref). -""" -function initial_condition_convergence_test(x, t, - equations::CompressibleEulerEquations3D) - RealT = eltype(x) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - ω = 2 * convert(RealT, pi) * f - ini = c + A * sin(ω * (x[1] + x[2] + x[3] - t)) - - rho = ini - rho_v1 = ini - rho_v2 = ini - rho_v3 = ini - rho_e = ini^2 - - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e) -end - -""" - source_terms_convergence_test(u, x, t, equations::CompressibleEulerEquations3D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::CompressibleEulerEquations3D) - # Same settings as in `initial_condition` - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - ω = 2 * convert(RealT, pi) * f - γ = equations.gamma - - x1, x2, x3 = x - si, co = sincos(ω * (x1 + x2 + x3 - t)) - rho = c + A * si - rho_x = ω * A * co - # Note that d/dt rho = -d/dx rho = -d/dy rho = - d/dz rho. - - tmp = (2 * rho - 1.5f0) * (γ - 1) - - du1 = 2 * rho_x - du2 = rho_x * (2 + tmp) - du3 = du2 - du4 = du2 - du5 = rho_x * (4 * rho + 3 * tmp) - - return SVector(du1, du2, du3, du4, du5) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerEquations3D) - -A weak blast wave taken from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, - equations::CompressibleEulerEquations3D) - # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Set up spherical coordinates - RealT = eltype(x) - inicenter = (0, 0, 0) - 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) - phi = atan(y_norm, x_norm) - theta = iszero(r) ? zero(RealT) : acos(z_norm / r) - - # Calculate primitive variables - rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) * sin(theta) - v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin(phi) * sin(theta) - v3 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(theta) - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - return prim2cons(SVector(rho, v1, v2, v3, p), equations) -end - -""" - initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::CompressibleEulerEquations3D) - -Setup used for convergence tests of the Euler equations with self-gravity used in -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -in combination with [`source_terms_eoc_test_coupled_euler_gravity`](@ref) -or [`source_terms_eoc_test_euler`](@ref). -""" -function initial_condition_eoc_test_coupled_euler_gravity(x, t, - equations::CompressibleEulerEquations3D) - # OBS! this assumes that γ = 2 other manufactured source terms are incorrect - if equations.gamma != 2 - error("adiabatic constant must be 2 for the coupling convergence test") + + function varnames(::typeof(cons2cons), ::CompressibleEulerEquations3D) + ("rho", "rho_v1", "rho_v2", "rho_v3", "rho_e") + end + function varnames(::typeof(cons2prim), ::CompressibleEulerEquations3D) + ("rho", "v1", "v2", "v3", "p") end - RealT = eltype(x) - c = 2 - A = convert(RealT, 0.1) - ini = c + A * sin(convert(RealT, pi) * (x[1] + x[2] + x[3] - t)) - G = 1 # gravitational constant - - rho = ini - v1 = 1 - v2 = 1 - v3 = 1 - p = ini^2 * G * 2 / (3 * convert(RealT, pi)) # "3" is the number of spatial dimensions - - return prim2cons(SVector(rho, v1, v2, v3, p), equations) -end - -""" - source_terms_eoc_test_coupled_euler_gravity(u, x, t, equations::CompressibleEulerEquations3D) - -Setup used for convergence tests of the Euler equations with self-gravity used in -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -in combination with [`initial_condition_eoc_test_coupled_euler_gravity`](@ref). -""" -@inline function source_terms_eoc_test_coupled_euler_gravity(u, x, t, - equations::CompressibleEulerEquations3D) - # Same settings as in `initial_condition_eoc_test_coupled_euler_gravity` - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - G = 1 # gravitational constant, must match coupling solver - C_grav = -4 * G / (3 * convert(RealT, pi)) # "3" is the number of spatial dimensions # 2D: -2.0*G/pi - - x1, x2, x3 = x - # TODO: sincospi - si, co = sincos(convert(RealT, pi) * (x1 + x2 + x3 - t)) - rhox = A * convert(RealT, pi) * co - rho = c + A * si - - # In "2 * rhox", the "2" is "number of spatial dimensions minus one" - du1 = 2 * rhox - du2 = 2 * rhox - du3 = 2 * rhox - du4 = 2 * rhox - du5 = 2 * rhox * (1.5f0 - C_grav * rho) # "3" in "3/2" is the number of spatial dimensions - - return SVector(du1, du2, du3, du4, du5) -end - -""" - source_terms_eoc_test_euler(u, x, t, equations::CompressibleEulerEquations3D) - -Setup used for convergence tests of the Euler equations with self-gravity used in -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -in combination with [`initial_condition_eoc_test_coupled_euler_gravity`](@ref). - -!!! note - This method is to be used for testing pure Euler simulations with analytic self-gravity. - If you intend to do coupled Euler-gravity simulations, you need to use - [`source_terms_eoc_test_coupled_euler_gravity`](@ref) instead. -""" -function source_terms_eoc_test_euler(u, x, t, equations::CompressibleEulerEquations3D) - # Same settings as in `initial_condition_eoc_test_coupled_euler_gravity` - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - G = 1 - C_grav = -4 * G / (3 * convert(RealT, pi)) # "3" is the number of spatial dimensions - - x1, x2, x3 = x - # TODO: sincospi - si, co = sincos(convert(RealT, pi) * (x1 + x2 + x3 - t)) - rhox = A * convert(RealT, pi) * co - rho = c + A * si - - du1 = rhox * 2 - du2 = rhox * (2 - C_grav * rho) - du3 = rhox * (2 - C_grav * rho) - du4 = rhox * (2 - C_grav * rho) - du5 = rhox * (3 - 5 * C_grav * rho) - - return SVector(du1, du2, du3, du4, du5) -end - -""" - boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, - equations::CompressibleEulerEquations3D) -Determine the boundary numerical surface flux for a slip wall condition. -Imposes a zero normal velocity at the wall. -Density is taken from the internal solution state and pressure is computed as an -exact solution of a 1D Riemann problem. Further details about this boundary state -are available in the paper: -- J. J. W. van der Vegt and H. van der Ven (2002) - Slip flow boundary conditions in discontinuous Galerkin discretizations of - the Euler equations of gas dynamics - [PDF](https://reports.nlr.nl/bitstream/handle/10921/692/TP-2002-300.pdf?sequence=1) - -Details about the 1D pressure Riemann solution can be found in Section 6.3.3 of the book -- Eleuterio F. Toro (2009) - Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction - 3rd edition - [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) -""" -@inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, - x, t, - surface_flux_function, - equations::CompressibleEulerEquations3D) - norm_ = norm(normal_direction) - # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later - normal = normal_direction / norm_ - - # Some vector that can't be identical to normal_vector (unless normal_vector == 0) - tangent1 = SVector(normal_direction[2], normal_direction[3], -normal_direction[1]) - # Orthogonal projection - tangent1 -= dot(normal, tangent1) * normal - tangent1 = normalize(tangent1) - - # Third orthogonal vector - tangent2 = normalize(cross(normal_direction, tangent1)) - - # rotate the internal solution state - u_local = rotate_to_x(u_inner, normal, tangent1, tangent2, equations) - - # compute the primitive variables - rho_local, v_normal, v_tangent1, v_tangent2, p_local = cons2prim(u_local, equations) - - # Get the solution of the pressure Riemann problem - # See Section 6.3.3 of - # Eleuterio F. Toro (2009) - # Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction - # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) - if v_normal <= 0 - sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed - p_star = p_local * - (1 + 0.5f0 * (equations.gamma - 1) * v_normal / sound_speed)^(2 * - equations.gamma * - equations.inv_gamma_minus_one) - else # v_normal > 0 - A = 2 / ((equations.gamma + 1) * rho_local) - B = p_local * (equations.gamma - 1) / (equations.gamma + 1) - p_star = p_local + - 0.5f0 * v_normal / A * - (v_normal + sqrt(v_normal^2 + 4 * A * (p_local + B))) + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::CompressibleEulerEquations3D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equations::CompressibleEulerEquations3D) + RealT = eltype(x) + rho = 1 + rho_v1 = convert(RealT, 0.1) + rho_v2 = convert(RealT, -0.2) + rho_v3 = convert(RealT, 0.7) + rho_e = 10 + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e) end - # For the slip wall we directly set the flux as the normal velocity is zero - return SVector(0, - p_star * normal[1], - p_star * normal[2], - p_star * normal[3], - 0) * norm_ -end - -""" - boundary_condition_slip_wall(u_inner, orientation, direction, x, t, - surface_flux_function, equations::CompressibleEulerEquations3D) - -Should be used together with [`TreeMesh`](@ref). -""" -@inline function boundary_condition_slip_wall(u_inner, orientation, - direction, x, t, - surface_flux_function, - equations::CompressibleEulerEquations3D) - # get the appropriate normal vector from the orientation - RealT = eltype(u_inner) - if orientation == 1 - normal_direction = SVector(one(RealT), zero(RealT), zero(RealT)) - elseif orientation == 2 - normal_direction = SVector(zero(RealT), one(RealT), zero(RealT)) - else # orientation == 3 - normal_direction = SVector(zero(RealT), zero(RealT), one(RealT)) + """ + initial_condition_convergence_test(x, t, equations::CompressibleEulerEquations3D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref). + """ + function initial_condition_convergence_test( + x, t, + equations::CompressibleEulerEquations3D + ) + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + ini = c + A * sin(ω * (x[1] + x[2] + x[3] - t)) + + rho = ini + rho_v1 = ini + rho_v2 = ini + rho_v3 = ini + rho_e = ini^2 + + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e) end - # compute and return the flux using `boundary_condition_slip_wall` routine above - return boundary_condition_slip_wall(u_inner, normal_direction, direction, - x, t, surface_flux_function, equations) -end - -""" - boundary_condition_slip_wall(u_inner, normal_direction, direction, x, t, - surface_flux_function, equations::CompressibleEulerEquations3D) - -Should be used together with [`StructuredMesh`](@ref). -""" -@inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, - direction, x, t, - surface_flux_function, - equations::CompressibleEulerEquations3D) - # flip sign of normal to make it outward pointing, then flip the sign of the normal flux back - # to be inward pointing on the -x, -y, and -z sides due to the orientation convention used by StructuredMesh - if isodd(direction) - boundary_flux = -boundary_condition_slip_wall(u_inner, -normal_direction, - x, t, surface_flux_function, - equations) - else - boundary_flux = boundary_condition_slip_wall(u_inner, normal_direction, - x, t, surface_flux_function, - equations) + """ + source_terms_convergence_test(u, x, t, equations::CompressibleEulerEquations3D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::CompressibleEulerEquations3D + ) + # Same settings as in `initial_condition` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + γ = equations.gamma + + x1, x2, x3 = x + si, co = sincos(ω * (x1 + x2 + x3 - t)) + rho = c + A * si + rho_x = ω * A * co + # Note that d/dt rho = -d/dx rho = -d/dy rho = - d/dz rho. + + tmp = (2 * rho - 1.5f0) * (γ - 1) + + du1 = 2 * rho_x + du2 = rho_x * (2 + tmp) + du3 = du2 + du4 = du2 + du5 = rho_x * (4 * rho + 3 * tmp) + + return SVector(du1, du2, du3, du4, du5) end - return boundary_flux -end - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, equations::CompressibleEulerEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - p = (equations.gamma - 1) * - (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3)) - if orientation == 1 - f1 = rho_v1 - f2 = rho_v1 * v1 + p - f3 = rho_v1 * v2 - f4 = rho_v1 * v3 - f5 = (rho_e + p) * v1 - elseif orientation == 2 - f1 = rho_v2 - f2 = rho_v2 * v1 - f3 = rho_v2 * v2 + p - f4 = rho_v2 * v3 - f5 = (rho_e + p) * v2 - else - f1 = rho_v3 - f2 = rho_v3 * v1 - f3 = rho_v3 * v2 - f4 = rho_v3 * v3 + p - f5 = (rho_e + p) * v3 + """ + initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerEquations3D) + + A weak blast wave taken from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave( + x, t, + equations::CompressibleEulerEquations3D + ) + # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Set up spherical coordinates + RealT = eltype(x) + inicenter = (0, 0, 0) + 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) + phi = atan(y_norm, x_norm) + theta = iszero(r) ? zero(RealT) : acos(z_norm / r) + + # Calculate primitive variables + rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) * sin(theta) + v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin(phi) * sin(theta) + v3 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(theta) + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + return prim2cons(SVector(rho, v1, v2, v3, p), equations) end - return SVector(f1, f2, f3, f4, f5) -end - -@inline function flux(u, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - rho_e = last(u) - rho, v1, v2, v3, p = cons2prim(u, equations) - - v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + - v3 * normal_direction[3] - rho_v_normal = rho * v_normal - f1 = rho_v_normal - f2 = rho_v_normal * v1 + p * normal_direction[1] - f3 = rho_v_normal * v2 + p * normal_direction[2] - f4 = rho_v_normal * v3 + p * normal_direction[3] - f5 = (rho_e + p) * v_normal - return SVector(f1, f2, f3, f4, f5) -end - -""" - flux_shima_etal(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerEquations3D) - -This flux is is a modification of the original kinetic energy preserving two-point flux by -- Yuichi Kuya, Kosuke Totani and Soshi Kawai (2018) - Kinetic energy and entropy preserving schemes for compressible flows - by split convective forms - [DOI: 10.1016/j.jcp.2018.08.058](https://doi.org/10.1016/j.jcp.2018.08.058) - -The modification is in the energy flux to guarantee pressure equilibrium and was developed by -- Nao Shima, Yuichi Kuya, Yoshiharu Tamaki, Soshi Kawai (JCP 2020) - Preventing spurious pressure oscillations in split convective form discretizations for - compressible flows - [DOI: 10.1016/j.jcp.2020.110060](https://doi.org/10.1016/j.jcp.2020.110060) -""" -@inline function flux_shima_etal(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations3D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - kin_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - - # Calculate fluxes depending on orientation - if orientation == 1 - pv1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - f1 = rho_avg * v1_avg - f2 = f1 * v1_avg + p_avg - f3 = f1 * v2_avg - f4 = f1 * v3_avg - f5 = p_avg * v1_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv1_avg - elseif orientation == 2 - pv2_avg = 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) - f1 = rho_avg * v2_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_avg - f4 = f1 * v3_avg - f5 = p_avg * v2_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv2_avg - else - pv3_avg = 0.5f0 * (p_ll * v3_rr + p_rr * v3_ll) - f1 = rho_avg * v3_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg - f4 = f1 * v3_avg + p_avg - f5 = p_avg * v3_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv3_avg + + """ + initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::CompressibleEulerEquations3D) + + Setup used for convergence tests of the Euler equations with self-gravity used in + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + in combination with [`source_terms_eoc_test_coupled_euler_gravity`](@ref) + or [`source_terms_eoc_test_euler`](@ref). + """ + function initial_condition_eoc_test_coupled_euler_gravity( + x, t, + equations::CompressibleEulerEquations3D + ) + # OBS! this assumes that γ = 2 other manufactured source terms are incorrect + if equations.gamma != 2 + error("adiabatic constant must be 2 for the coupling convergence test") + end + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + ini = c + A * sin(convert(RealT, pi) * (x[1] + x[2] + x[3] - t)) + G = 1 # gravitational constant + + rho = ini + v1 = 1 + v2 = 1 + v3 = 1 + p = ini^2 * G * 2 / (3 * convert(RealT, pi)) # "3" is the number of spatial dimensions + + return prim2cons(SVector(rho, v1, v2, v3, p), equations) end - return SVector(f1, f2, f3, f4, f5) -end + """ + source_terms_eoc_test_coupled_euler_gravity(u, x, t, equations::CompressibleEulerEquations3D) + + Setup used for convergence tests of the Euler equations with self-gravity used in + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + in combination with [`initial_condition_eoc_test_coupled_euler_gravity`](@ref). + """ + @inline function source_terms_eoc_test_coupled_euler_gravity( + u, x, t, + equations::CompressibleEulerEquations3D + ) + # Same settings as in `initial_condition_eoc_test_coupled_euler_gravity` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + G = 1 # gravitational constant, must match coupling solver + C_grav = -4 * G / (3 * convert(RealT, pi)) # "3" is the number of spatial dimensions # 2D: -2.0*G/pi + + x1, x2, x3 = x + # TODO: sincospi + si, co = sincos(convert(RealT, pi) * (x1 + x2 + x3 - t)) + rhox = A * convert(RealT, pi) * co + rho = c + A * si + + # In "2 * rhox", the "2" is "number of spatial dimensions minus one" + du1 = 2 * rhox + du2 = 2 * rhox + du3 = 2 * rhox + du4 = 2 * rhox + du5 = 2 * rhox * (1.5f0 - C_grav * rho) # "3" in "3/2" is the number of spatial dimensions + + return SVector(du1, du2, du3, du4, du5) + end -@inline function flux_shima_etal(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + - v3_ll * normal_direction[3] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + - v3_rr * normal_direction[3] - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - v_dot_n_avg = 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - - # Calculate fluxes depending on normal_direction - f1 = rho_avg * v_dot_n_avg - f2 = f1 * v1_avg + p_avg * normal_direction[1] - f3 = f1 * v2_avg + p_avg * normal_direction[2] - f4 = f1 * v3_avg + p_avg * normal_direction[3] - f5 = (f1 * velocity_square_avg + - p_avg * v_dot_n_avg * equations.inv_gamma_minus_one - + 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) - - return SVector(f1, f2, f3, f4, f5) -end - -""" - flux_kennedy_gruber(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerEquations3D) + """ + source_terms_eoc_test_euler(u, x, t, equations::CompressibleEulerEquations3D) + + Setup used for convergence tests of the Euler equations with self-gravity used in + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + in combination with [`initial_condition_eoc_test_coupled_euler_gravity`](@ref). + + !!! note + This method is to be used for testing pure Euler simulations with analytic self-gravity. + If you intend to do coupled Euler-gravity simulations, you need to use + [`source_terms_eoc_test_coupled_euler_gravity`](@ref) instead. + """ + function source_terms_eoc_test_euler(u, x, t, equations::CompressibleEulerEquations3D) + # Same settings as in `initial_condition_eoc_test_coupled_euler_gravity` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + G = 1 + C_grav = -4 * G / (3 * convert(RealT, pi)) # "3" is the number of spatial dimensions + + x1, x2, x3 = x + # TODO: sincospi + si, co = sincos(convert(RealT, pi) * (x1 + x2 + x3 - t)) + rhox = A * convert(RealT, pi) * co + rho = c + A * si + + du1 = rhox * 2 + du2 = rhox * (2 - C_grav * rho) + du3 = rhox * (2 - C_grav * rho) + du4 = rhox * (2 - C_grav * rho) + du5 = rhox * (3 - 5 * C_grav * rho) + + return SVector(du1, du2, du3, du4, du5) + end -Kinetic energy preserving two-point flux by -- Kennedy and Gruber (2008) - Reduced aliasing formulations of the convective terms within the - Navier-Stokes equations for a compressible fluid - [DOI: 10.1016/j.jcp.2007.09.020](https://doi.org/10.1016/j.jcp.2007.09.020) -""" -@inline function flux_kennedy_gruber(u_ll, u_rr, orientation::Integer, + """ + boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, equations::CompressibleEulerEquations3D) - # Unpack left and right state - rho_e_ll = last(u_ll) - rho_e_rr = last(u_rr) - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) - - # Calculate fluxes depending on orientation - if orientation == 1 - f1 = rho_avg * v1_avg - f2 = f1 * v1_avg + p_avg - f3 = f1 * v2_avg - f4 = f1 * v3_avg - f5 = (rho_avg * e_avg + p_avg) * v1_avg - elseif orientation == 2 - f1 = rho_avg * v2_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_avg - f4 = f1 * v3_avg - f5 = (rho_avg * e_avg + p_avg) * v2_avg - else - f1 = rho_avg * v3_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg - f4 = f1 * v3_avg + p_avg - f5 = (rho_avg * e_avg + p_avg) * v3_avg + + Determine the boundary numerical surface flux for a slip wall condition. + Imposes a zero normal velocity at the wall. + Density is taken from the internal solution state and pressure is computed as an + exact solution of a 1D Riemann problem. Further details about this boundary state + are available in the paper: + - J. J. W. van der Vegt and H. van der Ven (2002) + Slip flow boundary conditions in discontinuous Galerkin discretizations of + the Euler equations of gas dynamics + [PDF](https://reports.nlr.nl/bitstream/handle/10921/692/TP-2002-300.pdf?sequence=1) + + Details about the 1D pressure Riemann solution can be found in Section 6.3.3 of the book + - Eleuterio F. Toro (2009) + Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + 3rd edition + [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + """ + @inline function boundary_condition_slip_wall( + u_inner, normal_direction::AbstractVector, + x, t, + surface_flux_function, + equations::CompressibleEulerEquations3D + ) + norm_ = norm(normal_direction) + # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later + normal = normal_direction / norm_ + + # Some vector that can't be identical to normal_vector (unless normal_vector == 0) + tangent1 = SVector(normal_direction[2], normal_direction[3], -normal_direction[1]) + # Orthogonal projection + tangent1 -= dot(normal, tangent1) * normal + tangent1 = normalize(tangent1) + + # Third orthogonal vector + tangent2 = normalize(cross(normal_direction, tangent1)) + + # rotate the internal solution state + u_local = rotate_to_x(u_inner, normal, tangent1, tangent2, equations) + + # compute the primitive variables + rho_local, v_normal, v_tangent1, v_tangent2, p_local = cons2prim(u_local, equations) + + # Get the solution of the pressure Riemann problem + # See Section 6.3.3 of + # Eleuterio F. Toro (2009) + # Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + if v_normal <= 0 + sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed + p_star = p_local * + (1 + 0.5f0 * (equations.gamma - 1) * v_normal / sound_speed)^( + 2 * + equations.gamma * + equations.inv_gamma_minus_one + ) + else # v_normal > 0 + A = 2 / ((equations.gamma + 1) * rho_local) + B = p_local * (equations.gamma - 1) / (equations.gamma + 1) + p_star = p_local + + 0.5f0 * v_normal / A * + (v_normal + sqrt(v_normal^2 + 4 * A * (p_local + B))) + end + + # For the slip wall we directly set the flux as the normal velocity is zero + return SVector( + 0, + p_star * normal[1], + p_star * normal[2], + p_star * normal[3], + 0 + ) * norm_ end - return SVector(f1, f2, f3, f4, f5) -end + """ + boundary_condition_slip_wall(u_inner, orientation, direction, x, t, + surface_flux_function, equations::CompressibleEulerEquations3D) + + Should be used together with [`TreeMesh`](@ref). + """ + @inline function boundary_condition_slip_wall( + u_inner, orientation, + direction, x, t, + surface_flux_function, + equations::CompressibleEulerEquations3D + ) + # get the appropriate normal vector from the orientation + RealT = eltype(u_inner) + if orientation == 1 + normal_direction = SVector(one(RealT), zero(RealT), zero(RealT)) + elseif orientation == 2 + normal_direction = SVector(zero(RealT), one(RealT), zero(RealT)) + else # orientation == 3 + normal_direction = SVector(zero(RealT), zero(RealT), one(RealT)) + end -@inline function flux_kennedy_gruber(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - # Unpack left and right state - rho_e_ll = last(u_ll) - rho_e_rr = last(u_rr) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr = u_rr - - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - - # Average each factor of products in flux - rho_avg = 0.5f0 * (rho_ll + rho_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - v_dot_n_avg = v1_avg * normal_direction[1] + v2_avg * normal_direction[2] + - v3_avg * normal_direction[3] - p_avg = 0.5f0 * ((equations.gamma - 1) * - (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2 + v3_ll^2)) + - (equations.gamma - 1) * - (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2 + v3_rr^2))) - e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) - - # Calculate fluxes depending on normal_direction - f1 = rho_avg * v_dot_n_avg - f2 = f1 * v1_avg + p_avg * normal_direction[1] - f3 = f1 * v2_avg + p_avg * normal_direction[2] - f4 = f1 * v3_avg + p_avg * normal_direction[3] - f5 = f1 * e_avg + p_avg * v_dot_n_avg - - return SVector(f1, f2, f3, f4, f5) -end - -""" - flux_chandrashekar(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations3D) - -Entropy conserving two-point flux by -- Chandrashekar (2013) - Kinetic Energy Preserving and Entropy Stable Finite Volume Schemes - for Compressible Euler and Navier-Stokes Equations - [DOI: 10.4208/cicp.170712.010313a](https://doi.org/10.4208/cicp.170712.010313a) -""" -@inline function flux_chandrashekar(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations3D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - beta_ll = 0.5f0 * rho_ll / p_ll - beta_rr = 0.5f0 * rho_rr / p_rr - specific_kin_ll = 0.5f0 * (v1_ll^2 + v2_ll^2 + v3_ll^2) - specific_kin_rr = 0.5f0 * (v1_rr^2 + v2_rr^2 + v3_rr^2) - - # Compute the necessary mean values - rho_avg = 0.5f0 * (rho_ll + rho_rr) - rho_mean = ln_mean(rho_ll, rho_rr) - beta_mean = ln_mean(beta_ll, beta_rr) - beta_avg = 0.5f0 * (beta_ll + beta_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_mean = 0.5f0 * rho_avg / beta_avg - velocity_square_avg = specific_kin_ll + specific_kin_rr - - # Calculate fluxes depending on orientation - if orientation == 1 - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_mean - f3 = f1 * v2_avg - f4 = f1 * v3_avg - f5 = f1 * 0.5f0 * - (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + - f2 * v1_avg + f3 * v2_avg + f4 * v3_avg - elseif orientation == 2 - f1 = rho_mean * v2_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_mean - f4 = f1 * v3_avg - f5 = f1 * 0.5f0 * - (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + - f2 * v1_avg + f3 * v2_avg + f4 * v3_avg - else - f1 = rho_mean * v3_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg - f4 = f1 * v3_avg + p_mean - f5 = f1 * 0.5f0 * - (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + - f2 * v1_avg + f3 * v2_avg + f4 * v3_avg + # compute and return the flux using `boundary_condition_slip_wall` routine above + return boundary_condition_slip_wall( + u_inner, normal_direction, direction, + x, t, surface_flux_function, equations + ) end - return SVector(f1, f2, f3, f4, f5) -end - -@inline function flux_chandrashekar(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + - v3_ll * normal_direction[3] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + - v3_rr * normal_direction[3] - - beta_ll = 0.5f0 * rho_ll / p_ll - beta_rr = 0.5f0 * rho_rr / p_rr - specific_kin_ll = 0.5f0 * (v1_ll^2 + v2_ll^2 + v3_ll^2) - specific_kin_rr = 0.5f0 * (v1_rr^2 + v2_rr^2 + v3_rr^2) - - # Compute the necessary mean values - rho_avg = 0.5f0 * (rho_ll + rho_rr) - rho_mean = ln_mean(rho_ll, rho_rr) - beta_mean = ln_mean(beta_ll, beta_rr) - beta_avg = 0.5f0 * (beta_ll + beta_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_mean = 0.5f0 * rho_avg / beta_avg - velocity_square_avg = specific_kin_ll + specific_kin_rr - - # Multiply with average of normal velocities - f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - f2 = f1 * v1_avg + p_mean * normal_direction[1] - f3 = f1 * v2_avg + p_mean * normal_direction[2] - f4 = f1 * v3_avg + p_mean * normal_direction[3] - f5 = f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + - f2 * v1_avg + f3 * v2_avg + f4 * v3_avg - - return SVector(f1, f2, f3, f4, f5) -end - -""" - flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerEquations3D) - -Entropy conserving and kinetic energy preserving two-point flux by -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -See also -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_ranocha(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations3D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - # Compute the necessary mean values - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - - # Calculate fluxes depending on orientation - if orientation == 1 - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_avg - f3 = f1 * v2_avg - f4 = f1 * v3_avg - f5 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - elseif orientation == 2 - f1 = rho_mean * v2_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_avg - f4 = f1 * v3_avg - f5 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) - else # orientation == 3 - f1 = rho_mean * v3_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg - f4 = f1 * v3_avg + p_avg - f5 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5f0 * (p_ll * v3_rr + p_rr * v3_ll) + """ + boundary_condition_slip_wall(u_inner, normal_direction, direction, x, t, + surface_flux_function, equations::CompressibleEulerEquations3D) + + Should be used together with [`StructuredMesh`](@ref). + """ + @inline function boundary_condition_slip_wall( + u_inner, normal_direction::AbstractVector, + direction, x, t, + surface_flux_function, + equations::CompressibleEulerEquations3D + ) + # flip sign of normal to make it outward pointing, then flip the sign of the normal flux back + # to be inward pointing on the -x, -y, and -z sides due to the orientation convention used by StructuredMesh + if isodd(direction) + boundary_flux = -boundary_condition_slip_wall( + u_inner, -normal_direction, + x, t, surface_flux_function, + equations + ) + else + boundary_flux = boundary_condition_slip_wall( + u_inner, normal_direction, + x, t, surface_flux_function, + equations + ) + end + + return boundary_flux end - return SVector(f1, f2, f3, f4, f5) -end - -@inline function flux_ranocha(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + - v3_ll * normal_direction[3] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + - v3_rr * normal_direction[3] - - # Compute the necessary mean values - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - - # Calculate fluxes depending on normal_direction - f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - f2 = f1 * v1_avg + p_avg * normal_direction[1] - f3 = f1 * v2_avg + p_avg * normal_direction[2] - f4 = f1 * v3_avg + p_avg * normal_direction[3] - f5 = (f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) - - return SVector(f1, f2, f3, f4, f5) -end - -""" - splitting_steger_warming(u, orientation::Integer, - equations::CompressibleEulerEquations3D) - splitting_steger_warming(u, which::Union{Val{:minus}, Val{:plus}} - orientation::Integer, - equations::CompressibleEulerEquations3D) - -Splitting of the compressible Euler flux of Steger and Warming. - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. - -## References - -- Joseph L. Steger and R. F. Warming (1979) - Flux Vector Splitting of the Inviscid Gasdynamic Equations - With Application to Finite Difference Methods - [NASA Technical Memorandum](https://ntrs.nasa.gov/api/citations/19790020779/downloads/19790020779.pdf) -""" -@inline function splitting_steger_warming(u, orientation::Integer, - equations::CompressibleEulerEquations3D) - fm = splitting_steger_warming(u, Val{:minus}(), orientation, equations) - fp = splitting_steger_warming(u, Val{:plus}(), orientation, equations) - return fm, fp -end - -@inline function splitting_steger_warming(u, ::Val{:plus}, orientation::Integer, - equations::CompressibleEulerEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - p = (equations.gamma - 1) * - (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3)) - a = sqrt(equations.gamma * p / rho) - - if orientation == 1 - lambda1 = v1 - lambda2 = v1 + a - lambda3 = v1 - a - - lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) - lambda2_p = positive_part(lambda2) - lambda3_p = positive_part(lambda3) - - alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1p = rho_2gamma * alpha_p - f2p = rho_2gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) - f3p = rho_2gamma * alpha_p * v2 - f4p = rho_2gamma * alpha_p * v3 - f5p = rho_2gamma * - (alpha_p * 0.5f0 * (v1^2 + v2^2 + v3^2) + - a * v1 * - (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) - elseif orientation == 2 - lambda1 = v2 - lambda2 = v2 + a - lambda3 = v2 - a - - lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) - lambda2_p = positive_part(lambda2) - lambda3_p = positive_part(lambda3) - - alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1p = rho_2gamma * alpha_p - f2p = rho_2gamma * alpha_p * v1 - f3p = rho_2gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) - f4p = rho_2gamma * alpha_p * v3 - f5p = rho_2gamma * - (alpha_p * 0.5f0 * (v1^2 + v2^2 + v3^2) + - a * v2 * - (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) - else # orientation == 3 - lambda1 = v3 - lambda2 = v3 + a - lambda3 = v3 - a - - lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) - lambda2_p = positive_part(lambda2) - lambda3_p = positive_part(lambda3) - - alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1p = rho_2gamma * alpha_p - f2p = rho_2gamma * alpha_p * v1 - f3p = rho_2gamma * alpha_p * v2 - f4p = rho_2gamma * (alpha_p * v3 + a * (lambda2_p - lambda3_p)) - f5p = rho_2gamma * - (alpha_p * 0.5f0 * (v1^2 + v2^2 + v3^2) + - a * v3 * - (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) + # Calculate 1D flux for a single point + @inline function flux(u, orientation::Integer, equations::CompressibleEulerEquations3D) + rho, rho_v1, rho_v2, rho_v3, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + p = (equations.gamma - 1) * + (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3)) + if orientation == 1 + f1 = rho_v1 + f2 = rho_v1 * v1 + p + f3 = rho_v1 * v2 + f4 = rho_v1 * v3 + f5 = (rho_e + p) * v1 + elseif orientation == 2 + f1 = rho_v2 + f2 = rho_v2 * v1 + f3 = rho_v2 * v2 + p + f4 = rho_v2 * v3 + f5 = (rho_e + p) * v2 + else + f1 = rho_v3 + f2 = rho_v3 * v1 + f3 = rho_v3 * v2 + f4 = rho_v3 * v3 + p + f5 = (rho_e + p) * v3 + end + return SVector(f1, f2, f3, f4, f5) end - return SVector(f1p, f2p, f3p, f4p, f5p) -end - -@inline function splitting_steger_warming(u, ::Val{:minus}, orientation::Integer, - equations::CompressibleEulerEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - p = (equations.gamma - 1) * - (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3)) - a = sqrt(equations.gamma * p / rho) - - if orientation == 1 - lambda1 = v1 - lambda2 = v1 + a - lambda3 = v1 - a - - lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) - lambda2_m = negative_part(lambda2) - lambda3_m = negative_part(lambda3) - - alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1m = rho_2gamma * alpha_m - f2m = rho_2gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) - f3m = rho_2gamma * alpha_m * v2 - f4m = rho_2gamma * alpha_m * v3 - f5m = rho_2gamma * - (alpha_m * 0.5f0 * (v1^2 + v2^2 + v3^2) + - a * v1 * - (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) - elseif orientation == 2 - lambda1 = v2 - lambda2 = v2 + a - lambda3 = v2 - a - - lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) - lambda2_m = negative_part(lambda2) - lambda3_m = negative_part(lambda3) - - alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1m = rho_2gamma * alpha_m - f2m = rho_2gamma * alpha_m * v1 - f3m = rho_2gamma * (alpha_m * v2 + a * (lambda2_m - lambda3_m)) - f4m = rho_2gamma * alpha_m * v3 - f5m = rho_2gamma * - (alpha_m * 0.5f0 * (v1^2 + v2^2 + v3^2) + - a * v2 * - (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) - else # orientation == 3 - lambda1 = v3 - lambda2 = v3 + a - lambda3 = v3 - a - - lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) - lambda2_m = negative_part(lambda2) - lambda3_m = negative_part(lambda3) - - alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m - - rho_2gamma = 0.5f0 * rho / equations.gamma - f1m = rho_2gamma * alpha_m - f2m = rho_2gamma * alpha_m * v1 - f3m = rho_2gamma * alpha_m * v2 - f4m = rho_2gamma * (alpha_m * v3 + a * (lambda2_m - lambda3_m)) - f5m = rho_2gamma * - (alpha_m * 0.5f0 * (v1^2 + v2^2 + v3^2) + - a * v3 * - (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) + + @inline function flux( + u, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + rho_e = last(u) + rho, v1, v2, v3, p = cons2prim(u, equations) + + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + + v3 * normal_direction[3] + rho_v_normal = rho * v_normal + f1 = rho_v_normal + f2 = rho_v_normal * v1 + p * normal_direction[1] + f3 = rho_v_normal * v2 + p * normal_direction[2] + f4 = rho_v_normal * v3 + p * normal_direction[3] + f5 = (rho_e + p) * v_normal + return SVector(f1, f2, f3, f4, f5) end - return SVector(f1m, f2m, f3m, f4m, f5m) -end - -""" - FluxLMARS(c)(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerEquations3D) - -Low Mach number approximate Riemann solver (LMARS) for atmospheric flows using -an estimate `c` of the speed of sound. - -References: -- Xi Chen et al. (2013) - A Control-Volume Model of the Compressible Euler Equations with a Vertical - Lagrangian Coordinate - [DOI: 10.1175/MWR-D-12-00129.1](https://doi.org/10.1175/mwr-d-12-00129.1) -""" -@inline function (flux_lmars::FluxLMARS)(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations3D) - c = flux_lmars.speed_of_sound - - # Unpack left and right state - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - if orientation == 1 - v_ll = v1_ll - v_rr = v1_rr - elseif orientation == 2 - v_ll = v2_ll - v_rr = v2_rr - else # orientation == 3 - v_ll = v3_ll - v_rr = v3_rr + + """ + flux_shima_etal(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerEquations3D) + + This flux is is a modification of the original kinetic energy preserving two-point flux by + - Yuichi Kuya, Kosuke Totani and Soshi Kawai (2018) + Kinetic energy and entropy preserving schemes for compressible flows + by split convective forms + [DOI: 10.1016/j.jcp.2018.08.058](https://doi.org/10.1016/j.jcp.2018.08.058) + + The modification is in the energy flux to guarantee pressure equilibrium and was developed by + - Nao Shima, Yuichi Kuya, Yoshiharu Tamaki, Soshi Kawai (JCP 2020) + Preventing spurious pressure oscillations in split convective form discretizations for + compressible flows + [DOI: 10.1016/j.jcp.2020.110060](https://doi.org/10.1016/j.jcp.2020.110060) + """ + @inline function flux_shima_etal( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + kin_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + + # Calculate fluxes depending on orientation + if orientation == 1 + pv1_avg = 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + f1 = rho_avg * v1_avg + f2 = f1 * v1_avg + p_avg + f3 = f1 * v2_avg + f4 = f1 * v3_avg + f5 = p_avg * v1_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv1_avg + elseif orientation == 2 + pv2_avg = 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) + f1 = rho_avg * v2_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_avg + f4 = f1 * v3_avg + f5 = p_avg * v2_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv2_avg + else + pv3_avg = 0.5f0 * (p_ll * v3_rr + p_rr * v3_ll) + f1 = rho_avg * v3_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + f4 = f1 * v3_avg + p_avg + f5 = p_avg * v3_avg * equations.inv_gamma_minus_one + f1 * kin_avg + pv3_avg + end + + return SVector(f1, f2, f3, f4, f5) end - rho = 0.5f0 * (rho_ll + rho_rr) - p = 0.5f0 * (p_ll + p_rr) - 0.5f0 * c * rho * (v_rr - v_ll) - v = 0.5f0 * (v_ll + v_rr) - 1 / (2 * c * rho) * (p_rr - p_ll) - - # We treat the energy term analogous to the potential temperature term in the paper by - # Chen et al., i.e. we use p_ll and p_rr, and not p - if v >= 0 - f1, f2, f3, f4, f5 = v * u_ll - f5 = f5 + p_ll * v - else - f1, f2, f3, f4, f5 = v * u_rr - f5 = f5 + p_rr * v + @inline function flux_shima_etal( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + v_dot_n_avg = 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + + # Calculate fluxes depending on normal_direction + f1 = rho_avg * v_dot_n_avg + f2 = f1 * v1_avg + p_avg * normal_direction[1] + f3 = f1 * v2_avg + p_avg * normal_direction[2] + f4 = f1 * v3_avg + p_avg * normal_direction[3] + f5 = ( + f1 * velocity_square_avg + + p_avg * v_dot_n_avg * equations.inv_gamma_minus_one + + 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) + + return SVector(f1, f2, f3, f4, f5) end - if orientation == 1 - f2 += p - elseif orientation == 2 - f3 += p - else # orientation == 3 - f4 += p + """ + flux_kennedy_gruber(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerEquations3D) + + Kinetic energy preserving two-point flux by + - Kennedy and Gruber (2008) + Reduced aliasing formulations of the convective terms within the + Navier-Stokes equations for a compressible fluid + [DOI: 10.1016/j.jcp.2007.09.020](https://doi.org/10.1016/j.jcp.2007.09.020) + """ + @inline function flux_kennedy_gruber( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + # Unpack left and right state + rho_e_ll = last(u_ll) + rho_e_rr = last(u_rr) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) + + # Calculate fluxes depending on orientation + if orientation == 1 + f1 = rho_avg * v1_avg + f2 = f1 * v1_avg + p_avg + f3 = f1 * v2_avg + f4 = f1 * v3_avg + f5 = (rho_avg * e_avg + p_avg) * v1_avg + elseif orientation == 2 + f1 = rho_avg * v2_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_avg + f4 = f1 * v3_avg + f5 = (rho_avg * e_avg + p_avg) * v2_avg + else + f1 = rho_avg * v3_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + f4 = f1 * v3_avg + p_avg + f5 = (rho_avg * e_avg + p_avg) * v3_avg + end + + return SVector(f1, f2, f3, f4, f5) end - return SVector(f1, f2, f3, f4, f5) -end - -@inline function (flux_lmars::FluxLMARS)(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - c = flux_lmars.speed_of_sound - - # Unpack left and right state - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - v_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + - v3_ll * normal_direction[3] - v_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + - v3_rr * normal_direction[3] - - # Note that this is the same as computing v_ll and v_rr with a normalized normal vector - # and then multiplying v by `norm_` again, but this version is slightly faster. - norm_ = norm(normal_direction) - - rho = 0.5f0 * (rho_ll + rho_rr) - p = 0.5f0 * (p_ll + p_rr) - 0.5f0 * c * rho * (v_rr - v_ll) / norm_ - v = 0.5f0 * (v_ll + v_rr) - 1 / (2 * c * rho) * (p_rr - p_ll) * norm_ - - # We treat the energy term analogous to the potential temperature term in the paper by - # Chen et al., i.e. we use p_ll and p_rr, and not p - if v >= 0 - f1, f2, f3, f4, f5 = v * u_ll - f5 = f5 + p_ll * v - else - f1, f2, f3, f4, f5 = v * u_rr - f5 = f5 + p_rr * v + @inline function flux_kennedy_gruber( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + # Unpack left and right state + rho_e_ll = last(u_ll) + rho_e_rr = last(u_rr) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr = u_rr + + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + + # Average each factor of products in flux + rho_avg = 0.5f0 * (rho_ll + rho_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + v_dot_n_avg = v1_avg * normal_direction[1] + v2_avg * normal_direction[2] + + v3_avg * normal_direction[3] + p_avg = 0.5f0 * ( + (equations.gamma - 1) * + (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2 + v3_ll^2)) + + (equations.gamma - 1) * + (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2 + v3_rr^2)) + ) + e_avg = 0.5f0 * (rho_e_ll / rho_ll + rho_e_rr / rho_rr) + + # Calculate fluxes depending on normal_direction + f1 = rho_avg * v_dot_n_avg + f2 = f1 * v1_avg + p_avg * normal_direction[1] + f3 = f1 * v2_avg + p_avg * normal_direction[2] + f4 = f1 * v3_avg + p_avg * normal_direction[3] + f5 = f1 * e_avg + p_avg * v_dot_n_avg + + return SVector(f1, f2, f3, f4, f5) end - return SVector(f1, - f2 + p * normal_direction[1], - f3 + p * normal_direction[2], - f4 + p * normal_direction[3], - f5) -end + """ + flux_chandrashekar(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations3D) + + Entropy conserving two-point flux by + - Chandrashekar (2013) + Kinetic Energy Preserving and Entropy Stable Finite Volume Schemes + for Compressible Euler and Navier-Stokes Equations + [DOI: 10.4208/cicp.170712.010313a](https://doi.org/10.4208/cicp.170712.010313a) + """ + @inline function flux_chandrashekar( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + beta_ll = 0.5f0 * rho_ll / p_ll + beta_rr = 0.5f0 * rho_rr / p_rr + specific_kin_ll = 0.5f0 * (v1_ll^2 + v2_ll^2 + v3_ll^2) + specific_kin_rr = 0.5f0 * (v1_rr^2 + v2_rr^2 + v3_rr^2) + + # Compute the necessary mean values + rho_avg = 0.5f0 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5f0 * (beta_ll + beta_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_mean = 0.5f0 * rho_avg / beta_avg + velocity_square_avg = specific_kin_ll + specific_kin_rr + + # Calculate fluxes depending on orientation + if orientation == 1 + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_mean + f3 = f1 * v2_avg + f4 = f1 * v3_avg + f5 = f1 * 0.5f0 * + (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + f3 * v2_avg + f4 * v3_avg + elseif orientation == 2 + f1 = rho_mean * v2_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_mean + f4 = f1 * v3_avg + f5 = f1 * 0.5f0 * + (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + f3 * v2_avg + f4 * v3_avg + else + f1 = rho_mean * v3_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + f4 = f1 * v3_avg + p_mean + f5 = f1 * 0.5f0 * + (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + f3 * v2_avg + f4 * v3_avg + end -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the -# maximum velocity magnitude plus the maximum speed of sound -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations3D) - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - # Get the velocity value in the appropriate direction - if orientation == 1 - v_ll = v1_ll - v_rr = v1_rr - elseif orientation == 2 - v_ll = v2_ll - v_rr = v2_rr - else # orientation == 3 - v_ll = v3_ll - v_rr = v3_rr + return SVector(f1, f2, f3, f4, f5) + end + + @inline function flux_chandrashekar( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + beta_ll = 0.5f0 * rho_ll / p_ll + beta_rr = 0.5f0 * rho_rr / p_rr + specific_kin_ll = 0.5f0 * (v1_ll^2 + v2_ll^2 + v3_ll^2) + specific_kin_rr = 0.5f0 * (v1_rr^2 + v2_rr^2 + v3_rr^2) + + # Compute the necessary mean values + rho_avg = 0.5f0 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5f0 * (beta_ll + beta_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_mean = 0.5f0 * rho_avg / beta_avg + velocity_square_avg = specific_kin_ll + specific_kin_rr + + # Multiply with average of normal velocities + f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + f2 = f1 * v1_avg + p_mean * normal_direction[1] + f3 = f1 * v2_avg + p_mean * normal_direction[2] + f4 = f1 * v3_avg + p_mean * normal_direction[3] + f5 = f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - velocity_square_avg) + + f2 * v1_avg + f3 * v2_avg + f4 * v3_avg + + return SVector(f1, f2, f3, f4, f5) end - # Calculate sound speeds - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) -end + """ + flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerEquations3D) + + Entropy conserving and kinetic energy preserving two-point flux by + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + See also + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_ranocha( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + # Compute the necessary mean values + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + + # Calculate fluxes depending on orientation + if orientation == 1 + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_avg + f3 = f1 * v2_avg + f4 = f1 * v3_avg + f5 = f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + elseif orientation == 2 + f1 = rho_mean * v2_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_avg + f4 = f1 * v3_avg + f5 = f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) + else # orientation == 3 + f1 = rho_mean * v3_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + f4 = f1 * v3_avg + p_avg + f5 = f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5f0 * (p_ll * v3_rr + p_rr * v3_ll) + end -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - # Calculate normal velocities and sound speed - # left - v_ll = (v1_ll * normal_direction[1] - + v2_ll * normal_direction[2] - + v3_ll * normal_direction[3]) - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - # right - v_rr = (v1_rr * normal_direction[1] - + v2_rr * normal_direction[2] - + v3_rr * normal_direction[3]) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) -end - -# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations3D) - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - if orientation == 1 # x-direction - λ_min = v1_ll - sqrt(equations.gamma * p_ll / rho_ll) - λ_max = v1_rr + sqrt(equations.gamma * p_rr / rho_rr) - elseif orientation == 2 # y-direction - λ_min = v2_ll - sqrt(equations.gamma * p_ll / rho_ll) - λ_max = v2_rr + sqrt(equations.gamma * p_rr / rho_rr) - else # z-direction - λ_min = v3_ll - sqrt(equations.gamma * p_ll / rho_ll) - λ_max = v3_rr + sqrt(equations.gamma * p_rr / rho_rr) + return SVector(f1, f2, f3, f4, f5) end - return λ_min, λ_max -end + @inline function flux_ranocha( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + # Compute the necessary mean values + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + + # Calculate fluxes depending on normal_direction + f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + f2 = f1 * v1_avg + p_avg * normal_direction[1] + f3 = f1 * v2_avg + p_avg * normal_direction[2] + f4 = f1 * v3_avg + p_avg * normal_direction[3] + f5 = ( + f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) + + return SVector(f1, f2, f3, f4, f5) + end -@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + """ + splitting_steger_warming(u, orientation::Integer, + equations::CompressibleEulerEquations3D) + splitting_steger_warming(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, + equations::CompressibleEulerEquations3D) + + Splitting of the compressible Euler flux of Steger and Warming. + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + + ## References + + - Joseph L. Steger and R. F. Warming (1979) + Flux Vector Splitting of the Inviscid Gasdynamic Equations + With Application to Finite Difference Methods + [NASA Technical Memorandum](https://ntrs.nasa.gov/api/citations/19790020779/downloads/19790020779.pdf) + """ + @inline function splitting_steger_warming( + u, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + fm = splitting_steger_warming(u, Val{:minus}(), orientation, equations) + fp = splitting_steger_warming(u, Val{:plus}(), orientation, equations) + return fm, fp + end - v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + - v3_ll * normal_direction[3] - v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + - v3_rr * normal_direction[3] + @inline function splitting_steger_warming( + u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + rho, rho_v1, rho_v2, rho_v3, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + p = (equations.gamma - 1) * + (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3)) + a = sqrt(equations.gamma * p / rho) + + if orientation == 1 + lambda1 = v1 + lambda2 = v1 + a + lambda3 = v1 - a + + lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1p = rho_2gamma * alpha_p + f2p = rho_2gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) + f3p = rho_2gamma * alpha_p * v2 + f4p = rho_2gamma * alpha_p * v3 + f5p = rho_2gamma * + ( + alpha_p * 0.5f0 * (v1^2 + v2^2 + v3^2) + + a * v1 * + (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one + ) + elseif orientation == 2 + lambda1 = v2 + lambda2 = v2 + a + lambda3 = v2 - a + + lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1p = rho_2gamma * alpha_p + f2p = rho_2gamma * alpha_p * v1 + f3p = rho_2gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) + f4p = rho_2gamma * alpha_p * v3 + f5p = rho_2gamma * + ( + alpha_p * 0.5f0 * (v1^2 + v2^2 + v3^2) + + a * v2 * + (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one + ) + else # orientation == 3 + lambda1 = v3 + lambda2 = v3 + a + lambda3 = v3 - a + + lambda1_p = positive_part(lambda1) # Same as (lambda_i + abs(lambda_i)) / 2, but faster :) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1p = rho_2gamma * alpha_p + f2p = rho_2gamma * alpha_p * v1 + f3p = rho_2gamma * alpha_p * v2 + f4p = rho_2gamma * (alpha_p * v3 + a * (lambda2_p - lambda3_p)) + f5p = rho_2gamma * + ( + alpha_p * 0.5f0 * (v1^2 + v2^2 + v3^2) + + a * v3 * + (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one + ) + end + return SVector(f1p, f2p, f3p, f4p, f5p) + end - norm_ = norm(normal_direction) - # The v_normals are already scaled by the norm - λ_min = v_normal_ll - sqrt(equations.gamma * p_ll / rho_ll) * norm_ - λ_max = v_normal_rr + sqrt(equations.gamma * p_rr / rho_rr) * norm_ + @inline function splitting_steger_warming( + u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + rho, rho_v1, rho_v2, rho_v3, rho_e = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + p = (equations.gamma - 1) * + (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3)) + a = sqrt(equations.gamma * p / rho) + + if orientation == 1 + lambda1 = v1 + lambda2 = v1 + a + lambda3 = v1 - a + + lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1m = rho_2gamma * alpha_m + f2m = rho_2gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) + f3m = rho_2gamma * alpha_m * v2 + f4m = rho_2gamma * alpha_m * v3 + f5m = rho_2gamma * + ( + alpha_m * 0.5f0 * (v1^2 + v2^2 + v3^2) + + a * v1 * + (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one + ) + elseif orientation == 2 + lambda1 = v2 + lambda2 = v2 + a + lambda3 = v2 - a + + lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1m = rho_2gamma * alpha_m + f2m = rho_2gamma * alpha_m * v1 + f3m = rho_2gamma * (alpha_m * v2 + a * (lambda2_m - lambda3_m)) + f4m = rho_2gamma * alpha_m * v3 + f5m = rho_2gamma * + ( + alpha_m * 0.5f0 * (v1^2 + v2^2 + v3^2) + + a * v2 * + (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one + ) + else # orientation == 3 + lambda1 = v3 + lambda2 = v3 + a + lambda3 = v3 - a + + lambda1_m = negative_part(lambda1) # Same as (lambda_i - abs(lambda_i)) / 2, but faster :) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + rho_2gamma = 0.5f0 * rho / equations.gamma + f1m = rho_2gamma * alpha_m + f2m = rho_2gamma * alpha_m * v1 + f3m = rho_2gamma * alpha_m * v2 + f4m = rho_2gamma * (alpha_m * v3 + a * (lambda2_m - lambda3_m)) + f5m = rho_2gamma * + ( + alpha_m * 0.5f0 * (v1^2 + v2^2 + v3^2) + + a * v3 * + (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one + ) + end + return SVector(f1m, f2m, f3m, f4m, f5m) + end - return λ_min, λ_max -end + """ + FluxLMARS(c)(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerEquations3D) + + Low Mach number approximate Riemann solver (LMARS) for atmospheric flows using + an estimate `c` of the speed of sound. + + References: + - Xi Chen et al. (2013) + A Control-Volume Model of the Compressible Euler Equations with a Vertical + Lagrangian Coordinate + [DOI: 10.1175/MWR-D-12-00129.1](https://doi.org/10.1175/mwr-d-12-00129.1) + """ + @inline function (flux_lmars::FluxLMARS)( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + c = flux_lmars.speed_of_sound + + # Unpack left and right state + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + if orientation == 1 + v_ll = v1_ll + v_rr = v1_rr + elseif orientation == 2 + v_ll = v2_ll + v_rr = v2_rr + else # orientation == 3 + v_ll = v3_ll + v_rr = v3_rr + end -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations3D) - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - if orientation == 1 # x-direction - λ_min = min(v1_ll - c_ll, v1_rr - c_rr) - λ_max = max(v1_ll + c_ll, v1_rr + c_rr) - elseif orientation == 2 # y-direction - λ_min = min(v2_ll - c_ll, v2_rr - c_rr) - λ_max = max(v2_ll + c_ll, v2_rr + c_rr) - else # z-direction - λ_min = min(v3_ll - c_ll, v3_rr - c_rr) - λ_max = max(v3_ll + c_ll, v3_rr + c_rr) + rho = 0.5f0 * (rho_ll + rho_rr) + p = 0.5f0 * (p_ll + p_rr) - 0.5f0 * c * rho * (v_rr - v_ll) + v = 0.5f0 * (v_ll + v_rr) - 1 / (2 * c * rho) * (p_rr - p_ll) + + # We treat the energy term analogous to the potential temperature term in the paper by + # Chen et al., i.e. we use p_ll and p_rr, and not p + if v >= 0 + f1, f2, f3, f4, f5 = v * u_ll + f5 = f5 + p_ll * v + else + f1, f2, f3, f4, f5 = v * u_rr + f5 = f5 + p_rr * v + end + + if orientation == 1 + f2 += p + elseif orientation == 2 + f3 += p + else # orientation == 3 + f4 += p + end + + return SVector(f1, f2, f3, f4, f5) end - return λ_min, λ_max -end + @inline function (flux_lmars::FluxLMARS)( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + c = flux_lmars.speed_of_sound + + # Unpack left and right state + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + v_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + # Note that this is the same as computing v_ll and v_rr with a normalized normal vector + # and then multiplying v by `norm_` again, but this version is slightly faster. + norm_ = norm(normal_direction) + + rho = 0.5f0 * (rho_ll + rho_rr) + p = 0.5f0 * (p_ll + p_rr) - 0.5f0 * c * rho * (v_rr - v_ll) / norm_ + v = 0.5f0 * (v_ll + v_rr) - 1 / (2 * c * rho) * (p_rr - p_ll) * norm_ + + # We treat the energy term analogous to the potential temperature term in the paper by + # Chen et al., i.e. we use p_ll and p_rr, and not p + if v >= 0 + f1, f2, f3, f4, f5 = v * u_ll + f5 = f5 + p_ll * v + else + f1, f2, f3, f4, f5 = v * u_rr + f5 = f5 + p_rr * v + end -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - norm_ = norm(normal_direction) - - c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ - c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ - - v_normal_ll = v1_ll * normal_direction[1] + - v2_ll * normal_direction[2] + - v3_ll * normal_direction[3] - v_normal_rr = v1_rr * normal_direction[1] + - v2_rr * normal_direction[2] + - v3_rr * normal_direction[3] - - # The v_normals are already scaled by the norm - λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) - λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) - - return λ_min, λ_max -end - -# Rotate normal vector to x-axis; normal, tangent1 and tangent2 need to be orthonormal -# Called inside `FluxRotated` in `numerical_fluxes.jl` so the directions -# has been normalized prior to this rotation of the state vector -@inline function rotate_to_x(u, normal_vector, tangent1, tangent2, - equations::CompressibleEulerEquations3D) - # Multiply with [ 1 0 0 0 0; - # 0 ― normal_vector ― 0; - # 0 ― tangent1 ― 0; - # 0 ― tangent2 ― 0; - # 0 0 0 0 1 ] - return SVector(u[1], - normal_vector[1] * u[2] + normal_vector[2] * u[3] + - normal_vector[3] * u[4], - tangent1[1] * u[2] + tangent1[2] * u[3] + tangent1[3] * u[4], - tangent2[1] * u[2] + tangent2[2] * u[3] + tangent2[3] * u[4], - u[5]) -end - -# Rotate x-axis to normal vector; normal, tangent1 and tangent2 need to be orthonormal -# Called inside `FluxRotated` in `numerical_fluxes.jl` so the directions -# has been normalized prior to this back-rotation of the state vector -@inline function rotate_from_x(u, normal_vector, tangent1, tangent2, - equations::CompressibleEulerEquations3D) - # Multiply with [ 1 0 0 0 0; - # 0 | | | 0; - # 0 normal_vector tangent1 tangent2 0; - # 0 | | | 0; - # 0 0 0 0 1 ] - return SVector(u[1], - normal_vector[1] * u[2] + tangent1[1] * u[3] + tangent2[1] * u[4], - normal_vector[2] * u[2] + tangent1[2] * u[3] + tangent2[2] * u[4], - normal_vector[3] * u[2] + tangent1[3] * u[3] + tangent2[3] * u[4], - u[5]) -end - -""" - flux_hllc(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations3D) - -Computes the HLLC flux (HLL with Contact) for compressible Euler equations developed by E.F. Toro -[Lecture slides](http://www.prague-sum.com/download/2012/Toro_2-HLLC-RiemannSolver.pdf) -Signal speeds: [DOI: 10.1137/S1064827593260140](https://doi.org/10.1137/S1064827593260140) -""" -function flux_hllc(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations3D) - # Calculate primitive variables and speed of sound - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr = u_rr - - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - e_ll = rho_e_ll / rho_ll - p_ll = (equations.gamma - 1) * - (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2 + v3_ll^2)) - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - e_rr = rho_e_rr / rho_rr - p_rr = (equations.gamma - 1) * - (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2 + v3_rr^2)) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - # Obtain left and right fluxes - f_ll = flux(u_ll, orientation, equations) - f_rr = flux(u_rr, orientation, equations) - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr - if orientation == 1 # x-direction - vel_L = v1_ll - vel_R = v1_rr - elseif orientation == 2 # y-direction - vel_L = v2_ll - vel_R = v2_rr - else # z-direction - vel_L = v3_ll - vel_R = v3_rr + return SVector( + f1, + f2 + p * normal_direction[1], + f3 + p * normal_direction[2], + f4 + p * normal_direction[3], + f5 + ) end - vel_roe = (sqrt_rho_ll * vel_L + sqrt_rho_rr * vel_R) / sum_sqrt_rho - v1_roe = sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr - v2_roe = sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr - v3_roe = sqrt_rho_ll * v3_ll + sqrt_rho_rr * v3_rr - vel_roe_mag = (v1_roe^2 + v2_roe^2 + v3_roe^2) / sum_sqrt_rho^2 - H_ll = (rho_e_ll + p_ll) / rho_ll - H_rr = (rho_e_rr + p_rr) / rho_rr - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * vel_roe_mag)) - Ssl = min(vel_L - c_ll, vel_roe - c_roe) - Ssr = max(vel_R + c_rr, vel_roe + c_roe) - sMu_L = Ssl - vel_L - sMu_R = Ssr - vel_R - - if Ssl >= 0 - f1 = f_ll[1] - f2 = f_ll[2] - f3 = f_ll[3] - f4 = f_ll[4] - f5 = f_ll[5] - elseif Ssr <= 0 - f1 = f_rr[1] - f2 = f_rr[2] - f3 = f_rr[3] - f4 = f_rr[4] - f5 = f_rr[5] - else - SStar = (p_rr - p_ll + rho_ll * vel_L * sMu_L - rho_rr * vel_R * sMu_R) / - (rho_ll * sMu_L - rho_rr * sMu_R) - if Ssl <= 0 <= SStar - densStar = rho_ll * sMu_L / (Ssl - SStar) - enerStar = e_ll + (SStar - vel_L) * (SStar + p_ll / (rho_ll * sMu_L)) - UStar1 = densStar - UStar5 = densStar * enerStar - if orientation == 1 # x-direction - UStar2 = densStar * SStar - UStar3 = densStar * v2_ll - UStar4 = densStar * v3_ll - elseif orientation == 2 # y-direction - UStar2 = densStar * v1_ll - UStar3 = densStar * SStar - UStar4 = densStar * v3_ll - else # z-direction - UStar2 = densStar * v1_ll - UStar3 = densStar * v2_ll - UStar4 = densStar * SStar - end - f1 = f_ll[1] + Ssl * (UStar1 - rho_ll) - f2 = f_ll[2] + Ssl * (UStar2 - rho_v1_ll) - f3 = f_ll[3] + Ssl * (UStar3 - rho_v2_ll) - f4 = f_ll[4] + Ssl * (UStar4 - rho_v3_ll) - f5 = f_ll[5] + Ssl * (UStar5 - rho_e_ll) + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the + # maximum velocity magnitude plus the maximum speed of sound + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + # Get the velocity value in the appropriate direction + if orientation == 1 + v_ll = v1_ll + v_rr = v1_rr + elseif orientation == 2 + v_ll = v2_ll + v_rr = v2_rr + else # orientation == 3 + v_ll = v3_ll + v_rr = v3_rr + end + # Calculate sound speeds + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + # Calculate normal velocities and sound speed + # left + v_ll = ( + v1_ll * normal_direction[1] + + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + ) + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + # right + v_rr = ( + v1_rr * normal_direction[1] + + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + ) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) + end + + # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + if orientation == 1 # x-direction + λ_min = v1_ll - sqrt(equations.gamma * p_ll / rho_ll) + λ_max = v1_rr + sqrt(equations.gamma * p_rr / rho_rr) + elseif orientation == 2 # y-direction + λ_min = v2_ll - sqrt(equations.gamma * p_ll / rho_ll) + λ_max = v2_rr + sqrt(equations.gamma * p_rr / rho_rr) + else # z-direction + λ_min = v3_ll - sqrt(equations.gamma * p_ll / rho_ll) + λ_max = v3_rr + sqrt(equations.gamma * p_rr / rho_rr) + end + + return λ_min, λ_max + end + + @inline function min_max_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + norm_ = norm(normal_direction) + # The v_normals are already scaled by the norm + λ_min = v_normal_ll - sqrt(equations.gamma * p_ll / rho_ll) * norm_ + λ_max = v_normal_rr + sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + if orientation == 1 # x-direction + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + elseif orientation == 2 # y-direction + λ_min = min(v2_ll - c_ll, v2_rr - c_rr) + λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + else # z-direction + λ_min = min(v3_ll - c_ll, v3_rr - c_rr) + λ_max = max(v3_ll + c_ll, v3_rr + c_rr) + end + + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + norm_ = norm(normal_direction) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ + c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + v_normal_ll = v1_ll * normal_direction[1] + + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_normal_rr = v1_rr * normal_direction[1] + + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + # The v_normals are already scaled by the norm + λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) + λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) + + return λ_min, λ_max + end + + # Rotate normal vector to x-axis; normal, tangent1 and tangent2 need to be orthonormal + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the directions + # has been normalized prior to this rotation of the state vector + @inline function rotate_to_x( + u, normal_vector, tangent1, tangent2, + equations::CompressibleEulerEquations3D + ) + # Multiply with [ 1 0 0 0 0; + # 0 ― normal_vector ― 0; + # 0 ― tangent1 ― 0; + # 0 ― tangent2 ― 0; + # 0 0 0 0 1 ] + return SVector( + u[1], + normal_vector[1] * u[2] + normal_vector[2] * u[3] + + normal_vector[3] * u[4], + tangent1[1] * u[2] + tangent1[2] * u[3] + tangent1[3] * u[4], + tangent2[1] * u[2] + tangent2[2] * u[3] + tangent2[3] * u[4], + u[5] + ) + end + + # Rotate x-axis to normal vector; normal, tangent1 and tangent2 need to be orthonormal + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the directions + # has been normalized prior to this back-rotation of the state vector + @inline function rotate_from_x( + u, normal_vector, tangent1, tangent2, + equations::CompressibleEulerEquations3D + ) + # Multiply with [ 1 0 0 0 0; + # 0 | | | 0; + # 0 normal_vector tangent1 tangent2 0; + # 0 | | | 0; + # 0 0 0 0 1 ] + return SVector( + u[1], + normal_vector[1] * u[2] + tangent1[1] * u[3] + tangent2[1] * u[4], + normal_vector[2] * u[2] + tangent1[2] * u[3] + tangent2[2] * u[4], + normal_vector[3] * u[2] + tangent1[3] * u[3] + tangent2[3] * u[4], + u[5] + ) + end + + """ + flux_hllc(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations3D) + + Computes the HLLC flux (HLL with Contact) for compressible Euler equations developed by E.F. Toro + [Lecture slides](http://www.prague-sum.com/download/2012/Toro_2-HLLC-RiemannSolver.pdf) + Signal speeds: [DOI: 10.1137/S1064827593260140](https://doi.org/10.1137/S1064827593260140) + """ + function flux_hllc( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + # Calculate primitive variables and speed of sound + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr = u_rr + + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + e_ll = rho_e_ll / rho_ll + p_ll = (equations.gamma - 1) * + (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2 + v3_ll^2)) + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + e_rr = rho_e_rr / rho_rr + p_rr = (equations.gamma - 1) * + (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2 + v3_rr^2)) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + # Obtain left and right fluxes + f_ll = flux(u_ll, orientation, equations) + f_rr = flux(u_rr, orientation, equations) + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr + if orientation == 1 # x-direction + vel_L = v1_ll + vel_R = v1_rr + elseif orientation == 2 # y-direction + vel_L = v2_ll + vel_R = v2_rr + else # z-direction + vel_L = v3_ll + vel_R = v3_rr + end + vel_roe = (sqrt_rho_ll * vel_L + sqrt_rho_rr * vel_R) / sum_sqrt_rho + v1_roe = sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr + v2_roe = sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr + v3_roe = sqrt_rho_ll * v3_ll + sqrt_rho_rr * v3_rr + vel_roe_mag = (v1_roe^2 + v2_roe^2 + v3_roe^2) / sum_sqrt_rho^2 + H_ll = (rho_e_ll + p_ll) / rho_ll + H_rr = (rho_e_rr + p_rr) / rho_rr + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * vel_roe_mag)) + Ssl = min(vel_L - c_ll, vel_roe - c_roe) + Ssr = max(vel_R + c_rr, vel_roe + c_roe) + sMu_L = Ssl - vel_L + sMu_R = Ssr - vel_R + + if Ssl >= 0 + f1 = f_ll[1] + f2 = f_ll[2] + f3 = f_ll[3] + f4 = f_ll[4] + f5 = f_ll[5] + elseif Ssr <= 0 + f1 = f_rr[1] + f2 = f_rr[2] + f3 = f_rr[3] + f4 = f_rr[4] + f5 = f_rr[5] else - densStar = rho_rr * sMu_R / (Ssr - SStar) - enerStar = e_rr + (SStar - vel_R) * (SStar + p_rr / (rho_rr * sMu_R)) - UStar1 = densStar - UStar5 = densStar * enerStar - if orientation == 1 # x-direction - UStar2 = densStar * SStar - UStar3 = densStar * v2_rr - UStar4 = densStar * v3_rr - elseif orientation == 2 # y-direction - UStar2 = densStar * v1_rr - UStar3 = densStar * SStar - UStar4 = densStar * v3_rr - else # z-direction - UStar2 = densStar * v1_rr - UStar3 = densStar * v2_rr - UStar4 = densStar * SStar + SStar = (p_rr - p_ll + rho_ll * vel_L * sMu_L - rho_rr * vel_R * sMu_R) / + (rho_ll * sMu_L - rho_rr * sMu_R) + if Ssl <= 0 <= SStar + densStar = rho_ll * sMu_L / (Ssl - SStar) + enerStar = e_ll + (SStar - vel_L) * (SStar + p_ll / (rho_ll * sMu_L)) + UStar1 = densStar + UStar5 = densStar * enerStar + if orientation == 1 # x-direction + UStar2 = densStar * SStar + UStar3 = densStar * v2_ll + UStar4 = densStar * v3_ll + elseif orientation == 2 # y-direction + UStar2 = densStar * v1_ll + UStar3 = densStar * SStar + UStar4 = densStar * v3_ll + else # z-direction + UStar2 = densStar * v1_ll + UStar3 = densStar * v2_ll + UStar4 = densStar * SStar + end + f1 = f_ll[1] + Ssl * (UStar1 - rho_ll) + f2 = f_ll[2] + Ssl * (UStar2 - rho_v1_ll) + f3 = f_ll[3] + Ssl * (UStar3 - rho_v2_ll) + f4 = f_ll[4] + Ssl * (UStar4 - rho_v3_ll) + f5 = f_ll[5] + Ssl * (UStar5 - rho_e_ll) + else + densStar = rho_rr * sMu_R / (Ssr - SStar) + enerStar = e_rr + (SStar - vel_R) * (SStar + p_rr / (rho_rr * sMu_R)) + UStar1 = densStar + UStar5 = densStar * enerStar + if orientation == 1 # x-direction + UStar2 = densStar * SStar + UStar3 = densStar * v2_rr + UStar4 = densStar * v3_rr + elseif orientation == 2 # y-direction + UStar2 = densStar * v1_rr + UStar3 = densStar * SStar + UStar4 = densStar * v3_rr + else # z-direction + UStar2 = densStar * v1_rr + UStar3 = densStar * v2_rr + UStar4 = densStar * SStar + end + f1 = f_rr[1] + Ssr * (UStar1 - rho_rr) + f2 = f_rr[2] + Ssr * (UStar2 - rho_v1_rr) + f3 = f_rr[3] + Ssr * (UStar3 - rho_v2_rr) + f4 = f_rr[4] + Ssr * (UStar4 - rho_v3_rr) + f5 = f_rr[5] + Ssr * (UStar5 - rho_e_rr) end - f1 = f_rr[1] + Ssr * (UStar1 - rho_rr) - f2 = f_rr[2] + Ssr * (UStar2 - rho_v1_rr) - f3 = f_rr[3] + Ssr * (UStar3 - rho_v2_rr) - f4 = f_rr[4] + Ssr * (UStar4 - rho_v3_rr) - f5 = f_rr[5] + Ssr * (UStar5 - rho_e_rr) end + return SVector(f1, f2, f3, f4, f5) end - return SVector(f1, f2, f3, f4, f5) -end - -function flux_hllc(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - # Calculate primitive variables and speed of sound - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + - v3_ll * normal_direction[3] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + - v3_rr * normal_direction[3] - - norm_ = norm(normal_direction) - norm_sq = norm_ * norm_ - inv_norm_sq = inv(norm_sq) - - c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ - c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ - - # Obtain left and right fluxes - f_ll = flux(u_ll, normal_direction, equations) - f_rr = flux(u_rr, normal_direction, equations) - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr - - v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) / sum_sqrt_rho - v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) / sum_sqrt_rho - v3_roe = (sqrt_rho_ll * v3_ll + sqrt_rho_rr * v3_rr) / sum_sqrt_rho - vel_roe = v1_roe * normal_direction[1] + v2_roe * normal_direction[2] + - v3_roe * normal_direction[3] - vel_roe_mag = v1_roe^2 + v2_roe^2 + v3_roe^2 - - e_ll = u_ll[5] / rho_ll - e_rr = u_rr[5] / rho_rr - - H_ll = (u_ll[5] + p_ll) / rho_ll - H_rr = (u_rr[5] + p_rr) / rho_rr - - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * vel_roe_mag)) * norm_ - - Ssl = min(v_dot_n_ll - c_ll, vel_roe - c_roe) - Ssr = max(v_dot_n_rr + c_rr, vel_roe + c_roe) - sMu_L = Ssl - v_dot_n_ll - sMu_R = Ssr - v_dot_n_rr - - if Ssl >= 0 - f1 = f_ll[1] - f2 = f_ll[2] - f3 = f_ll[3] - f4 = f_ll[4] - f5 = f_ll[5] - elseif Ssr <= 0 - f1 = f_rr[1] - f2 = f_rr[2] - f3 = f_rr[3] - f4 = f_rr[4] - f5 = f_rr[5] - else - SStar = (rho_ll * v_dot_n_ll * sMu_L - rho_rr * v_dot_n_rr * sMu_R + - (p_rr - p_ll) * norm_sq) / (rho_ll * sMu_L - rho_rr * sMu_R) - if Ssl <= 0 <= SStar - densStar = rho_ll * sMu_L / (Ssl - SStar) - enerStar = e_ll + - (SStar - v_dot_n_ll) * - (SStar * inv_norm_sq + p_ll / (rho_ll * sMu_L)) - UStar1 = densStar - UStar2 = densStar * - (v1_ll + (SStar - v_dot_n_ll) * normal_direction[1] * inv_norm_sq) - UStar3 = densStar * - (v2_ll + (SStar - v_dot_n_ll) * normal_direction[2] * inv_norm_sq) - UStar4 = densStar * - (v3_ll + (SStar - v_dot_n_ll) * normal_direction[3] * inv_norm_sq) - UStar5 = densStar * enerStar - f1 = f_ll[1] + Ssl * (UStar1 - u_ll[1]) - f2 = f_ll[2] + Ssl * (UStar2 - u_ll[2]) - f3 = f_ll[3] + Ssl * (UStar3 - u_ll[3]) - f4 = f_ll[4] + Ssl * (UStar4 - u_ll[4]) - f5 = f_ll[5] + Ssl * (UStar5 - u_ll[5]) + + function flux_hllc( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + # Calculate primitive variables and speed of sound + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + norm_ = norm(normal_direction) + norm_sq = norm_ * norm_ + inv_norm_sq = inv(norm_sq) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ + c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + # Obtain left and right fluxes + f_ll = flux(u_ll, normal_direction, equations) + f_rr = flux(u_rr, normal_direction, equations) + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + sum_sqrt_rho = sqrt_rho_ll + sqrt_rho_rr + + v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) / sum_sqrt_rho + v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) / sum_sqrt_rho + v3_roe = (sqrt_rho_ll * v3_ll + sqrt_rho_rr * v3_rr) / sum_sqrt_rho + vel_roe = v1_roe * normal_direction[1] + v2_roe * normal_direction[2] + + v3_roe * normal_direction[3] + vel_roe_mag = v1_roe^2 + v2_roe^2 + v3_roe^2 + + e_ll = u_ll[5] / rho_ll + e_rr = u_rr[5] / rho_rr + + H_ll = (u_ll[5] + p_ll) / rho_ll + H_rr = (u_rr[5] + p_rr) / rho_rr + + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) / sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * vel_roe_mag)) * norm_ + + Ssl = min(v_dot_n_ll - c_ll, vel_roe - c_roe) + Ssr = max(v_dot_n_rr + c_rr, vel_roe + c_roe) + sMu_L = Ssl - v_dot_n_ll + sMu_R = Ssr - v_dot_n_rr + + if Ssl >= 0 + f1 = f_ll[1] + f2 = f_ll[2] + f3 = f_ll[3] + f4 = f_ll[4] + f5 = f_ll[5] + elseif Ssr <= 0 + f1 = f_rr[1] + f2 = f_rr[2] + f3 = f_rr[3] + f4 = f_rr[4] + f5 = f_rr[5] else - densStar = rho_rr * sMu_R / (Ssr - SStar) - enerStar = e_rr + - (SStar - v_dot_n_rr) * - (SStar * inv_norm_sq + p_rr / (rho_rr * sMu_R)) - UStar1 = densStar - UStar2 = densStar * - (v1_rr + (SStar - v_dot_n_rr) * normal_direction[1] * inv_norm_sq) - UStar3 = densStar * - (v2_rr + (SStar - v_dot_n_rr) * normal_direction[2] * inv_norm_sq) - UStar4 = densStar * - (v3_rr + (SStar - v_dot_n_rr) * normal_direction[3] * inv_norm_sq) - UStar5 = densStar * enerStar - f1 = f_rr[1] + Ssr * (UStar1 - u_rr[1]) - f2 = f_rr[2] + Ssr * (UStar2 - u_rr[2]) - f3 = f_rr[3] + Ssr * (UStar3 - u_rr[3]) - f4 = f_rr[4] + Ssr * (UStar4 - u_rr[4]) - f5 = f_rr[5] + Ssr * (UStar5 - u_rr[5]) + SStar = ( + rho_ll * v_dot_n_ll * sMu_L - rho_rr * v_dot_n_rr * sMu_R + + (p_rr - p_ll) * norm_sq + ) / (rho_ll * sMu_L - rho_rr * sMu_R) + if Ssl <= 0 <= SStar + densStar = rho_ll * sMu_L / (Ssl - SStar) + enerStar = e_ll + + (SStar - v_dot_n_ll) * + (SStar * inv_norm_sq + p_ll / (rho_ll * sMu_L)) + UStar1 = densStar + UStar2 = densStar * + (v1_ll + (SStar - v_dot_n_ll) * normal_direction[1] * inv_norm_sq) + UStar3 = densStar * + (v2_ll + (SStar - v_dot_n_ll) * normal_direction[2] * inv_norm_sq) + UStar4 = densStar * + (v3_ll + (SStar - v_dot_n_ll) * normal_direction[3] * inv_norm_sq) + UStar5 = densStar * enerStar + f1 = f_ll[1] + Ssl * (UStar1 - u_ll[1]) + f2 = f_ll[2] + Ssl * (UStar2 - u_ll[2]) + f3 = f_ll[3] + Ssl * (UStar3 - u_ll[3]) + f4 = f_ll[4] + Ssl * (UStar4 - u_ll[4]) + f5 = f_ll[5] + Ssl * (UStar5 - u_ll[5]) + else + densStar = rho_rr * sMu_R / (Ssr - SStar) + enerStar = e_rr + + (SStar - v_dot_n_rr) * + (SStar * inv_norm_sq + p_rr / (rho_rr * sMu_R)) + UStar1 = densStar + UStar2 = densStar * + (v1_rr + (SStar - v_dot_n_rr) * normal_direction[1] * inv_norm_sq) + UStar3 = densStar * + (v2_rr + (SStar - v_dot_n_rr) * normal_direction[2] * inv_norm_sq) + UStar4 = densStar * + (v3_rr + (SStar - v_dot_n_rr) * normal_direction[3] * inv_norm_sq) + UStar5 = densStar * enerStar + f1 = f_rr[1] + Ssr * (UStar1 - u_rr[1]) + f2 = f_rr[2] + Ssr * (UStar2 - u_rr[2]) + f3 = f_rr[3] + Ssr * (UStar3 - u_rr[3]) + f4 = f_rr[4] + Ssr * (UStar4 - u_rr[4]) + f5 = f_rr[5] + Ssr * (UStar5 - u_rr[5]) + end end + return SVector(f1, f2, f3, f4, f5) end - return SVector(f1, f2, f3, f4, f5) -end - -""" - min_max_speed_einfeldt(u_ll, u_rr, orientation, equations::CompressibleEulerEquations3D) - -Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. -Special estimates of the signal velocites and linearization of the Riemann problem developed -by Einfeldt to ensure that the internal energy and density remain positive during the computation -of the numerical flux. - -- Bernd Einfeldt (1988) - On Godunov-type methods for gas dynamics. - [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) -- Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) - On Godunov-type methods near low densities. - [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) -""" -@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquations3D) - # Calculate primitive variables, enthalpy and speed of sound - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - # `u_ll[5]` is total energy `rho_e_ll` on the left - H_ll = (u_ll[5] + p_ll) / rho_ll - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - - # `u_rr[5]` is total energy `rho_e_rr` on the right - H_rr = (u_rr[5] + p_rr) / rho_rr - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) - - v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) * inv_sum_sqrt_rho - v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) * inv_sum_sqrt_rho - v3_roe = (sqrt_rho_ll * v3_ll + sqrt_rho_rr * v3_rr) * inv_sum_sqrt_rho - v_roe_mag = v1_roe^2 + v2_roe^2 + v3_roe^2 - - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) - - # Compute convenience constant for positivity preservation, see - # https://doi.org/10.1016/0021-9991(91)90211-3 - beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) - - # Estimate the edges of the Riemann fan (with positivity conservation) - if orientation == 1 # x-direction - SsL = min(v1_roe - c_roe, v1_ll - beta * c_ll, 0) - SsR = max(v1_roe + c_roe, v1_rr + beta * c_rr, 0) - elseif orientation == 2 # y-direction - SsL = min(v2_roe - c_roe, v2_ll - beta * c_ll, 0) - SsR = max(v2_roe + c_roe, v2_rr + beta * c_rr, 0) - else # z-direction - SsL = min(v3_roe - c_roe, v3_ll - beta * c_ll, 0) - SsR = max(v3_roe + c_roe, v3_rr + beta * c_rr, 0) + + """ + min_max_speed_einfeldt(u_ll, u_rr, orientation, equations::CompressibleEulerEquations3D) + + Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. + Special estimates of the signal velocites and linearization of the Riemann problem developed + by Einfeldt to ensure that the internal energy and density remain positive during the computation + of the numerical flux. + + - Bernd Einfeldt (1988) + On Godunov-type methods for gas dynamics. + [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) + - Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) + On Godunov-type methods near low densities. + [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) + """ + @inline function min_max_speed_einfeldt( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquations3D + ) + # Calculate primitive variables, enthalpy and speed of sound + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + # `u_ll[5]` is total energy `rho_e_ll` on the left + H_ll = (u_ll[5] + p_ll) / rho_ll + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + + # `u_rr[5]` is total energy `rho_e_rr` on the right + H_rr = (u_rr[5] + p_rr) / rho_rr + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) + + v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) * inv_sum_sqrt_rho + v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) * inv_sum_sqrt_rho + v3_roe = (sqrt_rho_ll * v3_ll + sqrt_rho_rr * v3_rr) * inv_sum_sqrt_rho + v_roe_mag = v1_roe^2 + v2_roe^2 + v3_roe^2 + + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) + + # Compute convenience constant for positivity preservation, see + # https://doi.org/10.1016/0021-9991(91)90211-3 + beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) + + # Estimate the edges of the Riemann fan (with positivity conservation) + if orientation == 1 # x-direction + SsL = min(v1_roe - c_roe, v1_ll - beta * c_ll, 0) + SsR = max(v1_roe + c_roe, v1_rr + beta * c_rr, 0) + elseif orientation == 2 # y-direction + SsL = min(v2_roe - c_roe, v2_ll - beta * c_ll, 0) + SsR = max(v2_roe + c_roe, v2_rr + beta * c_rr, 0) + else # z-direction + SsL = min(v3_roe - c_roe, v3_ll - beta * c_ll, 0) + SsR = max(v3_roe + c_roe, v3_rr + beta * c_rr, 0) + end + + return SsL, SsR end - return SsL, SsR -end - -""" - min_max_speed_einfeldt(u_ll, u_rr, normal_direction, equations::CompressibleEulerEquations3D) - -Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. -Special estimates of the signal velocites and linearization of the Riemann problem developed -by Einfeldt to ensure that the internal energy and density remain positive during the computation -of the numerical flux. - -- Bernd Einfeldt (1988) - On Godunov-type methods for gas dynamics. - [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) -- Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) - On Godunov-type methods near low densities. - [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) -""" -@inline function min_max_speed_einfeldt(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquations3D) - # Calculate primitive variables, enthalpy and speed of sound - rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) - - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + - v3_ll * normal_direction[3] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + - v3_rr * normal_direction[3] - - norm_ = norm(normal_direction) - - # `u_ll[5]` is total energy `rho_e_ll` on the left - H_ll = (u_ll[5] + p_ll) / rho_ll - c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ - - # `u_rr[5]` is total energy `rho_e_rr` on the right - H_rr = (u_rr[5] + p_rr) / rho_rr - c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ - - # Compute Roe averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) - - v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) * inv_sum_sqrt_rho - v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) * inv_sum_sqrt_rho - v3_roe = (sqrt_rho_ll * v3_ll + sqrt_rho_rr * v3_rr) * inv_sum_sqrt_rho - v_roe = v1_roe * normal_direction[1] + v2_roe * normal_direction[2] + + """ + min_max_speed_einfeldt(u_ll, u_rr, normal_direction, equations::CompressibleEulerEquations3D) + + Computes the HLLE (Harten-Lax-van Leer-Einfeldt) flux for the compressible Euler equations. + Special estimates of the signal velocites and linearization of the Riemann problem developed + by Einfeldt to ensure that the internal energy and density remain positive during the computation + of the numerical flux. + + - Bernd Einfeldt (1988) + On Godunov-type methods for gas dynamics. + [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) + - Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) + On Godunov-type methods near low densities. + [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) + """ + @inline function min_max_speed_einfeldt( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquations3D + ) + # Calculate primitive variables, enthalpy and speed of sound + rho_ll, v1_ll, v2_ll, v3_ll, p_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr = cons2prim(u_rr, equations) + + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + + norm_ = norm(normal_direction) + + # `u_ll[5]` is total energy `rho_e_ll` on the left + H_ll = (u_ll[5] + p_ll) / rho_ll + c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ + + # `u_rr[5]` is total energy `rho_e_rr` on the right + H_rr = (u_rr[5] + p_rr) / rho_rr + c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + # Compute Roe averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sum_sqrt_rho = inv(sqrt_rho_ll + sqrt_rho_rr) + + v1_roe = (sqrt_rho_ll * v1_ll + sqrt_rho_rr * v1_rr) * inv_sum_sqrt_rho + v2_roe = (sqrt_rho_ll * v2_ll + sqrt_rho_rr * v2_rr) * inv_sum_sqrt_rho + v3_roe = (sqrt_rho_ll * v3_ll + sqrt_rho_rr * v3_rr) * inv_sum_sqrt_rho + v_roe = v1_roe * normal_direction[1] + v2_roe * normal_direction[2] + v3_roe * normal_direction[3] - v_roe_mag = v1_roe^2 + v2_roe^2 + v3_roe^2 - - H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho - c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) * norm_ - - # Compute convenience constant for positivity preservation, see - # https://doi.org/10.1016/0021-9991(91)90211-3 - beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) - - # Estimate the edges of the Riemann fan (with positivity conservation) - SsL = min(v_roe - c_roe, v_dot_n_ll - beta * c_ll, 0) - SsR = max(v_roe + c_roe, v_dot_n_rr + beta * c_rr, 0) - - return SsL, SsR -end - -@inline function max_abs_speeds(u, equations::CompressibleEulerEquations3D) - rho, v1, v2, v3, p = cons2prim(u, equations) - c = sqrt(equations.gamma * p / rho) - - return abs(v1) + c, abs(v2) + c, abs(v3) + c -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::CompressibleEulerEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - p = (equations.gamma - 1) * - (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3)) - - return SVector(rho, v1, v2, v3, p) -end - -# Convert conservative variables to entropy -@inline function cons2entropy(u, equations::CompressibleEulerEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - v_square = v1^2 + v2^2 + v3^2 - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho * v_square) - s = log(p) - equations.gamma * log(rho) - rho_p = rho / p - - w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - - 0.5f0 * rho_p * v_square - w2 = rho_p * v1 - w3 = rho_p * v2 - w4 = rho_p * v3 - w5 = -rho_p - - return SVector(w1, w2, w3, w4, w5) -end - -@inline function entropy2cons(w, equations::CompressibleEulerEquations3D) - # See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD - # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) - @unpack gamma = equations - - # convert to entropy `-rho * s` used by Hughes, France, Mallet (1986) - # instead of `-rho * s / (gamma - 1)` - V1, V2, V3, V4, V5 = w .* (gamma - 1) - - # s = specific entropy, eq. (53) - V_square = V2^2 + V3^2 + V4^2 - s = gamma - V1 + V_square / (2 * V5) - - # eq. (52) - rho_iota = ((gamma - 1) / (-V5)^gamma)^(equations.inv_gamma_minus_one) * - exp(-s * equations.inv_gamma_minus_one) - - # eq. (51) - rho = -rho_iota * V5 - rho_v1 = rho_iota * V2 - rho_v2 = rho_iota * V3 - rho_v3 = rho_iota * V4 - rho_e = rho_iota * (1 - V_square / (2 * V5)) - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::CompressibleEulerEquations3D) - rho, v1, v2, v3, p = prim - rho_v1 = rho * v1 - rho_v2 = rho * v2 - rho_v3 = rho * v3 - rho_e = p * equations.inv_gamma_minus_one + + v_roe_mag = v1_roe^2 + v2_roe^2 + v3_roe^2 + + H_roe = (sqrt_rho_ll * H_ll + sqrt_rho_rr * H_rr) * inv_sum_sqrt_rho + c_roe = sqrt((equations.gamma - 1) * (H_roe - 0.5f0 * v_roe_mag)) * norm_ + + # Compute convenience constant for positivity preservation, see + # https://doi.org/10.1016/0021-9991(91)90211-3 + beta = sqrt(0.5f0 * (equations.gamma - 1) / equations.gamma) + + # Estimate the edges of the Riemann fan (with positivity conservation) + SsL = min(v_roe - c_roe, v_dot_n_ll - beta * c_ll, 0) + SsR = max(v_roe + c_roe, v_dot_n_rr + beta * c_rr, 0) + + return SsL, SsR + end + + @inline function max_abs_speeds(u, equations::CompressibleEulerEquations3D) + rho, v1, v2, v3, p = cons2prim(u, equations) + c = sqrt(equations.gamma * p / rho) + + return abs(v1) + c, abs(v2) + c, abs(v3) + c + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::CompressibleEulerEquations3D) + rho, rho_v1, rho_v2, rho_v3, rho_e = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + p = (equations.gamma - 1) * + (rho_e - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3)) + + return SVector(rho, v1, v2, v3, p) + end + + # Convert conservative variables to entropy + @inline function cons2entropy(u, equations::CompressibleEulerEquations3D) + rho, rho_v1, rho_v2, rho_v3, rho_e = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + v_square = v1^2 + v2^2 + v3^2 + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho * v_square) + s = log(p) - equations.gamma * log(rho) + rho_p = rho / p + + w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - + 0.5f0 * rho_p * v_square + w2 = rho_p * v1 + w3 = rho_p * v2 + w4 = rho_p * v3 + w5 = -rho_p + + return SVector(w1, w2, w3, w4, w5) + end + + @inline function entropy2cons(w, equations::CompressibleEulerEquations3D) + # See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD + # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) + @unpack gamma = equations + + # convert to entropy `-rho * s` used by Hughes, France, Mallet (1986) + # instead of `-rho * s / (gamma - 1)` + V1, V2, V3, V4, V5 = w .* (gamma - 1) + + # s = specific entropy, eq. (53) + V_square = V2^2 + V3^2 + V4^2 + s = gamma - V1 + V_square / (2 * V5) + + # eq. (52) + rho_iota = ((gamma - 1) / (-V5)^gamma)^(equations.inv_gamma_minus_one) * + exp(-s * equations.inv_gamma_minus_one) + + # eq. (51) + rho = -rho_iota * V5 + rho_v1 = rho_iota * V2 + rho_v2 = rho_iota * V3 + rho_v3 = rho_iota * V4 + rho_e = rho_iota * (1 - V_square / (2 * V5)) + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::CompressibleEulerEquations3D) + rho, v1, v2, v3, p = prim + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_v3 = rho * v3 + rho_e = p * equations.inv_gamma_minus_one + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e) -end - -@inline function density(u, equations::CompressibleEulerEquations3D) - rho = u[1] - return rho -end - -@inline function pressure(u, equations::CompressibleEulerEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e = u - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho) - return p -end - -@inline function density_pressure(u, equations::CompressibleEulerEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e = u - rho_times_p = (equations.gamma - 1) * - (rho * rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2)) - return rho_times_p -end - -# Calculate thermodynamic entropy for a conservative state `u` -@inline function entropy_thermodynamic(u, equations::CompressibleEulerEquations3D) - rho, _ = u - p = pressure(u, equations) - - # Thermodynamic entropy - s = log(p) - equations.gamma * log(rho) - - return s -end - -# Calculate mathematical entropy for a conservative state `cons` -@inline function entropy_math(cons, equations::CompressibleEulerEquations3D) - S = -entropy_thermodynamic(cons, equations) * cons[1] * - equations.inv_gamma_minus_one - # Mathematical entropy - - return S -end - -# Default entropy is the mathematical entropy -@inline function entropy(cons, equations::CompressibleEulerEquations3D) - entropy_math(cons, equations) -end - -# Calculate total energy for a conservative state `cons` -@inline energy_total(cons, ::CompressibleEulerEquations3D) = cons[5] - -# Calculate kinetic energy for a conservative state `cons` -@inline function energy_kinetic(u, equations::CompressibleEulerEquations3D) - rho, rho_v1, rho_v2, rho_v3, _ = u - return 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho -end - -# Calculate internal energy for a conservative state `cons` -@inline function energy_internal(cons, equations::CompressibleEulerEquations3D) - return energy_total(cons, equations) - energy_kinetic(cons, equations) -end + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e) + end + + @inline function density(u, equations::CompressibleEulerEquations3D) + rho = u[1] + return rho + end + + @inline function pressure(u, equations::CompressibleEulerEquations3D) + rho, rho_v1, rho_v2, rho_v3, rho_e = u + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho) + return p + end + + @inline function density_pressure(u, equations::CompressibleEulerEquations3D) + rho, rho_v1, rho_v2, rho_v3, rho_e = u + rho_times_p = (equations.gamma - 1) * + (rho * rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2)) + return rho_times_p + end + + # Calculate thermodynamic entropy for a conservative state `u` + @inline function entropy_thermodynamic(u, equations::CompressibleEulerEquations3D) + rho, _ = u + p = pressure(u, equations) + + # Thermodynamic entropy + s = log(p) - equations.gamma * log(rho) + + return s + end + + # Calculate mathematical entropy for a conservative state `cons` + @inline function entropy_math(cons, equations::CompressibleEulerEquations3D) + S = -entropy_thermodynamic(cons, equations) * cons[1] * + equations.inv_gamma_minus_one + # Mathematical entropy + + return S + end + + # Default entropy is the mathematical entropy + @inline function entropy(cons, equations::CompressibleEulerEquations3D) + entropy_math(cons, equations) + end + + # Calculate total energy for a conservative state `cons` + @inline energy_total(cons, ::CompressibleEulerEquations3D) = cons[5] + + # Calculate kinetic energy for a conservative state `cons` + @inline function energy_kinetic(u, equations::CompressibleEulerEquations3D) + rho, rho_v1, rho_v2, rho_v3, _ = u + return 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho + end + + # Calculate internal energy for a conservative state `cons` + @inline function energy_internal(cons, equations::CompressibleEulerEquations3D) + return energy_total(cons, equations) - energy_kinetic(cons, equations) + end end # @muladd diff --git a/src/equations/compressible_euler_multicomponent_1d.jl b/src/equations/compressible_euler_multicomponent_1d.jl index da434579f7..e38decd859 100644 --- a/src/equations/compressible_euler_multicomponent_1d.jl +++ b/src/equations/compressible_euler_multicomponent_1d.jl @@ -3,619 +3,701 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - CompressibleEulerMulticomponentEquations1D(; gammas, gas_constants) - -Multicomponent version of the compressible Euler equations -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -\rho v_1 \\ \rho e \\ \rho_1 \\ \rho_2 \\ \vdots \\ \rho_{n} -\end{pmatrix} -+ -\frac{\partial}{\partial x} -\begin{pmatrix} -\rho v_1^2 + p \\ (\rho e +p) v_1 \\ \rho_1 v_1 \\ \rho_2 v_1 \\ \vdots \\ \rho_{n} v_1 -\end{pmatrix} - -= -\begin{pmatrix} -0 \\ 0 \\ 0 \\ 0 \\ \vdots \\ 0 -\end{pmatrix} -``` -for calorically perfect gas in one space dimension. -Here, ``\rho_i`` is the density of component ``i``, ``\rho=\sum_{i=1}^n\rho_i`` the sum of the individual ``\rho_i``, -``v_1`` the velocity, ``e`` the specific total energy **rather than** specific internal energy, and -```math -p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho v_1^2 \right) -``` -the pressure, -```math -\gamma=\frac{\sum_{i=1}^n\rho_i C_{v,i}\gamma_i}{\sum_{i=1}^n\rho_i C_{v,i}} -``` -total heat capacity ratio, ``\gamma_i`` heat capacity ratio of component ``i``, -```math -C_{v,i}=\frac{R}{\gamma_i-1} -``` -specific heat capacity at constant volume of component ``i``. - -In case of more than one component, the specific heat ratios `gammas` and the gas constants -`gas_constants` should be passed as tuples, e.g., `gammas=(1.4, 1.667)`. - -The remaining variables like the specific heats at constant volume `cv` or the specific heats at -constant pressure `cp` are then calculated considering a calorically perfect gas. -""" -struct CompressibleEulerMulticomponentEquations1D{NVARS, NCOMP, RealT <: Real} <: - AbstractCompressibleEulerMulticomponentEquations{1, NVARS, NCOMP} - gammas::SVector{NCOMP, RealT} - gas_constants::SVector{NCOMP, RealT} - cv::SVector{NCOMP, RealT} - cp::SVector{NCOMP, RealT} - - function CompressibleEulerMulticomponentEquations1D{NVARS, NCOMP, RealT}(gammas::SVector{NCOMP, - RealT}, - gas_constants::SVector{NCOMP, - RealT}) where { - NVARS, - NCOMP, - RealT <: - Real - } - NCOMP >= 1 || - throw(DimensionMismatch("`gammas` and `gas_constants` have to be filled with at least one value")) - - cv = gas_constants ./ (gammas .- 1) - cp = gas_constants + gas_constants ./ (gammas .- 1) - - new(gammas, gas_constants, cv, cp) + #! format: noindent + + @doc raw""" + CompressibleEulerMulticomponentEquations1D(; gammas, gas_constants) + + Multicomponent version of the compressible Euler equations + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + \rho v_1 \\ \rho e \\ \rho_1 \\ \rho_2 \\ \vdots \\ \rho_{n} + \end{pmatrix} + + + \frac{\partial}{\partial x} + \begin{pmatrix} + \rho v_1^2 + p \\ (\rho e +p) v_1 \\ \rho_1 v_1 \\ \rho_2 v_1 \\ \vdots \\ \rho_{n} v_1 + \end{pmatrix} + + = + \begin{pmatrix} + 0 \\ 0 \\ 0 \\ 0 \\ \vdots \\ 0 + \end{pmatrix} + ``` + for calorically perfect gas in one space dimension. + Here, ``\rho_i`` is the density of component ``i``, ``\rho=\sum_{i=1}^n\rho_i`` the sum of the individual ``\rho_i``, + ``v_1`` the velocity, ``e`` the specific total energy **rather than** specific internal energy, and + ```math + p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho v_1^2 \right) + ``` + the pressure, + ```math + \gamma=\frac{\sum_{i=1}^n\rho_i C_{v,i}\gamma_i}{\sum_{i=1}^n\rho_i C_{v,i}} + ``` + total heat capacity ratio, ``\gamma_i`` heat capacity ratio of component ``i``, + ```math + C_{v,i}=\frac{R}{\gamma_i-1} + ``` + specific heat capacity at constant volume of component ``i``. + + In case of more than one component, the specific heat ratios `gammas` and the gas constants + `gas_constants` should be passed as tuples, e.g., `gammas=(1.4, 1.667)`. + + The remaining variables like the specific heats at constant volume `cv` or the specific heats at + constant pressure `cp` are then calculated considering a calorically perfect gas. + """ + struct CompressibleEulerMulticomponentEquations1D{NVARS, NCOMP, RealT <: Real} <: + AbstractCompressibleEulerMulticomponentEquations{1, NVARS, NCOMP} + gammas::SVector{NCOMP, RealT} + gas_constants::SVector{NCOMP, RealT} + cv::SVector{NCOMP, RealT} + cp::SVector{NCOMP, RealT} + + function CompressibleEulerMulticomponentEquations1D{NVARS, NCOMP, RealT}( + gammas::SVector{ + NCOMP, + RealT, + }, + gas_constants::SVector{ + NCOMP, + RealT, + } + ) where { + NVARS, + NCOMP, + RealT <: + Real, + } + NCOMP >= 1 || + throw(DimensionMismatch("`gammas` and `gas_constants` have to be filled with at least one value")) + + cv = gas_constants ./ (gammas .- 1) + cp = gas_constants + gas_constants ./ (gammas .- 1) + + new(gammas, gas_constants, cv, cp) + end end -end - -function CompressibleEulerMulticomponentEquations1D(; gammas, gas_constants) - _gammas = promote(gammas...) - _gas_constants = promote(gas_constants...) - RealT = promote_type(eltype(_gammas), eltype(_gas_constants), - typeof(gas_constants[1] / (gammas[1] - 1))) - - NVARS = length(_gammas) + 2 - NCOMP = length(_gammas) - - __gammas = SVector(map(RealT, _gammas)) - __gas_constants = SVector(map(RealT, _gas_constants)) - - return CompressibleEulerMulticomponentEquations1D{NVARS, NCOMP, RealT}(__gammas, - __gas_constants) -end - -@inline function Base.real(::CompressibleEulerMulticomponentEquations1D{NVARS, NCOMP, - RealT}) where { - NVARS, - NCOMP, - RealT - } - RealT -end - -function varnames(::typeof(cons2cons), - equations::CompressibleEulerMulticomponentEquations1D) - cons = ("rho_v1", "rho_e") - rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) - return (cons..., rhos...) -end - -function varnames(::typeof(cons2prim), - equations::CompressibleEulerMulticomponentEquations1D) - prim = ("v1", "p") - rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) - return (prim..., rhos...) -end - -# Set initial conditions at physical location `x` for time `t` - -""" - initial_condition_convergence_test(x, t, equations::CompressibleEulerMulticomponentEquations1D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -function initial_condition_convergence_test(x, t, - equations::CompressibleEulerMulticomponentEquations1D) - RealT = eltype(x) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - ini = c + A * sin(omega * (x[1] - t)) - - v1 = 1 - - rho = ini - - # Here we compute an arbitrary number of different rhos. (one rho is double the next rho while the sum of all rhos is 1) - prim_rho = SVector{ncomponents(equations), real(equations)}(2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - rho - for i in eachcomponent(equations)) - - prim1 = rho * v1 - prim2 = rho^2 - - prim_other = SVector(prim1, prim2) - - return vcat(prim_other, prim_rho) -end - -""" - source_terms_convergence_test(u, x, t, equations::CompressibleEulerMulticomponentEquations1D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::CompressibleEulerMulticomponentEquations1D) - # Same settings as in `initial_condition` - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - - gamma = totalgamma(u, equations) - - x1, = x - si, co = sincos((t - x1) * omega) - tmp = (-((4 * si * A - 4 * c) + 1) * (gamma - 1) * co * A * omega) / 2 - - # Here we compute an arbitrary number of different rhos. (one rho is double the next rho while the sum of all rhos is 1 - du_rho = SVector{ncomponents(equations), real(equations)}(0 - for i in eachcomponent(equations)) - - du1 = tmp - du2 = tmp - - du_other = SVector(du1, du2) - - return vcat(du_other, du_rho) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerMulticomponentEquations1D) - -A for multicomponent adapted weak blast wave adapted to multicomponent and taken from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, - equations::CompressibleEulerMulticomponentEquations1D) - # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - RealT = eltype(x) - inicenter = SVector(0) - x_norm = x[1] - inicenter[1] - r = abs(x_norm) - cos_phi = x_norm > 0 ? 1 : -1 - - prim_rho = SVector{ncomponents(equations), real(equations)}(r > 0.5f0 ? - 2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - one(RealT) : - 2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - convert(RealT, 1.1691) - for i in eachcomponent(equations)) - - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - prim_other = SVector(v1, p) - - return prim2cons(vcat(prim_other, prim_rho), equations) -end - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, - equations::CompressibleEulerMulticomponentEquations1D) - rho_v1, rho_e = u - - rho = density(u, equations) - - v1 = rho_v1 / rho - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * rho * v1^2) - - f_rho = densities(u, v1, equations) - f1 = rho_v1 * v1 + p - f2 = (rho_e + p) * v1 - - f_other = SVector(f1, f2) - - return vcat(f_other, f_rho) -end - -""" - flux_chandrashekar(u_ll, u_rr, orientation, equations::CompressibleEulerMulticomponentEquations1D) - -Entropy conserving two-point flux by -- Ayoub Gouasmi, Karthik Duraisamy (2020) - "Formulation of Entropy-Stable schemes for the multicomponent compressible Euler equations" - [arXiv:1904.00972v3](https://arxiv.org/abs/1904.00972) [math.NA] 4 Feb 2020 -""" -@inline function flux_chandrashekar(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerMulticomponentEquations1D) - # Unpack left and right state - @unpack gammas, gas_constants, cv = equations - rho_v1_ll, rho_e_ll = u_ll - rho_v1_rr, rho_e_rr = u_rr - rhok_mean = SVector{ncomponents(equations), real(equations)}(ln_mean(u_ll[i + 2], - u_rr[i + 2]) - for i in eachcomponent(equations)) - rhok_avg = SVector{ncomponents(equations), real(equations)}(0.5f0 * (u_ll[i + 2] + - u_rr[i + 2]) - for i in eachcomponent(equations)) - - # Iterating over all partial densities - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - gamma_ll = totalgamma(u_ll, equations) - gamma_rr = totalgamma(u_rr, equations) - - # extract velocities - v1_ll = rho_v1_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v1_square = 0.5f0 * (v1_ll^2 + v1_rr^2) - v_sum = v1_avg - - RealT = eltype(u_ll) - enth = zero(RealT) - help1_ll = zero(RealT) - help1_rr = zero(RealT) - - for i in eachcomponent(equations) - enth += rhok_avg[i] * gas_constants[i] - help1_ll += u_ll[i + 2] * cv[i] - help1_rr += u_rr[i + 2] * cv[i] + + function CompressibleEulerMulticomponentEquations1D(; gammas, gas_constants) + _gammas = promote(gammas...) + _gas_constants = promote(gas_constants...) + RealT = promote_type( + eltype(_gammas), eltype(_gas_constants), + typeof(gas_constants[1] / (gammas[1] - 1)) + ) + + NVARS = length(_gammas) + 2 + NCOMP = length(_gammas) + + __gammas = SVector(map(RealT, _gammas)) + __gas_constants = SVector(map(RealT, _gas_constants)) + + return CompressibleEulerMulticomponentEquations1D{NVARS, NCOMP, RealT}( + __gammas, + __gas_constants + ) end - T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2)) / help1_ll - T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2)) / help1_rr - T = 0.5f0 * (1 / T_ll + 1 / T_rr) - T_log = ln_mean(1 / T_ll, 1 / T_rr) + @inline function Base.real( + ::CompressibleEulerMulticomponentEquations1D{ + NVARS, NCOMP, + RealT, + } + ) where { + NVARS, + NCOMP, + RealT, + } + RealT + end - # Calculate fluxes depending on orientation - help1 = zero(RealT) - help2 = zero(RealT) + function varnames( + ::typeof(cons2cons), + equations::CompressibleEulerMulticomponentEquations1D + ) + cons = ("rho_v1", "rho_e") + rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) + return (cons..., rhos...) + end - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v1_avg - for i in eachcomponent(equations)) - for i in eachcomponent(equations) - help1 += f_rho[i] * cv[i] - help2 += f_rho[i] + function varnames( + ::typeof(cons2prim), + equations::CompressibleEulerMulticomponentEquations1D + ) + prim = ("v1", "p") + rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) + return (prim..., rhos...) end - f1 = (help2) * v1_avg + enth / T - f2 = (help1) / T_log - 0.5f0 * (v1_square) * (help2) + v1_avg * f1 - - f_other = SVector(f1, f2) - - return vcat(f_other, f_rho) -end - -""" - flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerMulticomponentEquations1D) - -Adaption of the entropy conserving and kinetic energy preserving two-point flux by -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -See also -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_ranocha(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerMulticomponentEquations1D) - # Unpack left and right state - @unpack gammas, gas_constants, cv = equations - rho_v1_ll, rho_e_ll = u_ll - rho_v1_rr, rho_e_rr = u_rr - rhok_mean = SVector{ncomponents(equations), real(equations)}(ln_mean(u_ll[i + 2], - u_rr[i + 2]) - for i in eachcomponent(equations)) - rhok_avg = SVector{ncomponents(equations), real(equations)}(0.5f0 * (u_ll[i + 2] + - u_rr[i + 2]) - for i in eachcomponent(equations)) - - # Iterating over all partial densities - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - # Calculating gamma - gamma = totalgamma(0.5f0 * (u_ll + u_rr), equations) - inv_gamma_minus_one = 1 / (gamma - 1) - - # extract velocities - v1_ll = rho_v1_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - v1_avg = 0.5f0 * (v1_ll + v1_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr) - - # density flux - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v1_avg - for i in eachcomponent(equations)) - - # helpful variables - RealT = eltype(u_ll) - f_rho_sum = zero(RealT) - help1_ll = zero(RealT) - help1_rr = zero(RealT) - enth_ll = zero(RealT) - enth_rr = zero(RealT) - for i in eachcomponent(equations) - enth_ll += u_ll[i + 2] * gas_constants[i] - enth_rr += u_rr[i + 2] * gas_constants[i] - f_rho_sum += f_rho[i] - help1_ll += u_ll[i + 2] * cv[i] - help1_rr += u_rr[i + 2] * cv[i] + + # Set initial conditions at physical location `x` for time `t` + + """ + initial_condition_convergence_test(x, t, equations::CompressibleEulerMulticomponentEquations1D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + function initial_condition_convergence_test( + x, t, + equations::CompressibleEulerMulticomponentEquations1D + ) + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + ini = c + A * sin(omega * (x[1] - t)) + + v1 = 1 + + rho = ini + + # Here we compute an arbitrary number of different rhos. (one rho is double the next rho while the sum of all rhos is 1) + prim_rho = SVector{ncomponents(equations), real(equations)}( + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + rho + for i in eachcomponent(equations) + ) + + prim1 = rho * v1 + prim2 = rho^2 + + prim_other = SVector(prim1, prim2) + + return vcat(prim_other, prim_rho) end - # temperature and pressure - T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2)) / help1_ll - T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2)) / help1_rr - p_ll = T_ll * enth_ll - p_rr = T_rr * enth_rr - p_avg = 0.5f0 * (p_ll + p_rr) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + """ + source_terms_convergence_test(u, x, t, equations::CompressibleEulerMulticomponentEquations1D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::CompressibleEulerMulticomponentEquations1D + ) + # Same settings as in `initial_condition` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + + gamma = totalgamma(u, equations) + + x1, = x + si, co = sincos((t - x1) * omega) + tmp = (-((4 * si * A - 4 * c) + 1) * (gamma - 1) * co * A * omega) / 2 + + # Here we compute an arbitrary number of different rhos. (one rho is double the next rho while the sum of all rhos is 1 + du_rho = SVector{ncomponents(equations), real(equations)}( + 0 + for i in eachcomponent(equations) + ) + + du1 = tmp + du2 = tmp + + du_other = SVector(du1, du2) + + return vcat(du_other, du_rho) + end - # momentum and energy flux - f1 = f_rho_sum * v1_avg + p_avg - f2 = f_rho_sum * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + - 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - f_other = SVector(f1, f2) + """ + initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerMulticomponentEquations1D) + + A for multicomponent adapted weak blast wave adapted to multicomponent and taken from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave( + x, t, + equations::CompressibleEulerMulticomponentEquations1D + ) + # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + RealT = eltype(x) + inicenter = SVector(0) + x_norm = x[1] - inicenter[1] + r = abs(x_norm) + cos_phi = x_norm > 0 ? 1 : -1 + + prim_rho = SVector{ncomponents(equations), real(equations)}( + r > 0.5f0 ? + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + one(RealT) : + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + convert(RealT, 1.1691) + for i in eachcomponent(equations) + ) + + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + prim_other = SVector(v1, p) + + return prim2cons(vcat(prim_other, prim_rho), equations) + end - return vcat(f_other, f_rho) -end + # Calculate 1D flux for a single point + @inline function flux( + u, orientation::Integer, + equations::CompressibleEulerMulticomponentEquations1D + ) + rho_v1, rho_e = u -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerMulticomponentEquations1D) - rho_v1_ll, rho_e_ll = u_ll - rho_v1_rr, rho_e_rr = u_rr + rho = density(u, equations) - # Calculate primitive variables and speed of sound - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - gamma_ll = totalgamma(u_ll, equations) - gamma_rr = totalgamma(u_rr, equations) + v1 = rho_v1 / rho + gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - 0.5f0 * rho * v1^2) - v_ll = rho_v1_ll / rho_ll - v_rr = rho_v1_rr / rho_rr + f_rho = densities(u, v1, equations) + f1 = rho_v1 * v1 + p + f2 = (rho_e + p) * v1 - p_ll = (gamma_ll - 1) * (rho_e_ll - 0.5f0 * rho_ll * v_ll^2) - p_rr = (gamma_rr - 1) * (rho_e_rr - 0.5f0 * rho_rr * v_rr^2) - c_ll = sqrt(gamma_ll * p_ll / rho_ll) - c_rr = sqrt(gamma_rr * p_rr / rho_rr) + f_other = SVector(f1, f2) - λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) -end + return vcat(f_other, f_rho) + end + + """ + flux_chandrashekar(u_ll, u_rr, orientation, equations::CompressibleEulerMulticomponentEquations1D) + + Entropy conserving two-point flux by + - Ayoub Gouasmi, Karthik Duraisamy (2020) + "Formulation of Entropy-Stable schemes for the multicomponent compressible Euler equations" + [arXiv:1904.00972v3](https://arxiv.org/abs/1904.00972) [math.NA] 4 Feb 2020 + """ + @inline function flux_chandrashekar( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerMulticomponentEquations1D + ) + # Unpack left and right state + @unpack gammas, gas_constants, cv = equations + rho_v1_ll, rho_e_ll = u_ll + rho_v1_rr, rho_e_rr = u_rr + rhok_mean = SVector{ncomponents(equations), real(equations)}( + ln_mean( + u_ll[i + 2], + u_rr[i + 2] + ) + for i in eachcomponent(equations) + ) + rhok_avg = SVector{ncomponents(equations), real(equations)}( + 0.5f0 * ( + u_ll[i + 2] + + u_rr[i + 2] + ) + for i in eachcomponent(equations) + ) + + # Iterating over all partial densities + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + gamma_ll = totalgamma(u_ll, equations) + gamma_rr = totalgamma(u_rr, equations) + + # extract velocities + v1_ll = rho_v1_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v1_square = 0.5f0 * (v1_ll^2 + v1_rr^2) + v_sum = v1_avg + + RealT = eltype(u_ll) + enth = zero(RealT) + help1_ll = zero(RealT) + help1_rr = zero(RealT) + + for i in eachcomponent(equations) + enth += rhok_avg[i] * gas_constants[i] + help1_ll += u_ll[i + 2] * cv[i] + help1_rr += u_rr[i + 2] * cv[i] + end + + T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2)) / help1_ll + T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2)) / help1_rr + T = 0.5f0 * (1 / T_ll + 1 / T_rr) + T_log = ln_mean(1 / T_ll, 1 / T_rr) + + # Calculate fluxes depending on orientation + help1 = zero(RealT) + help2 = zero(RealT) + + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v1_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + help1 += f_rho[i] * cv[i] + help2 += f_rho[i] + end + f1 = (help2) * v1_avg + enth / T + f2 = (help1) / T_log - 0.5f0 * (v1_square) * (help2) + v1_avg * f1 + + f_other = SVector(f1, f2) + + return vcat(f_other, f_rho) + end -@inline function max_abs_speeds(u, - equations::CompressibleEulerMulticomponentEquations1D) - rho_v1, rho_e = u + """ + flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerMulticomponentEquations1D) + + Adaption of the entropy conserving and kinetic energy preserving two-point flux by + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + See also + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_ranocha( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerMulticomponentEquations1D + ) + # Unpack left and right state + @unpack gammas, gas_constants, cv = equations + rho_v1_ll, rho_e_ll = u_ll + rho_v1_rr, rho_e_rr = u_rr + rhok_mean = SVector{ncomponents(equations), real(equations)}( + ln_mean( + u_ll[i + 2], + u_rr[i + 2] + ) + for i in eachcomponent(equations) + ) + rhok_avg = SVector{ncomponents(equations), real(equations)}( + 0.5f0 * ( + u_ll[i + 2] + + u_rr[i + 2] + ) + for i in eachcomponent(equations) + ) + + # Iterating over all partial densities + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + # Calculating gamma + gamma = totalgamma(0.5f0 * (u_ll + u_rr), equations) + inv_gamma_minus_one = 1 / (gamma - 1) + + # extract velocities + v1_ll = rho_v1_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v1_avg = 0.5f0 * (v1_ll + v1_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr) + + # density flux + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v1_avg + for i in eachcomponent(equations) + ) + + # helpful variables + RealT = eltype(u_ll) + f_rho_sum = zero(RealT) + help1_ll = zero(RealT) + help1_rr = zero(RealT) + enth_ll = zero(RealT) + enth_rr = zero(RealT) + for i in eachcomponent(equations) + enth_ll += u_ll[i + 2] * gas_constants[i] + enth_rr += u_rr[i + 2] * gas_constants[i] + f_rho_sum += f_rho[i] + help1_ll += u_ll[i + 2] * cv[i] + help1_rr += u_rr[i + 2] * cv[i] + end + + # temperature and pressure + T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2)) / help1_ll + T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2)) / help1_rr + p_ll = T_ll * enth_ll + p_rr = T_rr * enth_rr + p_avg = 0.5f0 * (p_ll + p_rr) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + + # momentum and energy flux + f1 = f_rho_sum * v1_avg + p_avg + f2 = f_rho_sum * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + + 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + f_other = SVector(f1, f2) + + return vcat(f_other, f_rho) + end - rho = density(u, equations) - v1 = rho_v1 / rho + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerMulticomponentEquations1D + ) + rho_v1_ll, rho_e_ll = u_ll + rho_v1_rr, rho_e_rr = u_rr + + # Calculate primitive variables and speed of sound + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + gamma_ll = totalgamma(u_ll, equations) + gamma_rr = totalgamma(u_rr, equations) + + v_ll = rho_v1_ll / rho_ll + v_rr = rho_v1_rr / rho_rr + + p_ll = (gamma_ll - 1) * (rho_e_ll - 0.5f0 * rho_ll * v_ll^2) + p_rr = (gamma_rr - 1) * (rho_e_rr - 0.5f0 * rho_rr * v_rr^2) + c_ll = sqrt(gamma_ll * p_ll / rho_ll) + c_rr = sqrt(gamma_rr * p_rr / rho_rr) + + λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) + end - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2)) - c = sqrt(gamma * p / rho) + @inline function max_abs_speeds( + u, + equations::CompressibleEulerMulticomponentEquations1D + ) + rho_v1, rho_e = u - return (abs(v1) + c,) -end + rho = density(u, equations) + v1 = rho_v1 / rho -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::CompressibleEulerMulticomponentEquations1D) - rho_v1, rho_e = u + gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2)) + c = sqrt(gamma * p / rho) - prim_rho = SVector{ncomponents(equations), real(equations)}(u[i + 2] - for i in eachcomponent(equations)) + return (abs(v1) + c,) + end - rho = density(u, equations) - v1 = rho_v1 / rho - gamma = totalgamma(u, equations) + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::CompressibleEulerMulticomponentEquations1D) + rho_v1, rho_e = u - p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2)) - prim_other = SVector(v1, p) + prim_rho = SVector{ncomponents(equations), real(equations)}( + u[i + 2] + for i in eachcomponent(equations) + ) - return vcat(prim_other, prim_rho) -end + rho = density(u, equations) + v1 = rho_v1 / rho + gamma = totalgamma(u, equations) -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::CompressibleEulerMulticomponentEquations1D) - @unpack cv, gammas = equations - v1, p = prim + p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2)) + prim_other = SVector(v1, p) - RealT = eltype(prim) + return vcat(prim_other, prim_rho) + end - cons_rho = SVector{ncomponents(equations), RealT}(prim[i + 2] - for i in eachcomponent(equations)) - rho = density(prim, equations) - gamma = totalgamma(prim, equations) + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::CompressibleEulerMulticomponentEquations1D) + @unpack cv, gammas = equations + v1, p = prim - rho_v1 = rho * v1 + RealT = eltype(prim) - rho_e = p / (gamma - 1) + 0.5f0 * (rho_v1 * v1) + cons_rho = SVector{ncomponents(equations), RealT}( + prim[i + 2] + for i in eachcomponent(equations) + ) + rho = density(prim, equations) + gamma = totalgamma(prim, equations) - cons_other = SVector{2, RealT}(rho_v1, rho_e) + rho_v1 = rho * v1 - return vcat(cons_other, cons_rho) -end + rho_e = p / (gamma - 1) + 0.5f0 * (rho_v1 * v1) -# Convert conservative variables to entropy -@inline function cons2entropy(u, equations::CompressibleEulerMulticomponentEquations1D) - @unpack cv, gammas, gas_constants = equations + cons_other = SVector{2, RealT}(rho_v1, rho_e) - rho_v1, rho_e = u + return vcat(cons_other, cons_rho) + end - rho = density(u, equations) + # Convert conservative variables to entropy + @inline function cons2entropy(u, equations::CompressibleEulerMulticomponentEquations1D) + @unpack cv, gammas, gas_constants = equations - RealT = eltype(u) - help1 = zero(RealT) - gas_constant = zero(RealT) - for i in eachcomponent(equations) - help1 += u[i + 2] * cv[i] - gas_constant += gas_constants[i] * (u[i + 2] / rho) + rho_v1, rho_e = u + + rho = density(u, equations) + + RealT = eltype(u) + help1 = zero(RealT) + gas_constant = zero(RealT) + for i in eachcomponent(equations) + help1 += u[i + 2] * cv[i] + gas_constant += gas_constants[i] * (u[i + 2] / rho) + end + + v1 = rho_v1 / rho + v_square = v1^2 + gamma = totalgamma(u, equations) + + p = (gamma - 1) * (rho_e - 0.5f0 * rho * v_square) + s = log(p) - gamma * log(rho) - log(gas_constant) + rho_p = rho / p + T = (rho_e - 0.5f0 * rho * v_square) / (help1) + + entrop_rho = SVector{ncomponents(equations), real(equations)}( + ( + cv[i] * + (1 - log(T)) + + gas_constants[i] * + (1 + log(u[i + 2])) - + v1^2 / (2 * T) + ) + for i in eachcomponent(equations) + ) + + w1 = gas_constant * v1 * rho_p + w2 = gas_constant * (-rho_p) + + entrop_other = SVector(w1, w2) + + return vcat(entrop_other, entrop_rho) end - v1 = rho_v1 / rho - v_square = v1^2 - gamma = totalgamma(u, equations) - - p = (gamma - 1) * (rho_e - 0.5f0 * rho * v_square) - s = log(p) - gamma * log(rho) - log(gas_constant) - rho_p = rho / p - T = (rho_e - 0.5f0 * rho * v_square) / (help1) - - entrop_rho = SVector{ncomponents(equations), real(equations)}((cv[i] * - (1 - log(T)) + - gas_constants[i] * - (1 + log(u[i + 2])) - - v1^2 / (2 * T)) - for i in eachcomponent(equations)) - - w1 = gas_constant * v1 * rho_p - w2 = gas_constant * (-rho_p) - - entrop_other = SVector(w1, w2) - - return vcat(entrop_other, entrop_rho) -end - -# Convert entropy variables to conservative variables -@inline function entropy2cons(w, equations::CompressibleEulerMulticomponentEquations1D) - @unpack gammas, gas_constants, cv, cp = equations - T = -1 / w[2] - v1 = w[1] * T - cons_rho = SVector{ncomponents(equations), real(equations)}(exp(1 / - gas_constants[i] * - (-cv[i] * - log(-w[2]) - - cp[i] + w[i + 2] - - 0.5f0 * w[1]^2 / - w[2])) - for i in eachcomponent(equations)) - - RealT = eltype(w) - rho = zero(RealT) - help1 = zero(RealT) - help2 = zero(RealT) - p = zero(RealT) - for i in eachcomponent(equations) - rho += cons_rho[i] - help1 += cons_rho[i] * cv[i] * gammas[i] - help2 += cons_rho[i] * cv[i] - p += cons_rho[i] * gas_constants[i] * T + # Convert entropy variables to conservative variables + @inline function entropy2cons(w, equations::CompressibleEulerMulticomponentEquations1D) + @unpack gammas, gas_constants, cv, cp = equations + T = -1 / w[2] + v1 = w[1] * T + cons_rho = SVector{ncomponents(equations), real(equations)}( + exp( + 1 / + gas_constants[i] * + ( + -cv[i] * + log(-w[2]) - + cp[i] + w[i + 2] - + 0.5f0 * w[1]^2 / + w[2] + ) + ) + for i in eachcomponent(equations) + ) + + RealT = eltype(w) + rho = zero(RealT) + help1 = zero(RealT) + help2 = zero(RealT) + p = zero(RealT) + for i in eachcomponent(equations) + rho += cons_rho[i] + help1 += cons_rho[i] * cv[i] * gammas[i] + help2 += cons_rho[i] * cv[i] + p += cons_rho[i] * gas_constants[i] * T + end + u1 = rho * v1 + gamma = help1 / help2 + u2 = p / (gamma - 1) + 0.5f0 * rho * v1^2 + cons_other = SVector(u1, u2) + return vcat(cons_other, cons_rho) end - u1 = rho * v1 - gamma = help1 / help2 - u2 = p / (gamma - 1) + 0.5f0 * rho * v1^2 - cons_other = SVector(u1, u2) - return vcat(cons_other, cons_rho) -end - -@inline function total_entropy(u, equations::CompressibleEulerMulticomponentEquations1D) - @unpack cv, gammas, gas_constants = equations - rho_v1, rho_e = u - rho = density(u, equations) - T = temperature(u, equations) - - RealT = eltype(u) - total_entropy = zero(RealT) - for i in eachcomponent(equations) - total_entropy -= u[i + 2] * (cv[i] * log(T) - gas_constants[i] * log(u[i + 2])) + + @inline function total_entropy(u, equations::CompressibleEulerMulticomponentEquations1D) + @unpack cv, gammas, gas_constants = equations + rho_v1, rho_e = u + rho = density(u, equations) + T = temperature(u, equations) + + RealT = eltype(u) + total_entropy = zero(RealT) + for i in eachcomponent(equations) + total_entropy -= u[i + 2] * (cv[i] * log(T) - gas_constants[i] * log(u[i + 2])) + end + + return total_entropy end - return total_entropy -end + @inline function temperature(u, equations::CompressibleEulerMulticomponentEquations1D) + @unpack cv, gammas, gas_constants = equations -@inline function temperature(u, equations::CompressibleEulerMulticomponentEquations1D) - @unpack cv, gammas, gas_constants = equations + rho_v1, rho_e = u - rho_v1, rho_e = u + rho = density(u, equations) - rho = density(u, equations) + RealT = eltype(u) + help1 = zero(RealT) - RealT = eltype(u) - help1 = zero(RealT) + for i in eachcomponent(equations) + help1 += u[i + 2] * cv[i] + end - for i in eachcomponent(equations) - help1 += u[i + 2] * cv[i] - end + v1 = rho_v1 / rho + v_square = v1^2 + T = (rho_e - 0.5f0 * rho * v_square) / help1 - v1 = rho_v1 / rho - v_square = v1^2 - T = (rho_e - 0.5f0 * rho * v_square) / help1 + return T + end - return T -end + """ + totalgamma(u, equations::CompressibleEulerMulticomponentEquations1D) -""" - totalgamma(u, equations::CompressibleEulerMulticomponentEquations1D) + Function that calculates the total gamma out of all partial gammas using the + partial density fractions as well as the partial specific heats at constant volume. + """ + @inline function totalgamma(u, equations::CompressibleEulerMulticomponentEquations1D) + @unpack cv, gammas = equations -Function that calculates the total gamma out of all partial gammas using the -partial density fractions as well as the partial specific heats at constant volume. -""" -@inline function totalgamma(u, equations::CompressibleEulerMulticomponentEquations1D) - @unpack cv, gammas = equations + RealT = eltype(u) + help1 = zero(RealT) + help2 = zero(RealT) - RealT = eltype(u) - help1 = zero(RealT) - help2 = zero(RealT) + for i in eachcomponent(equations) + help1 += u[i + 2] * cv[i] * gammas[i] + help2 += u[i + 2] * cv[i] + end - for i in eachcomponent(equations) - help1 += u[i + 2] * cv[i] * gammas[i] - help2 += u[i + 2] * cv[i] + return help1 / help2 end - return help1 / help2 -end + @inline function pressure(u, equations::CompressibleEulerMulticomponentEquations1D) + rho_v1, rho_e = u -@inline function pressure(u, equations::CompressibleEulerMulticomponentEquations1D) - rho_v1, rho_e = u + rho = density(u, equations) + gamma = totalgamma(u, equations) - rho = density(u, equations) - gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2) / rho) - p = (gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2) / rho) + return p + end - return p -end + @inline function density(u, equations::CompressibleEulerMulticomponentEquations1D) + RealT = eltype(u) + rho = zero(RealT) -@inline function density(u, equations::CompressibleEulerMulticomponentEquations1D) - RealT = eltype(u) - rho = zero(RealT) + for i in eachcomponent(equations) + rho += u[i + 2] + end - for i in eachcomponent(equations) - rho += u[i + 2] + return rho end - return rho -end - -@inline function densities(u, v, equations::CompressibleEulerMulticomponentEquations1D) - return SVector{ncomponents(equations), real(equations)}(u[i + 2] * v - for i in eachcomponent(equations)) -end + @inline function densities(u, v, equations::CompressibleEulerMulticomponentEquations1D) + return SVector{ncomponents(equations), real(equations)}( + u[i + 2] * v + for i in eachcomponent(equations) + ) + end end # @muladd diff --git a/src/equations/compressible_euler_multicomponent_2d.jl b/src/equations/compressible_euler_multicomponent_2d.jl index 2424ad0301..166d62e0d6 100644 --- a/src/equations/compressible_euler_multicomponent_2d.jl +++ b/src/equations/compressible_euler_multicomponent_2d.jl @@ -3,826 +3,938 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - CompressibleEulerMulticomponentEquations2D(; gammas, gas_constants) - -Multicomponent version of the compressible Euler equations -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -\rho v_1 \\ \rho v_2 \\ \rho e \\ \rho_1 \\ \rho_2 \\ \vdots \\ \rho_{n} -\end{pmatrix} -+ -\frac{\partial}{\partial x} -\begin{pmatrix} -\rho v_1^2 + p \\ \rho v_1 v_2 \\ ( \rho e +p) v_1 \\ \rho_1 v_1 \\ \rho_2 v_1 \\ \vdots \\ \rho_{n} v_1 -\end{pmatrix} -+ -\frac{\partial}{\partial y} -\begin{pmatrix} -\rho v_1 v_2 \\ \rho v_2^2 + p \\ ( \rho e +p) v_2 \\ \rho_1 v_2 \\ \rho_2 v_2 \\ \vdots \\ \rho_{n} v_2 -\end{pmatrix} -= -\begin{pmatrix} -0 \\ 0 \\ 0 \\ 0 \\ 0 \\ \vdots \\ 0 -\end{pmatrix} -``` -for calorically perfect gas in two space dimensions. -Here, ``\rho_i`` is the density of component ``i``, ``\rho=\sum_{i=1}^n\rho_i`` the sum of the individual ``\rho_i``, -``v_1``, ``v_2`` the velocities, ``e`` the specific total energy **rather than** specific internal energy, and -```math -p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2 + v_2^2) \right) -``` -the pressure, -```math -\gamma=\frac{\sum_{i=1}^n\rho_i C_{v,i}\gamma_i}{\sum_{i=1}^n\rho_i C_{v,i}} -``` -total heat capacity ratio, ``\gamma_i`` heat capacity ratio of component ``i``, -```math -C_{v,i}=\frac{R}{\gamma_i-1} -``` -specific heat capacity at constant volume of component ``i``. - -In case of more than one component, the specific heat ratios `gammas` and the gas constants -`gas_constants` in [kJ/(kg*K)] should be passed as tuples, e.g., `gammas=(1.4, 1.667)`. - -The remaining variables like the specific heats at constant volume `cv` or the specific heats at -constant pressure `cp` are then calculated considering a calorically perfect gas. -""" -struct CompressibleEulerMulticomponentEquations2D{NVARS, NCOMP, RealT <: Real} <: - AbstractCompressibleEulerMulticomponentEquations{2, NVARS, NCOMP} - gammas::SVector{NCOMP, RealT} - gas_constants::SVector{NCOMP, RealT} - cv::SVector{NCOMP, RealT} - cp::SVector{NCOMP, RealT} - - function CompressibleEulerMulticomponentEquations2D{NVARS, NCOMP, RealT}(gammas::SVector{NCOMP, - RealT}, - gas_constants::SVector{NCOMP, - RealT}) where { - NVARS, - NCOMP, - RealT <: - Real - } - NCOMP >= 1 || - throw(DimensionMismatch("`gammas` and `gas_constants` have to be filled with at least one value")) - - cv = gas_constants ./ (gammas .- 1) - cp = gas_constants + gas_constants ./ (gammas .- 1) - - new(gammas, gas_constants, cv, cp) + #! format: noindent + + @doc raw""" + CompressibleEulerMulticomponentEquations2D(; gammas, gas_constants) + + Multicomponent version of the compressible Euler equations + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + \rho v_1 \\ \rho v_2 \\ \rho e \\ \rho_1 \\ \rho_2 \\ \vdots \\ \rho_{n} + \end{pmatrix} + + + \frac{\partial}{\partial x} + \begin{pmatrix} + \rho v_1^2 + p \\ \rho v_1 v_2 \\ ( \rho e +p) v_1 \\ \rho_1 v_1 \\ \rho_2 v_1 \\ \vdots \\ \rho_{n} v_1 + \end{pmatrix} + + + \frac{\partial}{\partial y} + \begin{pmatrix} + \rho v_1 v_2 \\ \rho v_2^2 + p \\ ( \rho e +p) v_2 \\ \rho_1 v_2 \\ \rho_2 v_2 \\ \vdots \\ \rho_{n} v_2 + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 \\ 0 \\ 0 \\ 0 \\ \vdots \\ 0 + \end{pmatrix} + ``` + for calorically perfect gas in two space dimensions. + Here, ``\rho_i`` is the density of component ``i``, ``\rho=\sum_{i=1}^n\rho_i`` the sum of the individual ``\rho_i``, + ``v_1``, ``v_2`` the velocities, ``e`` the specific total energy **rather than** specific internal energy, and + ```math + p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2 + v_2^2) \right) + ``` + the pressure, + ```math + \gamma=\frac{\sum_{i=1}^n\rho_i C_{v,i}\gamma_i}{\sum_{i=1}^n\rho_i C_{v,i}} + ``` + total heat capacity ratio, ``\gamma_i`` heat capacity ratio of component ``i``, + ```math + C_{v,i}=\frac{R}{\gamma_i-1} + ``` + specific heat capacity at constant volume of component ``i``. + + In case of more than one component, the specific heat ratios `gammas` and the gas constants + `gas_constants` in [kJ/(kg*K)] should be passed as tuples, e.g., `gammas=(1.4, 1.667)`. + + The remaining variables like the specific heats at constant volume `cv` or the specific heats at + constant pressure `cp` are then calculated considering a calorically perfect gas. + """ + struct CompressibleEulerMulticomponentEquations2D{NVARS, NCOMP, RealT <: Real} <: + AbstractCompressibleEulerMulticomponentEquations{2, NVARS, NCOMP} + gammas::SVector{NCOMP, RealT} + gas_constants::SVector{NCOMP, RealT} + cv::SVector{NCOMP, RealT} + cp::SVector{NCOMP, RealT} + + function CompressibleEulerMulticomponentEquations2D{NVARS, NCOMP, RealT}( + gammas::SVector{ + NCOMP, + RealT, + }, + gas_constants::SVector{ + NCOMP, + RealT, + } + ) where { + NVARS, + NCOMP, + RealT <: + Real, + } + NCOMP >= 1 || + throw(DimensionMismatch("`gammas` and `gas_constants` have to be filled with at least one value")) + + cv = gas_constants ./ (gammas .- 1) + cp = gas_constants + gas_constants ./ (gammas .- 1) + + new(gammas, gas_constants, cv, cp) + end end -end - -function CompressibleEulerMulticomponentEquations2D(; gammas, gas_constants) - _gammas = promote(gammas...) - _gas_constants = promote(gas_constants...) - RealT = promote_type(eltype(_gammas), eltype(_gas_constants), - typeof(gas_constants[1] / (gammas[1] - 1))) - - NVARS = length(_gammas) + 3 - NCOMP = length(_gammas) - - __gammas = SVector(map(RealT, _gammas)) - __gas_constants = SVector(map(RealT, _gas_constants)) - - return CompressibleEulerMulticomponentEquations2D{NVARS, NCOMP, RealT}(__gammas, - __gas_constants) -end - -@inline function Base.real(::CompressibleEulerMulticomponentEquations2D{NVARS, NCOMP, - RealT}) where { - NVARS, - NCOMP, - RealT - } - RealT -end - -function varnames(::typeof(cons2cons), - equations::CompressibleEulerMulticomponentEquations2D) - cons = ("rho_v1", "rho_v2", "rho_e") - rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) - return (cons..., rhos...) -end - -function varnames(::typeof(cons2prim), - equations::CompressibleEulerMulticomponentEquations2D) - prim = ("v1", "v2", "p") - rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) - return (prim..., rhos...) -end - -# Set initial conditions at physical location `x` for time `t` - -""" - initial_condition_convergence_test(x, t, equations::CompressibleEulerMulticomponentEquations2D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -function initial_condition_convergence_test(x, t, - equations::CompressibleEulerMulticomponentEquations2D) - RealT = eltype(x) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - ini = c + A * sin(omega * (x[1] + x[2] - t)) - - v1 = 1 - v2 = 1 - - rho = ini - - # Here we compute an arbitrary number of different rhos. (one rho is double the next rho while the sum of all rhos is 1) - prim_rho = SVector{ncomponents(equations), real(equations)}(2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - rho - for i in eachcomponent(equations)) - - prim1 = rho * v1 - prim2 = rho * v2 - prim3 = rho^2 - - prim_other = SVector(prim1, prim2, prim3) - - return vcat(prim_other, prim_rho) -end - -""" - source_terms_convergence_test(u, x, t, equations::CompressibleEulerMulticomponentEquations2D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::CompressibleEulerMulticomponentEquations2D) - # Same settings as in `initial_condition` - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - - gamma = totalgamma(u, equations) - - x1, x2 = x - si, co = sincos((x1 + x2 - t) * omega) - tmp1 = co * A * omega - tmp2 = si * A - tmp3 = gamma - 1 - tmp4 = (2 * c - 1) * tmp3 - tmp5 = (2 * tmp2 * gamma - 2 * tmp2 + tmp4 + 1) * tmp1 - tmp6 = tmp2 + c - - # Here we compute an arbitrary number of different rhos. (one rho is double the next rho while the sum of all rhos is 1 - du_rho = SVector{ncomponents(equations), real(equations)}(2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - tmp1 - for i in eachcomponent(equations)) - - du1 = tmp5 - du2 = tmp5 - du3 = 2 * ((tmp6 - 1) * tmp3 + tmp6 * gamma) * tmp1 - - du_other = SVector(du1, du2, du3) - - return vcat(du_other, du_rho) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerMulticomponentEquations2D) - -A for multicomponent adapted weak blast wave taken from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, - equations::CompressibleEulerMulticomponentEquations2D) - # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Set up polar coordinates - RealT = eltype(x) - inicenter = SVector(0, 0) - x_norm = x[1] - inicenter[1] - y_norm = x[2] - inicenter[2] - r = sqrt(x_norm^2 + y_norm^2) - phi = atan(y_norm, x_norm) - sin_phi, cos_phi = sincos(phi) - - prim_rho = SVector{ncomponents(equations), real(equations)}(r > 0.5f0 ? - 2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - one(RealT) : - 2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - convert(RealT, 1.1691) - for i in eachcomponent(equations)) - - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi - v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin_phi - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - prim_other = SVector(v1, v2, p) - - return prim2cons(vcat(prim_other, prim_rho), equations) -end - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, - equations::CompressibleEulerMulticomponentEquations2D) - rho_v1, rho_v2, rho_e = u - - rho = density(u, equations) - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2 + v2^2)) - - if orientation == 1 - f_rho = densities(u, v1, equations) - f1 = rho_v1 * v1 + p - f2 = rho_v2 * v1 - f3 = (rho_e + p) * v1 - else - f_rho = densities(u, v2, equations) - f1 = rho_v1 * v2 - f2 = rho_v2 * v2 + p - f3 = (rho_e + p) * v2 + + function CompressibleEulerMulticomponentEquations2D(; gammas, gas_constants) + _gammas = promote(gammas...) + _gas_constants = promote(gas_constants...) + RealT = promote_type( + eltype(_gammas), eltype(_gas_constants), + typeof(gas_constants[1] / (gammas[1] - 1)) + ) + + NVARS = length(_gammas) + 3 + NCOMP = length(_gammas) + + __gammas = SVector(map(RealT, _gammas)) + __gas_constants = SVector(map(RealT, _gas_constants)) + + return CompressibleEulerMulticomponentEquations2D{NVARS, NCOMP, RealT}( + __gammas, + __gas_constants + ) end - f_other = SVector(f1, f2, f3) - - return vcat(f_other, f_rho) -end - -# Calculate 1D flux for a single point -@inline function flux(u, normal_direction::AbstractVector, - equations::CompressibleEulerMulticomponentEquations2D) - rho_v1, rho_v2, rho_e = u - - rho = density(u, equations) - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2 + v2^2)) - - f_rho = densities(u, v_normal, equations) - f1 = rho_v1 * v_normal + p * normal_direction[1] - f2 = rho_v2 * v_normal + p * normal_direction[2] - f3 = (rho_e + p) * v_normal - - f_other = SVector(f1, f2, f3) - - return vcat(f_other, f_rho) -end - -""" - flux_chandrashekar(u_ll, u_rr, orientation, equations::CompressibleEulerMulticomponentEquations2D) - -Adaption of the entropy conserving two-point flux by -- Ayoub Gouasmi, Karthik Duraisamy (2020) - "Formulation of Entropy-Stable schemes for the multicomponent compressible Euler equations" - [arXiv:1904.00972v3](https://arxiv.org/abs/1904.00972) [math.NA] 4 Feb 2020 -""" -@inline function flux_chandrashekar(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerMulticomponentEquations2D) - # Unpack left and right state - @unpack gammas, gas_constants, cv = equations - rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll - rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr - rhok_mean = SVector{ncomponents(equations), real(equations)}(ln_mean(u_ll[i + 3], - u_rr[i + 3]) - for i in eachcomponent(equations)) - rhok_avg = SVector{ncomponents(equations), real(equations)}(0.5f0 * (u_ll[i + 3] + - u_rr[i + 3]) - for i in eachcomponent(equations)) - - # Iterating over all partial densities - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - # extract velocities - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v1_square = 0.5f0 * (v1_ll^2 + v1_rr^2) - v2_square = 0.5f0 * (v2_ll^2 + v2_rr^2) - v_sum = v1_avg + v2_avg - - RealT = eltype(u_ll) - enth = zero(RealT) - help1_ll = zero(RealT) - help1_rr = zero(RealT) - - for i in eachcomponent(equations) - enth += rhok_avg[i] * gas_constants[i] - help1_ll += u_ll[i + 3] * cv[i] - help1_rr += u_rr[i + 3] * cv[i] + @inline function Base.real( + ::CompressibleEulerMulticomponentEquations2D{ + NVARS, NCOMP, + RealT, + } + ) where { + NVARS, + NCOMP, + RealT, + } + RealT end - T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2)) / help1_ll - T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2)) / help1_rr - T = 0.5f0 * (1 / T_ll + 1 / T_rr) - T_log = ln_mean(1 / T_ll, 1 / T_rr) - - # Calculate fluxes depending on orientation - help1 = zero(RealT) - help2 = zero(RealT) - if orientation == 1 - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v1_avg - for i in eachcomponent(equations)) - for i in eachcomponent(equations) - help1 += f_rho[i] * cv[i] - help2 += f_rho[i] + function varnames( + ::typeof(cons2cons), + equations::CompressibleEulerMulticomponentEquations2D + ) + cons = ("rho_v1", "rho_v2", "rho_e") + rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) + return (cons..., rhos...) + end + + function varnames( + ::typeof(cons2prim), + equations::CompressibleEulerMulticomponentEquations2D + ) + prim = ("v1", "v2", "p") + rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) + return (prim..., rhos...) + end + + # Set initial conditions at physical location `x` for time `t` + + """ + initial_condition_convergence_test(x, t, equations::CompressibleEulerMulticomponentEquations2D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + function initial_condition_convergence_test( + x, t, + equations::CompressibleEulerMulticomponentEquations2D + ) + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + ini = c + A * sin(omega * (x[1] + x[2] - t)) + + v1 = 1 + v2 = 1 + + rho = ini + + # Here we compute an arbitrary number of different rhos. (one rho is double the next rho while the sum of all rhos is 1) + prim_rho = SVector{ncomponents(equations), real(equations)}( + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + rho + for i in eachcomponent(equations) + ) + + prim1 = rho * v1 + prim2 = rho * v2 + prim3 = rho^2 + + prim_other = SVector(prim1, prim2, prim3) + + return vcat(prim_other, prim_rho) + end + + """ + source_terms_convergence_test(u, x, t, equations::CompressibleEulerMulticomponentEquations2D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::CompressibleEulerMulticomponentEquations2D + ) + # Same settings as in `initial_condition` + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + + gamma = totalgamma(u, equations) + + x1, x2 = x + si, co = sincos((x1 + x2 - t) * omega) + tmp1 = co * A * omega + tmp2 = si * A + tmp3 = gamma - 1 + tmp4 = (2 * c - 1) * tmp3 + tmp5 = (2 * tmp2 * gamma - 2 * tmp2 + tmp4 + 1) * tmp1 + tmp6 = tmp2 + c + + # Here we compute an arbitrary number of different rhos. (one rho is double the next rho while the sum of all rhos is 1 + du_rho = SVector{ncomponents(equations), real(equations)}( + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + tmp1 + for i in eachcomponent(equations) + ) + + du1 = tmp5 + du2 = tmp5 + du3 = 2 * ((tmp6 - 1) * tmp3 + tmp6 * gamma) * tmp1 + + du_other = SVector(du1, du2, du3) + + return vcat(du_other, du_rho) + end + + """ + initial_condition_weak_blast_wave(x, t, equations::CompressibleEulerMulticomponentEquations2D) + + A for multicomponent adapted weak blast wave taken from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave( + x, t, + equations::CompressibleEulerMulticomponentEquations2D + ) + # From Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Set up polar coordinates + RealT = eltype(x) + inicenter = SVector(0, 0) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + phi = atan(y_norm, x_norm) + sin_phi, cos_phi = sincos(phi) + + prim_rho = SVector{ncomponents(equations), real(equations)}( + r > 0.5f0 ? + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + one(RealT) : + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + convert(RealT, 1.1691) + for i in eachcomponent(equations) + ) + + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi + v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin_phi + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + prim_other = SVector(v1, v2, p) + + return prim2cons(vcat(prim_other, prim_rho), equations) + end + + # Calculate 1D flux for a single point + @inline function flux( + u, orientation::Integer, + equations::CompressibleEulerMulticomponentEquations2D + ) + rho_v1, rho_v2, rho_e = u + + rho = density(u, equations) + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2 + v2^2)) + + if orientation == 1 + f_rho = densities(u, v1, equations) + f1 = rho_v1 * v1 + p + f2 = rho_v2 * v1 + f3 = (rho_e + p) * v1 + else + f_rho = densities(u, v2, equations) + f1 = rho_v1 * v2 + f2 = rho_v2 * v2 + p + f3 = (rho_e + p) * v2 end - f1 = (help2) * v1_avg + enth / T - f2 = (help2) * v2_avg - f3 = (help1) / T_log - 0.5f0 * (v1_square + v2_square) * (help2) + v1_avg * f1 + - v2_avg * f2 - else - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v2_avg - for i in eachcomponent(equations)) + + f_other = SVector(f1, f2, f3) + + return vcat(f_other, f_rho) + end + + # Calculate 1D flux for a single point + @inline function flux( + u, normal_direction::AbstractVector, + equations::CompressibleEulerMulticomponentEquations2D + ) + rho_v1, rho_v2, rho_e = u + + rho = density(u, equations) + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2 + v2^2)) + + f_rho = densities(u, v_normal, equations) + f1 = rho_v1 * v_normal + p * normal_direction[1] + f2 = rho_v2 * v_normal + p * normal_direction[2] + f3 = (rho_e + p) * v_normal + + f_other = SVector(f1, f2, f3) + + return vcat(f_other, f_rho) + end + + """ + flux_chandrashekar(u_ll, u_rr, orientation, equations::CompressibleEulerMulticomponentEquations2D) + + Adaption of the entropy conserving two-point flux by + - Ayoub Gouasmi, Karthik Duraisamy (2020) + "Formulation of Entropy-Stable schemes for the multicomponent compressible Euler equations" + [arXiv:1904.00972v3](https://arxiv.org/abs/1904.00972) [math.NA] 4 Feb 2020 + """ + @inline function flux_chandrashekar( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerMulticomponentEquations2D + ) + # Unpack left and right state + @unpack gammas, gas_constants, cv = equations + rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll + rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr + rhok_mean = SVector{ncomponents(equations), real(equations)}( + ln_mean( + u_ll[i + 3], + u_rr[i + 3] + ) + for i in eachcomponent(equations) + ) + rhok_avg = SVector{ncomponents(equations), real(equations)}( + 0.5f0 * ( + u_ll[i + 3] + + u_rr[i + 3] + ) + for i in eachcomponent(equations) + ) + + # Iterating over all partial densities + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + # extract velocities + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v1_square = 0.5f0 * (v1_ll^2 + v1_rr^2) + v2_square = 0.5f0 * (v2_ll^2 + v2_rr^2) + v_sum = v1_avg + v2_avg + + RealT = eltype(u_ll) + enth = zero(RealT) + help1_ll = zero(RealT) + help1_rr = zero(RealT) + for i in eachcomponent(equations) - help1 += f_rho[i] * cv[i] - help2 += f_rho[i] + enth += rhok_avg[i] * gas_constants[i] + help1_ll += u_ll[i + 3] * cv[i] + help1_rr += u_rr[i + 3] * cv[i] end - f1 = (help2) * v1_avg - f2 = (help2) * v2_avg + enth / T - f3 = (help1) / T_log - 0.5f0 * (v1_square + v2_square) * (help2) + v1_avg * f1 + - v2_avg * f2 + + T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2)) / help1_ll + T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2)) / help1_rr + T = 0.5f0 * (1 / T_ll + 1 / T_rr) + T_log = ln_mean(1 / T_ll, 1 / T_rr) + + # Calculate fluxes depending on orientation + help1 = zero(RealT) + help2 = zero(RealT) + if orientation == 1 + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v1_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + help1 += f_rho[i] * cv[i] + help2 += f_rho[i] + end + f1 = (help2) * v1_avg + enth / T + f2 = (help2) * v2_avg + f3 = (help1) / T_log - 0.5f0 * (v1_square + v2_square) * (help2) + v1_avg * f1 + + v2_avg * f2 + else + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v2_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + help1 += f_rho[i] * cv[i] + help2 += f_rho[i] + end + f1 = (help2) * v1_avg + f2 = (help2) * v2_avg + enth / T + f3 = (help1) / T_log - 0.5f0 * (v1_square + v2_square) * (help2) + v1_avg * f1 + + v2_avg * f2 + end + f_other = SVector(f1, f2, f3) + + return vcat(f_other, f_rho) end - f_other = SVector(f1, f2, f3) - - return vcat(f_other, f_rho) -end - -""" - flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, - equations::CompressibleEulerMulticomponentEquations2D) - -Adaption of the entropy conserving and kinetic energy preserving two-point flux by -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -See also -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_ranocha(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerMulticomponentEquations2D) - # Unpack left and right state - @unpack gammas, gas_constants, cv = equations - rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll - rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr - rhok_mean = SVector{ncomponents(equations), real(equations)}(ln_mean(u_ll[i + 3], - u_rr[i + 3]) - for i in eachcomponent(equations)) - rhok_avg = SVector{ncomponents(equations), real(equations)}(0.5f0 * (u_ll[i + 3] + - u_rr[i + 3]) - for i in eachcomponent(equations)) - - # Iterating over all partial densities - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - # Calculating gamma - gamma = totalgamma(0.5f0 * (u_ll + u_rr), equations) - inv_gamma_minus_one = 1 / (gamma - 1) - - # extract velocities - v1_ll = rho_v1_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_ll = rho_v2_ll / rho_ll - v2_rr = rho_v2_rr / rho_rr - v2_avg = 0.5f0 * (v2_ll + v2_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) - - # helpful variables - RealT = eltype(u_ll) - help1_ll = zero(RealT) - help1_rr = zero(RealT) - enth_ll = zero(RealT) - enth_rr = zero(RealT) - for i in eachcomponent(equations) - enth_ll += u_ll[i + 3] * gas_constants[i] - enth_rr += u_rr[i + 3] * gas_constants[i] - help1_ll += u_ll[i + 3] * cv[i] - help1_rr += u_rr[i + 3] * cv[i] + + """ + flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, + equations::CompressibleEulerMulticomponentEquations2D) + + Adaption of the entropy conserving and kinetic energy preserving two-point flux by + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + See also + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_ranocha( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerMulticomponentEquations2D + ) + # Unpack left and right state + @unpack gammas, gas_constants, cv = equations + rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll + rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr + rhok_mean = SVector{ncomponents(equations), real(equations)}( + ln_mean( + u_ll[i + 3], + u_rr[i + 3] + ) + for i in eachcomponent(equations) + ) + rhok_avg = SVector{ncomponents(equations), real(equations)}( + 0.5f0 * ( + u_ll[i + 3] + + u_rr[i + 3] + ) + for i in eachcomponent(equations) + ) + + # Iterating over all partial densities + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + # Calculating gamma + gamma = totalgamma(0.5f0 * (u_ll + u_rr), equations) + inv_gamma_minus_one = 1 / (gamma - 1) + + # extract velocities + v1_ll = rho_v1_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_ll = rho_v2_ll / rho_ll + v2_rr = rho_v2_rr / rho_rr + v2_avg = 0.5f0 * (v2_ll + v2_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) + + # helpful variables + RealT = eltype(u_ll) + help1_ll = zero(RealT) + help1_rr = zero(RealT) + enth_ll = zero(RealT) + enth_rr = zero(RealT) + for i in eachcomponent(equations) + enth_ll += u_ll[i + 3] * gas_constants[i] + enth_rr += u_rr[i + 3] * gas_constants[i] + help1_ll += u_ll[i + 3] * cv[i] + help1_rr += u_rr[i + 3] * cv[i] + end + + # temperature and pressure + T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2)) / help1_ll + T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2)) / help1_rr + p_ll = T_ll * enth_ll + p_rr = T_rr * enth_rr + p_avg = 0.5f0 * (p_ll + p_rr) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + + f_rho_sum = zero(RealT) + if orientation == 1 + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v1_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + f_rho_sum += f_rho[i] + end + f1 = f_rho_sum * v1_avg + p_avg + f2 = f_rho_sum * v2_avg + f3 = f_rho_sum * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + + 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) + else + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v2_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + f_rho_sum += f_rho[i] + end + f1 = f_rho_sum * v1_avg + f2 = f_rho_sum * v2_avg + p_avg + f3 = f_rho_sum * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + + 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) + end + + # momentum and energy flux + f_other = SVector(f1, f2, f3) + + return vcat(f_other, f_rho) end - # temperature and pressure - T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2)) / help1_ll - T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2)) / help1_rr - p_ll = T_ll * enth_ll - p_rr = T_rr * enth_rr - p_avg = 0.5f0 * (p_ll + p_rr) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - - f_rho_sum = zero(RealT) - if orientation == 1 - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v1_avg - for i in eachcomponent(equations)) + @inline function flux_ranocha( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerMulticomponentEquations2D + ) + # Unpack left and right state + @unpack gammas, gas_constants, cv = equations + rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll + rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr + rhok_mean = SVector{ncomponents(equations), real(equations)}( + ln_mean( + u_ll[i + 3], + u_rr[i + 3] + ) + for i in eachcomponent(equations) + ) + rhok_avg = SVector{ncomponents(equations), real(equations)}( + 0.5f0 * ( + u_ll[i + 3] + + u_rr[i + 3] + ) + for i in eachcomponent(equations) + ) + + # Iterating over all partial densities + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + # Calculating gamma + gamma = totalgamma(0.5f0 * (u_ll + u_rr), equations) + inv_gamma_minus_one = 1 / (gamma - 1) + + # extract velocities + v1_ll = rho_v1_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_ll = rho_v2_ll / rho_ll + v2_rr = rho_v2_rr / rho_rr + v2_avg = 0.5f0 * (v2_ll + v2_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # helpful variables + RealT = eltype(u_ll) + help1_ll = zero(RealT) + help1_rr = zero(RealT) + enth_ll = zero(RealT) + enth_rr = zero(RealT) for i in eachcomponent(equations) - f_rho_sum += f_rho[i] + enth_ll += u_ll[i + 3] * gas_constants[i] + enth_rr += u_rr[i + 3] * gas_constants[i] + help1_ll += u_ll[i + 3] * cv[i] + help1_rr += u_rr[i + 3] * cv[i] end - f1 = f_rho_sum * v1_avg + p_avg - f2 = f_rho_sum * v2_avg - f3 = f_rho_sum * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + - 0.5f0 * (p_ll * v1_rr + p_rr * v1_ll) - else - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v2_avg - for i in eachcomponent(equations)) + + # temperature and pressure + T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2)) / help1_ll + T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2)) / help1_rr + p_ll = T_ll * enth_ll + p_rr = T_rr * enth_rr + p_avg = 0.5f0 * (p_ll + p_rr) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + + f_rho_sum = zero(RealT) + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * 0.5f0 * + (v_dot_n_ll + v_dot_n_rr) + for i in eachcomponent(equations) + ) for i in eachcomponent(equations) f_rho_sum += f_rho[i] end - f1 = f_rho_sum * v1_avg - f2 = f_rho_sum * v2_avg + p_avg + f1 = f_rho_sum * v1_avg + p_avg * normal_direction[1] + f2 = f_rho_sum * v2_avg + p_avg * normal_direction[2] f3 = f_rho_sum * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + - 0.5f0 * (p_ll * v2_rr + p_rr * v2_ll) + 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + + # momentum and energy flux + f_other = SVector(f1, f2, f3) + + return vcat(f_other, f_rho) end - # momentum and energy flux - f_other = SVector(f1, f2, f3) - - return vcat(f_other, f_rho) -end - -@inline function flux_ranocha(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerMulticomponentEquations2D) - # Unpack left and right state - @unpack gammas, gas_constants, cv = equations - rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll - rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr - rhok_mean = SVector{ncomponents(equations), real(equations)}(ln_mean(u_ll[i + 3], - u_rr[i + 3]) - for i in eachcomponent(equations)) - rhok_avg = SVector{ncomponents(equations), real(equations)}(0.5f0 * (u_ll[i + 3] + - u_rr[i + 3]) - for i in eachcomponent(equations)) - - # Iterating over all partial densities - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - # Calculating gamma - gamma = totalgamma(0.5f0 * (u_ll + u_rr), equations) - inv_gamma_minus_one = 1 / (gamma - 1) - - # extract velocities - v1_ll = rho_v1_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_ll = rho_v2_ll / rho_ll - v2_rr = rho_v2_rr / rho_rr - v2_avg = 0.5f0 * (v2_ll + v2_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr) - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # helpful variables - RealT = eltype(u_ll) - help1_ll = zero(RealT) - help1_rr = zero(RealT) - enth_ll = zero(RealT) - enth_rr = zero(RealT) - for i in eachcomponent(equations) - enth_ll += u_ll[i + 3] * gas_constants[i] - enth_rr += u_rr[i + 3] * gas_constants[i] - help1_ll += u_ll[i + 3] * cv[i] - help1_rr += u_rr[i + 3] * cv[i] + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerMulticomponentEquations2D + ) + rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll + rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr + + # Get the density and gas gamma + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + gamma_ll = totalgamma(u_ll, equations) + gamma_rr = totalgamma(u_rr, equations) + + # Get the velocities based on direction + if orientation == 1 + v_ll = rho_v1_ll / rho_ll + v_rr = rho_v1_rr / rho_rr + else # orientation == 2 + v_ll = rho_v2_ll / rho_ll + v_rr = rho_v2_rr / rho_rr + end + + # Compute the sound speeds on the left and right + p_ll = (gamma_ll - 1) * (rho_e_ll - 0.5f0 * (rho_v1_ll^2 + rho_v2_ll^2) / rho_ll) + c_ll = sqrt(gamma_ll * p_ll / rho_ll) + p_rr = (gamma_rr - 1) * (rho_e_rr - 0.5f0 * (rho_v1_rr^2 + rho_v2_rr^2) / rho_rr) + c_rr = sqrt(gamma_rr * p_rr / rho_rr) + + λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) end - # temperature and pressure - T_ll = (rho_e_ll - 0.5f0 * rho_ll * (v1_ll^2 + v2_ll^2)) / help1_ll - T_rr = (rho_e_rr - 0.5f0 * rho_rr * (v1_rr^2 + v2_rr^2)) / help1_rr - p_ll = T_ll * enth_ll - p_rr = T_rr * enth_rr - p_avg = 0.5f0 * (p_ll + p_rr) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - - f_rho_sum = zero(RealT) - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * 0.5f0 * - (v_dot_n_ll + v_dot_n_rr) - for i in eachcomponent(equations)) - for i in eachcomponent(equations) - f_rho_sum += f_rho[i] + @inline function max_abs_speeds( + u, + equations::CompressibleEulerMulticomponentEquations2D + ) + rho_v1, rho_v2, rho_e = u + + rho = density(u, equations) + v1 = rho_v1 / rho + v2 = rho_v2 / rho + + gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2 + v2^2)) + c = sqrt(gamma * p / rho) + + return (abs(v1) + c, abs(v2) + c) end - f1 = f_rho_sum * v1_avg + p_avg * normal_direction[1] - f2 = f_rho_sum * v2_avg + p_avg * normal_direction[2] - f3 = f_rho_sum * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + - 0.5f0 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) - - # momentum and energy flux - f_other = SVector(f1, f2, f3) - - return vcat(f_other, f_rho) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerMulticomponentEquations2D) - rho_v1_ll, rho_v2_ll, rho_e_ll = u_ll - rho_v1_rr, rho_v2_rr, rho_e_rr = u_rr - - # Get the density and gas gamma - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - gamma_ll = totalgamma(u_ll, equations) - gamma_rr = totalgamma(u_rr, equations) - - # Get the velocities based on direction - if orientation == 1 - v_ll = rho_v1_ll / rho_ll - v_rr = rho_v1_rr / rho_rr - else # orientation == 2 - v_ll = rho_v2_ll / rho_ll - v_rr = rho_v2_rr / rho_rr + + @inline function rotate_to_x( + u, normal_vector, + equations::CompressibleEulerMulticomponentEquations2D + ) + # cos and sin of the angle between the x-axis and the normalized normal_vector are + # the normalized vector's x and y coordinates respectively (see unit circle). + c = normal_vector[1] + s = normal_vector[2] + + # Apply the 2D rotation matrix with normal and tangent directions of the form + # [ n_1 n_2 0 0; + # t_1 t_2 0 0; + # 0 0 1 0 + # 0 0 0 1] + # where t_1 = -n_2 and t_2 = n_1 + + densities = @view u[4:end] + return SVector( + c * u[1] + s * u[2], + -s * u[1] + c * u[2], + u[3], + densities... + ) end - # Compute the sound speeds on the left and right - p_ll = (gamma_ll - 1) * (rho_e_ll - 0.5f0 * (rho_v1_ll^2 + rho_v2_ll^2) / rho_ll) - c_ll = sqrt(gamma_ll * p_ll / rho_ll) - p_rr = (gamma_rr - 1) * (rho_e_rr - 0.5f0 * (rho_v1_rr^2 + rho_v2_rr^2) / rho_rr) - c_rr = sqrt(gamma_rr * p_rr / rho_rr) - - λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) -end - -@inline function max_abs_speeds(u, - equations::CompressibleEulerMulticomponentEquations2D) - rho_v1, rho_v2, rho_e = u - - rho = density(u, equations) - v1 = rho_v1 / rho - v2 = rho_v2 / rho - - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2 + v2^2)) - c = sqrt(gamma * p / rho) - - return (abs(v1) + c, abs(v2) + c) -end - -@inline function rotate_to_x(u, normal_vector, - equations::CompressibleEulerMulticomponentEquations2D) - # cos and sin of the angle between the x-axis and the normalized normal_vector are - # the normalized vector's x and y coordinates respectively (see unit circle). - c = normal_vector[1] - s = normal_vector[2] - - # Apply the 2D rotation matrix with normal and tangent directions of the form - # [ n_1 n_2 0 0; - # t_1 t_2 0 0; - # 0 0 1 0 - # 0 0 0 1] - # where t_1 = -n_2 and t_2 = n_1 - - densities = @view u[4:end] - return SVector(c * u[1] + s * u[2], - -s * u[1] + c * u[2], - u[3], - densities...) -end - -# Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction -# has been normalized prior to this back-rotation of the state vector -@inline function rotate_from_x(u, normal_vector, - equations::CompressibleEulerMulticomponentEquations2D) - # cos and sin of the angle between the x-axis and the normalized normal_vector are - # the normalized vector's x and y coordinates respectively (see unit circle). - c = normal_vector[1] - s = normal_vector[2] - - # Apply the 2D back-rotation matrix with normal and tangent directions of the form - # [ n_1 t_1 0 0; - # n_2 t_2 0 0; - # 0 0 1 0; - # 0 0 0 1 ] - # where t_1 = -n_2 and t_2 = n_1 - - densities = @view u[4:end] - return SVector(c * u[1] - s * u[2], - s * u[1] + c * u[2], - u[3], - densities...) -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::CompressibleEulerMulticomponentEquations2D) - rho_v1, rho_v2, rho_e = u - - prim_rho = SVector{ncomponents(equations), real(equations)}(u[i + 3] - for i in eachcomponent(equations)) - - rho = density(u, equations) - v1 = rho_v1 / rho - v2 = rho_v2 / rho - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2 + v2^2)) - prim_other = SVector(v1, v2, p) - - return vcat(prim_other, prim_rho) -end - -# Convert conservative variables to entropy -@inline function cons2entropy(u, equations::CompressibleEulerMulticomponentEquations2D) - @unpack cv, gammas, gas_constants = equations - rho_v1, rho_v2, rho_e = u - - rho = density(u, equations) - - # Multicomponent stuff - RealT = eltype(u) - help1 = zero(RealT) - gas_constant = zero(RealT) - for i in eachcomponent(equations) - help1 += u[i + 3] * cv[i] - gas_constant += gas_constants[i] * (u[i + 3] / rho) + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction + # has been normalized prior to this back-rotation of the state vector + @inline function rotate_from_x( + u, normal_vector, + equations::CompressibleEulerMulticomponentEquations2D + ) + # cos and sin of the angle between the x-axis and the normalized normal_vector are + # the normalized vector's x and y coordinates respectively (see unit circle). + c = normal_vector[1] + s = normal_vector[2] + + # Apply the 2D back-rotation matrix with normal and tangent directions of the form + # [ n_1 t_1 0 0; + # n_2 t_2 0 0; + # 0 0 1 0; + # 0 0 0 1 ] + # where t_1 = -n_2 and t_2 = n_1 + + densities = @view u[4:end] + return SVector( + c * u[1] - s * u[2], + s * u[1] + c * u[2], + u[3], + densities... + ) end - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v_square = v1^2 + v2^2 - gamma = totalgamma(u, equations) - - p = (gamma - 1) * (rho_e - 0.5f0 * rho * v_square) - s = log(p) - gamma * log(rho) - log(gas_constant) - rho_p = rho / p - T = (rho_e - 0.5f0 * rho * v_square) / (help1) - - entrop_rho = SVector{ncomponents(equations), real(equations)}((cv[i] * - (1 - log(T)) + - gas_constants[i] * - (1 + log(u[i + 3])) - - v_square / (2 * T)) - for i in eachcomponent(equations)) - - w1 = gas_constant * v1 * rho_p - w2 = gas_constant * v2 * rho_p - w3 = gas_constant * (-rho_p) - - entrop_other = SVector(w1, w2, w3) - - return vcat(entrop_other, entrop_rho) -end - -# Convert entropy variables to conservative variables -@inline function entropy2cons(w, equations::CompressibleEulerMulticomponentEquations2D) - @unpack gammas, gas_constants, cp, cv = equations - T = -1 / w[3] - v1 = w[1] * T - v2 = w[2] * T - v_squared = v1^2 + v2^2 - cons_rho = SVector{ncomponents(equations), real(equations)}(exp((w[i + 3] - - cv[i] * - (1 - log(T)) + - v_squared / - (2 * T)) / - gas_constants[i] - - 1) - for i in eachcomponent(equations)) - - RealT = eltype(w) - rho = zero(RealT) - help1 = zero(RealT) - help2 = zero(RealT) - p = zero(RealT) - for i in eachcomponent(equations) - rho += cons_rho[i] - help1 += cons_rho[i] * cv[i] * gammas[i] - help2 += cons_rho[i] * cv[i] - p += cons_rho[i] * gas_constants[i] * T + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::CompressibleEulerMulticomponentEquations2D) + rho_v1, rho_v2, rho_e = u + + prim_rho = SVector{ncomponents(equations), real(equations)}( + u[i + 3] + for i in eachcomponent(equations) + ) + + rho = density(u, equations) + v1 = rho_v1 / rho + v2 = rho_v2 / rho + gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - 0.5f0 * rho * (v1^2 + v2^2)) + prim_other = SVector(v1, v2, p) + + return vcat(prim_other, prim_rho) end - u1 = rho * v1 - u2 = rho * v2 - gamma = help1 / help2 - u3 = p / (gamma - 1) + 0.5f0 * rho * v_squared - cons_other = SVector(u1, u2, u3) - return vcat(cons_other, cons_rho) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::CompressibleEulerMulticomponentEquations2D) - @unpack cv, gammas = equations - v1, v2, p = prim - - cons_rho = SVector{ncomponents(equations), real(equations)}(prim[i + 3] - for i in eachcomponent(equations)) - rho = density(prim, equations) - gamma = totalgamma(prim, equations) - - rho_v1 = rho * v1 - rho_v2 = rho * v2 - rho_e = p / (gamma - 1) + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2) - - cons_other = SVector(rho_v1, rho_v2, rho_e) - - return vcat(cons_other, cons_rho) -end - -@inline function total_entropy(u, equations::CompressibleEulerMulticomponentEquations2D) - @unpack cv, gammas, gas_constants = equations - rho = density(u, equations) - T = temperature(u, equations) - - RealT = eltype(u) - total_entropy = zero(RealT) - for i in eachcomponent(equations) - total_entropy -= u[i + 3] * (cv[i] * log(T) - gas_constants[i] * log(u[i + 3])) + + # Convert conservative variables to entropy + @inline function cons2entropy(u, equations::CompressibleEulerMulticomponentEquations2D) + @unpack cv, gammas, gas_constants = equations + rho_v1, rho_v2, rho_e = u + + rho = density(u, equations) + + # Multicomponent stuff + RealT = eltype(u) + help1 = zero(RealT) + gas_constant = zero(RealT) + for i in eachcomponent(equations) + help1 += u[i + 3] * cv[i] + gas_constant += gas_constants[i] * (u[i + 3] / rho) + end + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v_square = v1^2 + v2^2 + gamma = totalgamma(u, equations) + + p = (gamma - 1) * (rho_e - 0.5f0 * rho * v_square) + s = log(p) - gamma * log(rho) - log(gas_constant) + rho_p = rho / p + T = (rho_e - 0.5f0 * rho * v_square) / (help1) + + entrop_rho = SVector{ncomponents(equations), real(equations)}( + ( + cv[i] * + (1 - log(T)) + + gas_constants[i] * + (1 + log(u[i + 3])) - + v_square / (2 * T) + ) + for i in eachcomponent(equations) + ) + + w1 = gas_constant * v1 * rho_p + w2 = gas_constant * v2 * rho_p + w3 = gas_constant * (-rho_p) + + entrop_other = SVector(w1, w2, w3) + + return vcat(entrop_other, entrop_rho) end - return total_entropy -end + # Convert entropy variables to conservative variables + @inline function entropy2cons(w, equations::CompressibleEulerMulticomponentEquations2D) + @unpack gammas, gas_constants, cp, cv = equations + T = -1 / w[3] + v1 = w[1] * T + v2 = w[2] * T + v_squared = v1^2 + v2^2 + cons_rho = SVector{ncomponents(equations), real(equations)}( + exp( + ( + w[i + 3] - + cv[i] * + (1 - log(T)) + + v_squared / + (2 * T) + ) / + gas_constants[i] - + 1 + ) + for i in eachcomponent(equations) + ) + + RealT = eltype(w) + rho = zero(RealT) + help1 = zero(RealT) + help2 = zero(RealT) + p = zero(RealT) + for i in eachcomponent(equations) + rho += cons_rho[i] + help1 += cons_rho[i] * cv[i] * gammas[i] + help2 += cons_rho[i] * cv[i] + p += cons_rho[i] * gas_constants[i] * T + end + u1 = rho * v1 + u2 = rho * v2 + gamma = help1 / help2 + u3 = p / (gamma - 1) + 0.5f0 * rho * v_squared + cons_other = SVector(u1, u2, u3) + return vcat(cons_other, cons_rho) + end -@inline function temperature(u, equations::CompressibleEulerMulticomponentEquations2D) - @unpack cv, gammas, gas_constants = equations + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::CompressibleEulerMulticomponentEquations2D) + @unpack cv, gammas = equations + v1, v2, p = prim - rho_v1, rho_v2, rho_e = u + cons_rho = SVector{ncomponents(equations), real(equations)}( + prim[i + 3] + for i in eachcomponent(equations) + ) + rho = density(prim, equations) + gamma = totalgamma(prim, equations) - rho = density(u, equations) - RealT = eltype(u) - help1 = zero(RealT) + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_e = p / (gamma - 1) + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2) - for i in eachcomponent(equations) - help1 += u[i + 3] * cv[i] + cons_other = SVector(rho_v1, rho_v2, rho_e) + + return vcat(cons_other, cons_rho) + end + + @inline function total_entropy(u, equations::CompressibleEulerMulticomponentEquations2D) + @unpack cv, gammas, gas_constants = equations + rho = density(u, equations) + T = temperature(u, equations) + + RealT = eltype(u) + total_entropy = zero(RealT) + for i in eachcomponent(equations) + total_entropy -= u[i + 3] * (cv[i] * log(T) - gas_constants[i] * log(u[i + 3])) + end + + return total_entropy end - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v_square = v1^2 + v2^2 - T = (rho_e - 0.5f0 * rho * v_square) / help1 + @inline function temperature(u, equations::CompressibleEulerMulticomponentEquations2D) + @unpack cv, gammas, gas_constants = equations - return T -end + rho_v1, rho_v2, rho_e = u -""" - totalgamma(u, equations::CompressibleEulerMulticomponentEquations2D) + rho = density(u, equations) + RealT = eltype(u) + help1 = zero(RealT) -Function that calculates the total gamma out of all partial gammas using the -partial density fractions as well as the partial specific heats at constant volume. -""" -@inline function totalgamma(u, equations::CompressibleEulerMulticomponentEquations2D) - @unpack cv, gammas = equations + for i in eachcomponent(equations) + help1 += u[i + 3] * cv[i] + end - RealT = eltype(u) - help1 = zero(RealT) - help2 = zero(RealT) + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v_square = v1^2 + v2^2 + T = (rho_e - 0.5f0 * rho * v_square) / help1 - for i in eachcomponent(equations) - help1 += u[i + 3] * cv[i] * gammas[i] - help2 += u[i + 3] * cv[i] + return T end - return help1 / help2 -end + """ + totalgamma(u, equations::CompressibleEulerMulticomponentEquations2D) + + Function that calculates the total gamma out of all partial gammas using the + partial density fractions as well as the partial specific heats at constant volume. + """ + @inline function totalgamma(u, equations::CompressibleEulerMulticomponentEquations2D) + @unpack cv, gammas = equations -@inline function density_pressure(u, - equations::CompressibleEulerMulticomponentEquations2D) - rho_v1, rho_v2, rho_e = u + RealT = eltype(u) + help1 = zero(RealT) + help2 = zero(RealT) - rho = density(u, equations) - gamma = totalgamma(u, equations) - rho_times_p = (gamma - 1) * (rho * rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2)) + for i in eachcomponent(equations) + help1 += u[i + 3] * cv[i] * gammas[i] + help2 += u[i + 3] * cv[i] + end - return rho_times_p -end + return help1 / help2 + end -@inline function density(u, equations::CompressibleEulerMulticomponentEquations2D) - RealT = eltype(u) - rho = zero(RealT) + @inline function density_pressure( + u, + equations::CompressibleEulerMulticomponentEquations2D + ) + rho_v1, rho_v2, rho_e = u - for i in eachcomponent(equations) - rho += u[i + 3] + rho = density(u, equations) + gamma = totalgamma(u, equations) + rho_times_p = (gamma - 1) * (rho * rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2)) + + return rho_times_p end - return rho -end + @inline function density(u, equations::CompressibleEulerMulticomponentEquations2D) + RealT = eltype(u) + rho = zero(RealT) + + for i in eachcomponent(equations) + rho += u[i + 3] + end -@inline function densities(u, v, equations::CompressibleEulerMulticomponentEquations2D) - return SVector{ncomponents(equations), real(equations)}(u[i + 3] * v - for i in eachcomponent(equations)) -end + return rho + end + + @inline function densities(u, v, equations::CompressibleEulerMulticomponentEquations2D) + return SVector{ncomponents(equations), real(equations)}( + u[i + 3] * v + for i in eachcomponent(equations) + ) + end end # @muladd diff --git a/src/equations/compressible_euler_quasi_1d.jl b/src/equations/compressible_euler_quasi_1d.jl index 936487186e..f71ffd7cd0 100644 --- a/src/equations/compressible_euler_quasi_1d.jl +++ b/src/equations/compressible_euler_quasi_1d.jl @@ -3,358 +3,386 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - CompressibleEulerEquationsQuasi1D(gamma) - -The quasi-1d compressible Euler equations (see Chan et al. [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) for details) -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -a \rho \\ a \rho v_1 \\ a e -\end{pmatrix} -+ -\frac{\partial}{\partial x} -\begin{pmatrix} -a \rho v_1 \\ a \rho v_1^2 \\ a v_1 (e +p) -\end{pmatrix} -+ -a \frac{\partial}{\partial x} -\begin{pmatrix} -0 \\ p \\ 0 -\end{pmatrix} -= -\begin{pmatrix} -0 \\ 0 \\ 0 -\end{pmatrix} -``` -for an ideal gas with ratio of specific heats `gamma` in one space dimension. -Here, ``\rho`` is the density, ``v_1`` the velocity, ``e`` the specific total energy **rather than** specific internal energy, -``a`` the (possibly) variable nozzle width, and -```math -p = (\gamma - 1) \left( e - \frac{1}{2} \rho v_1^2 \right) -``` -the pressure. - -The nozzle width function ``a(x)`` is set inside the initial condition routine -for a particular problem setup. To test the conservative form of the compressible Euler equations one can set the -nozzle width variable ``a`` to one. - -In addition to the unknowns, Trixi.jl currently stores the nozzle width values at the approximation points -despite being fixed in time. -This affects the implementation and use of these equations in various ways: -* The flux values corresponding to the nozzle width must be zero. -* The nozzle width values must be included when defining initial conditions, boundary conditions or - source terms. -* [`AnalysisCallback`](@ref) analyzes this variable. -* Trixi.jl's visualization tools will visualize the nozzle width by default. -""" -struct CompressibleEulerEquationsQuasi1D{RealT <: Real} <: - AbstractCompressibleEulerEquations{1, 4} - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - - function CompressibleEulerEquationsQuasi1D(gamma) - γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) - new{typeof(γ)}(γ, inv_gamma_minus_one) + #! format: noindent + + @doc raw""" + CompressibleEulerEquationsQuasi1D(gamma) + + The quasi-1d compressible Euler equations (see Chan et al. [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) for details) + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + a \rho \\ a \rho v_1 \\ a e + \end{pmatrix} + + + \frac{\partial}{\partial x} + \begin{pmatrix} + a \rho v_1 \\ a \rho v_1^2 \\ a v_1 (e +p) + \end{pmatrix} + + + a \frac{\partial}{\partial x} + \begin{pmatrix} + 0 \\ p \\ 0 + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 \\ 0 + \end{pmatrix} + ``` + for an ideal gas with ratio of specific heats `gamma` in one space dimension. + Here, ``\rho`` is the density, ``v_1`` the velocity, ``e`` the specific total energy **rather than** specific internal energy, + ``a`` the (possibly) variable nozzle width, and + ```math + p = (\gamma - 1) \left( e - \frac{1}{2} \rho v_1^2 \right) + ``` + the pressure. + + The nozzle width function ``a(x)`` is set inside the initial condition routine + for a particular problem setup. To test the conservative form of the compressible Euler equations one can set the + nozzle width variable ``a`` to one. + + In addition to the unknowns, Trixi.jl currently stores the nozzle width values at the approximation points + despite being fixed in time. + This affects the implementation and use of these equations in various ways: + * The flux values corresponding to the nozzle width must be zero. + * The nozzle width values must be included when defining initial conditions, boundary conditions or + source terms. + * [`AnalysisCallback`](@ref) analyzes this variable. + * Trixi.jl's visualization tools will visualize the nozzle width by default. + """ + struct CompressibleEulerEquationsQuasi1D{RealT <: Real} <: + AbstractCompressibleEulerEquations{1, 4} + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + + function CompressibleEulerEquationsQuasi1D(gamma) + γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) + new{typeof(γ)}(γ, inv_gamma_minus_one) + end end -end - -have_nonconservative_terms(::CompressibleEulerEquationsQuasi1D) = True() -function varnames(::typeof(cons2cons), ::CompressibleEulerEquationsQuasi1D) - ("a_rho", "a_rho_v1", "a_e", "a") -end -function varnames(::typeof(cons2prim), ::CompressibleEulerEquationsQuasi1D) - ("rho", "v1", "p", "a") -end - -""" - initial_condition_convergence_test(x, t, equations::CompressibleEulerEquationsQuasi1D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -function initial_condition_convergence_test(x, t, - equations::CompressibleEulerEquationsQuasi1D) - RealT = eltype(x) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - ω = 2 * convert(RealT, pi) * f - ini = c + A * sin(ω * (x[1] - t)) - - rho = ini - v1 = 1 - e = ini^2 / rho - p = (equations.gamma - 1) * (e - 0.5f0 * rho * v1^2) - a = 1.5f0 - 0.5f0 * cos(x[1] * convert(RealT, pi)) - - return prim2cons(SVector(rho, v1, p, a), equations) -end - -""" - source_terms_convergence_test(u, x, t, equations::CompressibleEulerEquationsQuasi1D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). - -This manufactured solution source term is specifically designed for the mozzle width 'a(x) = 1.5 - 0.5 * cos(x[1] * pi)' -as defined in [`initial_condition_convergence_test`](@ref). -""" -@inline function source_terms_convergence_test(u, x, t, + + have_nonconservative_terms(::CompressibleEulerEquationsQuasi1D) = True() + function varnames(::typeof(cons2cons), ::CompressibleEulerEquationsQuasi1D) + ("a_rho", "a_rho_v1", "a_e", "a") + end + function varnames(::typeof(cons2prim), ::CompressibleEulerEquationsQuasi1D) + ("rho", "v1", "p", "a") + end + + """ + initial_condition_convergence_test(x, t, equations::CompressibleEulerEquationsQuasi1D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + function initial_condition_convergence_test( + x, t, + equations::CompressibleEulerEquationsQuasi1D + ) + RealT = eltype(x) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + ini = c + A * sin(ω * (x[1] - t)) + + rho = ini + v1 = 1 + e = ini^2 / rho + p = (equations.gamma - 1) * (e - 0.5f0 * rho * v1^2) + a = 1.5f0 - 0.5f0 * cos(x[1] * convert(RealT, pi)) + + return prim2cons(SVector(rho, v1, p, a), equations) + end + + """ + source_terms_convergence_test(u, x, t, equations::CompressibleEulerEquationsQuasi1D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + + This manufactured solution source term is specifically designed for the mozzle width 'a(x) = 1.5 - 0.5 * cos(x[1] * pi)' + as defined in [`initial_condition_convergence_test`](@ref). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::CompressibleEulerEquationsQuasi1D + ) + # Same settings as in `initial_condition_convergence_test`. + # Derivatives calculated with ForwardDiff.jl + RealT = eltype(u) + c = 2 + A = convert(RealT, 0.1) + L = 2 + f = 1.0f0 / L + ω = 2 * convert(RealT, pi) * f + x1, = x + ini(x1, t) = c + A * sin(ω * (x1 - t)) + + rho(x1, t) = ini(x1, t) + v1(x1, t) = 1 + e(x1, t) = ini(x1, t)^2 / rho(x1, t) + p1(x1, t) = (equations.gamma - 1) * (e(x1, t) - 0.5f0 * rho(x1, t) * v1(x1, t)^2) + a(x1, t) = 1.5f0 - 0.5f0 * cos(x1 * pi) + + arho(x1, t) = a(x1, t) * rho(x1, t) + arhou(x1, t) = arho(x1, t) * v1(x1, t) + aE(x1, t) = a(x1, t) * e(x1, t) + + darho_dt(x1, t) = ForwardDiff.derivative(t -> arho(x1, t), t) + darhou_dx(x1, t) = ForwardDiff.derivative(x1 -> arhou(x1, t), x1) + + arhouu(x1, t) = arhou(x1, t) * v1(x1, t) + darhou_dt(x1, t) = ForwardDiff.derivative(t -> arhou(x1, t), t) + darhouu_dx(x1, t) = ForwardDiff.derivative(x1 -> arhouu(x1, t), x1) + dp1_dx(x1, t) = ForwardDiff.derivative(x1 -> p1(x1, t), x1) + + auEp(x1, t) = a(x1, t) * v1(x1, t) * (e(x1, t) + p1(x1, t)) + daE_dt(x1, t) = ForwardDiff.derivative(t -> aE(x1, t), t) + dauEp_dx(x1, t) = ForwardDiff.derivative(x1 -> auEp(x1, t), x1) + + du1 = darho_dt(x1, t) + darhou_dx(x1, t) + du2 = darhou_dt(x1, t) + darhouu_dx(x1, t) + a(x1, t) * dp1_dx(x1, t) + du3 = daE_dt(x1, t) + dauEp_dx(x1, t) + + return SVector(du1, du2, du3, 0) + end + + # Calculate 1D flux for a single point + @inline function flux( + u, orientation::Integer, + equations::CompressibleEulerEquationsQuasi1D + ) + a_rho, a_rho_v1, a_e, a = u + rho, v1, p, a = cons2prim(u, equations) + e = a_e / a + + # Ignore orientation since it is always "1" in 1D + f1 = a_rho_v1 + f2 = a_rho_v1 * v1 + f3 = a * v1 * (e + p) + + return SVector(f1, f2, f3, 0) + end + + """ + flux_nonconservative_chan_etal(u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquationsQuasi1D) + flux_nonconservative_chan_etal(u_ll, u_rr, normal_direction, + equations::CompressibleEulerEquationsQuasi1D) + flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, normal_rr, + equations::CompressibleEulerEquationsQuasi1D) + + Non-symmetric two-point volume flux discretizing the nonconservative (source) term + that contains the gradient of the pressure [`CompressibleEulerEquationsQuasi1D`](@ref) + and the nozzle width. + + Further details are available in the paper: + - Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023) + High order entropy stable schemes for the quasi-one-dimensional + shallow water and compressible Euler equations + [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) + """ + @inline function flux_nonconservative_chan_etal( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquationsQuasi1D + ) + #Variables + _, _, p_ll, a_ll = cons2prim(u_ll, equations) + _, _, p_rr, _ = cons2prim(u_rr, equations) + + # For flux differencing using non-conservative terms, we return the + # non-conservative flux scaled by 2. This cancels with a factor of 0.5 + # in the arithmetic average of {p}. + p_avg = p_ll + p_rr + + return SVector(0, a_ll * p_avg, 0, 0) + end + + # While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that + # the normal component is incorporated into the numerical flux. + # + # See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a + # similar implementation. + @inline function flux_nonconservative_chan_etal( + u_ll, u_rr, + normal_direction::AbstractVector, + equations::CompressibleEulerEquationsQuasi1D + ) + return normal_direction[1] * + flux_nonconservative_chan_etal(u_ll, u_rr, 1, equations) + end + + @inline function flux_nonconservative_chan_etal( + u_ll, u_rr, + normal_ll::AbstractVector, + normal_rr::AbstractVector, + equations::CompressibleEulerEquationsQuasi1D + ) + # normal_ll should be equal to normal_rr in 1D + return flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, equations) + end + + """ + @inline function flux_chan_etal(u_ll, u_rr, orientation::Integer, equations::CompressibleEulerEquationsQuasi1D) - # Same settings as in `initial_condition_convergence_test`. - # Derivatives calculated with ForwardDiff.jl - RealT = eltype(u) - c = 2 - A = convert(RealT, 0.1) - L = 2 - f = 1.0f0 / L - ω = 2 * convert(RealT, pi) * f - x1, = x - ini(x1, t) = c + A * sin(ω * (x1 - t)) - - rho(x1, t) = ini(x1, t) - v1(x1, t) = 1 - e(x1, t) = ini(x1, t)^2 / rho(x1, t) - p1(x1, t) = (equations.gamma - 1) * (e(x1, t) - 0.5f0 * rho(x1, t) * v1(x1, t)^2) - a(x1, t) = 1.5f0 - 0.5f0 * cos(x1 * pi) - - arho(x1, t) = a(x1, t) * rho(x1, t) - arhou(x1, t) = arho(x1, t) * v1(x1, t) - aE(x1, t) = a(x1, t) * e(x1, t) - - darho_dt(x1, t) = ForwardDiff.derivative(t -> arho(x1, t), t) - darhou_dx(x1, t) = ForwardDiff.derivative(x1 -> arhou(x1, t), x1) - - arhouu(x1, t) = arhou(x1, t) * v1(x1, t) - darhou_dt(x1, t) = ForwardDiff.derivative(t -> arhou(x1, t), t) - darhouu_dx(x1, t) = ForwardDiff.derivative(x1 -> arhouu(x1, t), x1) - dp1_dx(x1, t) = ForwardDiff.derivative(x1 -> p1(x1, t), x1) - - auEp(x1, t) = a(x1, t) * v1(x1, t) * (e(x1, t) + p1(x1, t)) - daE_dt(x1, t) = ForwardDiff.derivative(t -> aE(x1, t), t) - dauEp_dx(x1, t) = ForwardDiff.derivative(x1 -> auEp(x1, t), x1) - - du1 = darho_dt(x1, t) + darhou_dx(x1, t) - du2 = darhou_dt(x1, t) + darhouu_dx(x1, t) + a(x1, t) * dp1_dx(x1, t) - du3 = daE_dt(x1, t) + dauEp_dx(x1, t) - - return SVector(du1, du2, du3, 0) -end - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, - equations::CompressibleEulerEquationsQuasi1D) - a_rho, a_rho_v1, a_e, a = u - rho, v1, p, a = cons2prim(u, equations) - e = a_e / a - - # Ignore orientation since it is always "1" in 1D - f1 = a_rho_v1 - f2 = a_rho_v1 * v1 - f3 = a * v1 * (e + p) - - return SVector(f1, f2, f3, 0) -end - -""" - flux_nonconservative_chan_etal(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquationsQuasi1D) - flux_nonconservative_chan_etal(u_ll, u_rr, normal_direction, - equations::CompressibleEulerEquationsQuasi1D) - flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, normal_rr, - equations::CompressibleEulerEquationsQuasi1D) - -Non-symmetric two-point volume flux discretizing the nonconservative (source) term -that contains the gradient of the pressure [`CompressibleEulerEquationsQuasi1D`](@ref) -and the nozzle width. - -Further details are available in the paper: -- Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023) - High order entropy stable schemes for the quasi-one-dimensional - shallow water and compressible Euler equations - [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) -""" -@inline function flux_nonconservative_chan_etal(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquationsQuasi1D) - #Variables - _, _, p_ll, a_ll = cons2prim(u_ll, equations) - _, _, p_rr, _ = cons2prim(u_rr, equations) - - # For flux differencing using non-conservative terms, we return the - # non-conservative flux scaled by 2. This cancels with a factor of 0.5 - # in the arithmetic average of {p}. - p_avg = p_ll + p_rr - - return SVector(0, a_ll * p_avg, 0, 0) -end - -# While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that -# the normal component is incorporated into the numerical flux. -# -# See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a -# similar implementation. -@inline function flux_nonconservative_chan_etal(u_ll, u_rr, - normal_direction::AbstractVector, - equations::CompressibleEulerEquationsQuasi1D) - return normal_direction[1] * - flux_nonconservative_chan_etal(u_ll, u_rr, 1, equations) -end - -@inline function flux_nonconservative_chan_etal(u_ll, u_rr, - normal_ll::AbstractVector, - normal_rr::AbstractVector, - equations::CompressibleEulerEquationsQuasi1D) - # normal_ll should be equal to normal_rr in 1D - return flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, equations) -end - -""" -@inline function flux_chan_etal(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquationsQuasi1D) - -Conservative (symmetric) part of the entropy conservative flux for quasi 1D compressible Euler equations split form. -This flux is a generalization of [`flux_ranocha`](@ref) for [`CompressibleEulerEquations1D`](@ref). -Further details are available in the paper: -- Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023) - High order entropy stable schemes for the quasi-one-dimensional - shallow water and compressible Euler equations - [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) -""" -@inline function flux_chan_etal(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquationsQuasi1D) - # Unpack left and right state - rho_ll, v1_ll, p_ll, a_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, p_rr, a_rr = cons2prim(u_rr, equations) - - # Compute the necessary mean values - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - a_v1_avg = 0.5f0 * (a_ll * v1_ll + a_rr * v1_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr) - - # Calculate fluxes - # Ignore orientation since it is always "1" in 1D - f1 = rho_mean * a_v1_avg - f2 = rho_mean * a_v1_avg * v1_avg - f3 = f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5f0 * (p_ll * a_rr * v1_rr + p_rr * a_ll * v1_ll) - - return SVector(f1, f2, f3, 0) -end - -# While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that -# the normal component is incorporated into the numerical flux. -# -# See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a -# similar implementation. -@inline function flux_chan_etal(u_ll, u_rr, normal_direction::AbstractVector, - equations::CompressibleEulerEquationsQuasi1D) - return normal_direction[1] * flux_chan_etal(u_ll, u_rr, 1, equations) -end - -# Calculate estimates for maximum wave speed for local Lax-Friedrichs-type dissipation as the -# maximum velocity magnitude plus the maximum speed of sound -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::CompressibleEulerEquationsQuasi1D) - a_rho_ll, a_rho_v1_ll, a_e_ll, a_ll = u_ll - a_rho_rr, a_rho_v1_rr, a_e_rr, a_rr = u_rr - - # Calculate primitive variables and speed of sound - rho_ll = a_rho_ll / a_ll - e_ll = a_e_ll / a_ll - v1_ll = a_rho_v1_ll / a_rho_ll - v_mag_ll = abs(v1_ll) - p_ll = (equations.gamma - 1) * (e_ll - 0.5f0 * rho_ll * v_mag_ll^2) - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - rho_rr = a_rho_rr / a_rr - e_rr = a_e_rr / a_rr - v1_rr = a_rho_v1_rr / a_rho_rr - v_mag_rr = abs(v1_rr) - p_rr = (equations.gamma - 1) * (e_rr - 0.5f0 * rho_rr * v_mag_rr^2) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - λ_max = max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) -end - -@inline function max_abs_speeds(u, equations::CompressibleEulerEquationsQuasi1D) - a_rho, a_rho_v1, a_e, a = u - rho = a_rho / a - v1 = a_rho_v1 / a_rho - e = a_e / a - p = (equations.gamma - 1) * (e - 0.5f0 * rho * v1^2) - c = sqrt(equations.gamma * p / rho) - - return (abs(v1) + c,) -end - -# Convert conservative variables to primitive. We use the convention that the primitive -# variables for the quasi-1D equations are `(rho, v1, p)` (i.e., the same as the primitive -# variables for `CompressibleEulerEquations1D`) -@inline function cons2prim(u, equations::CompressibleEulerEquationsQuasi1D) - a_rho, a_rho_v1, a_e, a = u - q = cons2prim(SVector(a_rho, a_rho_v1, a_e) / a, - CompressibleEulerEquations1D(equations.gamma)) - - return SVector(q[1], q[2], q[3], a) -end - -# The entropy for the quasi-1D compressible Euler equations is the entropy for the -# 1D compressible Euler equations scaled by the channel width `a`. -@inline function entropy(u, equations::CompressibleEulerEquationsQuasi1D) - a_rho, a_rho_v1, a_e, a = u - return a * entropy(SVector(a_rho, a_rho_v1, a_e) / a, - CompressibleEulerEquations1D(equations.gamma)) -end - -# Convert conservative variables to entropy. The entropy variables for the -# quasi-1D compressible Euler equations are identical to the entropy variables -# for the standard Euler equations for an appropriate definition of `entropy`. -@inline function cons2entropy(u, equations::CompressibleEulerEquationsQuasi1D) - a_rho, a_rho_v1, a_e, a = u - w = cons2entropy(SVector(a_rho, a_rho_v1, a_e) / a, - CompressibleEulerEquations1D(equations.gamma)) - - # we follow the convention for other spatially-varying equations such as - # `ShallowWaterEquations1D` and return the spatially varying coefficient - # `a` as the final entropy variable. - return SVector(w[1], w[2], w[3], a) -end - -# Convert primitive to conservative variables -@inline function prim2cons(u, equations::CompressibleEulerEquationsQuasi1D) - rho, v1, p, a = u - q = prim2cons(u, CompressibleEulerEquations1D(equations.gamma)) - - return SVector(a * q[1], a * q[2], a * q[3], a) -end - -@inline function density(u, equations::CompressibleEulerEquationsQuasi1D) - a_rho, _, _, a = u - rho = a_rho / a - return rho -end - -@inline function pressure(u, equations::CompressibleEulerEquationsQuasi1D) - a_rho, a_rho_v1, a_e, a = u - return pressure(SVector(a_rho, a_rho_v1, a_e) / a, - CompressibleEulerEquations1D(equations.gamma)) -end - -@inline function density_pressure(u, equations::CompressibleEulerEquationsQuasi1D) - a_rho, a_rho_v1, a_e, a = u - return density_pressure(SVector(a_rho, a_rho_v1, a_e) / a, - CompressibleEulerEquations1D(equations.gamma)) -end + + Conservative (symmetric) part of the entropy conservative flux for quasi 1D compressible Euler equations split form. + This flux is a generalization of [`flux_ranocha`](@ref) for [`CompressibleEulerEquations1D`](@ref). + Further details are available in the paper: + - Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023) + High order entropy stable schemes for the quasi-one-dimensional + shallow water and compressible Euler equations + [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) + """ + @inline function flux_chan_etal( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquationsQuasi1D + ) + # Unpack left and right state + rho_ll, v1_ll, p_ll, a_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, p_rr, a_rr = cons2prim(u_rr, equations) + + # Compute the necessary mean values + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + a_v1_avg = 0.5f0 * (a_ll * v1_ll + a_rr * v1_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr) + + # Calculate fluxes + # Ignore orientation since it is always "1" in 1D + f1 = rho_mean * a_v1_avg + f2 = rho_mean * a_v1_avg * v1_avg + f3 = f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5f0 * (p_ll * a_rr * v1_rr + p_rr * a_ll * v1_ll) + + return SVector(f1, f2, f3, 0) + end + + # While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that + # the normal component is incorporated into the numerical flux. + # + # See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a + # similar implementation. + @inline function flux_chan_etal( + u_ll, u_rr, normal_direction::AbstractVector, + equations::CompressibleEulerEquationsQuasi1D + ) + return normal_direction[1] * flux_chan_etal(u_ll, u_rr, 1, equations) + end + + # Calculate estimates for maximum wave speed for local Lax-Friedrichs-type dissipation as the + # maximum velocity magnitude plus the maximum speed of sound + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::CompressibleEulerEquationsQuasi1D + ) + a_rho_ll, a_rho_v1_ll, a_e_ll, a_ll = u_ll + a_rho_rr, a_rho_v1_rr, a_e_rr, a_rr = u_rr + + # Calculate primitive variables and speed of sound + rho_ll = a_rho_ll / a_ll + e_ll = a_e_ll / a_ll + v1_ll = a_rho_v1_ll / a_rho_ll + v_mag_ll = abs(v1_ll) + p_ll = (equations.gamma - 1) * (e_ll - 0.5f0 * rho_ll * v_mag_ll^2) + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + rho_rr = a_rho_rr / a_rr + e_rr = a_e_rr / a_rr + v1_rr = a_rho_v1_rr / a_rho_rr + v_mag_rr = abs(v1_rr) + p_rr = (equations.gamma - 1) * (e_rr - 0.5f0 * rho_rr * v_mag_rr^2) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + λ_max = max(v_mag_ll, v_mag_rr) + max(c_ll, c_rr) + end + + @inline function max_abs_speeds(u, equations::CompressibleEulerEquationsQuasi1D) + a_rho, a_rho_v1, a_e, a = u + rho = a_rho / a + v1 = a_rho_v1 / a_rho + e = a_e / a + p = (equations.gamma - 1) * (e - 0.5f0 * rho * v1^2) + c = sqrt(equations.gamma * p / rho) + + return (abs(v1) + c,) + end + + # Convert conservative variables to primitive. We use the convention that the primitive + # variables for the quasi-1D equations are `(rho, v1, p)` (i.e., the same as the primitive + # variables for `CompressibleEulerEquations1D`) + @inline function cons2prim(u, equations::CompressibleEulerEquationsQuasi1D) + a_rho, a_rho_v1, a_e, a = u + q = cons2prim( + SVector(a_rho, a_rho_v1, a_e) / a, + CompressibleEulerEquations1D(equations.gamma) + ) + + return SVector(q[1], q[2], q[3], a) + end + + # The entropy for the quasi-1D compressible Euler equations is the entropy for the + # 1D compressible Euler equations scaled by the channel width `a`. + @inline function entropy(u, equations::CompressibleEulerEquationsQuasi1D) + a_rho, a_rho_v1, a_e, a = u + return a * entropy( + SVector(a_rho, a_rho_v1, a_e) / a, + CompressibleEulerEquations1D(equations.gamma) + ) + end + + # Convert conservative variables to entropy. The entropy variables for the + # quasi-1D compressible Euler equations are identical to the entropy variables + # for the standard Euler equations for an appropriate definition of `entropy`. + @inline function cons2entropy(u, equations::CompressibleEulerEquationsQuasi1D) + a_rho, a_rho_v1, a_e, a = u + w = cons2entropy( + SVector(a_rho, a_rho_v1, a_e) / a, + CompressibleEulerEquations1D(equations.gamma) + ) + + # we follow the convention for other spatially-varying equations such as + # `ShallowWaterEquations1D` and return the spatially varying coefficient + # `a` as the final entropy variable. + return SVector(w[1], w[2], w[3], a) + end + + # Convert primitive to conservative variables + @inline function prim2cons(u, equations::CompressibleEulerEquationsQuasi1D) + rho, v1, p, a = u + q = prim2cons(u, CompressibleEulerEquations1D(equations.gamma)) + + return SVector(a * q[1], a * q[2], a * q[3], a) + end + + @inline function density(u, equations::CompressibleEulerEquationsQuasi1D) + a_rho, _, _, a = u + rho = a_rho / a + return rho + end + + @inline function pressure(u, equations::CompressibleEulerEquationsQuasi1D) + a_rho, a_rho_v1, a_e, a = u + return pressure( + SVector(a_rho, a_rho_v1, a_e) / a, + CompressibleEulerEquations1D(equations.gamma) + ) + end + + @inline function density_pressure(u, equations::CompressibleEulerEquationsQuasi1D) + a_rho, a_rho_v1, a_e, a = u + return density_pressure( + SVector(a_rho, a_rho_v1, a_e) / a, + CompressibleEulerEquations1D(equations.gamma) + ) + end end # @muladd diff --git a/src/equations/compressible_navier_stokes_1d.jl b/src/equations/compressible_navier_stokes_1d.jl index 024ce2d7e8..e2bba012be 100644 --- a/src/equations/compressible_navier_stokes_1d.jl +++ b/src/equations/compressible_navier_stokes_1d.jl @@ -3,393 +3,483 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - CompressibleNavierStokesDiffusion1D(equations; mu, Pr, - gradient_variables=GradientVariablesPrimitive()) - -Contains the diffusion (i.e. parabolic) terms applied -to mass, momenta, and total energy together with the advective terms from -the [`CompressibleEulerEquations1D`](@ref). - -- `equations`: instance of the [`CompressibleEulerEquations1D`](@ref) -- `mu`: dynamic viscosity, -- `Pr`: Prandtl number, -- `gradient_variables`: which variables the gradients are taken with respect to. - Defaults to `GradientVariablesPrimitive()`. - -Fluid properties such as the dynamic viscosity ``\mu`` can be provided in any consistent unit system, e.g., -[``\mu``] = kg m⁻¹ s⁻¹. -The viscosity ``\mu`` may be a constant or a function of the current state, e.g., -depending on temperature (Sutherland's law): ``\mu = \mu(T)``. -In the latter case, the function `mu` needs to have the signature `mu(u, equations)`. - -The particular form of the compressible Navier-Stokes implemented is -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -\rho \\ \rho v \\ \rho e -\end{pmatrix} -+ -\frac{\partial}{\partial x} -\begin{pmatrix} - \rho v \\ \rho v^2 + p \\ (\rho e + p) v -\end{pmatrix} -= -\frac{\partial}{\partial x} -\begin{pmatrix} -0 \\ \tau \\ \tau v - q -\end{pmatrix} -``` -where the system is closed with the ideal gas assumption giving -```math -p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho v^2 \right) -``` -as the pressure. The value of the adiabatic constant `gamma` is taken from the [`CompressibleEulerEquations1D`](@ref). -The terms on the right hand side of the system above -are built from the viscous stress -```math -\tau = \mu \frac{\partial}{\partial x} v -``` -where the heat flux is -```math -q = -\kappa \frac{\partial}{\partial x} \left(T\right),\quad T = \frac{p}{R\rho} -``` -where ``T`` is the temperature and ``\kappa`` is the thermal conductivity for Fick's law. -Under the assumption that the gas has a constant Prandtl number, -the thermal conductivity is -```math -\kappa = \frac{\gamma \mu R}{(\gamma - 1)\textrm{Pr}}. -``` -From this combination of temperature ``T`` and thermal conductivity ``\kappa`` we see -that the gas constant `R` cancels and the heat flux becomes -```math -q = -\kappa \frac{\partial}{\partial x} \left(T\right) = -\frac{\gamma \mu}{(\gamma - 1)\textrm{Pr}} \frac{\partial}{\partial x} \left(\frac{p}{\rho}\right) -``` -which is the form implemented below in the [`flux`](@ref) function. - -In one spatial dimensions we require gradients for two quantities, e.g., -primitive quantities -```math -\frac{\partial}{\partial x} v,\, \frac{\partial}{\partial x} T -``` -or the entropy variables -```math -\frac{\partial}{\partial x} w_2,\, \frac{\partial}{\partial x} w_3 -``` -where -```math -w_2 = \frac{\rho v1}{p},\, w_3 = -\frac{\rho}{p} -``` -""" -struct CompressibleNavierStokesDiffusion1D{GradientVariables, RealT <: Real, Mu, - E <: AbstractCompressibleEulerEquations{1}} <: - AbstractCompressibleNavierStokesDiffusion{1, 3, GradientVariables} + #! format: noindent + + @doc raw""" + CompressibleNavierStokesDiffusion1D(equations; mu, Pr, + gradient_variables=GradientVariablesPrimitive()) + + Contains the diffusion (i.e. parabolic) terms applied + to mass, momenta, and total energy together with the advective terms from + the [`CompressibleEulerEquations1D`](@ref). + + - `equations`: instance of the [`CompressibleEulerEquations1D`](@ref) + - `mu`: dynamic viscosity, + - `Pr`: Prandtl number, + - `gradient_variables`: which variables the gradients are taken with respect to. + Defaults to `GradientVariablesPrimitive()`. + + Fluid properties such as the dynamic viscosity ``\mu`` can be provided in any consistent unit system, e.g., + [``\mu``] = kg m⁻¹ s⁻¹. + The viscosity ``\mu`` may be a constant or a function of the current state, e.g., + depending on temperature (Sutherland's law): ``\mu = \mu(T)``. + In the latter case, the function `mu` needs to have the signature `mu(u, equations)`. + + The particular form of the compressible Navier-Stokes implemented is + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + \rho \\ \rho v \\ \rho e + \end{pmatrix} + + + \frac{\partial}{\partial x} + \begin{pmatrix} + \rho v \\ \rho v^2 + p \\ (\rho e + p) v + \end{pmatrix} + = + \frac{\partial}{\partial x} + \begin{pmatrix} + 0 \\ \tau \\ \tau v - q + \end{pmatrix} + ``` + where the system is closed with the ideal gas assumption giving + ```math + p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho v^2 \right) + ``` + as the pressure. The value of the adiabatic constant `gamma` is taken from the [`CompressibleEulerEquations1D`](@ref). + The terms on the right hand side of the system above + are built from the viscous stress + ```math + \tau = \mu \frac{\partial}{\partial x} v + ``` + where the heat flux is + ```math + q = -\kappa \frac{\partial}{\partial x} \left(T\right),\quad T = \frac{p}{R\rho} + ``` + where ``T`` is the temperature and ``\kappa`` is the thermal conductivity for Fick's law. + Under the assumption that the gas has a constant Prandtl number, + the thermal conductivity is + ```math + \kappa = \frac{\gamma \mu R}{(\gamma - 1)\textrm{Pr}}. + ``` + From this combination of temperature ``T`` and thermal conductivity ``\kappa`` we see + that the gas constant `R` cancels and the heat flux becomes + ```math + q = -\kappa \frac{\partial}{\partial x} \left(T\right) = -\frac{\gamma \mu}{(\gamma - 1)\textrm{Pr}} \frac{\partial}{\partial x} \left(\frac{p}{\rho}\right) + ``` + which is the form implemented below in the [`flux`](@ref) function. + + In one spatial dimensions we require gradients for two quantities, e.g., + primitive quantities + ```math + \frac{\partial}{\partial x} v,\, \frac{\partial}{\partial x} T + ``` + or the entropy variables + ```math + \frac{\partial}{\partial x} w_2,\, \frac{\partial}{\partial x} w_3 + ``` + where + ```math + w_2 = \frac{\rho v1}{p},\, w_3 = -\frac{\rho}{p} + ``` + """ + struct CompressibleNavierStokesDiffusion1D{ + GradientVariables, RealT <: Real, Mu, + E <: AbstractCompressibleEulerEquations{1}, + } <: + AbstractCompressibleNavierStokesDiffusion{1, 3, GradientVariables} + # TODO: parabolic + # 1) For now save gamma and inv(gamma-1) again, but could potentially reuse them from the Euler equations + # 2) Add NGRADS as a type parameter here and in AbstractEquationsParabolic, add `ngradients(...)` accessor function + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + + mu::Mu # viscosity + Pr::RealT # Prandtl number + kappa::RealT # thermal diffusivity for Fick's law + + equations_hyperbolic::E # CompressibleEulerEquations1D + gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy + end + + # default to primitive gradient variables + function CompressibleNavierStokesDiffusion1D( + equations::CompressibleEulerEquations1D; + mu, Prandtl, + gradient_variables = GradientVariablesPrimitive() + ) + gamma = equations.gamma + inv_gamma_minus_one = equations.inv_gamma_minus_one + + # Under the assumption of constant Prandtl number the thermal conductivity + # constant is kappa = gamma μ / ((gamma-1) Prandtl). + # Important note! Factor of μ is accounted for later in `flux`. + # This avoids recomputation of kappa for non-constant μ. + kappa = gamma * inv_gamma_minus_one / Prandtl + + CompressibleNavierStokesDiffusion1D{ + typeof(gradient_variables), typeof(gamma), + typeof(mu), + typeof(equations), + }( + gamma, inv_gamma_minus_one, + mu, Prandtl, kappa, + equations, + gradient_variables + ) + end + # TODO: parabolic - # 1) For now save gamma and inv(gamma-1) again, but could potentially reuse them from the Euler equations - # 2) Add NGRADS as a type parameter here and in AbstractEquationsParabolic, add `ngradients(...)` accessor function - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - - mu::Mu # viscosity - Pr::RealT # Prandtl number - kappa::RealT # thermal diffusivity for Fick's law - - equations_hyperbolic::E # CompressibleEulerEquations1D - gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy -end - -# default to primitive gradient variables -function CompressibleNavierStokesDiffusion1D(equations::CompressibleEulerEquations1D; - mu, Prandtl, - gradient_variables = GradientVariablesPrimitive()) - gamma = equations.gamma - inv_gamma_minus_one = equations.inv_gamma_minus_one - - # Under the assumption of constant Prandtl number the thermal conductivity - # constant is kappa = gamma μ / ((gamma-1) Prandtl). - # Important note! Factor of μ is accounted for later in `flux`. - # This avoids recomputation of kappa for non-constant μ. - kappa = gamma * inv_gamma_minus_one / Prandtl - - CompressibleNavierStokesDiffusion1D{typeof(gradient_variables), typeof(gamma), - typeof(mu), - typeof(equations)}(gamma, inv_gamma_minus_one, - mu, Prandtl, kappa, - equations, - gradient_variables) -end - -# TODO: parabolic -# This is the flexibility a user should have to select the different gradient variable types -# varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion1D) = ("v1", "v2", "T") -# varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion1D) = ("w2", "w3", "w4") - -function varnames(variable_mapping, - equations_parabolic::CompressibleNavierStokesDiffusion1D) - varnames(variable_mapping, equations_parabolic.equations_hyperbolic) -end - -# we specialize this function to compute gradients of primitive variables instead of -# conservative variables. -function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive}) - cons2prim -end -function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy}) - cons2entropy -end - -# Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2 -# of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner -# "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive -# MHD Equations. Part II: Subcell Finite Volume Shock Capturing" -# where one sets the magnetic field components equal to 0. -function flux(u, gradients, orientation::Integer, - equations::CompressibleNavierStokesDiffusion1D) - # Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`. - rho, v1, _ = convert_transformed_to_primitive(u, equations) - # Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, v2, T) - # either computed directly or reverse engineered from the gradient of the entropy variables - # by way of the `convert_gradient_variables` function. - _, dv1dx, dTdx = convert_derivative_to_primitive(u, gradients, equations) - - # Viscous stress (tensor) - tau_11 = dv1dx - - # Fick's law q = -kappa * grad(T) = -kappa * grad(p / (R rho)) - # with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr) - # Note, the gas constant cancels under this formulation, so it is not present - # in the implementation - q1 = equations.kappa * dTdx - - # In the simplest cases, the user passed in `mu` or `mu()` - # (which returns just a constant) but - # more complex functions like Sutherland's law are possible. - # `dynamic_viscosity` is a helper function that handles both cases - # by dispatching on the type of `equations.mu`. - mu = dynamic_viscosity(u, equations) - - # viscous flux components in the x-direction - f1 = 0 - f2 = tau_11 * mu - f3 = (v1 * tau_11 + q1) * mu - - return SVector(f1, f2, f3) -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion1D) - rho, rho_v1, _ = u - - v1 = rho_v1 / rho - T = temperature(u, equations) - - return SVector(rho, v1, T) -end - -# Convert conservative variables to entropy -# TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms -# This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion1D`, -# but this may be confusing to new users. -function cons2entropy(u, equations::CompressibleNavierStokesDiffusion1D) - cons2entropy(u, equations.equations_hyperbolic) -end -function entropy2cons(w, equations::CompressibleNavierStokesDiffusion1D) - entropy2cons(w, equations.equations_hyperbolic) -end - -# the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables. -# For CNS, it is simplest to formulate the viscous terms in primitive variables, so we transform the transformed -# variables into primitive variables. -@inline function convert_transformed_to_primitive(u_transformed, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive}) - return u_transformed -end - -# TODO: parabolic. Make this more efficient! -@inline function convert_transformed_to_primitive(u_transformed, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy}) - # note: this uses CompressibleNavierStokesDiffusion1D versions of cons2prim and entropy2cons - return cons2prim(entropy2cons(u_transformed, equations), equations) -end - -# Takes the solution values `u` and gradient of the entropy variables (w_2, w_3, w_4) and -# reverse engineers the gradients to be terms of the primitive variables (v1, v2, T). -# Helpful because then the diffusive fluxes have the same form as on paper. -# Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused. -# TODO: parabolic; entropy stable viscous terms -@inline function convert_derivative_to_primitive(u, gradient, - ::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive}) - return gradient -end - -# the first argument is always the "transformed" variables. -@inline function convert_derivative_to_primitive(w, gradient_entropy_vars, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy}) - - # TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back. - # We can fix this if we directly compute v1, v2, T from the entropy variables - u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion1D - rho, rho_v1, _ = u - - v1 = rho_v1 / rho - T = temperature(u, equations) - - return SVector(gradient_entropy_vars[1], - T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[3]), # grad(u) = T*(grad(w_2)+v1*grad(w_3)) - T * T * gradient_entropy_vars[3]) -end - -# This routine is required because `prim2cons` is called in `initial_condition`, which -# is called with `equations::CompressibleEulerEquations1D`. This means it is inconsistent -# with `cons2prim(..., ::CompressibleNavierStokesDiffusion1D)` as defined above. -# TODO: parabolic. Is there a way to clean this up? -@inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion1D) - prim2cons(u, equations.equations_hyperbolic) -end - -@inline function temperature(u, equations::CompressibleNavierStokesDiffusion1D) - rho, rho_v1, rho_e = u - - p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1^2 / rho) - T = p / rho - return T -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - u_inner, - orientation::Integer, - direction, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive}) - v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t, - equations) - return SVector(u_inner[1], v1, u_inner[3]) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - u_inner, - orientation::Integer, - direction, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive}) - # rho, v1, v2, _ = u_inner - normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x, - t, - equations) - v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t, - equations) - _, tau_1n, _ = flux_inner # extract fluxes for 2nd equation - normal_energy_flux = v1 * tau_1n + normal_heat_flux - return SVector(flux_inner[1], flux_inner[2], normal_energy_flux) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - u_inner, - orientation::Integer, - direction, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive}) - v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t, - equations) - T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t, - equations) - return SVector(u_inner[1], v1, T) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - u_inner, - orientation::Integer, - direction, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive}) - return flux_inner -end - -# specialized BC impositions for GradientVariablesEntropy. - -# This should return a SVector containing the boundary values of entropy variables. -# Here, `w_inner` are the transformed variables (e.g., entropy variables). -# -# Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions -# for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022. -# DOI: 10.1016/j.jcp.2021.110723 -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - w_inner, - orientation::Integer, - direction, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy}) - v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t, - equations) - negative_rho_inv_p = w_inner[3] # w_3 = -rho / p - return SVector(w_inner[1], -v1 * negative_rho_inv_p, negative_rho_inv_p) -end - -# this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness. -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - w_inner, - orientation::Integer, - direction, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy}) - normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x, - t, - equations) - v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t, - equations) - _, tau_1n, _ = flux_inner # extract fluxes for 2nd equation - normal_energy_flux = v1 * tau_1n + normal_heat_flux - return SVector(flux_inner[1], flux_inner[2], normal_energy_flux) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - w_inner, - orientation::Integer, - direction, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy}) - v1 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, t, - equations) - T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t, - equations) - - # the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w3. - w3 = -1 / T - return SVector(w_inner[1], -v1 * w3, w3) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - w_inner, - orientation::Integer, - direction, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy}) - return SVector(flux_inner[1], flux_inner[2], flux_inner[3]) -end + # This is the flexibility a user should have to select the different gradient variable types + # varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion1D) = ("v1", "v2", "T") + # varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion1D) = ("w2", "w3", "w4") + + function varnames( + variable_mapping, + equations_parabolic::CompressibleNavierStokesDiffusion1D + ) + varnames(variable_mapping, equations_parabolic.equations_hyperbolic) + end + + # we specialize this function to compute gradients of primitive variables instead of + # conservative variables. + function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive}) + cons2prim + end + function gradient_variable_transformation(::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy}) + cons2entropy + end + + # Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2 + # of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner + # "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive + # MHD Equations. Part II: Subcell Finite Volume Shock Capturing" + # where one sets the magnetic field components equal to 0. + function flux( + u, gradients, orientation::Integer, + equations::CompressibleNavierStokesDiffusion1D + ) + # Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`. + rho, v1, _ = convert_transformed_to_primitive(u, equations) + # Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, v2, T) + # either computed directly or reverse engineered from the gradient of the entropy variables + # by way of the `convert_gradient_variables` function. + _, dv1dx, dTdx = convert_derivative_to_primitive(u, gradients, equations) + + # Viscous stress (tensor) + tau_11 = dv1dx + + # Fick's law q = -kappa * grad(T) = -kappa * grad(p / (R rho)) + # with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr) + # Note, the gas constant cancels under this formulation, so it is not present + # in the implementation + q1 = equations.kappa * dTdx + + # In the simplest cases, the user passed in `mu` or `mu()` + # (which returns just a constant) but + # more complex functions like Sutherland's law are possible. + # `dynamic_viscosity` is a helper function that handles both cases + # by dispatching on the type of `equations.mu`. + mu = dynamic_viscosity(u, equations) + + # viscous flux components in the x-direction + f1 = 0 + f2 = tau_11 * mu + f3 = (v1 * tau_11 + q1) * mu + + return SVector(f1, f2, f3) + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion1D) + rho, rho_v1, _ = u + + v1 = rho_v1 / rho + T = temperature(u, equations) + + return SVector(rho, v1, T) + end + + # Convert conservative variables to entropy + # TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms + # This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion1D`, + # but this may be confusing to new users. + function cons2entropy(u, equations::CompressibleNavierStokesDiffusion1D) + cons2entropy(u, equations.equations_hyperbolic) + end + function entropy2cons(w, equations::CompressibleNavierStokesDiffusion1D) + entropy2cons(w, equations.equations_hyperbolic) + end + + # the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables. + # For CNS, it is simplest to formulate the viscous terms in primitive variables, so we transform the transformed + # variables into primitive variables. + @inline function convert_transformed_to_primitive( + u_transformed, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive} + ) + return u_transformed + end + + # TODO: parabolic. Make this more efficient! + @inline function convert_transformed_to_primitive( + u_transformed, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy} + ) + # note: this uses CompressibleNavierStokesDiffusion1D versions of cons2prim and entropy2cons + return cons2prim(entropy2cons(u_transformed, equations), equations) + end + + # Takes the solution values `u` and gradient of the entropy variables (w_2, w_3, w_4) and + # reverse engineers the gradients to be terms of the primitive variables (v1, v2, T). + # Helpful because then the diffusive fluxes have the same form as on paper. + # Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused. + # TODO: parabolic; entropy stable viscous terms + @inline function convert_derivative_to_primitive( + u, gradient, + ::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive} + ) + return gradient + end + + # the first argument is always the "transformed" variables. + @inline function convert_derivative_to_primitive( + w, gradient_entropy_vars, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy} + ) + + # TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back. + # We can fix this if we directly compute v1, v2, T from the entropy variables + u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion1D + rho, rho_v1, _ = u + + v1 = rho_v1 / rho + T = temperature(u, equations) + + return SVector( + gradient_entropy_vars[1], + T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[3]), # grad(u) = T*(grad(w_2)+v1*grad(w_3)) + T * T * gradient_entropy_vars[3] + ) + end + + # This routine is required because `prim2cons` is called in `initial_condition`, which + # is called with `equations::CompressibleEulerEquations1D`. This means it is inconsistent + # with `cons2prim(..., ::CompressibleNavierStokesDiffusion1D)` as defined above. + # TODO: parabolic. Is there a way to clean this up? + @inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion1D) + prim2cons(u, equations.equations_hyperbolic) + end + + @inline function temperature(u, equations::CompressibleNavierStokesDiffusion1D) + rho, rho_v1, rho_e = u + + p = (equations.gamma - 1) * (rho_e - 0.5f0 * rho_v1^2 / rho) + T = p / rho + return T + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + u_inner, + orientation::Integer, + direction, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive} + ) + v1 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, t, + equations + ) + return SVector(u_inner[1], v1, u_inner[3]) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + u_inner, + orientation::Integer, + direction, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive} + ) + # rho, v1, v2, _ = u_inner + normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function( + x, + t, + equations + ) + v1 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, t, + equations + ) + _, tau_1n, _ = flux_inner # extract fluxes for 2nd equation + normal_energy_flux = v1 * tau_1n + normal_heat_flux + return SVector(flux_inner[1], flux_inner[2], normal_energy_flux) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + u_inner, + orientation::Integer, + direction, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive} + ) + v1 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, t, + equations + ) + T = boundary_condition.boundary_condition_heat_flux.boundary_value_function( + x, t, + equations + ) + return SVector(u_inner[1], v1, T) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + u_inner, + orientation::Integer, + direction, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesPrimitive} + ) + return flux_inner + end + + # specialized BC impositions for GradientVariablesEntropy. + + # This should return a SVector containing the boundary values of entropy variables. + # Here, `w_inner` are the transformed variables (e.g., entropy variables). + # + # Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions + # for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022. + # DOI: 10.1016/j.jcp.2021.110723 + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + w_inner, + orientation::Integer, + direction, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy} + ) + v1 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, t, + equations + ) + negative_rho_inv_p = w_inner[3] # w_3 = -rho / p + return SVector(w_inner[1], -v1 * negative_rho_inv_p, negative_rho_inv_p) + end + + # this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness. + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + w_inner, + orientation::Integer, + direction, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy} + ) + normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function( + x, + t, + equations + ) + v1 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, t, + equations + ) + _, tau_1n, _ = flux_inner # extract fluxes for 2nd equation + normal_energy_flux = v1 * tau_1n + normal_heat_flux + return SVector(flux_inner[1], flux_inner[2], normal_energy_flux) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + w_inner, + orientation::Integer, + direction, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy} + ) + v1 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, t, + equations + ) + T = boundary_condition.boundary_condition_heat_flux.boundary_value_function( + x, t, + equations + ) + + # the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w3. + w3 = -1 / T + return SVector(w_inner[1], -v1 * w3, w3) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + w_inner, + orientation::Integer, + direction, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion1D{GradientVariablesEntropy} + ) + return SVector(flux_inner[1], flux_inner[2], flux_inner[3]) + end end # @muladd diff --git a/src/equations/compressible_navier_stokes_2d.jl b/src/equations/compressible_navier_stokes_2d.jl index 5708abb4e0..884aa5e163 100644 --- a/src/equations/compressible_navier_stokes_2d.jl +++ b/src/equations/compressible_navier_stokes_2d.jl @@ -3,456 +3,552 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - CompressibleNavierStokesDiffusion2D(equations; mu, Pr, - gradient_variables=GradientVariablesPrimitive()) - -Contains the diffusion (i.e. parabolic) terms applied -to mass, momenta, and total energy together with the advective terms from -the [`CompressibleEulerEquations2D`](@ref). - -- `equations`: instance of the [`CompressibleEulerEquations2D`](@ref) -- `mu`: dynamic viscosity, -- `Pr`: Prandtl number, -- `gradient_variables`: which variables the gradients are taken with respect to. - Defaults to `GradientVariablesPrimitive()`. - -Fluid properties such as the dynamic viscosity ``\mu`` can be provided in any consistent unit system, e.g., -[``\mu``] = kg m⁻¹ s⁻¹. -The viscosity ``\mu`` may be a constant or a function of the current state, e.g., -depending on temperature (Sutherland's law): ``\mu = \mu(T)``. -In the latter case, the function `mu` needs to have the signature `mu(u, equations)`. - -The particular form of the compressible Navier-Stokes implemented is -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -\rho \\ \rho \mathbf{v} \\ \rho e -\end{pmatrix} -+ -\nabla \cdot -\begin{pmatrix} - \rho \mathbf{v} \\ \rho \mathbf{v}\mathbf{v}^T + p \underline{I} \\ (\rho e + p) \mathbf{v} -\end{pmatrix} -= -\nabla \cdot -\begin{pmatrix} -0 \\ \underline{\tau} \\ \underline{\tau}\mathbf{v} - \mathbf{q} -\end{pmatrix} -``` -where the system is closed with the ideal gas assumption giving -```math -p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2+v_2^2) \right) -``` -as the pressure. The value of the adiabatic constant `gamma` is taken from the [`CompressibleEulerEquations2D`](@ref). -The terms on the right hand side of the system above -are built from the viscous stress tensor -```math -\underline{\tau} = \mu \left(\nabla\mathbf{v} + \left(\nabla\mathbf{v}\right)^T\right) - \frac{2}{3} \mu \left(\nabla\cdot\mathbf{v}\right)\underline{I} -``` -where ``\underline{I}`` is the ``2\times 2`` identity matrix and the heat flux is -```math -\mathbf{q} = -\kappa\nabla\left(T\right),\quad T = \frac{p}{R\rho} -``` -where ``T`` is the temperature and ``\kappa`` is the thermal conductivity for Fick's law. -Under the assumption that the gas has a constant Prandtl number, -the thermal conductivity is -```math -\kappa = \frac{\gamma \mu R}{(\gamma - 1)\textrm{Pr}}. -``` -From this combination of temperature ``T`` and thermal conductivity ``\kappa`` we see -that the gas constant `R` cancels and the heat flux becomes -```math -\mathbf{q} = -\kappa\nabla\left(T\right) = -\frac{\gamma \mu}{(\gamma - 1)\textrm{Pr}}\nabla\left(\frac{p}{\rho}\right) -``` -which is the form implemented below in the [`flux`](@ref) function. - -In two spatial dimensions we require gradients for three quantities, e.g., -primitive quantities -```math -\nabla v_1,\, \nabla v_2,\, \nabla T -``` -or the entropy variables -```math -\nabla w_2,\, \nabla w_3,\, \nabla w_4 -``` -where -```math -w_2 = \frac{\rho v_1}{p},\, w_3 = \frac{\rho v_2}{p},\, w_4 = -\frac{\rho}{p} -``` -""" -struct CompressibleNavierStokesDiffusion2D{GradientVariables, RealT <: Real, Mu, - E <: AbstractCompressibleEulerEquations{2}} <: - AbstractCompressibleNavierStokesDiffusion{2, 4, GradientVariables} + #! format: noindent + + @doc raw""" + CompressibleNavierStokesDiffusion2D(equations; mu, Pr, + gradient_variables=GradientVariablesPrimitive()) + + Contains the diffusion (i.e. parabolic) terms applied + to mass, momenta, and total energy together with the advective terms from + the [`CompressibleEulerEquations2D`](@ref). + + - `equations`: instance of the [`CompressibleEulerEquations2D`](@ref) + - `mu`: dynamic viscosity, + - `Pr`: Prandtl number, + - `gradient_variables`: which variables the gradients are taken with respect to. + Defaults to `GradientVariablesPrimitive()`. + + Fluid properties such as the dynamic viscosity ``\mu`` can be provided in any consistent unit system, e.g., + [``\mu``] = kg m⁻¹ s⁻¹. + The viscosity ``\mu`` may be a constant or a function of the current state, e.g., + depending on temperature (Sutherland's law): ``\mu = \mu(T)``. + In the latter case, the function `mu` needs to have the signature `mu(u, equations)`. + + The particular form of the compressible Navier-Stokes implemented is + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + \rho \\ \rho \mathbf{v} \\ \rho e + \end{pmatrix} + + + \nabla \cdot + \begin{pmatrix} + \rho \mathbf{v} \\ \rho \mathbf{v}\mathbf{v}^T + p \underline{I} \\ (\rho e + p) \mathbf{v} + \end{pmatrix} + = + \nabla \cdot + \begin{pmatrix} + 0 \\ \underline{\tau} \\ \underline{\tau}\mathbf{v} - \mathbf{q} + \end{pmatrix} + ``` + where the system is closed with the ideal gas assumption giving + ```math + p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2+v_2^2) \right) + ``` + as the pressure. The value of the adiabatic constant `gamma` is taken from the [`CompressibleEulerEquations2D`](@ref). + The terms on the right hand side of the system above + are built from the viscous stress tensor + ```math + \underline{\tau} = \mu \left(\nabla\mathbf{v} + \left(\nabla\mathbf{v}\right)^T\right) - \frac{2}{3} \mu \left(\nabla\cdot\mathbf{v}\right)\underline{I} + ``` + where ``\underline{I}`` is the ``2\times 2`` identity matrix and the heat flux is + ```math + \mathbf{q} = -\kappa\nabla\left(T\right),\quad T = \frac{p}{R\rho} + ``` + where ``T`` is the temperature and ``\kappa`` is the thermal conductivity for Fick's law. + Under the assumption that the gas has a constant Prandtl number, + the thermal conductivity is + ```math + \kappa = \frac{\gamma \mu R}{(\gamma - 1)\textrm{Pr}}. + ``` + From this combination of temperature ``T`` and thermal conductivity ``\kappa`` we see + that the gas constant `R` cancels and the heat flux becomes + ```math + \mathbf{q} = -\kappa\nabla\left(T\right) = -\frac{\gamma \mu}{(\gamma - 1)\textrm{Pr}}\nabla\left(\frac{p}{\rho}\right) + ``` + which is the form implemented below in the [`flux`](@ref) function. + + In two spatial dimensions we require gradients for three quantities, e.g., + primitive quantities + ```math + \nabla v_1,\, \nabla v_2,\, \nabla T + ``` + or the entropy variables + ```math + \nabla w_2,\, \nabla w_3,\, \nabla w_4 + ``` + where + ```math + w_2 = \frac{\rho v_1}{p},\, w_3 = \frac{\rho v_2}{p},\, w_4 = -\frac{\rho}{p} + ``` + """ + struct CompressibleNavierStokesDiffusion2D{ + GradientVariables, RealT <: Real, Mu, + E <: AbstractCompressibleEulerEquations{2}, + } <: + AbstractCompressibleNavierStokesDiffusion{2, 4, GradientVariables} + # TODO: parabolic + # 1) For now save gamma and inv(gamma-1) again, but could potentially reuse them from the Euler equations + # 2) Add NGRADS as a type parameter here and in AbstractEquationsParabolic, add `ngradients(...)` accessor function + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + + mu::Mu # viscosity + Pr::RealT # Prandtl number + kappa::RealT # thermal diffusivity for Fick's law + + equations_hyperbolic::E # CompressibleEulerEquations2D + gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy + end + + # default to primitive gradient variables + function CompressibleNavierStokesDiffusion2D( + equations::CompressibleEulerEquations2D; + mu, Prandtl, + gradient_variables = GradientVariablesPrimitive() + ) + gamma = equations.gamma + inv_gamma_minus_one = equations.inv_gamma_minus_one + + # Under the assumption of constant Prandtl number the thermal conductivity + # constant is kappa = gamma μ / ((gamma-1) Prandtl). + # Important note! Factor of μ is accounted for later in `flux`. + # This avoids recomputation of kappa for non-constant μ. + kappa = gamma * inv_gamma_minus_one / Prandtl + + CompressibleNavierStokesDiffusion2D{ + typeof(gradient_variables), typeof(gamma), + typeof(mu), + typeof(equations), + }( + gamma, inv_gamma_minus_one, + mu, Prandtl, kappa, + equations, + gradient_variables + ) + end + # TODO: parabolic - # 1) For now save gamma and inv(gamma-1) again, but could potentially reuse them from the Euler equations - # 2) Add NGRADS as a type parameter here and in AbstractEquationsParabolic, add `ngradients(...)` accessor function - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - - mu::Mu # viscosity - Pr::RealT # Prandtl number - kappa::RealT # thermal diffusivity for Fick's law - - equations_hyperbolic::E # CompressibleEulerEquations2D - gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy -end - -# default to primitive gradient variables -function CompressibleNavierStokesDiffusion2D(equations::CompressibleEulerEquations2D; - mu, Prandtl, - gradient_variables = GradientVariablesPrimitive()) - gamma = equations.gamma - inv_gamma_minus_one = equations.inv_gamma_minus_one - - # Under the assumption of constant Prandtl number the thermal conductivity - # constant is kappa = gamma μ / ((gamma-1) Prandtl). - # Important note! Factor of μ is accounted for later in `flux`. - # This avoids recomputation of kappa for non-constant μ. - kappa = gamma * inv_gamma_minus_one / Prandtl - - CompressibleNavierStokesDiffusion2D{typeof(gradient_variables), typeof(gamma), - typeof(mu), - typeof(equations)}(gamma, inv_gamma_minus_one, - mu, Prandtl, kappa, - equations, - gradient_variables) -end - -# TODO: parabolic -# This is the flexibility a user should have to select the different gradient variable types -# varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion2D) = ("v1", "v2", "T") -# varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion2D) = ("w2", "w3", "w4") - -function varnames(variable_mapping, - equations_parabolic::CompressibleNavierStokesDiffusion2D) - varnames(variable_mapping, equations_parabolic.equations_hyperbolic) -end - -# we specialize this function to compute gradients of primitive variables instead of -# conservative variables. -function gradient_variable_transformation(::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) - cons2prim -end -function gradient_variable_transformation(::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy}) - cons2entropy -end - -# Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2 -# of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner -# "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive -# MHD Equations. Part II: Subcell Finite Volume Shock Capturing" -# where one sets the magnetic field components equal to 0. -function flux(u, gradients, orientation::Integer, - equations::CompressibleNavierStokesDiffusion2D) - # Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`. - rho, v1, v2, _ = convert_transformed_to_primitive(u, equations) - # Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, v2, T) - # either computed directly or reverse engineered from the gradient of the entropy variables - # by way of the `convert_gradient_variables` function. - _, dv1dx, dv2dx, dTdx = convert_derivative_to_primitive(u, gradients[1], equations) - _, dv1dy, dv2dy, dTdy = convert_derivative_to_primitive(u, gradients[2], equations) - - # Components of viscous stress tensor - - # (4 * (v1)_x / 3 - 2 * (v2)_y / 3) - tau_11 = 4 * dv1dx / 3 - 2 * dv2dy / 3 - # ((v1)_y + (v2)_x) - # stress tensor is symmetric - tau_12 = dv1dy + dv2dx # = tau_21 - # (4/3 * (v2)_y - 2/3 * (v1)_x) - tau_22 = 4 * dv2dy / 3 - 2 * dv1dx / 3 - - # Fick's law q = -kappa * grad(T) = -kappa * grad(p / (R rho)) - # with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr) - # Note, the gas constant cancels under this formulation, so it is not present - # in the implementation - q1 = equations.kappa * dTdx - q2 = equations.kappa * dTdy - - # In the simplest cases, the user passed in `mu` or `mu()` - # (which returns just a constant) but - # more complex functions like Sutherland's law are possible. - # `dynamic_viscosity` is a helper function that handles both cases - # by dispatching on the type of `equations.mu`. - mu = dynamic_viscosity(u, equations) - - if orientation == 1 - # viscous flux components in the x-direction - f1 = 0 - f2 = tau_11 * mu - f3 = tau_12 * mu - f4 = (v1 * tau_11 + v2 * tau_12 + q1) * mu - - return SVector(f1, f2, f3, f4) - else # if orientation == 2 - # viscous flux components in the y-direction - # Note, symmetry is exploited for tau_12 = tau_21 - g1 = 0 - g2 = tau_12 * mu # tau_21 * mu - g3 = tau_22 * mu - g4 = (v1 * tau_12 + v2 * tau_22 + q2) * mu - - return SVector(g1, g2, g3, g4) + # This is the flexibility a user should have to select the different gradient variable types + # varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion2D) = ("v1", "v2", "T") + # varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion2D) = ("w2", "w3", "w4") + + function varnames( + variable_mapping, + equations_parabolic::CompressibleNavierStokesDiffusion2D + ) + varnames(variable_mapping, equations_parabolic.equations_hyperbolic) + end + + # we specialize this function to compute gradients of primitive variables instead of + # conservative variables. + function gradient_variable_transformation(::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) + cons2prim + end + function gradient_variable_transformation(::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy}) + cons2entropy + end + + # Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2 + # of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner + # "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive + # MHD Equations. Part II: Subcell Finite Volume Shock Capturing" + # where one sets the magnetic field components equal to 0. + function flux( + u, gradients, orientation::Integer, + equations::CompressibleNavierStokesDiffusion2D + ) + # Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`. + rho, v1, v2, _ = convert_transformed_to_primitive(u, equations) + # Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, v2, T) + # either computed directly or reverse engineered from the gradient of the entropy variables + # by way of the `convert_gradient_variables` function. + _, dv1dx, dv2dx, dTdx = convert_derivative_to_primitive(u, gradients[1], equations) + _, dv1dy, dv2dy, dTdy = convert_derivative_to_primitive(u, gradients[2], equations) + + # Components of viscous stress tensor + + # (4 * (v1)_x / 3 - 2 * (v2)_y / 3) + tau_11 = 4 * dv1dx / 3 - 2 * dv2dy / 3 + # ((v1)_y + (v2)_x) + # stress tensor is symmetric + tau_12 = dv1dy + dv2dx # = tau_21 + # (4/3 * (v2)_y - 2/3 * (v1)_x) + tau_22 = 4 * dv2dy / 3 - 2 * dv1dx / 3 + + # Fick's law q = -kappa * grad(T) = -kappa * grad(p / (R rho)) + # with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr) + # Note, the gas constant cancels under this formulation, so it is not present + # in the implementation + q1 = equations.kappa * dTdx + q2 = equations.kappa * dTdy + + # In the simplest cases, the user passed in `mu` or `mu()` + # (which returns just a constant) but + # more complex functions like Sutherland's law are possible. + # `dynamic_viscosity` is a helper function that handles both cases + # by dispatching on the type of `equations.mu`. + mu = dynamic_viscosity(u, equations) + + if orientation == 1 + # viscous flux components in the x-direction + f1 = 0 + f2 = tau_11 * mu + f3 = tau_12 * mu + f4 = (v1 * tau_11 + v2 * tau_12 + q1) * mu + + return SVector(f1, f2, f3, f4) + else # if orientation == 2 + # viscous flux components in the y-direction + # Note, symmetry is exploited for tau_12 = tau_21 + g1 = 0 + g2 = tau_12 * mu # tau_21 * mu + g3 = tau_22 * mu + g4 = (v1 * tau_12 + v2 * tau_22 + q2) * mu + + return SVector(g1, g2, g3, g4) + end + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion2D) + rho, rho_v1, rho_v2, _ = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + T = temperature(u, equations) + + return SVector(rho, v1, v2, T) + end + + # Convert conservative variables to entropy + # TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms + # This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion2D`, + # but this may be confusing to new users. + function cons2entropy(u, equations::CompressibleNavierStokesDiffusion2D) + cons2entropy(u, equations.equations_hyperbolic) + end + function entropy2cons(w, equations::CompressibleNavierStokesDiffusion2D) + entropy2cons(w, equations.equations_hyperbolic) + end + + # the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables. + # For CNS, it is simplest to formulate the viscous terms in primitive variables, so we transform the transformed + # variables into primitive variables. + @inline function convert_transformed_to_primitive( + u_transformed, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive} + ) + return u_transformed + end + + # TODO: parabolic. Make this more efficient! + @inline function convert_transformed_to_primitive( + u_transformed, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy} + ) + # note: this uses CompressibleNavierStokesDiffusion2D versions of cons2prim and entropy2cons + return cons2prim(entropy2cons(u_transformed, equations), equations) + end + + # Takes the solution values `u` and gradient of the entropy variables (w_2, w_3, w_4) and + # reverse engineers the gradients to be terms of the primitive variables (v1, v2, T). + # Helpful because then the diffusive fluxes have the same form as on paper. + # Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused. + # TODO: parabolic; entropy stable viscous terms + @inline function convert_derivative_to_primitive( + u, gradient, + ::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive} + ) + return gradient + end + + # the first argument is always the "transformed" variables. + @inline function convert_derivative_to_primitive( + w, gradient_entropy_vars, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy} + ) + + # TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back. + # We can fix this if we directly compute v1, v2, T from the entropy variables + u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion2D + rho, rho_v1, rho_v2, _ = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + T = temperature(u, equations) + + return SVector( + gradient_entropy_vars[1], + T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[4]), # grad(u) = T*(grad(w_2)+v1*grad(w_4)) + T * (gradient_entropy_vars[3] + v2 * gradient_entropy_vars[4]), # grad(v) = T*(grad(w_3)+v2*grad(w_4)) + T * T * gradient_entropy_vars[4] + ) + end + + # This routine is required because `prim2cons` is called in `initial_condition`, which + # is called with `equations::CompressibleEulerEquations2D`. This means it is inconsistent + # with `cons2prim(..., ::CompressibleNavierStokesDiffusion2D)` as defined above. + # TODO: parabolic. Is there a way to clean this up? + @inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion2D) + prim2cons(u, equations.equations_hyperbolic) + end + + @inline function temperature(u, equations::CompressibleNavierStokesDiffusion2D) + rho, rho_v1, rho_v2, rho_e = u + + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho) + T = p / rho + return T + end + + @inline function enstrophy(u, gradients, equations::CompressibleNavierStokesDiffusion2D) + # Enstrophy is 0.5 rho ω⋅ω where ω = ∇ × v + + omega = vorticity(u, gradients, equations) + return 0.5f0 * u[1] * omega^2 + end + + @inline function vorticity(u, gradients, equations::CompressibleNavierStokesDiffusion2D) + # Ensure that we have velocity `gradients` by way of the `convert_gradient_variables` function. + _, dv1dx, dv2dx, _ = convert_derivative_to_primitive(u, gradients[1], equations) + _, dv1dy, dv2dy, _ = convert_derivative_to_primitive(u, gradients[2], equations) + + return dv2dx - dv1dy + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + u_inner, + normal::AbstractVector, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive} + ) + v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + return SVector(u_inner[1], v1, v2, u_inner[4]) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + u_inner, + normal::AbstractVector, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive} + ) + # rho, v1, v2, _ = u_inner + normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function( + x, + t, + equations + ) + v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + _, tau_1n, tau_2n, _ = flux_inner # extract fluxes for 2nd and 3rd equations + normal_energy_flux = v1 * tau_1n + v2 * tau_2n + normal_heat_flux + return SVector(flux_inner[1], flux_inner[2], flux_inner[3], normal_energy_flux) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + u_inner, + normal::AbstractVector, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive} + ) + v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + T = boundary_condition.boundary_condition_heat_flux.boundary_value_function( + x, t, + equations + ) + return SVector(u_inner[1], v1, v2, T) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + u_inner, + normal::AbstractVector, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive} + ) + return flux_inner + end + + # specialized BC impositions for GradientVariablesEntropy. + + # This should return a SVector containing the boundary values of entropy variables. + # Here, `w_inner` are the transformed variables (e.g., entropy variables). + # + # Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions + # for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022. + # DOI: 10.1016/j.jcp.2021.110723 + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + w_inner, + normal::AbstractVector, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy} + ) + v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + negative_rho_inv_p = w_inner[4] # w_4 = -rho / p + return SVector( + w_inner[1], -v1 * negative_rho_inv_p, -v2 * negative_rho_inv_p, + negative_rho_inv_p + ) + end + + # this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness. + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + w_inner, + normal::AbstractVector, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy} + ) + normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function( + x, + t, + equations + ) + v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + _, tau_1n, tau_2n, _ = flux_inner # extract fluxes for 2nd and 3rd equations + normal_energy_flux = v1 * tau_1n + v2 * tau_2n + normal_heat_flux + return SVector(flux_inner[1], flux_inner[2], flux_inner[3], normal_energy_flux) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + w_inner, + normal::AbstractVector, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy} + ) + v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + T = boundary_condition.boundary_condition_heat_flux.boundary_value_function( + x, t, + equations + ) + + # the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w4. Similarly for w3 + w4 = -1 / T + return SVector(w_inner[1], -v1 * w4, -v2 * w4, w4) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + w_inner, + normal::AbstractVector, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy} + ) + return SVector(flux_inner[1], flux_inner[2], flux_inner[3], flux_inner[4]) + end + + # Dirichlet Boundary Condition for P4est mesh + + @inline function (boundary_condition::BoundaryConditionDirichlet)( + flux_inner, + u_inner, + normal::AbstractVector, + x, t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive} + ) + # BCs are usually specified as conservative variables so we convert them to primitive variables + # because the gradients are assumed to be with respect to the primitive variables + u_boundary = boundary_condition.boundary_value_function(x, t, equations) + + return cons2prim(u_boundary, equations) + end + + @inline function (boundary_condition::BoundaryConditionDirichlet)( + flux_inner, + u_inner, + normal::AbstractVector, + x, t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive} + ) + # for Dirichlet boundary conditions, we do not impose any conditions on the viscous fluxes + return flux_inner end -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion2D) - rho, rho_v1, rho_v2, _ = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - T = temperature(u, equations) - - return SVector(rho, v1, v2, T) -end - -# Convert conservative variables to entropy -# TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms -# This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion2D`, -# but this may be confusing to new users. -function cons2entropy(u, equations::CompressibleNavierStokesDiffusion2D) - cons2entropy(u, equations.equations_hyperbolic) -end -function entropy2cons(w, equations::CompressibleNavierStokesDiffusion2D) - entropy2cons(w, equations.equations_hyperbolic) -end - -# the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables. -# For CNS, it is simplest to formulate the viscous terms in primitive variables, so we transform the transformed -# variables into primitive variables. -@inline function convert_transformed_to_primitive(u_transformed, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) - return u_transformed -end - -# TODO: parabolic. Make this more efficient! -@inline function convert_transformed_to_primitive(u_transformed, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy}) - # note: this uses CompressibleNavierStokesDiffusion2D versions of cons2prim and entropy2cons - return cons2prim(entropy2cons(u_transformed, equations), equations) -end - -# Takes the solution values `u` and gradient of the entropy variables (w_2, w_3, w_4) and -# reverse engineers the gradients to be terms of the primitive variables (v1, v2, T). -# Helpful because then the diffusive fluxes have the same form as on paper. -# Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused. -# TODO: parabolic; entropy stable viscous terms -@inline function convert_derivative_to_primitive(u, gradient, - ::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) - return gradient -end - -# the first argument is always the "transformed" variables. -@inline function convert_derivative_to_primitive(w, gradient_entropy_vars, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy}) - - # TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back. - # We can fix this if we directly compute v1, v2, T from the entropy variables - u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion2D - rho, rho_v1, rho_v2, _ = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - T = temperature(u, equations) - - return SVector(gradient_entropy_vars[1], - T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[4]), # grad(u) = T*(grad(w_2)+v1*grad(w_4)) - T * (gradient_entropy_vars[3] + v2 * gradient_entropy_vars[4]), # grad(v) = T*(grad(w_3)+v2*grad(w_4)) - T * T * gradient_entropy_vars[4]) -end - -# This routine is required because `prim2cons` is called in `initial_condition`, which -# is called with `equations::CompressibleEulerEquations2D`. This means it is inconsistent -# with `cons2prim(..., ::CompressibleNavierStokesDiffusion2D)` as defined above. -# TODO: parabolic. Is there a way to clean this up? -@inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion2D) - prim2cons(u, equations.equations_hyperbolic) -end - -@inline function temperature(u, equations::CompressibleNavierStokesDiffusion2D) - rho, rho_v1, rho_v2, rho_e = u - - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2) / rho) - T = p / rho - return T -end - -@inline function enstrophy(u, gradients, equations::CompressibleNavierStokesDiffusion2D) - # Enstrophy is 0.5 rho ω⋅ω where ω = ∇ × v - - omega = vorticity(u, gradients, equations) - return 0.5f0 * u[1] * omega^2 -end - -@inline function vorticity(u, gradients, equations::CompressibleNavierStokesDiffusion2D) - # Ensure that we have velocity `gradients` by way of the `convert_gradient_variables` function. - _, dv1dx, dv2dx, _ = convert_derivative_to_primitive(u, gradients[1], equations) - _, dv1dy, dv2dy, _ = convert_derivative_to_primitive(u, gradients[2], equations) - - return dv2dx - dv1dy -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - u_inner, - normal::AbstractVector, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) - v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - return SVector(u_inner[1], v1, v2, u_inner[4]) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - u_inner, - normal::AbstractVector, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) - # rho, v1, v2, _ = u_inner - normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x, - t, - equations) - v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - _, tau_1n, tau_2n, _ = flux_inner # extract fluxes for 2nd and 3rd equations - normal_energy_flux = v1 * tau_1n + v2 * tau_2n + normal_heat_flux - return SVector(flux_inner[1], flux_inner[2], flux_inner[3], normal_energy_flux) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - u_inner, - normal::AbstractVector, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) - v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t, - equations) - return SVector(u_inner[1], v1, v2, T) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - u_inner, - normal::AbstractVector, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) - return flux_inner -end - -# specialized BC impositions for GradientVariablesEntropy. - -# This should return a SVector containing the boundary values of entropy variables. -# Here, `w_inner` are the transformed variables (e.g., entropy variables). -# -# Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions -# for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022. -# DOI: 10.1016/j.jcp.2021.110723 -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - w_inner, - normal::AbstractVector, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy}) - v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - negative_rho_inv_p = w_inner[4] # w_4 = -rho / p - return SVector(w_inner[1], -v1 * negative_rho_inv_p, -v2 * negative_rho_inv_p, - negative_rho_inv_p) -end - -# this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness. -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - w_inner, - normal::AbstractVector, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy}) - normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x, - t, - equations) - v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - _, tau_1n, tau_2n, _ = flux_inner # extract fluxes for 2nd and 3rd equations - normal_energy_flux = v1 * tau_1n + v2 * tau_2n + normal_heat_flux - return SVector(flux_inner[1], flux_inner[2], flux_inner[3], normal_energy_flux) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - w_inner, - normal::AbstractVector, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy}) - v1, v2 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t, - equations) - - # the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w4. Similarly for w3 - w4 = -1 / T - return SVector(w_inner[1], -v1 * w4, -v2 * w4, w4) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - w_inner, - normal::AbstractVector, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesEntropy}) - return SVector(flux_inner[1], flux_inner[2], flux_inner[3], flux_inner[4]) -end - -# Dirichlet Boundary Condition for P4est mesh - -@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner, - u_inner, - normal::AbstractVector, - x, t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) - # BCs are usually specified as conservative variables so we convert them to primitive variables - # because the gradients are assumed to be with respect to the primitive variables - u_boundary = boundary_condition.boundary_value_function(x, t, equations) - - return cons2prim(u_boundary, equations) -end - -@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner, - u_inner, - normal::AbstractVector, - x, t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion2D{GradientVariablesPrimitive}) - # for Dirichlet boundary conditions, we do not impose any conditions on the viscous fluxes - return flux_inner -end end # @muladd diff --git a/src/equations/compressible_navier_stokes_3d.jl b/src/equations/compressible_navier_stokes_3d.jl index 9622882fc1..2c8f513d15 100644 --- a/src/equations/compressible_navier_stokes_3d.jl +++ b/src/equations/compressible_navier_stokes_3d.jl @@ -3,466 +3,576 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - CompressibleNavierStokesDiffusion3D(equations; mu, Pr, - gradient_variables=GradientVariablesPrimitive()) - -Contains the diffusion (i.e. parabolic) terms applied -to mass, momenta, and total energy together with the advective terms from -the [`CompressibleEulerEquations3D`](@ref). - -- `equations`: instance of the [`CompressibleEulerEquations3D`](@ref) -- `mu`: dynamic viscosity, -- `Pr`: Prandtl number, -- `gradient_variables`: which variables the gradients are taken with respect to. - Defaults to `GradientVariablesPrimitive()`. - -Fluid properties such as the dynamic viscosity ``\mu`` can be provided in any consistent unit system, e.g., -[``\mu``] = kg m⁻¹ s⁻¹. -The viscosity ``\mu`` may be a constant or a function of the current state, e.g., -depending on temperature (Sutherland's law): ``\mu = \mu(T)``. -In the latter case, the function `mu` needs to have the signature `mu(u, equations)`. - -The particular form of the compressible Navier-Stokes implemented is -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -\rho \\ \rho \mathbf{v} \\ \rho e -\end{pmatrix} -+ -\nabla \cdot -\begin{pmatrix} - \rho \mathbf{v} \\ \rho \mathbf{v}\mathbf{v}^T + p \underline{I} \\ (\rho e + p) \mathbf{v} -\end{pmatrix} -= -\nabla \cdot -\begin{pmatrix} -0 \\ \underline{\tau} \\ \underline{\tau}\mathbf{v} - \mathbf{q} -\end{pmatrix} -``` -where the system is closed with the ideal gas assumption giving -```math -p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2+v_2^2+v_3^2) \right) -``` -as the pressure. The value of the adiabatic constant `gamma` is taken from the [`CompressibleEulerEquations2D`](@ref). -The terms on the right hand side of the system above -are built from the viscous stress tensor -```math -\underline{\tau} = \mu \left(\nabla\mathbf{v} + \left(\nabla\mathbf{v}\right)^T\right) - \frac{2}{3} \mu \left(\nabla\cdot\mathbf{v}\right)\underline{I} -``` -where ``\underline{I}`` is the ``3\times 3`` identity matrix and the heat flux is -```math -\mathbf{q} = -\kappa\nabla\left(T\right),\quad T = \frac{p}{R\rho} -``` -where ``T`` is the temperature and ``\kappa`` is the thermal conductivity for Fick's law. -Under the assumption that the gas has a constant Prandtl number, -the thermal conductivity is -```math -\kappa = \frac{\gamma \mu R}{(\gamma - 1)\textrm{Pr}}. -``` -From this combination of temperature ``T`` and thermal conductivity ``\kappa`` we see -that the gas constant `R` cancels and the heat flux becomes -```math -\mathbf{q} = -\kappa\nabla\left(T\right) = -\frac{\gamma \mu}{(\gamma - 1)\textrm{Pr}}\nabla\left(\frac{p}{\rho}\right) -``` -which is the form implemented below in the [`flux`](@ref) function. - -In two spatial dimensions we require gradients for three quantities, e.g., -primitive quantities -```math -\nabla v_1,\, \nabla v_2,\, \nabla v_3,\, \nabla T -``` -or the entropy variables -```math -\nabla w_2,\, \nabla w_3,\, \nabla w_4\, \nabla w_5 -``` -where -```math -w_2 = \frac{\rho v_1}{p},\, w_3 = \frac{\rho v_2}{p},\, w_4 = \frac{\rho v_3}{p},\, w_5 = -\frac{\rho}{p} -``` -""" -struct CompressibleNavierStokesDiffusion3D{GradientVariables, RealT <: Real, Mu, - E <: AbstractCompressibleEulerEquations{3}} <: - AbstractCompressibleNavierStokesDiffusion{3, 5, GradientVariables} + #! format: noindent + + @doc raw""" + CompressibleNavierStokesDiffusion3D(equations; mu, Pr, + gradient_variables=GradientVariablesPrimitive()) + + Contains the diffusion (i.e. parabolic) terms applied + to mass, momenta, and total energy together with the advective terms from + the [`CompressibleEulerEquations3D`](@ref). + + - `equations`: instance of the [`CompressibleEulerEquations3D`](@ref) + - `mu`: dynamic viscosity, + - `Pr`: Prandtl number, + - `gradient_variables`: which variables the gradients are taken with respect to. + Defaults to `GradientVariablesPrimitive()`. + + Fluid properties such as the dynamic viscosity ``\mu`` can be provided in any consistent unit system, e.g., + [``\mu``] = kg m⁻¹ s⁻¹. + The viscosity ``\mu`` may be a constant or a function of the current state, e.g., + depending on temperature (Sutherland's law): ``\mu = \mu(T)``. + In the latter case, the function `mu` needs to have the signature `mu(u, equations)`. + + The particular form of the compressible Navier-Stokes implemented is + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + \rho \\ \rho \mathbf{v} \\ \rho e + \end{pmatrix} + + + \nabla \cdot + \begin{pmatrix} + \rho \mathbf{v} \\ \rho \mathbf{v}\mathbf{v}^T + p \underline{I} \\ (\rho e + p) \mathbf{v} + \end{pmatrix} + = + \nabla \cdot + \begin{pmatrix} + 0 \\ \underline{\tau} \\ \underline{\tau}\mathbf{v} - \mathbf{q} + \end{pmatrix} + ``` + where the system is closed with the ideal gas assumption giving + ```math + p = (\gamma - 1) \left( \rho e - \frac{1}{2} \rho (v_1^2+v_2^2+v_3^2) \right) + ``` + as the pressure. The value of the adiabatic constant `gamma` is taken from the [`CompressibleEulerEquations2D`](@ref). + The terms on the right hand side of the system above + are built from the viscous stress tensor + ```math + \underline{\tau} = \mu \left(\nabla\mathbf{v} + \left(\nabla\mathbf{v}\right)^T\right) - \frac{2}{3} \mu \left(\nabla\cdot\mathbf{v}\right)\underline{I} + ``` + where ``\underline{I}`` is the ``3\times 3`` identity matrix and the heat flux is + ```math + \mathbf{q} = -\kappa\nabla\left(T\right),\quad T = \frac{p}{R\rho} + ``` + where ``T`` is the temperature and ``\kappa`` is the thermal conductivity for Fick's law. + Under the assumption that the gas has a constant Prandtl number, + the thermal conductivity is + ```math + \kappa = \frac{\gamma \mu R}{(\gamma - 1)\textrm{Pr}}. + ``` + From this combination of temperature ``T`` and thermal conductivity ``\kappa`` we see + that the gas constant `R` cancels and the heat flux becomes + ```math + \mathbf{q} = -\kappa\nabla\left(T\right) = -\frac{\gamma \mu}{(\gamma - 1)\textrm{Pr}}\nabla\left(\frac{p}{\rho}\right) + ``` + which is the form implemented below in the [`flux`](@ref) function. + + In two spatial dimensions we require gradients for three quantities, e.g., + primitive quantities + ```math + \nabla v_1,\, \nabla v_2,\, \nabla v_3,\, \nabla T + ``` + or the entropy variables + ```math + \nabla w_2,\, \nabla w_3,\, \nabla w_4\, \nabla w_5 + ``` + where + ```math + w_2 = \frac{\rho v_1}{p},\, w_3 = \frac{\rho v_2}{p},\, w_4 = \frac{\rho v_3}{p},\, w_5 = -\frac{\rho}{p} + ``` + """ + struct CompressibleNavierStokesDiffusion3D{ + GradientVariables, RealT <: Real, Mu, + E <: AbstractCompressibleEulerEquations{3}, + } <: + AbstractCompressibleNavierStokesDiffusion{3, 5, GradientVariables} + # TODO: parabolic + # 1) For now save gamma and inv(gamma-1) again, but could potentially reuse them from the Euler equations + # 2) Add NGRADS as a type parameter here and in AbstractEquationsParabolic, add `ngradients(...)` accessor function + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + + mu::Mu # viscosity + Pr::RealT # Prandtl number + kappa::RealT # thermal diffusivity for Fick's law + + equations_hyperbolic::E # CompressibleEulerEquations3D + gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy + end + + # default to primitive gradient variables + function CompressibleNavierStokesDiffusion3D( + equations::CompressibleEulerEquations3D; + mu, Prandtl, + gradient_variables = GradientVariablesPrimitive() + ) + gamma = equations.gamma + inv_gamma_minus_one = equations.inv_gamma_minus_one + + # Under the assumption of constant Prandtl number the thermal conductivity + # constant is kappa = gamma μ / ((gamma-1) Prandtl). + # Important note! Factor of μ is accounted for later in `flux`. + # This avoids recomputation of kappa for non-constant μ. + kappa = gamma * inv_gamma_minus_one / Prandtl + + CompressibleNavierStokesDiffusion3D{ + typeof(gradient_variables), typeof(gamma), + typeof(mu), + typeof(equations), + }( + gamma, inv_gamma_minus_one, + mu, Prandtl, kappa, + equations, + gradient_variables + ) + end + # TODO: parabolic - # 1) For now save gamma and inv(gamma-1) again, but could potentially reuse them from the Euler equations - # 2) Add NGRADS as a type parameter here and in AbstractEquationsParabolic, add `ngradients(...)` accessor function - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - - mu::Mu # viscosity - Pr::RealT # Prandtl number - kappa::RealT # thermal diffusivity for Fick's law - - equations_hyperbolic::E # CompressibleEulerEquations3D - gradient_variables::GradientVariables # GradientVariablesPrimitive or GradientVariablesEntropy -end - -# default to primitive gradient variables -function CompressibleNavierStokesDiffusion3D(equations::CompressibleEulerEquations3D; - mu, Prandtl, - gradient_variables = GradientVariablesPrimitive()) - gamma = equations.gamma - inv_gamma_minus_one = equations.inv_gamma_minus_one - - # Under the assumption of constant Prandtl number the thermal conductivity - # constant is kappa = gamma μ / ((gamma-1) Prandtl). - # Important note! Factor of μ is accounted for later in `flux`. - # This avoids recomputation of kappa for non-constant μ. - kappa = gamma * inv_gamma_minus_one / Prandtl - - CompressibleNavierStokesDiffusion3D{typeof(gradient_variables), typeof(gamma), - typeof(mu), - typeof(equations)}(gamma, inv_gamma_minus_one, - mu, Prandtl, kappa, - equations, - gradient_variables) -end - -# TODO: parabolic -# This is the flexibility a user should have to select the different gradient variable types -# varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion3D) = ("v1", "v2", "v3", "T") -# varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion3D) = ("w2", "w3", "w4", "w5") - -function varnames(variable_mapping, - equations_parabolic::CompressibleNavierStokesDiffusion3D) - varnames(variable_mapping, equations_parabolic.equations_hyperbolic) -end - -# we specialize this function to compute gradients of primitive variables instead of -# conservative variables. -function gradient_variable_transformation(::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive}) - cons2prim -end -function gradient_variable_transformation(::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy}) - cons2entropy -end - -# Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2 -# of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner -# "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive -# MHD Equations. Part II: Subcell Finite Volume Shock Capturing" -# where one sets the magnetic field components equal to 0. -function flux(u, gradients, orientation::Integer, - equations::CompressibleNavierStokesDiffusion3D) - # Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`. - rho, v1, v2, v3, _ = convert_transformed_to_primitive(u, equations) - # Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, v2, v3, T) - # either computed directly or reverse engineered from the gradient of the entropy variables - # by way of the `convert_gradient_variables` function. - _, dv1dx, dv2dx, dv3dx, dTdx = convert_derivative_to_primitive(u, gradients[1], - equations) - _, dv1dy, dv2dy, dv3dy, dTdy = convert_derivative_to_primitive(u, gradients[2], - equations) - _, dv1dz, dv2dz, dv3dz, dTdz = convert_derivative_to_primitive(u, gradients[3], - equations) - - # Components of viscous stress tensor - - # Diagonal parts - # (4 * (v1)_x / 3 - 2 * ((v2)_y + (v3)_z)) / 3) - tau_11 = 4 * dv1dx / 3 - 2 * (dv2dy + dv3dz) / 3 - # (4 * (v2)_y / 3 - 2 * ((v1)_x + (v3)_z) / 3) - tau_22 = 4 * dv2dy / 3 - 2 * (dv1dx + dv3dz) / 3 - # (4 * (v3)_z / 3 - 2 * ((v1)_x + (v2)_y) / 3) - tau_33 = 4 * dv3dz / 3 - 2 * (dv1dx + dv2dy) / 3 - - # Off diagonal parts, exploit that stress tensor is symmetric - # ((v1)_y + (v2)_x) - tau_12 = dv1dy + dv2dx # = tau_21 - # ((v1)_z + (v3)_x) - tau_13 = dv1dz + dv3dx # = tau_31 - # ((v2)_z + (v3)_y) - tau_23 = dv2dz + dv3dy # = tau_32 - - # Fick's law q = -kappa * grad(T) = -kappa * grad(p / (R rho)) - # with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr) - # Note, the gas constant cancels under this formulation, so it is not present - # in the implementation - q1 = equations.kappa * dTdx - q2 = equations.kappa * dTdy - q3 = equations.kappa * dTdz - - # In the simplest cases, the user passed in `mu` or `mu()` - # (which returns just a constant) but - # more complex functions like Sutherland's law are possible. - # `dynamic_viscosity` is a helper function that handles both cases - # by dispatching on the type of `equations.mu`. - mu = dynamic_viscosity(u, equations) - - if orientation == 1 - # viscous flux components in the x-direction - f1 = 0 - f2 = tau_11 * mu - f3 = tau_12 * mu - f4 = tau_13 * mu - f5 = (v1 * tau_11 + v2 * tau_12 + v3 * tau_13 + q1) * mu - - return SVector(f1, f2, f3, f4, f5) - elseif orientation == 2 - # viscous flux components in the y-direction - # Note, symmetry is exploited for tau_12 = tau_21 - g1 = 0 - g2 = tau_12 * mu # tau_21 * mu - g3 = tau_22 * mu - g4 = tau_23 * mu - g5 = (v1 * tau_12 + v2 * tau_22 + v3 * tau_23 + q2) * mu - - return SVector(g1, g2, g3, g4, g5) - else # if orientation == 3 - # viscous flux components in the z-direction - # Note, symmetry is exploited for tau_13 = tau_31, tau_23 = tau_32 - h1 = 0 - h2 = tau_13 * mu # tau_31 * mu - h3 = tau_23 * mu # tau_32 * mu - h4 = tau_33 * mu - h5 = (v1 * tau_13 + v2 * tau_23 + v3 * tau_33 + q3) * mu - - return SVector(h1, h2, h3, h4, h5) + # This is the flexibility a user should have to select the different gradient variable types + # varnames(::typeof(cons2prim) , ::CompressibleNavierStokesDiffusion3D) = ("v1", "v2", "v3", "T") + # varnames(::typeof(cons2entropy), ::CompressibleNavierStokesDiffusion3D) = ("w2", "w3", "w4", "w5") + + function varnames( + variable_mapping, + equations_parabolic::CompressibleNavierStokesDiffusion3D + ) + varnames(variable_mapping, equations_parabolic.equations_hyperbolic) + end + + # we specialize this function to compute gradients of primitive variables instead of + # conservative variables. + function gradient_variable_transformation(::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive}) + cons2prim + end + function gradient_variable_transformation(::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy}) + cons2entropy + end + + # Explicit formulas for the diffusive Navier-Stokes fluxes are available, e.g., in Section 2 + # of the paper by Rueda-Ramírez, Hennemann, Hindenlang, Winters, and Gassner + # "An Entropy Stable Nodal Discontinuous Galerkin Method for the resistive + # MHD Equations. Part II: Subcell Finite Volume Shock Capturing" + # where one sets the magnetic field components equal to 0. + function flux( + u, gradients, orientation::Integer, + equations::CompressibleNavierStokesDiffusion3D + ) + # Here, `u` is assumed to be the "transformed" variables specified by `gradient_variable_transformation`. + rho, v1, v2, v3, _ = convert_transformed_to_primitive(u, equations) + # Here `gradients` is assumed to contain the gradients of the primitive variables (rho, v1, v2, v3, T) + # either computed directly or reverse engineered from the gradient of the entropy variables + # by way of the `convert_gradient_variables` function. + _, dv1dx, dv2dx, dv3dx, dTdx = convert_derivative_to_primitive( + u, gradients[1], + equations + ) + _, dv1dy, dv2dy, dv3dy, dTdy = convert_derivative_to_primitive( + u, gradients[2], + equations + ) + _, dv1dz, dv2dz, dv3dz, dTdz = convert_derivative_to_primitive( + u, gradients[3], + equations + ) + + # Components of viscous stress tensor + + # Diagonal parts + # (4 * (v1)_x / 3 - 2 * ((v2)_y + (v3)_z)) / 3) + tau_11 = 4 * dv1dx / 3 - 2 * (dv2dy + dv3dz) / 3 + # (4 * (v2)_y / 3 - 2 * ((v1)_x + (v3)_z) / 3) + tau_22 = 4 * dv2dy / 3 - 2 * (dv1dx + dv3dz) / 3 + # (4 * (v3)_z / 3 - 2 * ((v1)_x + (v2)_y) / 3) + tau_33 = 4 * dv3dz / 3 - 2 * (dv1dx + dv2dy) / 3 + + # Off diagonal parts, exploit that stress tensor is symmetric + # ((v1)_y + (v2)_x) + tau_12 = dv1dy + dv2dx # = tau_21 + # ((v1)_z + (v3)_x) + tau_13 = dv1dz + dv3dx # = tau_31 + # ((v2)_z + (v3)_y) + tau_23 = dv2dz + dv3dy # = tau_32 + + # Fick's law q = -kappa * grad(T) = -kappa * grad(p / (R rho)) + # with thermal diffusivity constant kappa = gamma μ R / ((gamma-1) Pr) + # Note, the gas constant cancels under this formulation, so it is not present + # in the implementation + q1 = equations.kappa * dTdx + q2 = equations.kappa * dTdy + q3 = equations.kappa * dTdz + + # In the simplest cases, the user passed in `mu` or `mu()` + # (which returns just a constant) but + # more complex functions like Sutherland's law are possible. + # `dynamic_viscosity` is a helper function that handles both cases + # by dispatching on the type of `equations.mu`. + mu = dynamic_viscosity(u, equations) + + if orientation == 1 + # viscous flux components in the x-direction + f1 = 0 + f2 = tau_11 * mu + f3 = tau_12 * mu + f4 = tau_13 * mu + f5 = (v1 * tau_11 + v2 * tau_12 + v3 * tau_13 + q1) * mu + + return SVector(f1, f2, f3, f4, f5) + elseif orientation == 2 + # viscous flux components in the y-direction + # Note, symmetry is exploited for tau_12 = tau_21 + g1 = 0 + g2 = tau_12 * mu # tau_21 * mu + g3 = tau_22 * mu + g4 = tau_23 * mu + g5 = (v1 * tau_12 + v2 * tau_22 + v3 * tau_23 + q2) * mu + + return SVector(g1, g2, g3, g4, g5) + else # if orientation == 3 + # viscous flux components in the z-direction + # Note, symmetry is exploited for tau_13 = tau_31, tau_23 = tau_32 + h1 = 0 + h2 = tau_13 * mu # tau_31 * mu + h3 = tau_23 * mu # tau_32 * mu + h4 = tau_33 * mu + h5 = (v1 * tau_13 + v2 * tau_23 + v3 * tau_33 + q3) * mu + + return SVector(h1, h2, h3, h4, h5) + end + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion3D) + rho, rho_v1, rho_v2, rho_v3, _ = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + T = temperature(u, equations) + + return SVector(rho, v1, v2, v3, T) + end + + # Convert conservative variables to entropy + # TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms + # This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion2D`, + # but this may be confusing to new users. + function cons2entropy(u, equations::CompressibleNavierStokesDiffusion3D) + cons2entropy(u, equations.equations_hyperbolic) + end + function entropy2cons(w, equations::CompressibleNavierStokesDiffusion3D) + entropy2cons(w, equations.equations_hyperbolic) + end + + # the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables. + # For CNS, it is simplest to formulate the viscous terms in primitive variables, so we transform the transformed + # variables into primitive variables. + @inline function convert_transformed_to_primitive( + u_transformed, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive} + ) + return u_transformed + end + + # TODO: parabolic. Make this more efficient! + @inline function convert_transformed_to_primitive( + u_transformed, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy} + ) + # note: this uses CompressibleNavierStokesDiffusion3D versions of cons2prim and entropy2cons + return cons2prim(entropy2cons(u_transformed, equations), equations) + end + + # Takes the solution values `u` and gradient of the entropy variables (w_2, w_3, w_4, w_5) and + # reverse engineers the gradients to be terms of the primitive variables (v1, v2, v3, T). + # Helpful because then the diffusive fluxes have the same form as on paper. + # Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused. + # TODO: parabolic; entropy stable viscous terms + @inline function convert_derivative_to_primitive( + u, gradient, + ::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive} + ) + return gradient + end + + # the first argument is always the "transformed" variables. + @inline function convert_derivative_to_primitive( + w, gradient_entropy_vars, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy} + ) + + # TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back. + # We can fix this if we directly compute v1, v2, v3, T from the entropy variables + u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion3D + rho, rho_v1, rho_v2, rho_v3, _ = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + T = temperature(u, equations) + + return SVector( + gradient_entropy_vars[1], + T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[5]), # grad(u) = T*(grad(w_2)+v1*grad(w_5)) + T * (gradient_entropy_vars[3] + v2 * gradient_entropy_vars[5]), # grad(v) = T*(grad(w_3)+v2*grad(w_5)) + T * (gradient_entropy_vars[4] + v3 * gradient_entropy_vars[5]), # grad(v) = T*(grad(w_4)+v3*grad(w_5)) + T * T * gradient_entropy_vars[5] + ) + end + + # This routine is required because `prim2cons` is called in `initial_condition`, which + # is called with `equations::CompressibleEulerEquations3D`. This means it is inconsistent + # with `cons2prim(..., ::CompressibleNavierStokesDiffusion3D)` as defined above. + # TODO: parabolic. Is there a way to clean this up? + @inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion3D) + prim2cons(u, equations.equations_hyperbolic) + end + + @inline function temperature(u, equations::CompressibleNavierStokesDiffusion3D) + rho, rho_v1, rho_v2, rho_v3, rho_e = u + + p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho) + T = p / rho + return T + end + + @inline function enstrophy(u, gradients, equations::CompressibleNavierStokesDiffusion3D) + # Enstrophy is 0.5 rho ω⋅ω where ω = ∇ × v + + omega = vorticity(u, gradients, equations) + return 0.5f0 * u[1] * (omega[1]^2 + omega[2]^2 + omega[3]^2) + end + + @inline function vorticity(u, gradients, equations::CompressibleNavierStokesDiffusion3D) + # Ensure that we have velocity `gradients` by way of the `convert_gradient_variables` function. + _, dv1dx, dv2dx, dv3dx, _ = convert_derivative_to_primitive( + u, gradients[1], + equations + ) + _, dv1dy, dv2dy, dv3dy, _ = convert_derivative_to_primitive( + u, gradients[2], + equations + ) + _, dv1dz, dv2dz, dv3dz, _ = convert_derivative_to_primitive( + u, gradients[3], + equations + ) + + return SVector(dv3dy - dv2dz, dv1dz - dv3dx, dv2dx - dv1dy) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + u_inner, + normal::AbstractVector, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive} + ) + v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + return SVector(u_inner[1], v1, v2, v3, u_inner[5]) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + u_inner, + normal::AbstractVector, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive} + ) + # rho, v1, v2, v3, _ = u_inner + normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function( + x, + t, + equations + ) + v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + _, tau_1n, tau_2n, tau_3n, _ = flux_inner # extract fluxes for 2nd, 3rd, and 4th equations + normal_energy_flux = v1 * tau_1n + v2 * tau_2n + v3 * tau_3n + normal_heat_flux + return SVector( + flux_inner[1], flux_inner[2], flux_inner[3], flux_inner[4], + normal_energy_flux + ) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + u_inner, + normal::AbstractVector, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive} + ) + v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + T = boundary_condition.boundary_condition_heat_flux.boundary_value_function( + x, t, + equations + ) + return SVector(u_inner[1], v1, v2, v3, T) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + u_inner, + normal::AbstractVector, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive} + ) + return flux_inner + end + + # specialized BC impositions for GradientVariablesEntropy. + + # This should return a SVector containing the boundary values of entropy variables. + # Here, `w_inner` are the transformed variables (e.g., entropy variables). + # + # Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions + # for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022. + # DOI: 10.1016/j.jcp.2021.110723 + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + w_inner, + normal::AbstractVector, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy} + ) + v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + negative_rho_inv_p = w_inner[5] # w_5 = -rho / p + return SVector( + w_inner[1], -v1 * negative_rho_inv_p, -v2 * negative_rho_inv_p, + -v3 * negative_rho_inv_p, negative_rho_inv_p + ) + end + + # this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness. + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Adiabatic, + } + )( + flux_inner, + w_inner, + normal::AbstractVector, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy} + ) + normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function( + x, + t, + equations + ) + v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + _, tau_1n, tau_2n, tau_3n, _ = flux_inner # extract fluxes for 2nd, 3rd, and 4th equations + normal_energy_flux = v1 * tau_1n + v2 * tau_2n + v3 * tau_3n + normal_heat_flux + return SVector( + flux_inner[1], flux_inner[2], flux_inner[3], flux_inner[4], + normal_energy_flux + ) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + w_inner, + normal::AbstractVector, + x, + t, + operator_type::Gradient, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy} + ) + v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function( + x, + t, + equations + ) + T = boundary_condition.boundary_condition_heat_flux.boundary_value_function( + x, t, + equations + ) + + # the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w5. Similarly for w3 and w4 + w5 = -1 / T + return SVector(w_inner[1], -v1 * w5, -v2 * w5, -v3 * w5, w5) + end + + @inline function ( + boundary_condition::BoundaryConditionNavierStokesWall{ + <:NoSlip, + <:Isothermal, + } + )( + flux_inner, + w_inner, + normal::AbstractVector, + x, + t, + operator_type::Divergence, + equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy} + ) + return SVector( + flux_inner[1], flux_inner[2], flux_inner[3], flux_inner[4], + flux_inner[5] + ) end -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::CompressibleNavierStokesDiffusion3D) - rho, rho_v1, rho_v2, rho_v3, _ = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - T = temperature(u, equations) - - return SVector(rho, v1, v2, v3, T) -end - -# Convert conservative variables to entropy -# TODO: parabolic. We can improve efficiency by not computing w_1, which involves logarithms -# This can be done by specializing `cons2entropy` and `entropy2cons` to `CompressibleNavierStokesDiffusion2D`, -# but this may be confusing to new users. -function cons2entropy(u, equations::CompressibleNavierStokesDiffusion3D) - cons2entropy(u, equations.equations_hyperbolic) -end -function entropy2cons(w, equations::CompressibleNavierStokesDiffusion3D) - entropy2cons(w, equations.equations_hyperbolic) -end - -# the `flux` function takes in transformed variables `u` which depend on the type of the gradient variables. -# For CNS, it is simplest to formulate the viscous terms in primitive variables, so we transform the transformed -# variables into primitive variables. -@inline function convert_transformed_to_primitive(u_transformed, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive}) - return u_transformed -end - -# TODO: parabolic. Make this more efficient! -@inline function convert_transformed_to_primitive(u_transformed, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy}) - # note: this uses CompressibleNavierStokesDiffusion3D versions of cons2prim and entropy2cons - return cons2prim(entropy2cons(u_transformed, equations), equations) -end - -# Takes the solution values `u` and gradient of the entropy variables (w_2, w_3, w_4, w_5) and -# reverse engineers the gradients to be terms of the primitive variables (v1, v2, v3, T). -# Helpful because then the diffusive fluxes have the same form as on paper. -# Note, the first component of `gradient_entropy_vars` contains gradient(rho) which is unused. -# TODO: parabolic; entropy stable viscous terms -@inline function convert_derivative_to_primitive(u, gradient, - ::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive}) - return gradient -end - -# the first argument is always the "transformed" variables. -@inline function convert_derivative_to_primitive(w, gradient_entropy_vars, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy}) - - # TODO: parabolic. This is inefficient to pass in transformed variables but then transform them back. - # We can fix this if we directly compute v1, v2, v3, T from the entropy variables - u = entropy2cons(w, equations) # calls a "modified" entropy2cons defined for CompressibleNavierStokesDiffusion3D - rho, rho_v1, rho_v2, rho_v3, _ = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - T = temperature(u, equations) - - return SVector(gradient_entropy_vars[1], - T * (gradient_entropy_vars[2] + v1 * gradient_entropy_vars[5]), # grad(u) = T*(grad(w_2)+v1*grad(w_5)) - T * (gradient_entropy_vars[3] + v2 * gradient_entropy_vars[5]), # grad(v) = T*(grad(w_3)+v2*grad(w_5)) - T * (gradient_entropy_vars[4] + v3 * gradient_entropy_vars[5]), # grad(v) = T*(grad(w_4)+v3*grad(w_5)) - T * T * gradient_entropy_vars[5]) -end - -# This routine is required because `prim2cons` is called in `initial_condition`, which -# is called with `equations::CompressibleEulerEquations3D`. This means it is inconsistent -# with `cons2prim(..., ::CompressibleNavierStokesDiffusion3D)` as defined above. -# TODO: parabolic. Is there a way to clean this up? -@inline function prim2cons(u, equations::CompressibleNavierStokesDiffusion3D) - prim2cons(u, equations.equations_hyperbolic) -end - -@inline function temperature(u, equations::CompressibleNavierStokesDiffusion3D) - rho, rho_v1, rho_v2, rho_v3, rho_e = u - - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho) - T = p / rho - return T -end - -@inline function enstrophy(u, gradients, equations::CompressibleNavierStokesDiffusion3D) - # Enstrophy is 0.5 rho ω⋅ω where ω = ∇ × v - - omega = vorticity(u, gradients, equations) - return 0.5f0 * u[1] * (omega[1]^2 + omega[2]^2 + omega[3]^2) -end - -@inline function vorticity(u, gradients, equations::CompressibleNavierStokesDiffusion3D) - # Ensure that we have velocity `gradients` by way of the `convert_gradient_variables` function. - _, dv1dx, dv2dx, dv3dx, _ = convert_derivative_to_primitive(u, gradients[1], - equations) - _, dv1dy, dv2dy, dv3dy, _ = convert_derivative_to_primitive(u, gradients[2], - equations) - _, dv1dz, dv2dz, dv3dz, _ = convert_derivative_to_primitive(u, gradients[3], - equations) - - return SVector(dv3dy - dv2dz, dv1dz - dv3dx, dv2dx - dv1dy) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - u_inner, - normal::AbstractVector, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive}) - v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - return SVector(u_inner[1], v1, v2, v3, u_inner[5]) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - u_inner, - normal::AbstractVector, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive}) - # rho, v1, v2, v3, _ = u_inner - normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x, - t, - equations) - v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - _, tau_1n, tau_2n, tau_3n, _ = flux_inner # extract fluxes for 2nd, 3rd, and 4th equations - normal_energy_flux = v1 * tau_1n + v2 * tau_2n + v3 * tau_3n + normal_heat_flux - return SVector(flux_inner[1], flux_inner[2], flux_inner[3], flux_inner[4], - normal_energy_flux) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - u_inner, - normal::AbstractVector, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive}) - v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t, - equations) - return SVector(u_inner[1], v1, v2, v3, T) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - u_inner, - normal::AbstractVector, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesPrimitive}) - return flux_inner -end - -# specialized BC impositions for GradientVariablesEntropy. - -# This should return a SVector containing the boundary values of entropy variables. -# Here, `w_inner` are the transformed variables (e.g., entropy variables). -# -# Taken from "Entropy stable modal discontinuous Galerkin schemes and wall boundary conditions -# for the compressible Navier-Stokes equations" by Chan, Lin, Warburton 2022. -# DOI: 10.1016/j.jcp.2021.110723 -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - w_inner, - normal::AbstractVector, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy}) - v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - negative_rho_inv_p = w_inner[5] # w_5 = -rho / p - return SVector(w_inner[1], -v1 * negative_rho_inv_p, -v2 * negative_rho_inv_p, - -v3 * negative_rho_inv_p, negative_rho_inv_p) -end - -# this is actually identical to the specialization for GradientVariablesPrimitive, but included for completeness. -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Adiabatic})(flux_inner, - w_inner, - normal::AbstractVector, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy}) - normal_heat_flux = boundary_condition.boundary_condition_heat_flux.boundary_value_normal_flux_function(x, - t, - equations) - v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - _, tau_1n, tau_2n, tau_3n, _ = flux_inner # extract fluxes for 2nd, 3rd, and 4th equations - normal_energy_flux = v1 * tau_1n + v2 * tau_2n + v3 * tau_3n + normal_heat_flux - return SVector(flux_inner[1], flux_inner[2], flux_inner[3], flux_inner[4], - normal_energy_flux) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - w_inner, - normal::AbstractVector, - x, - t, - operator_type::Gradient, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy}) - v1, v2, v3 = boundary_condition.boundary_condition_velocity.boundary_value_function(x, - t, - equations) - T = boundary_condition.boundary_condition_heat_flux.boundary_value_function(x, t, - equations) - - # the entropy variables w2 = rho * v1 / p = v1 / T = -v1 * w5. Similarly for w3 and w4 - w5 = -1 / T - return SVector(w_inner[1], -v1 * w5, -v2 * w5, -v3 * w5, w5) -end - -@inline function (boundary_condition::BoundaryConditionNavierStokesWall{<:NoSlip, - <:Isothermal})(flux_inner, - w_inner, - normal::AbstractVector, - x, - t, - operator_type::Divergence, - equations::CompressibleNavierStokesDiffusion3D{GradientVariablesEntropy}) - return SVector(flux_inner[1], flux_inner[2], flux_inner[3], flux_inner[4], - flux_inner[5]) -end end # @muladd diff --git a/src/equations/equations.jl b/src/equations/equations.jl index 7875954c5f..6f58ac786d 100644 --- a/src/equations/equations.jl +++ b/src/equations/equations.jl @@ -3,517 +3,539 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Retrieve number of variables from equation instance -@inline nvariables(::AbstractEquations{NDIMS, NVARS}) where {NDIMS, NVARS} = NVARS - -# TODO: Taal performance, 1:NVARS vs. Base.OneTo(NVARS) vs. SOneTo(NVARS) -""" - eachvariable(equations::AbstractEquations) - -Return an iterator over the indices that specify the location in relevant data structures -for the variables in `equations`. In particular, not the variables themselves are returned. -""" -@inline eachvariable(equations::AbstractEquations) = Base.OneTo(nvariables(equations)) - -""" - get_name(equations::AbstractEquations) - -Returns the canonical, human-readable name for the given system of equations. - -# Examples -```jldoctest -julia> Trixi.get_name(CompressibleEulerEquations1D(1.4)) -"CompressibleEulerEquations1D" -``` -""" -get_name(equations::AbstractEquations) = equations |> typeof |> nameof |> string - -""" - varnames(conversion_function, equations) - -Return the list of variable names when applying `conversion_function` to the -conserved variables associated to `equations`. This function is mainly used -internally to determine output to screen and for IO, e.g., for the -[`AnalysisCallback`](@ref) and the [`SaveSolutionCallback`](@ref). -Common choices of the `conversion_function` are [`cons2cons`](@ref) and -[`cons2prim`](@ref). -""" -function varnames end - -# Return the index of `varname` in `varnames(solution_variables, equations)` if available. -# Otherwise, throw an error. -function get_variable_index(varname, equations; - solution_variables = cons2cons) - index = findfirst(==(varname), varnames(solution_variables, equations)) - if isnothing(index) - throw(ArgumentError("$varname is no valid variable.")) + #! format: noindent + + # Retrieve number of variables from equation instance + @inline nvariables(::AbstractEquations{NDIMS, NVARS}) where {NDIMS, NVARS} = NVARS + + # TODO: Taal performance, 1:NVARS vs. Base.OneTo(NVARS) vs. SOneTo(NVARS) + """ + eachvariable(equations::AbstractEquations) + + Return an iterator over the indices that specify the location in relevant data structures + for the variables in `equations`. In particular, not the variables themselves are returned. + """ + @inline eachvariable(equations::AbstractEquations) = Base.OneTo(nvariables(equations)) + + """ + get_name(equations::AbstractEquations) + + Returns the canonical, human-readable name for the given system of equations. + + # Examples + ```jldoctest + julia> Trixi.get_name(CompressibleEulerEquations1D(1.4)) + "CompressibleEulerEquations1D" + ``` + """ + get_name(equations::AbstractEquations) = equations |> typeof |> nameof |> string + + """ + varnames(conversion_function, equations) + + Return the list of variable names when applying `conversion_function` to the + conserved variables associated to `equations`. This function is mainly used + internally to determine output to screen and for IO, e.g., for the + [`AnalysisCallback`](@ref) and the [`SaveSolutionCallback`](@ref). + Common choices of the `conversion_function` are [`cons2cons`](@ref) and + [`cons2prim`](@ref). + """ + function varnames end + + # Return the index of `varname` in `varnames(solution_variables, equations)` if available. + # Otherwise, throw an error. + function get_variable_index( + varname, equations; + solution_variables = cons2cons + ) + index = findfirst(==(varname), varnames(solution_variables, equations)) + if isnothing(index) + throw(ArgumentError("$varname is no valid variable.")) + end + + return index end - return index -end + # Add methods to show some information on systems of equations. + function Base.show(io::IO, equations::AbstractEquations) + # Since this is not performance-critical, we can use `@nospecialize` to reduce latency. + @nospecialize equations # reduce precompilation time -# Add methods to show some information on systems of equations. -function Base.show(io::IO, equations::AbstractEquations) - # Since this is not performance-critical, we can use `@nospecialize` to reduce latency. - @nospecialize equations # reduce precompilation time + print(io, get_name(equations), " with ") + if nvariables(equations) == 1 + print(io, "one variable") + else + print(io, nvariables(equations), " variables") + end + end - print(io, get_name(equations), " with ") - if nvariables(equations) == 1 - print(io, "one variable") - else - print(io, nvariables(equations), " variables") + function Base.show(io::IO, ::MIME"text/plain", equations::AbstractEquations) + # Since this is not performance-critical, we can use `@nospecialize` to reduce latency. + @nospecialize equations # reduce precompilation time + + if get(io, :compact, false) + show(io, equations) + else + summary_header(io, get_name(equations)) + summary_line(io, "#variables", nvariables(equations)) + for variable in eachvariable(equations) + summary_line( + increment_indent(io), + "variable " * string(variable), + varnames(cons2cons, equations)[variable] + ) + end + summary_footer(io) + end end -end - -function Base.show(io::IO, ::MIME"text/plain", equations::AbstractEquations) - # Since this is not performance-critical, we can use `@nospecialize` to reduce latency. - @nospecialize equations # reduce precompilation time - - if get(io, :compact, false) - show(io, equations) - else - summary_header(io, get_name(equations)) - summary_line(io, "#variables", nvariables(equations)) - for variable in eachvariable(equations) - summary_line(increment_indent(io), - "variable " * string(variable), - varnames(cons2cons, equations)[variable]) + + @inline Base.ndims(::AbstractEquations{NDIMS}) where {NDIMS} = NDIMS + + # Equations act like scalars in broadcasting. + # Using `Ref(equations)` would be more convenient in some circumstances. + # However, this does not work with Julia v1.9.3 correctly due to a (performance) + # bug in Julia, see + # - https://github.com/trixi-framework/Trixi.jl/pull/1618 + # - https://github.com/JuliaLang/julia/issues/51118 + # Thus, we use the workaround below. + Base.broadcastable(equations::AbstractEquations) = (equations,) + + """ + flux(u, orientation_or_normal, equations) + + Given the conservative variables `u`, calculate the (physical) flux in Cartesian + direction `orientation::Integer` or in arbitrary direction `normal::AbstractVector` + for the corresponding set of governing `equations`. + `orientation` is `1`, `2`, and `3` for the x-, y-, and z-directions, respectively. + """ + function flux end + + """ + flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1}) + + Enables calling `flux` with a non-integer argument `normal_direction` for one-dimensional + equations. Returns the value of `flux(u, 1, equations)` scaled by `normal_direction[1]`. + """ + @inline function flux( + u, normal_direction::AbstractVector, + equations::AbstractEquations{1} + ) + # Call `flux` with `orientation::Int = 1` for dispatch. Note that the actual + # `orientation` argument is ignored. + return normal_direction[1] * flux(u, 1, equations) + end + + """ + rotate_to_x(u, normal, equations) + + Apply the rotation that maps `normal` onto the x-axis to the convservative variables `u`. + This is used by [`FluxRotated`](@ref) to calculate the numerical flux of rotationally + invariant equations in arbitrary normal directions. + + See also: [`rotate_from_x`](@ref) + """ + function rotate_to_x end + + """ + rotate_from_x(u, normal, equations) + + Apply the rotation that maps the x-axis onto `normal` to the convservative variables `u`. + This is used by [`FluxRotated`](@ref) to calculate the numerical flux of rotationally + invariant equations in arbitrary normal directions. + + See also: [`rotate_to_x`](@ref) + """ + function rotate_from_x end + + """ + BoundaryConditionDirichlet(boundary_value_function) + + Create a Dirichlet boundary condition that uses the function `boundary_value_function` + to specify the values at the boundary. + This can be used to create a boundary condition that specifies exact boundary values + by passing the exact solution of the equation. + The passed boundary value function will be called with the same arguments as an initial condition function is called, i.e., as + ```julia + boundary_value_function(x, t, equations) + ``` + where `x` specifies the coordinates, `t` is the current time, and `equation` is the corresponding system of equations. + + # Examples + ```julia + julia> BoundaryConditionDirichlet(initial_condition_convergence_test) + ``` + """ + struct BoundaryConditionDirichlet{B} + boundary_value_function::B + end + + # Dirichlet-type boundary condition for use with TreeMesh or StructuredMesh + @inline function (boundary_condition::BoundaryConditionDirichlet)( + u_inner, + orientation_or_normal, + direction, + x, t, + surface_flux_function, + equations + ) + u_boundary = boundary_condition.boundary_value_function(x, t, equations) + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function( + u_inner, u_boundary, orientation_or_normal, + equations + ) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function( + u_boundary, u_inner, orientation_or_normal, + equations + ) end - summary_footer(io) + + return flux + end + + # Dirichlet-type boundary condition for use with UnstructuredMesh2D + # Note: For unstructured we lose the concept of an "absolute direction" + @inline function (boundary_condition::BoundaryConditionDirichlet)( + u_inner, + normal_direction::AbstractVector, + x, t, + surface_flux_function, + equations + ) + # get the external value of the solution + u_boundary = boundary_condition.boundary_value_function(x, t, equations) + + # Calculate boundary flux + flux = surface_flux_function(u_inner, u_boundary, normal_direction, equations) + + return flux + end + + # operator types used for dispatch on parabolic boundary fluxes + struct Gradient end + struct Divergence end + + """ + BoundaryConditionNeumann(boundary_normal_flux_function) + + Similar to `BoundaryConditionDirichlet`, but creates a Neumann boundary condition for parabolic + equations that uses the function `boundary_normal_flux_function` to specify the values of the normal + flux at the boundary. + The passed boundary value function will be called with the same arguments as an initial condition function is called, i.e., as + ```julia + boundary_normal_flux_function(x, t, equations) + ``` + where `x` specifies the coordinates, `t` is the current time, and `equation` is the corresponding system of equations. + """ + struct BoundaryConditionNeumann{B} + boundary_normal_flux_function::B + end + + """ + NonConservativeLocal() + + Struct used for multiple dispatch on non-conservative flux functions in the format of "local * symmetric". + When the argument `nonconservative_type` is of type `NonConservativeLocal`, + the function returns the local part of the non-conservative term. + """ + struct NonConservativeLocal end + + """ + NonConservativeSymmetric() + + Struct used for multiple dispatch on non-conservative flux functions in the format of "local * symmetric". + When the argument `nonconservative_type` is of type `NonConservativeSymmetric`, + the function returns the symmetric part of the non-conservative term. + """ + struct NonConservativeSymmetric end + + # set sensible default values that may be overwritten by specific equations + """ + have_nonconservative_terms(equations) + + Trait function determining whether `equations` represent a conservation law + with or without nonconservative terms. Classical conservation laws such as the + [`CompressibleEulerEquations2D`](@ref) do not have nonconservative terms. The + [`ShallowWaterEquations2D`](@ref) with non-constant bottom topography are an + example of equations with nonconservative terms. + The return value will be `True()` or `False()` to allow dispatching on the return type. + """ + have_nonconservative_terms(::AbstractEquations) = False() + """ + n_nonconservative_terms(equations) + + Number of nonconservative terms in the form local * symmetric for a particular equation. + This function needs to be specialized only if equations with nonconservative terms are + combined with certain solvers (e.g., subcell limiting). + """ + function n_nonconservative_terms end + have_constant_speed(::AbstractEquations) = False() + + """ + default_analysis_errors(equations) + + Default analysis errors (`:l2_error` and `:linf_error`) used by the + [`AnalysisCallback`](@ref). + """ + default_analysis_errors(::AbstractEquations) = (:l2_error, :linf_error) + + """ + default_analysis_integrals(equations) + + Default analysis integrals used by the [`AnalysisCallback`](@ref). + """ + default_analysis_integrals(::AbstractEquations) = (entropy_timederivative,) + + """ + cons2cons(u, equations) + + Return the conserved variables `u`. While this function is as trivial as `identity`, + it is also as useful. + """ + @inline cons2cons(u, ::AbstractEquations) = u + + @inline Base.first(u, ::AbstractEquations) = first(u) + + """ + cons2prim(u, equations) + + Convert the conserved variables `u` to the primitive variables for a given set of + `equations`. `u` is a vector type of the correct length `nvariables(equations)`. + Notice the function doesn't include any error checks for the purpose of efficiency, + so please make sure your input is correct. + The inverse conversion is performed by [`prim2cons`](@ref). + """ + function cons2prim end + + """ + prim2cons(u, equations) + + Convert the primitive variables `u` to the conserved variables for a given set of + `equations`. `u` is a vector type of the correct length `nvariables(equations)`. + Notice the function doesn't include any error checks for the purpose of efficiency, + so please make sure your input is correct. + The inverse conversion is performed by [`cons2prim`](@ref). + """ + function prim2cons end + + """ + entropy(u, equations) + + Return the chosen entropy of the conserved variables `u` for a given set of + `equations`. + + `u` is a vector of the conserved variables at a single node, i.e., a vector + of the correct length `nvariables(equations)`. + """ + function entropy end + + """ + cons2entropy(u, equations) + + Convert the conserved variables `u` to the entropy variables for a given set of + `equations` with chosen standard [`entropy`](@ref). + + `u` is a vector type of the correct length `nvariables(equations)`. + Notice the function doesn't include any error checks for the purpose of efficiency, + so please make sure your input is correct. + The inverse conversion is performed by [`entropy2cons`](@ref). + """ + function cons2entropy end + + """ + entropy2cons(w, equations) + + Convert the entropy variables `w` based on a standard [`entropy`](@ref) to the + conserved variables for a given set of `equations`. + `u` is a vector type of the correct length `nvariables(equations)`. + Notice the function doesn't include any error checks for the purpose of efficiency, + so please make sure your input is correct. + The inverse conversion is performed by [`cons2entropy`](@ref). + """ + function entropy2cons end + + """ + energy_total(u, equations) + + Return the total energy of the conserved variables `u` for a given set of + `equations`, e.g., the [`CompressibleEulerEquations2D`](@ref). + + `u` is a vector of the conserved variables at a single node, i.e., a vector + of the correct length `nvariables(equations)`. + """ + function energy_total end + + """ + energy_kinetic(u, equations) + + Return the kinetic energy of the conserved variables `u` for a given set of + `equations`, e.g., the [`CompressibleEulerEquations2D`](@ref). + + `u` is a vector of the conserved variables at a single node, i.e., a vector + of the correct length `nvariables(equations)`. + """ + function energy_kinetic end + + """ + energy_internal(u, equations) + + Return the internal energy of the conserved variables `u` for a given set of + `equations`, e.g., the [`CompressibleEulerEquations2D`](@ref). + + `u` is a vector of the conserved variables at a single node, i.e., a vector + of the correct length `nvariables(equations)`. + """ + function energy_internal end + + # Default implementation of gradient for `variable`. Used for subcell limiting. + # Implementing a gradient function for a specific variable improves the performance. + @inline function gradient_conservative(variable, u, equations) + return ForwardDiff.gradient(x -> variable(x, equations), u) + end + + #################################################################################################### + # Include files with actual implementations for different systems of equations. + + # Numerical flux formulations that are independent of the specific system of equations + include("numerical_fluxes.jl") + + # Linear scalar advection + abstract type AbstractLinearScalarAdvectionEquation{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("linear_scalar_advection_1d.jl") + include("linear_scalar_advection_2d.jl") + include("linear_scalar_advection_3d.jl") + + # Inviscid Burgers + abstract type AbstractInviscidBurgersEquation{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("inviscid_burgers_1d.jl") + + # Shallow water equations + abstract type AbstractShallowWaterEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("shallow_water_1d.jl") + include("shallow_water_2d.jl") + include("shallow_water_quasi_1d.jl") + + # CompressibleEulerEquations + abstract type AbstractCompressibleEulerEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("compressible_euler_1d.jl") + include("compressible_euler_2d.jl") + include("compressible_euler_3d.jl") + include("compressible_euler_quasi_1d.jl") + + # CompressibleEulerMulticomponentEquations + abstract type AbstractCompressibleEulerMulticomponentEquations{NDIMS, NVARS, NCOMP} <: + AbstractEquations{NDIMS, NVARS} end + include("compressible_euler_multicomponent_1d.jl") + include("compressible_euler_multicomponent_2d.jl") + + # PolytropicEulerEquations + abstract type AbstractPolytropicEulerEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("polytropic_euler_2d.jl") + + # Retrieve number of components from equation instance for the multicomponent case + @inline function ncomponents( + ::AbstractCompressibleEulerMulticomponentEquations{ + NDIMS, + NVARS, + NCOMP, + } + ) where { + NDIMS, + NVARS, + NCOMP, + } + NCOMP + end + """ + eachcomponent(equations::AbstractCompressibleEulerMulticomponentEquations) + + Return an iterator over the indices that specify the location in relevant data structures + for the components in `AbstractCompressibleEulerMulticomponentEquations`. + In particular, not the components themselves are returned. + """ + @inline function eachcomponent(equations::AbstractCompressibleEulerMulticomponentEquations) + Base.OneTo(ncomponents(equations)) + end + + # Ideal MHD + abstract type AbstractIdealGlmMhdEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("ideal_glm_mhd_1d.jl") + include("ideal_glm_mhd_2d.jl") + include("ideal_glm_mhd_3d.jl") + + # IdealGlmMhdMulticomponentEquations + abstract type AbstractIdealGlmMhdMulticomponentEquations{NDIMS, NVARS, NCOMP} <: + AbstractEquations{NDIMS, NVARS} end + include("ideal_glm_mhd_multicomponent_1d.jl") + include("ideal_glm_mhd_multicomponent_2d.jl") + + # Retrieve number of components from equation instance for the multicomponent case + @inline function ncomponents( + ::AbstractIdealGlmMhdMulticomponentEquations{ + NDIMS, NVARS, + NCOMP, + } + ) where { + NDIMS, + NVARS, + NCOMP, + } + NCOMP end -end - -@inline Base.ndims(::AbstractEquations{NDIMS}) where {NDIMS} = NDIMS - -# Equations act like scalars in broadcasting. -# Using `Ref(equations)` would be more convenient in some circumstances. -# However, this does not work with Julia v1.9.3 correctly due to a (performance) -# bug in Julia, see -# - https://github.com/trixi-framework/Trixi.jl/pull/1618 -# - https://github.com/JuliaLang/julia/issues/51118 -# Thus, we use the workaround below. -Base.broadcastable(equations::AbstractEquations) = (equations,) - -""" - flux(u, orientation_or_normal, equations) - -Given the conservative variables `u`, calculate the (physical) flux in Cartesian -direction `orientation::Integer` or in arbitrary direction `normal::AbstractVector` -for the corresponding set of governing `equations`. -`orientation` is `1`, `2`, and `3` for the x-, y-, and z-directions, respectively. -""" -function flux end - -""" - flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1}) - -Enables calling `flux` with a non-integer argument `normal_direction` for one-dimensional -equations. Returns the value of `flux(u, 1, equations)` scaled by `normal_direction[1]`. -""" -@inline function flux(u, normal_direction::AbstractVector, - equations::AbstractEquations{1}) - # Call `flux` with `orientation::Int = 1` for dispatch. Note that the actual - # `orientation` argument is ignored. - return normal_direction[1] * flux(u, 1, equations) -end - -""" - rotate_to_x(u, normal, equations) - -Apply the rotation that maps `normal` onto the x-axis to the convservative variables `u`. -This is used by [`FluxRotated`](@ref) to calculate the numerical flux of rotationally -invariant equations in arbitrary normal directions. - -See also: [`rotate_from_x`](@ref) -""" -function rotate_to_x end - -""" - rotate_from_x(u, normal, equations) - -Apply the rotation that maps the x-axis onto `normal` to the convservative variables `u`. -This is used by [`FluxRotated`](@ref) to calculate the numerical flux of rotationally -invariant equations in arbitrary normal directions. - -See also: [`rotate_to_x`](@ref) -""" -function rotate_from_x end - -""" - BoundaryConditionDirichlet(boundary_value_function) - -Create a Dirichlet boundary condition that uses the function `boundary_value_function` -to specify the values at the boundary. -This can be used to create a boundary condition that specifies exact boundary values -by passing the exact solution of the equation. -The passed boundary value function will be called with the same arguments as an initial condition function is called, i.e., as -```julia -boundary_value_function(x, t, equations) -``` -where `x` specifies the coordinates, `t` is the current time, and `equation` is the corresponding system of equations. - -# Examples -```julia -julia> BoundaryConditionDirichlet(initial_condition_convergence_test) -``` -""" -struct BoundaryConditionDirichlet{B} - boundary_value_function::B -end - -# Dirichlet-type boundary condition for use with TreeMesh or StructuredMesh -@inline function (boundary_condition::BoundaryConditionDirichlet)(u_inner, - orientation_or_normal, - direction, - x, t, - surface_flux_function, - equations) - u_boundary = boundary_condition.boundary_value_function(x, t, equations) - - # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation_or_normal, - equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation_or_normal, - equations) + """ + eachcomponent(equations::AbstractIdealGlmMhdMulticomponentEquations) + + Return an iterator over the indices that specify the location in relevant data structures + for the components in `AbstractIdealGlmMhdMulticomponentEquations`. + In particular, not the components themselves are returned. + """ + @inline function eachcomponent(equations::AbstractIdealGlmMhdMulticomponentEquations) + Base.OneTo(ncomponents(equations)) end - return flux -end - -# Dirichlet-type boundary condition for use with UnstructuredMesh2D -# Note: For unstructured we lose the concept of an "absolute direction" -@inline function (boundary_condition::BoundaryConditionDirichlet)(u_inner, - normal_direction::AbstractVector, - x, t, - surface_flux_function, - equations) - # get the external value of the solution - u_boundary = boundary_condition.boundary_value_function(x, t, equations) - - # Calculate boundary flux - flux = surface_flux_function(u_inner, u_boundary, normal_direction, equations) - - return flux -end - -# operator types used for dispatch on parabolic boundary fluxes -struct Gradient end -struct Divergence end - -""" - BoundaryConditionNeumann(boundary_normal_flux_function) - -Similar to `BoundaryConditionDirichlet`, but creates a Neumann boundary condition for parabolic -equations that uses the function `boundary_normal_flux_function` to specify the values of the normal -flux at the boundary. -The passed boundary value function will be called with the same arguments as an initial condition function is called, i.e., as -```julia -boundary_normal_flux_function(x, t, equations) -``` -where `x` specifies the coordinates, `t` is the current time, and `equation` is the corresponding system of equations. -""" -struct BoundaryConditionNeumann{B} - boundary_normal_flux_function::B -end - -""" - NonConservativeLocal() - -Struct used for multiple dispatch on non-conservative flux functions in the format of "local * symmetric". -When the argument `nonconservative_type` is of type `NonConservativeLocal`, -the function returns the local part of the non-conservative term. -""" -struct NonConservativeLocal end - -""" - NonConservativeSymmetric() - -Struct used for multiple dispatch on non-conservative flux functions in the format of "local * symmetric". -When the argument `nonconservative_type` is of type `NonConservativeSymmetric`, -the function returns the symmetric part of the non-conservative term. -""" -struct NonConservativeSymmetric end - -# set sensible default values that may be overwritten by specific equations -""" - have_nonconservative_terms(equations) - -Trait function determining whether `equations` represent a conservation law -with or without nonconservative terms. Classical conservation laws such as the -[`CompressibleEulerEquations2D`](@ref) do not have nonconservative terms. The -[`ShallowWaterEquations2D`](@ref) with non-constant bottom topography are an -example of equations with nonconservative terms. -The return value will be `True()` or `False()` to allow dispatching on the return type. -""" -have_nonconservative_terms(::AbstractEquations) = False() -""" - n_nonconservative_terms(equations) - -Number of nonconservative terms in the form local * symmetric for a particular equation. -This function needs to be specialized only if equations with nonconservative terms are -combined with certain solvers (e.g., subcell limiting). -""" -function n_nonconservative_terms end -have_constant_speed(::AbstractEquations) = False() - -""" - default_analysis_errors(equations) - -Default analysis errors (`:l2_error` and `:linf_error`) used by the -[`AnalysisCallback`](@ref). -""" -default_analysis_errors(::AbstractEquations) = (:l2_error, :linf_error) - -""" - default_analysis_integrals(equations) - -Default analysis integrals used by the [`AnalysisCallback`](@ref). -""" -default_analysis_integrals(::AbstractEquations) = (entropy_timederivative,) - -""" - cons2cons(u, equations) - -Return the conserved variables `u`. While this function is as trivial as `identity`, -it is also as useful. -""" -@inline cons2cons(u, ::AbstractEquations) = u - -@inline Base.first(u, ::AbstractEquations) = first(u) - -""" - cons2prim(u, equations) - -Convert the conserved variables `u` to the primitive variables for a given set of -`equations`. `u` is a vector type of the correct length `nvariables(equations)`. -Notice the function doesn't include any error checks for the purpose of efficiency, -so please make sure your input is correct. -The inverse conversion is performed by [`prim2cons`](@ref). -""" -function cons2prim end - -""" - prim2cons(u, equations) - -Convert the primitive variables `u` to the conserved variables for a given set of -`equations`. `u` is a vector type of the correct length `nvariables(equations)`. -Notice the function doesn't include any error checks for the purpose of efficiency, -so please make sure your input is correct. -The inverse conversion is performed by [`cons2prim`](@ref). -""" -function prim2cons end - -""" - entropy(u, equations) - -Return the chosen entropy of the conserved variables `u` for a given set of -`equations`. - -`u` is a vector of the conserved variables at a single node, i.e., a vector -of the correct length `nvariables(equations)`. -""" -function entropy end - -""" - cons2entropy(u, equations) - -Convert the conserved variables `u` to the entropy variables for a given set of -`equations` with chosen standard [`entropy`](@ref). - -`u` is a vector type of the correct length `nvariables(equations)`. -Notice the function doesn't include any error checks for the purpose of efficiency, -so please make sure your input is correct. -The inverse conversion is performed by [`entropy2cons`](@ref). -""" -function cons2entropy end - -""" - entropy2cons(w, equations) - -Convert the entropy variables `w` based on a standard [`entropy`](@ref) to the -conserved variables for a given set of `equations`. -`u` is a vector type of the correct length `nvariables(equations)`. -Notice the function doesn't include any error checks for the purpose of efficiency, -so please make sure your input is correct. -The inverse conversion is performed by [`cons2entropy`](@ref). -""" -function entropy2cons end - -""" - energy_total(u, equations) - -Return the total energy of the conserved variables `u` for a given set of -`equations`, e.g., the [`CompressibleEulerEquations2D`](@ref). - -`u` is a vector of the conserved variables at a single node, i.e., a vector -of the correct length `nvariables(equations)`. -""" -function energy_total end - -""" - energy_kinetic(u, equations) - -Return the kinetic energy of the conserved variables `u` for a given set of -`equations`, e.g., the [`CompressibleEulerEquations2D`](@ref). - -`u` is a vector of the conserved variables at a single node, i.e., a vector -of the correct length `nvariables(equations)`. -""" -function energy_kinetic end - -""" - energy_internal(u, equations) - -Return the internal energy of the conserved variables `u` for a given set of -`equations`, e.g., the [`CompressibleEulerEquations2D`](@ref). - -`u` is a vector of the conserved variables at a single node, i.e., a vector -of the correct length `nvariables(equations)`. -""" -function energy_internal end - -# Default implementation of gradient for `variable`. Used for subcell limiting. -# Implementing a gradient function for a specific variable improves the performance. -@inline function gradient_conservative(variable, u, equations) - return ForwardDiff.gradient(x -> variable(x, equations), u) -end - -#################################################################################################### -# Include files with actual implementations for different systems of equations. - -# Numerical flux formulations that are independent of the specific system of equations -include("numerical_fluxes.jl") - -# Linear scalar advection -abstract type AbstractLinearScalarAdvectionEquation{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("linear_scalar_advection_1d.jl") -include("linear_scalar_advection_2d.jl") -include("linear_scalar_advection_3d.jl") - -# Inviscid Burgers -abstract type AbstractInviscidBurgersEquation{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("inviscid_burgers_1d.jl") - -# Shallow water equations -abstract type AbstractShallowWaterEquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("shallow_water_1d.jl") -include("shallow_water_2d.jl") -include("shallow_water_quasi_1d.jl") - -# CompressibleEulerEquations -abstract type AbstractCompressibleEulerEquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("compressible_euler_1d.jl") -include("compressible_euler_2d.jl") -include("compressible_euler_3d.jl") -include("compressible_euler_quasi_1d.jl") - -# CompressibleEulerMulticomponentEquations -abstract type AbstractCompressibleEulerMulticomponentEquations{NDIMS, NVARS, NCOMP} <: - AbstractEquations{NDIMS, NVARS} end -include("compressible_euler_multicomponent_1d.jl") -include("compressible_euler_multicomponent_2d.jl") - -# PolytropicEulerEquations -abstract type AbstractPolytropicEulerEquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("polytropic_euler_2d.jl") - -# Retrieve number of components from equation instance for the multicomponent case -@inline function ncomponents(::AbstractCompressibleEulerMulticomponentEquations{NDIMS, - NVARS, - NCOMP}) where { - NDIMS, - NVARS, - NCOMP - } - NCOMP -end -""" - eachcomponent(equations::AbstractCompressibleEulerMulticomponentEquations) - -Return an iterator over the indices that specify the location in relevant data structures -for the components in `AbstractCompressibleEulerMulticomponentEquations`. -In particular, not the components themselves are returned. -""" -@inline function eachcomponent(equations::AbstractCompressibleEulerMulticomponentEquations) - Base.OneTo(ncomponents(equations)) -end - -# Ideal MHD -abstract type AbstractIdealGlmMhdEquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("ideal_glm_mhd_1d.jl") -include("ideal_glm_mhd_2d.jl") -include("ideal_glm_mhd_3d.jl") - -# IdealGlmMhdMulticomponentEquations -abstract type AbstractIdealGlmMhdMulticomponentEquations{NDIMS, NVARS, NCOMP} <: - AbstractEquations{NDIMS, NVARS} end -include("ideal_glm_mhd_multicomponent_1d.jl") -include("ideal_glm_mhd_multicomponent_2d.jl") - -# Retrieve number of components from equation instance for the multicomponent case -@inline function ncomponents(::AbstractIdealGlmMhdMulticomponentEquations{NDIMS, NVARS, - NCOMP}) where { - NDIMS, - NVARS, - NCOMP - } - NCOMP -end -""" - eachcomponent(equations::AbstractIdealGlmMhdMulticomponentEquations) - -Return an iterator over the indices that specify the location in relevant data structures -for the components in `AbstractIdealGlmMhdMulticomponentEquations`. -In particular, not the components themselves are returned. -""" -@inline function eachcomponent(equations::AbstractIdealGlmMhdMulticomponentEquations) - Base.OneTo(ncomponents(equations)) -end - -# Diffusion equation: first order hyperbolic system -abstract type AbstractHyperbolicDiffusionEquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("hyperbolic_diffusion_1d.jl") -include("hyperbolic_diffusion_2d.jl") -include("hyperbolic_diffusion_3d.jl") - -# Lattice-Boltzmann equation (advection part only) -abstract type AbstractLatticeBoltzmannEquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("lattice_boltzmann_2d.jl") -include("lattice_boltzmann_3d.jl") - -# Acoustic perturbation equations -abstract type AbstractAcousticPerturbationEquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("acoustic_perturbation_2d.jl") - -# Linearized Euler equations -abstract type AbstractLinearizedEulerEquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("linearized_euler_1d.jl") -include("linearized_euler_2d.jl") -include("linearized_euler_3d.jl") - -abstract type AbstractEquationsParabolic{NDIMS, NVARS, GradientVariables} <: - AbstractEquations{NDIMS, NVARS} end - -# Lighthill-Witham-Richards (LWR) traffic flow model -abstract type AbstractTrafficFlowLWREquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("traffic_flow_lwr_1d.jl") - -abstract type AbstractMaxwellEquations{NDIMS, NVARS} <: - AbstractEquations{NDIMS, NVARS} end -include("maxwell_1d.jl") + # Diffusion equation: first order hyperbolic system + abstract type AbstractHyperbolicDiffusionEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("hyperbolic_diffusion_1d.jl") + include("hyperbolic_diffusion_2d.jl") + include("hyperbolic_diffusion_3d.jl") + + # Lattice-Boltzmann equation (advection part only) + abstract type AbstractLatticeBoltzmannEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("lattice_boltzmann_2d.jl") + include("lattice_boltzmann_3d.jl") + + # Acoustic perturbation equations + abstract type AbstractAcousticPerturbationEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("acoustic_perturbation_2d.jl") + + # Linearized Euler equations + abstract type AbstractLinearizedEulerEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("linearized_euler_1d.jl") + include("linearized_euler_2d.jl") + include("linearized_euler_3d.jl") + + abstract type AbstractEquationsParabolic{NDIMS, NVARS, GradientVariables} <: + AbstractEquations{NDIMS, NVARS} end + + # Lighthill-Witham-Richards (LWR) traffic flow model + abstract type AbstractTrafficFlowLWREquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("traffic_flow_lwr_1d.jl") + + abstract type AbstractMaxwellEquations{NDIMS, NVARS} <: + AbstractEquations{NDIMS, NVARS} end + include("maxwell_1d.jl") end # @muladd diff --git a/src/equations/equations_parabolic.jl b/src/equations/equations_parabolic.jl index a063e9f275..6554e06e82 100644 --- a/src/equations/equations_parabolic.jl +++ b/src/equations/equations_parabolic.jl @@ -9,14 +9,14 @@ struct GradientVariablesConservative end # Linear scalar diffusion for use in linear scalar advection-diffusion problems abstract type AbstractLaplaceDiffusion{NDIMS, NVARS} <: - AbstractEquationsParabolic{NDIMS, NVARS, GradientVariablesConservative} end +AbstractEquationsParabolic{NDIMS, NVARS, GradientVariablesConservative} end include("laplace_diffusion_1d.jl") include("laplace_diffusion_2d.jl") include("laplace_diffusion_3d.jl") # Compressible Navier-Stokes equations abstract type AbstractCompressibleNavierStokesDiffusion{NDIMS, NVARS, GradientVariables} <: - AbstractEquationsParabolic{NDIMS, NVARS, GradientVariables} end +AbstractEquationsParabolic{NDIMS, NVARS, GradientVariables} end include("compressible_navier_stokes.jl") include("compressible_navier_stokes_1d.jl") include("compressible_navier_stokes_2d.jl") diff --git a/src/equations/hyperbolic_diffusion_1d.jl b/src/equations/hyperbolic_diffusion_1d.jl index 2b774470fe..18a5e779f1 100644 --- a/src/equations/hyperbolic_diffusion_1d.jl +++ b/src/equations/hyperbolic_diffusion_1d.jl @@ -3,203 +3,217 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - HyperbolicDiffusionEquations1D - -The linear hyperbolic diffusion equations in one space dimension. -A description of this system can be found in Sec. 2.5 of the book -- Masatsuka (2013) - I Do Like CFD, Too: Vol 1. - Freely available at [http://www.cfdbooks.com/](http://www.cfdbooks.com/) -Further analysis can be found in the paper -- Nishikawa (2007) - A first-order system approach for diffusion equation. I: Second-order residual-distribution - schemes - [DOI: 10.1016/j.jcp.2007.07.029](https://doi.org/10.1016/j.jcp.2007.07.029) -""" -struct HyperbolicDiffusionEquations1D{RealT <: Real} <: - AbstractHyperbolicDiffusionEquations{1, 2} - Lr::RealT # reference length scale - inv_Tr::RealT # inverse of the reference time scale - nu::RealT # diffusion constant -end - -function HyperbolicDiffusionEquations1D(; nu = 1.0, Lr = inv(2pi)) - Tr = Lr^2 / nu - HyperbolicDiffusionEquations1D(promote(Lr, inv(Tr), nu)...) -end - -varnames(::typeof(cons2cons), ::HyperbolicDiffusionEquations1D) = ("phi", "q1") -varnames(::typeof(cons2prim), ::HyperbolicDiffusionEquations1D) = ("phi", "q1") -function default_analysis_errors(::HyperbolicDiffusionEquations1D) - (:l2_error, :linf_error, :residual) -end - -@inline function residual_steady_state(du, ::HyperbolicDiffusionEquations1D) - abs(du[1]) -end - -""" - initial_condition_poisson_nonperiodic(x, t, equations::HyperbolicDiffusionEquations1D) - -A non-priodic smooth initial condition. Can be used for convergence tests in combination with -[`source_terms_poisson_nonperiodic`](@ref) and [`boundary_condition_poisson_nonperiodic`](@ref). -!!! note - The solution is periodic but the initial guess is not. -""" -function initial_condition_poisson_nonperiodic(x, t, + #! format: noindent + + @doc raw""" + HyperbolicDiffusionEquations1D + + The linear hyperbolic diffusion equations in one space dimension. + A description of this system can be found in Sec. 2.5 of the book + - Masatsuka (2013) + I Do Like CFD, Too: Vol 1. + Freely available at [http://www.cfdbooks.com/](http://www.cfdbooks.com/) + Further analysis can be found in the paper + - Nishikawa (2007) + A first-order system approach for diffusion equation. I: Second-order residual-distribution + schemes + [DOI: 10.1016/j.jcp.2007.07.029](https://doi.org/10.1016/j.jcp.2007.07.029) + """ + struct HyperbolicDiffusionEquations1D{RealT <: Real} <: + AbstractHyperbolicDiffusionEquations{1, 2} + Lr::RealT # reference length scale + inv_Tr::RealT # inverse of the reference time scale + nu::RealT # diffusion constant + end + + function HyperbolicDiffusionEquations1D(; nu = 1.0, Lr = inv(2pi)) + Tr = Lr^2 / nu + HyperbolicDiffusionEquations1D(promote(Lr, inv(Tr), nu)...) + end + + varnames(::typeof(cons2cons), ::HyperbolicDiffusionEquations1D) = ("phi", "q1") + varnames(::typeof(cons2prim), ::HyperbolicDiffusionEquations1D) = ("phi", "q1") + function default_analysis_errors(::HyperbolicDiffusionEquations1D) + (:l2_error, :linf_error, :residual) + end + + @inline function residual_steady_state(du, ::HyperbolicDiffusionEquations1D) + abs(du[1]) + end + + """ + initial_condition_poisson_nonperiodic(x, t, equations::HyperbolicDiffusionEquations1D) + + A non-priodic smooth initial condition. Can be used for convergence tests in combination with + [`source_terms_poisson_nonperiodic`](@ref) and [`boundary_condition_poisson_nonperiodic`](@ref). + !!! note + The solution is periodic but the initial guess is not. + """ + function initial_condition_poisson_nonperiodic( + x, t, + equations::HyperbolicDiffusionEquations1D + ) + # elliptic equation: -νΔϕ = f + # Taken from Section 6.1 of Nishikawa https://doi.org/10.1016/j.jcp.2007.07.029 + RealT = eltype(x) + if t == 0 + # initial "guess" of the solution and its derivative + phi = x[1]^2 - x[1] + q1 = 2 * x[1] - 1 + else + phi = sinpi(x[1]) # ϕ + q1 = convert(RealT, pi) * cospi(x[1]) # ϕ_x + end + return SVector(phi, q1) + end + + """ + source_terms_poisson_nonperiodic(u, x, t, + equations::HyperbolicDiffusionEquations1D) + + Source terms that include the forcing function `f(x)` and right hand side for the hyperbolic + diffusion system that is used with [`initial_condition_poisson_nonperiodic`](@ref) and + [`boundary_condition_poisson_nonperiodic`](@ref). + """ + @inline function source_terms_poisson_nonperiodic( + u, x, t, + equations::HyperbolicDiffusionEquations1D + ) + # elliptic equation: -νΔϕ = f + # analytical solution: ϕ = sin(πx) and f = π^2sin(πx) + RealT = eltype(u) + @unpack inv_Tr = equations + + dphi = convert(RealT, pi)^2 * sinpi(x[1]) + dq1 = -inv_Tr * u[2] + + return SVector(dphi, dq1) + end + + """ + boundary_condition_poisson_nonperiodic(u_inner, orientation, direction, x, t, + surface_flux_function, equations::HyperbolicDiffusionEquations1D) - # elliptic equation: -νΔϕ = f - # Taken from Section 6.1 of Nishikawa https://doi.org/10.1016/j.jcp.2007.07.029 - RealT = eltype(x) - if t == 0 - # initial "guess" of the solution and its derivative - phi = x[1]^2 - x[1] - q1 = 2 * x[1] - 1 - else + + Boundary conditions used for convergence tests in combination with + [`initial_condition_poisson_nonperiodic`](@ref) and [`source_terms_poisson_nonperiodic`](@ref). + """ + function boundary_condition_poisson_nonperiodic( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::HyperbolicDiffusionEquations1D + ) + # elliptic equation: -νΔϕ = f + RealT = eltype(u_inner) phi = sinpi(x[1]) # ϕ q1 = convert(RealT, pi) * cospi(x[1]) # ϕ_x + u_boundary = SVector(phi, q1) + + # Calculate boundary flux + if direction == 2 # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end + + """ + source_terms_harmonic(u, x, t, equations::HyperbolicDiffusionEquations1D) + + Source term that only includes the forcing from the hyperbolic diffusion system. + """ + @inline function source_terms_harmonic( + u, x, t, + equations::HyperbolicDiffusionEquations1D + ) + # harmonic solution of the form ϕ = A + B * x, so f = 0 + @unpack inv_Tr = equations + + dq1 = -inv_Tr * u[2] + + return SVector(0, dq1) + end + + """ + initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::HyperbolicDiffusionEquations1D) + + Setup used for convergence tests of the Euler equations with self-gravity used in + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + in combination with [`source_terms_harmonic`](@ref). + """ + function initial_condition_eoc_test_coupled_euler_gravity( + x, t, + equations::HyperbolicDiffusionEquations1D + ) + + # Determine phi_x + RealT = eltype(x) + G = 1 # gravitational constant + C = -4 * G / convert(RealT, pi) # -4 * G / ndims * pi + A = convert(RealT, 0.1) # perturbation coefficient must match Euler setup + rho1 = A * sinpi(x[1] - t) + # initialize with ansatz of gravity potential + phi = C * rho1 + q1 = C * A * convert(RealT, pi) * cospi(x[1] - t) # = gravity acceleration in x-direction + + return SVector(phi, q1) + end + + # Calculate 1D flux in for a single point + @inline function flux( + u, orientation::Integer, + equations::HyperbolicDiffusionEquations1D + ) + phi, q1 = u + @unpack inv_Tr = equations + + # Ignore orientation since it is always "1" in 1D + f1 = -equations.nu * q1 + f2 = -phi * inv_Tr + + return SVector(f1, f2) end - return SVector(phi, q1) -end - -""" - source_terms_poisson_nonperiodic(u, x, t, - equations::HyperbolicDiffusionEquations1D) - -Source terms that include the forcing function `f(x)` and right hand side for the hyperbolic -diffusion system that is used with [`initial_condition_poisson_nonperiodic`](@ref) and -[`boundary_condition_poisson_nonperiodic`](@ref). -""" -@inline function source_terms_poisson_nonperiodic(u, x, t, - equations::HyperbolicDiffusionEquations1D) - # elliptic equation: -νΔϕ = f - # analytical solution: ϕ = sin(πx) and f = π^2sin(πx) - RealT = eltype(u) - @unpack inv_Tr = equations - - dphi = convert(RealT, pi)^2 * sinpi(x[1]) - dq1 = -inv_Tr * u[2] - - return SVector(dphi, dq1) -end - -""" - boundary_condition_poisson_nonperiodic(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::HyperbolicDiffusionEquations1D) - -Boundary conditions used for convergence tests in combination with -[`initial_condition_poisson_nonperiodic`](@ref) and [`source_terms_poisson_nonperiodic`](@ref). -""" -function boundary_condition_poisson_nonperiodic(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::HyperbolicDiffusionEquations1D) - # elliptic equation: -νΔϕ = f - RealT = eltype(u_inner) - phi = sinpi(x[1]) # ϕ - q1 = convert(RealT, pi) * cospi(x[1]) # ϕ_x - u_boundary = SVector(phi, q1) - - # Calculate boundary flux - if direction == 2 # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::HyperbolicDiffusionEquations1D + ) + λ_max = sqrt(equations.nu * equations.inv_Tr) end - return flux -end - -""" - source_terms_harmonic(u, x, t, equations::HyperbolicDiffusionEquations1D) - -Source term that only includes the forcing from the hyperbolic diffusion system. -""" -@inline function source_terms_harmonic(u, x, t, - equations::HyperbolicDiffusionEquations1D) - # harmonic solution of the form ϕ = A + B * x, so f = 0 - @unpack inv_Tr = equations - - dq1 = -inv_Tr * u[2] - - return SVector(0, dq1) -end - -""" - initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::HyperbolicDiffusionEquations1D) - -Setup used for convergence tests of the Euler equations with self-gravity used in -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -in combination with [`source_terms_harmonic`](@ref). -""" -function initial_condition_eoc_test_coupled_euler_gravity(x, t, - equations::HyperbolicDiffusionEquations1D) - - # Determine phi_x - RealT = eltype(x) - G = 1 # gravitational constant - C = -4 * G / convert(RealT, pi) # -4 * G / ndims * pi - A = convert(RealT, 0.1) # perturbation coefficient must match Euler setup - rho1 = A * sinpi(x[1] - t) - # initialize with ansatz of gravity potential - phi = C * rho1 - q1 = C * A * convert(RealT, pi) * cospi(x[1] - t) # = gravity acceleration in x-direction - - return SVector(phi, q1) -end - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, - equations::HyperbolicDiffusionEquations1D) - phi, q1 = u - @unpack inv_Tr = equations - - # Ignore orientation since it is always "1" in 1D - f1 = -equations.nu * q1 - f2 = -phi * inv_Tr - - return SVector(f1, f2) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::HyperbolicDiffusionEquations1D) - λ_max = sqrt(equations.nu * equations.inv_Tr) -end - -@inline have_constant_speed(::HyperbolicDiffusionEquations1D) = True() - -@inline function max_abs_speeds(eq::HyperbolicDiffusionEquations1D) - return sqrt(eq.nu * eq.inv_Tr) -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equations::HyperbolicDiffusionEquations1D) = u - -# Convert conservative variables to entropy found in I Do Like CFD, Too, Vol. 1 -@inline function cons2entropy(u, equations::HyperbolicDiffusionEquations1D) - phi, q1 = u - - w1 = phi - w2 = equations.Lr^2 * q1 - - return SVector(w1, w2) -end - -# Calculate entropy for a conservative state `u` (here: same as total energy) -@inline function entropy(u, equations::HyperbolicDiffusionEquations1D) - energy_total(u, equations) -end - -# Calculate total energy for a conservative state `u` -@inline function energy_total(u, equations::HyperbolicDiffusionEquations1D) - # energy function as found in equations (2.5.12) in the book "I Do Like CFD, Vol. 1" - phi, q1 = u - return 0.5f0 * (phi^2 + equations.Lr^2 * q1^2) -end + @inline have_constant_speed(::HyperbolicDiffusionEquations1D) = True() + + @inline function max_abs_speeds(eq::HyperbolicDiffusionEquations1D) + return sqrt(eq.nu * eq.inv_Tr) + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equations::HyperbolicDiffusionEquations1D) = u + + # Convert conservative variables to entropy found in I Do Like CFD, Too, Vol. 1 + @inline function cons2entropy(u, equations::HyperbolicDiffusionEquations1D) + phi, q1 = u + + w1 = phi + w2 = equations.Lr^2 * q1 + + return SVector(w1, w2) + end + + # Calculate entropy for a conservative state `u` (here: same as total energy) + @inline function entropy(u, equations::HyperbolicDiffusionEquations1D) + energy_total(u, equations) + end + + # Calculate total energy for a conservative state `u` + @inline function energy_total(u, equations::HyperbolicDiffusionEquations1D) + # energy function as found in equations (2.5.12) in the book "I Do Like CFD, Vol. 1" + phi, q1 = u + return 0.5f0 * (phi^2 + equations.Lr^2 * q1^2) + end end # @muladd diff --git a/src/equations/hyperbolic_diffusion_2d.jl b/src/equations/hyperbolic_diffusion_2d.jl index 28f0c2bf47..718f55090f 100644 --- a/src/equations/hyperbolic_diffusion_2d.jl +++ b/src/equations/hyperbolic_diffusion_2d.jl @@ -3,251 +3,273 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - HyperbolicDiffusionEquations2D - -The linear hyperbolic diffusion equations in two space dimensions. -A description of this system can be found in Sec. 2.5 of the book "I Do Like CFD, Too: Vol 1". -The book is freely available at [http://www.cfdbooks.com/](http://www.cfdbooks.com/) and further analysis can be found in -the paper by Nishikawa [DOI: 10.1016/j.jcp.2007.07.029](https://doi.org/10.1016/j.jcp.2007.07.029) -""" -struct HyperbolicDiffusionEquations2D{RealT <: Real} <: - AbstractHyperbolicDiffusionEquations{2, 3} - Lr::RealT # reference length scale - inv_Tr::RealT # inverse of the reference time scale - nu::RealT # diffusion constant -end - -function HyperbolicDiffusionEquations2D(; nu = 1.0, Lr = inv(2pi)) - Tr = Lr^2 / nu - HyperbolicDiffusionEquations2D(promote(Lr, inv(Tr), nu)...) -end - -varnames(::typeof(cons2cons), ::HyperbolicDiffusionEquations2D) = ("phi", "q1", "q2") -varnames(::typeof(cons2prim), ::HyperbolicDiffusionEquations2D) = ("phi", "q1", "q2") -function default_analysis_errors(::HyperbolicDiffusionEquations2D) - (:l2_error, :linf_error, :residual) -end - -@inline function residual_steady_state(du, ::HyperbolicDiffusionEquations2D) - abs(du[1]) -end - -# Set initial conditions at physical location `x` for pseudo-time `t` -@inline function initial_condition_poisson_nonperiodic(x, t, - equations::HyperbolicDiffusionEquations2D) - # elliptic equation: -ν Δϕ = f in Ω, u = g on ∂Ω - RealT = eltype(x) - if iszero(t) - phi = one(RealT) - q1 = one(RealT) - q2 = one(RealT) - else - # TODO: sincospi - sinpi_x1, cospi_x1 = sincos(convert(RealT, pi) * x[1]) - sinpi_2x2, cospi_2x2 = sincos(convert(RealT, pi) * 2 * x[2]) - phi = 2 * cospi_x1 * sinpi_2x2 + 2 # ϕ - q1 = -2 * convert(RealT, pi) * sinpi_x1 * sinpi_2x2 # ϕ_x - q2 = 4 * convert(RealT, pi) * cospi_x1 * cospi_2x2 # ϕ_y - end - return SVector(phi, q1, q2) -end - -@inline function source_terms_poisson_nonperiodic(u, x, t, - equations::HyperbolicDiffusionEquations2D) - # elliptic equation: -ν Δϕ = f in Ω, u = g on ∂Ω - # analytical solution: ϕ = 2cos(πx)sin(2πy) + 2 and f = 10π^2cos(πx)sin(2πy) - RealT = eltype(u) - @unpack inv_Tr = equations - - x1, x2 = x - du1 = 10 * convert(RealT, pi)^2 * cospi(x1) * sinpi(2 * x2) - du2 = -inv_Tr * u[2] - du3 = -inv_Tr * u[3] - - return SVector(du1, du2, du3) -end - -@inline function boundary_condition_poisson_nonperiodic(u_inner, orientation, direction, - x, t, - surface_flux_function, - equations::HyperbolicDiffusionEquations2D) - # elliptic equation: -ν Δϕ = f in Ω, u = g on ∂Ω - u_boundary = initial_condition_poisson_nonperiodic(x, one(t), equations) - - # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) - end - - return flux -end - -""" - source_terms_harmonic(u, x, t, equations::HyperbolicDiffusionEquations2D) - -Source term that only includes the forcing from the hyperbolic diffusion system. -""" -@inline function source_terms_harmonic(u, x, t, - equations::HyperbolicDiffusionEquations2D) - # harmonic solution ϕ = (sinh(πx)sin(πy) + sinh(πy)sin(πx))/sinh(π), so f = 0 - @unpack inv_Tr = equations - phi, q1, q2 = u - - du2 = -inv_Tr * q1 - du3 = -inv_Tr * q2 - - return SVector(0, du2, du3) -end - -""" - initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::HyperbolicDiffusionEquations2D) - -Setup used for convergence tests of the Euler equations with self-gravity used in -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -in combination with [`source_terms_harmonic`](@ref). -""" -function initial_condition_eoc_test_coupled_euler_gravity(x, t, - equations::HyperbolicDiffusionEquations2D) - - # Determine phi_x, phi_y - RealT = eltype(x) - G = 1 # gravitational constant - C = -2 * G / convert(RealT, pi) - A = convert(RealT, 0.1) # perturbation coefficient must match Euler setup - rho1 = A * sinpi(x[1] + x[2] - t) - # initialize with ansatz of gravity potential - phi = C * rho1 - q1 = C * A * convert(RealT, pi) * cospi(x[1] + x[2] - t) # = gravity acceleration in x-direction - q2 = q1 # = gravity acceleration in y-direction - - return SVector(phi, q1, q2) -end - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, - equations::HyperbolicDiffusionEquations2D) - phi, q1, q2 = u - @unpack inv_Tr = equations - - RealT = eltype(u) - if orientation == 1 - f1 = -equations.nu * q1 - f2 = -phi * inv_Tr - f3 = zero(RealT) - else - f1 = -equations.nu * q2 - f2 = zero(RealT) - f3 = -phi * inv_Tr - end - - return SVector(f1, f2, f3) -end - -# Note, this directional vector is not normalized -@inline function flux(u, normal_direction::AbstractVector, - equations::HyperbolicDiffusionEquations2D) - phi, q1, q2 = u - @unpack inv_Tr = equations - - f1 = -equations.nu * (normal_direction[1] * q1 + normal_direction[2] * q2) - f2 = -phi * inv_Tr * normal_direction[1] - f3 = -phi * inv_Tr * normal_direction[2] - - return SVector(f1, f2, f3) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::HyperbolicDiffusionEquations2D) - sqrt(equations.nu * equations.inv_Tr) -end - -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::HyperbolicDiffusionEquations2D) - sqrt(equations.nu * equations.inv_Tr) * norm(normal_direction) -end - -@inline function flux_godunov(u_ll, u_rr, orientation::Integer, - equations::HyperbolicDiffusionEquations2D) - # Obtain left and right fluxes - phi_ll, q1_ll, q2_ll = u_ll - phi_rr, q1_rr, q2_rr = u_rr - f_ll = flux(u_ll, orientation, equations) - f_rr = flux(u_rr, orientation, equations) - - # this is an optimized version of the application of the upwind dissipation matrix: - # dissipation = 0.5*R_n*|Λ|*inv(R_n)[[u]] - λ_max = sqrt(equations.nu * equations.inv_Tr) - f1 = 0.5f0 * (f_ll[1] + f_rr[1]) - 0.5f0 * λ_max * (phi_rr - phi_ll) - if orientation == 1 # x-direction - f2 = 0.5f0 * (f_ll[2] + f_rr[2]) - 0.5f0 * λ_max * (q1_rr - q1_ll) - f3 = 0.5f0 * (f_ll[3] + f_rr[3]) - else # y-direction - f2 = 0.5f0 * (f_ll[2] + f_rr[2]) - f3 = 0.5f0 * (f_ll[3] + f_rr[3]) - 0.5f0 * λ_max * (q2_rr - q2_ll) - end - - return SVector(f1, f2, f3) -end - -@inline function flux_godunov(u_ll, u_rr, normal_direction::AbstractVector, - equations::HyperbolicDiffusionEquations2D) - # Obtain left and right fluxes - phi_ll, q1_ll, q2_ll = u_ll - phi_rr, q1_rr, q2_rr = u_rr - f_ll = flux(u_ll, normal_direction, equations) - f_rr = flux(u_rr, normal_direction, equations) - - # this is an optimized version of the application of the upwind dissipation matrix: - # dissipation = 0.5*R_n*|Λ|*inv(R_n)[[u]] - λ_max = sqrt(equations.nu * equations.inv_Tr) - f1 = 0.5f0 * (f_ll[1] + f_rr[1]) - - 0.5f0 * λ_max * (phi_rr - phi_ll) * - sqrt(normal_direction[1]^2 + normal_direction[2]^2) - f2 = 0.5f0 * (f_ll[2] + f_rr[2]) - - 0.5f0 * λ_max * (q1_rr - q1_ll) * normal_direction[1] - f3 = 0.5f0 * (f_ll[3] + f_rr[3]) - - 0.5f0 * λ_max * (q2_rr - q2_ll) * normal_direction[2] - - return SVector(f1, f2, f3) -end - -@inline have_constant_speed(::HyperbolicDiffusionEquations2D) = True() - -@inline function max_abs_speeds(eq::HyperbolicDiffusionEquations2D) - λ = sqrt(eq.nu * eq.inv_Tr) - return λ, λ -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equations::HyperbolicDiffusionEquations2D) = u - -# Convert conservative variables to entropy found in I Do Like CFD, Too, Vol. 1 -@inline function cons2entropy(u, equations::HyperbolicDiffusionEquations2D) - phi, q1, q2 = u - w1 = phi - w2 = equations.Lr^2 * q1 - w3 = equations.Lr^2 * q2 - - return SVector(w1, w2, w3) -end - -# Calculate entropy for a conservative state `u` (here: same as total energy) -@inline function entropy(u, equations::HyperbolicDiffusionEquations2D) - energy_total(u, equations) -end - -# Calculate total energy for a conservative state `u` -@inline function energy_total(u, equations::HyperbolicDiffusionEquations2D) - # energy function as found in equations (2.5.12) in the book "I Do Like CFD, Vol. 1" - phi, q1, q2 = u - return 0.5f0 * (phi^2 + equations.Lr^2 * (q1^2 + q2^2)) -end + #! format: noindent + + @doc raw""" + HyperbolicDiffusionEquations2D + + The linear hyperbolic diffusion equations in two space dimensions. + A description of this system can be found in Sec. 2.5 of the book "I Do Like CFD, Too: Vol 1". + The book is freely available at [http://www.cfdbooks.com/](http://www.cfdbooks.com/) and further analysis can be found in + the paper by Nishikawa [DOI: 10.1016/j.jcp.2007.07.029](https://doi.org/10.1016/j.jcp.2007.07.029) + """ + struct HyperbolicDiffusionEquations2D{RealT <: Real} <: + AbstractHyperbolicDiffusionEquations{2, 3} + Lr::RealT # reference length scale + inv_Tr::RealT # inverse of the reference time scale + nu::RealT # diffusion constant + end + + function HyperbolicDiffusionEquations2D(; nu = 1.0, Lr = inv(2pi)) + Tr = Lr^2 / nu + HyperbolicDiffusionEquations2D(promote(Lr, inv(Tr), nu)...) + end + + varnames(::typeof(cons2cons), ::HyperbolicDiffusionEquations2D) = ("phi", "q1", "q2") + varnames(::typeof(cons2prim), ::HyperbolicDiffusionEquations2D) = ("phi", "q1", "q2") + function default_analysis_errors(::HyperbolicDiffusionEquations2D) + (:l2_error, :linf_error, :residual) + end + + @inline function residual_steady_state(du, ::HyperbolicDiffusionEquations2D) + abs(du[1]) + end + + # Set initial conditions at physical location `x` for pseudo-time `t` + @inline function initial_condition_poisson_nonperiodic( + x, t, + equations::HyperbolicDiffusionEquations2D + ) + # elliptic equation: -ν Δϕ = f in Ω, u = g on ∂Ω + RealT = eltype(x) + if iszero(t) + phi = one(RealT) + q1 = one(RealT) + q2 = one(RealT) + else + # TODO: sincospi + sinpi_x1, cospi_x1 = sincos(convert(RealT, pi) * x[1]) + sinpi_2x2, cospi_2x2 = sincos(convert(RealT, pi) * 2 * x[2]) + phi = 2 * cospi_x1 * sinpi_2x2 + 2 # ϕ + q1 = -2 * convert(RealT, pi) * sinpi_x1 * sinpi_2x2 # ϕ_x + q2 = 4 * convert(RealT, pi) * cospi_x1 * cospi_2x2 # ϕ_y + end + return SVector(phi, q1, q2) + end + + @inline function source_terms_poisson_nonperiodic( + u, x, t, + equations::HyperbolicDiffusionEquations2D + ) + # elliptic equation: -ν Δϕ = f in Ω, u = g on ∂Ω + # analytical solution: ϕ = 2cos(πx)sin(2πy) + 2 and f = 10π^2cos(πx)sin(2πy) + RealT = eltype(u) + @unpack inv_Tr = equations + + x1, x2 = x + du1 = 10 * convert(RealT, pi)^2 * cospi(x1) * sinpi(2 * x2) + du2 = -inv_Tr * u[2] + du3 = -inv_Tr * u[3] + + return SVector(du1, du2, du3) + end + + @inline function boundary_condition_poisson_nonperiodic( + u_inner, orientation, direction, + x, t, + surface_flux_function, + equations::HyperbolicDiffusionEquations2D + ) + # elliptic equation: -ν Δϕ = f in Ω, u = g on ∂Ω + u_boundary = initial_condition_poisson_nonperiodic(x, one(t), equations) + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end + + """ + source_terms_harmonic(u, x, t, equations::HyperbolicDiffusionEquations2D) + + Source term that only includes the forcing from the hyperbolic diffusion system. + """ + @inline function source_terms_harmonic( + u, x, t, + equations::HyperbolicDiffusionEquations2D + ) + # harmonic solution ϕ = (sinh(πx)sin(πy) + sinh(πy)sin(πx))/sinh(π), so f = 0 + @unpack inv_Tr = equations + phi, q1, q2 = u + + du2 = -inv_Tr * q1 + du3 = -inv_Tr * q2 + + return SVector(0, du2, du3) + end + + """ + initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::HyperbolicDiffusionEquations2D) + + Setup used for convergence tests of the Euler equations with self-gravity used in + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + in combination with [`source_terms_harmonic`](@ref). + """ + function initial_condition_eoc_test_coupled_euler_gravity( + x, t, + equations::HyperbolicDiffusionEquations2D + ) + + # Determine phi_x, phi_y + RealT = eltype(x) + G = 1 # gravitational constant + C = -2 * G / convert(RealT, pi) + A = convert(RealT, 0.1) # perturbation coefficient must match Euler setup + rho1 = A * sinpi(x[1] + x[2] - t) + # initialize with ansatz of gravity potential + phi = C * rho1 + q1 = C * A * convert(RealT, pi) * cospi(x[1] + x[2] - t) # = gravity acceleration in x-direction + q2 = q1 # = gravity acceleration in y-direction + + return SVector(phi, q1, q2) + end + + # Calculate 1D flux in for a single point + @inline function flux( + u, orientation::Integer, + equations::HyperbolicDiffusionEquations2D + ) + phi, q1, q2 = u + @unpack inv_Tr = equations + + RealT = eltype(u) + if orientation == 1 + f1 = -equations.nu * q1 + f2 = -phi * inv_Tr + f3 = zero(RealT) + else + f1 = -equations.nu * q2 + f2 = zero(RealT) + f3 = -phi * inv_Tr + end + + return SVector(f1, f2, f3) + end + + # Note, this directional vector is not normalized + @inline function flux( + u, normal_direction::AbstractVector, + equations::HyperbolicDiffusionEquations2D + ) + phi, q1, q2 = u + @unpack inv_Tr = equations + + f1 = -equations.nu * (normal_direction[1] * q1 + normal_direction[2] * q2) + f2 = -phi * inv_Tr * normal_direction[1] + f3 = -phi * inv_Tr * normal_direction[2] + + return SVector(f1, f2, f3) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::HyperbolicDiffusionEquations2D + ) + sqrt(equations.nu * equations.inv_Tr) + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::HyperbolicDiffusionEquations2D + ) + sqrt(equations.nu * equations.inv_Tr) * norm(normal_direction) + end + + @inline function flux_godunov( + u_ll, u_rr, orientation::Integer, + equations::HyperbolicDiffusionEquations2D + ) + # Obtain left and right fluxes + phi_ll, q1_ll, q2_ll = u_ll + phi_rr, q1_rr, q2_rr = u_rr + f_ll = flux(u_ll, orientation, equations) + f_rr = flux(u_rr, orientation, equations) + + # this is an optimized version of the application of the upwind dissipation matrix: + # dissipation = 0.5*R_n*|Λ|*inv(R_n)[[u]] + λ_max = sqrt(equations.nu * equations.inv_Tr) + f1 = 0.5f0 * (f_ll[1] + f_rr[1]) - 0.5f0 * λ_max * (phi_rr - phi_ll) + if orientation == 1 # x-direction + f2 = 0.5f0 * (f_ll[2] + f_rr[2]) - 0.5f0 * λ_max * (q1_rr - q1_ll) + f3 = 0.5f0 * (f_ll[3] + f_rr[3]) + else # y-direction + f2 = 0.5f0 * (f_ll[2] + f_rr[2]) + f3 = 0.5f0 * (f_ll[3] + f_rr[3]) - 0.5f0 * λ_max * (q2_rr - q2_ll) + end + + return SVector(f1, f2, f3) + end + + @inline function flux_godunov( + u_ll, u_rr, normal_direction::AbstractVector, + equations::HyperbolicDiffusionEquations2D + ) + # Obtain left and right fluxes + phi_ll, q1_ll, q2_ll = u_ll + phi_rr, q1_rr, q2_rr = u_rr + f_ll = flux(u_ll, normal_direction, equations) + f_rr = flux(u_rr, normal_direction, equations) + + # this is an optimized version of the application of the upwind dissipation matrix: + # dissipation = 0.5*R_n*|Λ|*inv(R_n)[[u]] + λ_max = sqrt(equations.nu * equations.inv_Tr) + f1 = 0.5f0 * (f_ll[1] + f_rr[1]) - + 0.5f0 * λ_max * (phi_rr - phi_ll) * + sqrt(normal_direction[1]^2 + normal_direction[2]^2) + f2 = 0.5f0 * (f_ll[2] + f_rr[2]) - + 0.5f0 * λ_max * (q1_rr - q1_ll) * normal_direction[1] + f3 = 0.5f0 * (f_ll[3] + f_rr[3]) - + 0.5f0 * λ_max * (q2_rr - q2_ll) * normal_direction[2] + + return SVector(f1, f2, f3) + end + + @inline have_constant_speed(::HyperbolicDiffusionEquations2D) = True() + + @inline function max_abs_speeds(eq::HyperbolicDiffusionEquations2D) + λ = sqrt(eq.nu * eq.inv_Tr) + return λ, λ + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equations::HyperbolicDiffusionEquations2D) = u + + # Convert conservative variables to entropy found in I Do Like CFD, Too, Vol. 1 + @inline function cons2entropy(u, equations::HyperbolicDiffusionEquations2D) + phi, q1, q2 = u + w1 = phi + w2 = equations.Lr^2 * q1 + w3 = equations.Lr^2 * q2 + + return SVector(w1, w2, w3) + end + + # Calculate entropy for a conservative state `u` (here: same as total energy) + @inline function entropy(u, equations::HyperbolicDiffusionEquations2D) + energy_total(u, equations) + end + + # Calculate total energy for a conservative state `u` + @inline function energy_total(u, equations::HyperbolicDiffusionEquations2D) + # energy function as found in equations (2.5.12) in the book "I Do Like CFD, Vol. 1" + phi, q1, q2 = u + return 0.5f0 * (phi^2 + equations.Lr^2 * (q1^2 + q2^2)) + end end # @muladd diff --git a/src/equations/hyperbolic_diffusion_3d.jl b/src/equations/hyperbolic_diffusion_3d.jl index e9017b1544..b407ed1525 100644 --- a/src/equations/hyperbolic_diffusion_3d.jl +++ b/src/equations/hyperbolic_diffusion_3d.jl @@ -3,250 +3,266 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - HyperbolicDiffusionEquations3D - -The linear hyperbolic diffusion equations in three space dimensions. -A description of this system can be found in Sec. 2.5 of the book "I Do Like CFD, Too: Vol 1". -The book is freely available at [http://www.cfdbooks.com/](http://www.cfdbooks.com/) and further analysis can be found in -the paper by Nishikawa [DOI: 10.1016/j.jcp.2007.07.029](https://doi.org/10.1016/j.jcp.2007.07.029) -""" -struct HyperbolicDiffusionEquations3D{RealT <: Real} <: - AbstractHyperbolicDiffusionEquations{3, 4} - Lr::RealT # reference length scale - inv_Tr::RealT # inverse of the reference time scale - nu::RealT # diffusion constant -end - -function HyperbolicDiffusionEquations3D(; nu = 1.0, Lr = inv(2pi)) - Tr = Lr^2 / nu - HyperbolicDiffusionEquations3D(promote(Lr, inv(Tr), nu)...) -end - -function varnames(::typeof(cons2cons), ::HyperbolicDiffusionEquations3D) - ("phi", "q1", "q2", "q3") -end -function varnames(::typeof(cons2prim), ::HyperbolicDiffusionEquations3D) - ("phi", "q1", "q2", "q3") -end -function default_analysis_errors(::HyperbolicDiffusionEquations3D) - (:l2_error, :linf_error, :residual) -end - -""" - residual_steady_state(du, ::AbstractHyperbolicDiffusionEquations) - -Used to determine the termination criterion of a [`SteadyStateCallback`](@ref). -For hyperbolic diffusion, this checks convergence of the potential ``\\phi``. -""" -@inline function residual_steady_state(du, ::HyperbolicDiffusionEquations3D) - abs(du[1]) -end - -# Set initial conditions at physical location `x` for pseudo-time `t` -function initial_condition_poisson_nonperiodic(x, t, - equations::HyperbolicDiffusionEquations3D) - # elliptic equation: -νΔϕ = f - RealT = eltype(x) - if t == 0 - phi = one(RealT) - q1 = one(RealT) - q2 = one(RealT) - q3 = one(RealT) - else - phi = 2 * cospi(x[1]) * - sinpi(2 * x[2]) * - sinpi(2 * x[3]) + 2 # ϕ + #! format: noindent + + @doc raw""" + HyperbolicDiffusionEquations3D + + The linear hyperbolic diffusion equations in three space dimensions. + A description of this system can be found in Sec. 2.5 of the book "I Do Like CFD, Too: Vol 1". + The book is freely available at [http://www.cfdbooks.com/](http://www.cfdbooks.com/) and further analysis can be found in + the paper by Nishikawa [DOI: 10.1016/j.jcp.2007.07.029](https://doi.org/10.1016/j.jcp.2007.07.029) + """ + struct HyperbolicDiffusionEquations3D{RealT <: Real} <: + AbstractHyperbolicDiffusionEquations{3, 4} + Lr::RealT # reference length scale + inv_Tr::RealT # inverse of the reference time scale + nu::RealT # diffusion constant + end + + function HyperbolicDiffusionEquations3D(; nu = 1.0, Lr = inv(2pi)) + Tr = Lr^2 / nu + HyperbolicDiffusionEquations3D(promote(Lr, inv(Tr), nu)...) + end + + function varnames(::typeof(cons2cons), ::HyperbolicDiffusionEquations3D) + ("phi", "q1", "q2", "q3") + end + function varnames(::typeof(cons2prim), ::HyperbolicDiffusionEquations3D) + ("phi", "q1", "q2", "q3") + end + function default_analysis_errors(::HyperbolicDiffusionEquations3D) + (:l2_error, :linf_error, :residual) + end + + """ + residual_steady_state(du, ::AbstractHyperbolicDiffusionEquations) + + Used to determine the termination criterion of a [`SteadyStateCallback`](@ref). + For hyperbolic diffusion, this checks convergence of the potential ``\\phi``. + """ + @inline function residual_steady_state(du, ::HyperbolicDiffusionEquations3D) + abs(du[1]) + end + + # Set initial conditions at physical location `x` for pseudo-time `t` + function initial_condition_poisson_nonperiodic( + x, t, + equations::HyperbolicDiffusionEquations3D + ) + # elliptic equation: -νΔϕ = f + RealT = eltype(x) + if t == 0 + phi = one(RealT) + q1 = one(RealT) + q2 = one(RealT) + q3 = one(RealT) + else + phi = 2 * cospi(x[1]) * + sinpi(2 * x[2]) * + sinpi(2 * x[3]) + 2 # ϕ + q1 = -2 * convert(RealT, pi) * sinpi(x[1]) * + sinpi(2 * x[2]) * sinpi(2 * x[3]) # ϕ_x + q2 = 4 * convert(RealT, pi) * cospi(x[1]) * + cospi(2 * x[2]) * sinpi(2 * x[3]) # ϕ_y + q3 = 4 * convert(RealT, pi) * cospi(x[1]) * + sinpi(2 * x[2]) * cospi(2 * x[3]) # ϕ_z + end + return SVector(phi, q1, q2, q3) + end + + @inline function source_terms_poisson_nonperiodic( + u, x, t, + equations::HyperbolicDiffusionEquations3D + ) + # elliptic equation: -νΔϕ = f + # analytical solution: ϕ = 2 cos(πx)sin(2πy)sin(2πz) + 2 and f = 18 π^2 cos(πx)sin(2πy)sin(2πz) + RealT = eltype(u) + @unpack inv_Tr = equations + + x1, x2, x3 = x + du1 = 18 * convert(RealT, pi)^2 * cospi(x1) * sinpi(2 * x2) * sinpi(2 * x3) + du2 = -inv_Tr * u[2] + du3 = -inv_Tr * u[3] + du4 = -inv_Tr * u[4] + + return SVector(du1, du2, du3, du4) + end + + function boundary_condition_poisson_nonperiodic( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::HyperbolicDiffusionEquations3D + ) + # elliptic equation: -νΔϕ = f + RealT = eltype(u_inner) + phi = 2 * cospi(x[1]) * sinpi(2 * x[2]) * + sinpi(2 * x[3]) + 2 # ϕ q1 = -2 * convert(RealT, pi) * sinpi(x[1]) * - sinpi(2 * x[2]) * sinpi(2 * x[3]) # ϕ_x + sinpi(2 * x[2]) * sinpi(2 * x[3]) # ϕ_x q2 = 4 * convert(RealT, pi) * cospi(x[1]) * - cospi(2 * x[2]) * sinpi(2 * x[3]) # ϕ_y + cospi(2 * x[2]) * sinpi(2 * x[3]) # ϕ_y q3 = 4 * convert(RealT, pi) * cospi(x[1]) * - sinpi(2 * x[2]) * cospi(2 * x[3]) # ϕ_z - end - return SVector(phi, q1, q2, q3) -end - -@inline function source_terms_poisson_nonperiodic(u, x, t, - equations::HyperbolicDiffusionEquations3D) - # elliptic equation: -νΔϕ = f - # analytical solution: ϕ = 2 cos(πx)sin(2πy)sin(2πz) + 2 and f = 18 π^2 cos(πx)sin(2πy)sin(2πz) - RealT = eltype(u) - @unpack inv_Tr = equations - - x1, x2, x3 = x - du1 = 18 * convert(RealT, pi)^2 * cospi(x1) * sinpi(2 * x2) * sinpi(2 * x3) - du2 = -inv_Tr * u[2] - du3 = -inv_Tr * u[3] - du4 = -inv_Tr * u[4] - - return SVector(du1, du2, du3, du4) -end - -function boundary_condition_poisson_nonperiodic(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::HyperbolicDiffusionEquations3D) - # elliptic equation: -νΔϕ = f - RealT = eltype(u_inner) - phi = 2 * cospi(x[1]) * sinpi(2 * x[2]) * - sinpi(2 * x[3]) + 2 # ϕ - q1 = -2 * convert(RealT, pi) * sinpi(x[1]) * - sinpi(2 * x[2]) * sinpi(2 * x[3]) # ϕ_x - q2 = 4 * convert(RealT, pi) * cospi(x[1]) * - cospi(2 * x[2]) * sinpi(2 * x[3]) # ϕ_y - q3 = 4 * convert(RealT, pi) * cospi(x[1]) * - sinpi(2 * x[2]) * cospi(2 * x[3]) # ϕ_z - u_boundary = SVector(phi, q1, q2, q3) - - # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) - end - - return flux -end - -""" - source_terms_harmonic(u, x, t, equations::HyperbolicDiffusionEquations3D) - -Source term that only includes the forcing from the hyperbolic diffusion system. -""" -@inline function source_terms_harmonic(u, x, t, - equations::HyperbolicDiffusionEquations3D) - # harmonic solution ϕ = (sinh(πx)sin(πy) + sinh(πy)sin(πx))/sinh(π), so f = 0 - @unpack inv_Tr = equations - - du1 = 0 - du2 = -inv_Tr * u[2] - du3 = -inv_Tr * u[3] - du4 = -inv_Tr * u[4] - - return SVector(du1, du2, du3, du4) -end - -""" - initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::HyperbolicDiffusionEquations3D) - -Setup used for convergence tests of the Euler equations with self-gravity used in -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics - [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) -in combination with [`source_terms_harmonic`](@ref). -""" -function initial_condition_eoc_test_coupled_euler_gravity(x, t, - equations::HyperbolicDiffusionEquations3D) - - # Determine phi_x, phi_y - RealT = eltype(x) - G = 1 # gravitational constant - C_grav = -4 * G / (3 * convert(RealT, pi)) # "3" is the number of spatial dimensions # 2D: -2.0*G/pi - A = convert(RealT, 0.1) # perturbation coefficient must match Euler setup - rho1 = A * sinpi(x[1] + x[2] + x[3] - t) - # initialize with ansatz of gravity potential - phi = C_grav * rho1 - q1 = C_grav * A * convert(RealT, pi) * - cospi(x[1] + x[2] + x[3] - t) # = gravity acceleration in x-direction - q2 = q1 # = gravity acceleration in y-direction - q3 = q1 # = gravity acceleration in z-direction - - return SVector(phi, q1, q2, q3) -end - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, - equations::HyperbolicDiffusionEquations3D) - phi, q1, q2, q3 = u - - RealT = eltype(u) - if orientation == 1 - f1 = -equations.nu * q1 - f2 = -phi * equations.inv_Tr - f3 = zero(RealT) - f4 = zero(RealT) - elseif orientation == 2 - f1 = -equations.nu * q2 - f2 = zero(RealT) - f3 = -phi * equations.inv_Tr - f4 = zero(RealT) - else - f1 = -equations.nu * q3 - f2 = zero(RealT) - f3 = zero(RealT) - f4 = -phi * equations.inv_Tr - end - - return SVector(f1, f2, f3, f4) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::HyperbolicDiffusionEquations3D) - λ_max = sqrt(equations.nu * equations.inv_Tr) -end - -@inline function flux_godunov(u_ll, u_rr, orientation::Integer, - equations::HyperbolicDiffusionEquations3D) - # Obtain left and right fluxes - phi_ll, q1_ll, q2_ll, q3_ll = u_ll - phi_rr, q1_rr, q2_rr, q3_rr = u_rr - f_ll = flux(u_ll, orientation, equations) - f_rr = flux(u_rr, orientation, equations) - - # this is an optimized version of the application of the upwind dissipation matrix: - # dissipation = 0.5*R_n*|Λ|*inv(R_n)[[u]] - λ_max = sqrt(equations.nu * equations.inv_Tr) - f1 = 0.5f0 * (f_ll[1] + f_rr[1]) - 0.5f0 * λ_max * (phi_rr - phi_ll) - if orientation == 1 # x-direction - f2 = 0.5f0 * (f_ll[2] + f_rr[2]) - 0.5f0 * λ_max * (q1_rr - q1_ll) - f3 = 0.5f0 * (f_ll[3] + f_rr[3]) - f4 = 0.5f0 * (f_ll[4] + f_rr[4]) - elseif orientation == 2 # y-direction - f2 = 0.5f0 * (f_ll[2] + f_rr[2]) - f3 = 0.5f0 * (f_ll[3] + f_rr[3]) - 0.5f0 * λ_max * (q2_rr - q2_ll) - f4 = 0.5f0 * (f_ll[4] + f_rr[4]) - else # y-direction - f2 = 0.5f0 * (f_ll[2] + f_rr[2]) - f3 = 0.5f0 * (f_ll[3] + f_rr[3]) - f4 = 0.5f0 * (f_ll[4] + f_rr[4]) - 0.5f0 * λ_max * (q3_rr - q3_ll) - end - - return SVector(f1, f2, f3, f4) -end - -@inline have_constant_speed(::HyperbolicDiffusionEquations3D) = True() - -@inline function max_abs_speeds(eq::HyperbolicDiffusionEquations3D) - λ = sqrt(eq.nu * eq.inv_Tr) - return λ, λ, λ -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equations::HyperbolicDiffusionEquations3D) = u - -# Convert conservative variables to entropy found in I Do Like CFD, Too, Vol. 1 -@inline function cons2entropy(u, equations::HyperbolicDiffusionEquations3D) - phi, q1, q2, q3 = u - w1 = phi - w2 = equations.Lr^2 * q1 - w3 = equations.Lr^2 * q2 - w4 = equations.Lr^2 * q3 - - return SVector(w1, w2, w3, w4) -end - -# Calculate entropy for a conservative state `u` (here: same as total energy) -@inline function entropy(u, equations::HyperbolicDiffusionEquations3D) - energy_total(u, equations) -end - -# Calculate total energy for a conservative state `u` -@inline function energy_total(u, equations::HyperbolicDiffusionEquations3D) - # energy function as found in equation (2.5.12) in the book "I Do Like CFD, Vol. 1" - phi, q1, q2, q3 = u - return 0.5f0 * (phi^2 + equations.Lr^2 * (q1^2 + q2^2 + q3^2)) -end + sinpi(2 * x[2]) * cospi(2 * x[3]) # ϕ_z + u_boundary = SVector(phi, q1, q2, q3) + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end + + """ + source_terms_harmonic(u, x, t, equations::HyperbolicDiffusionEquations3D) + + Source term that only includes the forcing from the hyperbolic diffusion system. + """ + @inline function source_terms_harmonic( + u, x, t, + equations::HyperbolicDiffusionEquations3D + ) + # harmonic solution ϕ = (sinh(πx)sin(πy) + sinh(πy)sin(πx))/sinh(π), so f = 0 + @unpack inv_Tr = equations + + du1 = 0 + du2 = -inv_Tr * u[2] + du3 = -inv_Tr * u[3] + du4 = -inv_Tr * u[4] + + return SVector(du1, du2, du3, du4) + end + + """ + initial_condition_eoc_test_coupled_euler_gravity(x, t, equations::HyperbolicDiffusionEquations3D) + + Setup used for convergence tests of the Euler equations with self-gravity used in + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics + [arXiv: 2008.10593](https://arxiv.org/abs/2008.10593) + in combination with [`source_terms_harmonic`](@ref). + """ + function initial_condition_eoc_test_coupled_euler_gravity( + x, t, + equations::HyperbolicDiffusionEquations3D + ) + + # Determine phi_x, phi_y + RealT = eltype(x) + G = 1 # gravitational constant + C_grav = -4 * G / (3 * convert(RealT, pi)) # "3" is the number of spatial dimensions # 2D: -2.0*G/pi + A = convert(RealT, 0.1) # perturbation coefficient must match Euler setup + rho1 = A * sinpi(x[1] + x[2] + x[3] - t) + # initialize with ansatz of gravity potential + phi = C_grav * rho1 + q1 = C_grav * A * convert(RealT, pi) * + cospi(x[1] + x[2] + x[3] - t) # = gravity acceleration in x-direction + q2 = q1 # = gravity acceleration in y-direction + q3 = q1 # = gravity acceleration in z-direction + + return SVector(phi, q1, q2, q3) + end + + # Calculate 1D flux in for a single point + @inline function flux( + u, orientation::Integer, + equations::HyperbolicDiffusionEquations3D + ) + phi, q1, q2, q3 = u + + RealT = eltype(u) + if orientation == 1 + f1 = -equations.nu * q1 + f2 = -phi * equations.inv_Tr + f3 = zero(RealT) + f4 = zero(RealT) + elseif orientation == 2 + f1 = -equations.nu * q2 + f2 = zero(RealT) + f3 = -phi * equations.inv_Tr + f4 = zero(RealT) + else + f1 = -equations.nu * q3 + f2 = zero(RealT) + f3 = zero(RealT) + f4 = -phi * equations.inv_Tr + end + + return SVector(f1, f2, f3, f4) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::HyperbolicDiffusionEquations3D + ) + λ_max = sqrt(equations.nu * equations.inv_Tr) + end + + @inline function flux_godunov( + u_ll, u_rr, orientation::Integer, + equations::HyperbolicDiffusionEquations3D + ) + # Obtain left and right fluxes + phi_ll, q1_ll, q2_ll, q3_ll = u_ll + phi_rr, q1_rr, q2_rr, q3_rr = u_rr + f_ll = flux(u_ll, orientation, equations) + f_rr = flux(u_rr, orientation, equations) + + # this is an optimized version of the application of the upwind dissipation matrix: + # dissipation = 0.5*R_n*|Λ|*inv(R_n)[[u]] + λ_max = sqrt(equations.nu * equations.inv_Tr) + f1 = 0.5f0 * (f_ll[1] + f_rr[1]) - 0.5f0 * λ_max * (phi_rr - phi_ll) + if orientation == 1 # x-direction + f2 = 0.5f0 * (f_ll[2] + f_rr[2]) - 0.5f0 * λ_max * (q1_rr - q1_ll) + f3 = 0.5f0 * (f_ll[3] + f_rr[3]) + f4 = 0.5f0 * (f_ll[4] + f_rr[4]) + elseif orientation == 2 # y-direction + f2 = 0.5f0 * (f_ll[2] + f_rr[2]) + f3 = 0.5f0 * (f_ll[3] + f_rr[3]) - 0.5f0 * λ_max * (q2_rr - q2_ll) + f4 = 0.5f0 * (f_ll[4] + f_rr[4]) + else # y-direction + f2 = 0.5f0 * (f_ll[2] + f_rr[2]) + f3 = 0.5f0 * (f_ll[3] + f_rr[3]) + f4 = 0.5f0 * (f_ll[4] + f_rr[4]) - 0.5f0 * λ_max * (q3_rr - q3_ll) + end + + return SVector(f1, f2, f3, f4) + end + + @inline have_constant_speed(::HyperbolicDiffusionEquations3D) = True() + + @inline function max_abs_speeds(eq::HyperbolicDiffusionEquations3D) + λ = sqrt(eq.nu * eq.inv_Tr) + return λ, λ, λ + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equations::HyperbolicDiffusionEquations3D) = u + + # Convert conservative variables to entropy found in I Do Like CFD, Too, Vol. 1 + @inline function cons2entropy(u, equations::HyperbolicDiffusionEquations3D) + phi, q1, q2, q3 = u + w1 = phi + w2 = equations.Lr^2 * q1 + w3 = equations.Lr^2 * q2 + w4 = equations.Lr^2 * q3 + + return SVector(w1, w2, w3, w4) + end + + # Calculate entropy for a conservative state `u` (here: same as total energy) + @inline function entropy(u, equations::HyperbolicDiffusionEquations3D) + energy_total(u, equations) + end + + # Calculate total energy for a conservative state `u` + @inline function energy_total(u, equations::HyperbolicDiffusionEquations3D) + # energy function as found in equation (2.5.12) in the book "I Do Like CFD, Vol. 1" + phi, q1, q2, q3 = u + return 0.5f0 * (phi^2 + equations.Lr^2 * (q1^2 + q2^2 + q3^2)) + end end # @muladd diff --git a/src/equations/ideal_glm_mhd_1d.jl b/src/equations/ideal_glm_mhd_1d.jl index 3253e4410f..c6b23b1141 100644 --- a/src/equations/ideal_glm_mhd_1d.jl +++ b/src/equations/ideal_glm_mhd_1d.jl @@ -3,717 +3,765 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -@doc raw""" - IdealGlmMhdEquations1D(gamma) + @doc raw""" + IdealGlmMhdEquations1D(gamma) -The ideal compressible GLM-MHD equations for an ideal gas with ratio of -specific heats `gamma` in one space dimension. + The ideal compressible GLM-MHD equations for an ideal gas with ratio of + specific heats `gamma` in one space dimension. -!!! note - There is no divergence cleaning variable `psi` because the divergence-free constraint - is satisfied trivially in one spatial dimension. -""" -struct IdealGlmMhdEquations1D{RealT <: Real} <: AbstractIdealGlmMhdEquations{1, 8} - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + !!! note + There is no divergence cleaning variable `psi` because the divergence-free constraint + is satisfied trivially in one spatial dimension. + """ + struct IdealGlmMhdEquations1D{RealT <: Real} <: AbstractIdealGlmMhdEquations{1, 8} + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - function IdealGlmMhdEquations1D(gamma) - γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) - new{typeof(γ)}(γ, inv_gamma_minus_one) + function IdealGlmMhdEquations1D(gamma) + γ, inv_gamma_minus_one = promote(gamma, inv(gamma - 1)) + new{typeof(γ)}(γ, inv_gamma_minus_one) + end + end + + have_nonconservative_terms(::IdealGlmMhdEquations1D) = False() + function varnames(::typeof(cons2cons), ::IdealGlmMhdEquations1D) + ("rho", "rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3") + end + function varnames(::typeof(cons2prim), ::IdealGlmMhdEquations1D) + ("rho", "v1", "v2", "v3", "p", "B1", "B2", "B3") + end + function default_analysis_integrals(::IdealGlmMhdEquations1D) + (entropy_timederivative, Val(:l2_divb), Val(:linf_divb)) + end + + """ + initial_condition_constant(x, t, equations::IdealGlmMhdEquations1D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equations::IdealGlmMhdEquations1D) + RealT = eltype(x) + rho = 1 + rho_v1 = convert(RealT, 0.1) + rho_v2 = -convert(RealT, 0.2) + rho_v3 = -0.5f0 + rho_e = 50 + B1 = 3 + B2 = -convert(RealT, 1.2) + B3 = 0.5f0 + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3) + end + + """ + initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations1D) + + An Alfvén wave as smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations1D) + # smooth Alfvén wave test from Derigs et al. FLASH (2016) + # domain must be set to [0, 1], γ = 5/3 + RealT = eltype(x) + rho = 1 + v1 = 0 + # TODO: sincospi + si, co = sincos(2 * convert(RealT, pi) * x[1]) + v2 = convert(RealT, 0.1) * si + v3 = convert(RealT, 0.1) * co + p = convert(RealT, 0.1) + B1 = 1 + B2 = v2 + B3 = v3 + return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3), equations) end -end - -have_nonconservative_terms(::IdealGlmMhdEquations1D) = False() -function varnames(::typeof(cons2cons), ::IdealGlmMhdEquations1D) - ("rho", "rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3") -end -function varnames(::typeof(cons2prim), ::IdealGlmMhdEquations1D) - ("rho", "v1", "v2", "v3", "p", "B1", "B2", "B3") -end -function default_analysis_integrals(::IdealGlmMhdEquations1D) - (entropy_timederivative, Val(:l2_divb), Val(:linf_divb)) -end - -""" - initial_condition_constant(x, t, equations::IdealGlmMhdEquations1D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equations::IdealGlmMhdEquations1D) - RealT = eltype(x) - rho = 1 - rho_v1 = convert(RealT, 0.1) - rho_v2 = -convert(RealT, 0.2) - rho_v3 = -0.5f0 - rho_e = 50 - B1 = 3 - B2 = -convert(RealT, 1.2) - B3 = 0.5f0 - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3) -end - -""" - initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations1D) - -An Alfvén wave as smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations1D) - # smooth Alfvén wave test from Derigs et al. FLASH (2016) - # domain must be set to [0, 1], γ = 5/3 - RealT = eltype(x) - rho = 1 - v1 = 0 - # TODO: sincospi - si, co = sincos(2 * convert(RealT, pi) * x[1]) - v2 = convert(RealT, 0.1) * si - v3 = convert(RealT, 0.1) * co - p = convert(RealT, 0.1) - B1 = 1 - B2 = v2 - B3 = v3 - return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3), equations) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations1D) - -A weak blast wave adapted from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations1D) - # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Same discontinuity in the velocities but with magnetic fields - # Set up polar coordinates - RealT = eltype(x) - inicenter = (0,) - x_norm = x[1] - inicenter[1] - r = sqrt(x_norm^2) - phi = atan(x_norm) - - # Calculate primitive variables - rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - return prim2cons(SVector(rho, v1, 0, 0, p, 1, 1, 1, 0), equations) -end - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, equations::IdealGlmMhdEquations1D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) - p_over_gamma_minus_one = (rho_e - kin_en - mag_en) - p = (equations.gamma - 1) * p_over_gamma_minus_one - - # Ignore orientation since it is always "1" in 1D - f1 = rho_v1 - f2 = rho_v1 * v1 + p + mag_en - B1^2 - f3 = rho_v1 * v2 - B1 * B2 - f4 = rho_v1 * v3 - B1 * B3 - f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v1 - - B1 * (v1 * B1 + v2 * B2 + v3 * B3) - f6 = 0 - f7 = v1 * B2 - v2 * B1 - f8 = v1 * B3 - v3 * B1 - - return SVector(f1, f2, f3, f4, f5, f6, f7, f8) -end - -""" - flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations1D) - -Entropy conserving two-point flux by -- Derigs et al. (2018) - Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field - divergence diminishing ideal magnetohydrodynamics equations - [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) -""" -function flux_derigs_etal(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations1D) - # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr = u_rr - - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 - vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 - mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 - mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 - p_ll = (equations.gamma - 1) * - (rho_e_ll - 0.5f0 * rho_ll * vel_norm_ll - 0.5f0 * mag_norm_ll) - p_rr = (equations.gamma - 1) * - (rho_e_rr - 0.5f0 * rho_rr * vel_norm_rr - 0.5f0 * mag_norm_rr) - beta_ll = 0.5f0 * rho_ll / p_ll - beta_rr = 0.5f0 * rho_rr / p_rr - # for convenience store v⋅B - vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll - vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr - - # Compute the necessary mean values needed for either direction - rho_avg = 0.5f0 * (rho_ll + rho_rr) - rho_mean = ln_mean(rho_ll, rho_rr) - beta_mean = ln_mean(beta_ll, beta_rr) - beta_avg = 0.5f0 * (beta_ll + beta_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_mean = 0.5f0 * rho_avg / beta_avg - B1_avg = 0.5f0 * (B1_ll + B1_rr) - B2_avg = 0.5f0 * (B2_ll + B2_rr) - B3_avg = 0.5f0 * (B3_ll + B3_rr) - vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) - mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) - vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) - - # Ignore orientation since it is always "1" in 1D - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_mean + 0.5f0 * mag_norm_avg - B1_avg * B1_avg - f3 = f1 * v2_avg - B1_avg * B2_avg - f4 = f1 * v3_avg - B1_avg * B3_avg - f6 = 0 - f7 = v1_avg * B2_avg - v2_avg * B1_avg - f8 = v1_avg * B3_avg - v3_avg * B1_avg - # total energy flux is complicated and involves the previous eight components - v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) - f5 = (f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + - f2 * v1_avg + f3 * v2_avg + - f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg - 0.5f0 * v1_mag_avg + - B1_avg * vel_dot_mag_avg) - - return SVector(f1, f2, f3, f4, f5, f6, f7, f8) -end - -""" - flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, - equations::IdealGlmMhdEquations1D) - -Entropy conserving and kinetic energy preserving two-point flux of -Hindenlang and Gassner (2019), extending [`flux_ranocha`](@ref) to the MHD equations. - -## References -- Florian Hindenlang, Gregor Gassner (2019) - A new entropy conservative two-point flux for ideal MHD equations derived from - first principles. - Presented at HONOM 2019: European workshop on high order numerical methods - for evolutionary PDEs, theory and applications -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_hindenlang_gassner(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations1D) - # Unpack left and right states - rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr = cons2prim(u_rr, equations) - - # Compute the necessary mean values needed for either direction - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) - - # Calculate fluxes depending on orientation with specific direction averages - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_avg + magnetic_square_avg - - 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) - f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) - f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) - #f5 below - f6 = 0 - f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) - f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) - # total energy flux is complicated and involves the previous components - f5 = (f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v1_rr + p_rr * v1_ll - + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) - + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) - - - (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) - - - (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll))) - - return SVector(f1, f2, f3, f4, f5, f6, f7, f8) -end - -""" - flux_hllc(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations1D) - -- Li (2005) -An HLLC Riemann solver for magneto-hydrodynamics -[DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020). -""" -function flux_hllc(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations1D) - # Unpack left and right states - rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr = cons2prim(u_rr, equations) - - # Total pressure, i.e., thermal + magnetic pressures (eq. (12)) - p_tot_ll = p_ll + 0.5f0 * (B1_ll^2 + B2_ll^2 + B3_ll^2) - p_tot_rr = p_rr + 0.5f0 * (B1_rr^2 + B2_rr^2 + B3_rr^2) - - # Conserved variables - rho_v1_ll = u_ll[2] - rho_v2_ll = u_ll[3] - rho_v3_ll = u_ll[4] - - rho_v1_rr = u_rr[2] - rho_v2_rr = u_rr[3] - rho_v3_rr = u_rr[4] - - # Obtain left and right fluxes - f_ll = flux(u_ll, orientation, equations) - f_rr = flux(u_rr, orientation, equations) - - SsL, SsR = min_max_speed_einfeldt(u_ll, u_rr, orientation, equations) - sMu_L = SsL - v1_ll - sMu_R = SsR - v1_rr - if SsL >= 0 - f1 = f_ll[1] - f2 = f_ll[2] - f3 = f_ll[3] - f4 = f_ll[4] - f5 = f_ll[5] - f6 = f_ll[6] - f7 = f_ll[7] - f8 = f_ll[8] - elseif SsR <= 0 - f1 = f_rr[1] - f2 = f_rr[2] - f3 = f_rr[3] - f4 = f_rr[4] - f5 = f_rr[5] - f6 = f_rr[6] - f7 = f_rr[7] - f8 = f_rr[8] - else - # Compute the "HLLC-speed", eq. (14) from paper mentioned above - #= + + """ + initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations1D) + + A weak blast wave adapted from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations1D) + # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Same discontinuity in the velocities but with magnetic fields + # Set up polar coordinates + RealT = eltype(x) + inicenter = (0,) + x_norm = x[1] - inicenter[1] + r = sqrt(x_norm^2) + phi = atan(x_norm) + + # Calculate primitive variables + rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + return prim2cons(SVector(rho, v1, 0, 0, p, 1, 1, 1, 0), equations) + end + + # Calculate 1D flux in for a single point + @inline function flux(u, orientation::Integer, equations::IdealGlmMhdEquations1D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) + p_over_gamma_minus_one = (rho_e - kin_en - mag_en) + p = (equations.gamma - 1) * p_over_gamma_minus_one + + # Ignore orientation since it is always "1" in 1D + f1 = rho_v1 + f2 = rho_v1 * v1 + p + mag_en - B1^2 + f3 = rho_v1 * v2 - B1 * B2 + f4 = rho_v1 * v3 - B1 * B3 + f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v1 - + B1 * (v1 * B1 + v2 * B2 + v3 * B3) + f6 = 0 + f7 = v1 * B2 - v2 * B1 + f8 = v1 * B3 - v3 * B1 + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8) + end + + """ + flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations1D) + + Entropy conserving two-point flux by + - Derigs et al. (2018) + Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field + divergence diminishing ideal magnetohydrodynamics equations + [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) + """ + function flux_derigs_etal( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations1D + ) + # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr = u_rr + + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 + vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 + mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 + mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 + p_ll = (equations.gamma - 1) * + (rho_e_ll - 0.5f0 * rho_ll * vel_norm_ll - 0.5f0 * mag_norm_ll) + p_rr = (equations.gamma - 1) * + (rho_e_rr - 0.5f0 * rho_rr * vel_norm_rr - 0.5f0 * mag_norm_rr) + beta_ll = 0.5f0 * rho_ll / p_ll + beta_rr = 0.5f0 * rho_rr / p_rr + # for convenience store v⋅B + vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll + vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr + + # Compute the necessary mean values needed for either direction + rho_avg = 0.5f0 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5f0 * (beta_ll + beta_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_mean = 0.5f0 * rho_avg / beta_avg + B1_avg = 0.5f0 * (B1_ll + B1_rr) + B2_avg = 0.5f0 * (B2_ll + B2_rr) + B3_avg = 0.5f0 * (B3_ll + B3_rr) + vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) + mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) + vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) + + # Ignore orientation since it is always "1" in 1D + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_mean + 0.5f0 * mag_norm_avg - B1_avg * B1_avg + f3 = f1 * v2_avg - B1_avg * B2_avg + f4 = f1 * v3_avg - B1_avg * B3_avg + f6 = 0 + f7 = v1_avg * B2_avg - v2_avg * B1_avg + f8 = v1_avg * B3_avg - v3_avg * B1_avg + # total energy flux is complicated and involves the previous eight components + v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) + f5 = ( + f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + + f2 * v1_avg + f3 * v2_avg + + f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg - 0.5f0 * v1_mag_avg + + B1_avg * vel_dot_mag_avg + ) + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8) + end + + """ + flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, + equations::IdealGlmMhdEquations1D) + + Entropy conserving and kinetic energy preserving two-point flux of + Hindenlang and Gassner (2019), extending [`flux_ranocha`](@ref) to the MHD equations. + + ## References + - Florian Hindenlang, Gregor Gassner (2019) + A new entropy conservative two-point flux for ideal MHD equations derived from + first principles. + Presented at HONOM 2019: European workshop on high order numerical methods + for evolutionary PDEs, theory and applications + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_hindenlang_gassner( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations1D + ) + # Unpack left and right states + rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr = cons2prim(u_rr, equations) + + # Compute the necessary mean values needed for either direction + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) + + # Calculate fluxes depending on orientation with specific direction averages + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_avg + magnetic_square_avg - + 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) + f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) + f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) + #f5 below + f6 = 0 + f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) + f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) + # total energy flux is complicated and involves the previous components + f5 = ( + f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v1_rr + p_rr * v1_ll + + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) + + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) + - + (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) + - + (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll) + ) + ) + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8) + end + + """ + flux_hllc(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations1D) + + - Li (2005) + An HLLC Riemann solver for magneto-hydrodynamics + [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020). + """ + function flux_hllc( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations1D + ) + # Unpack left and right states + rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr = cons2prim(u_rr, equations) + + # Total pressure, i.e., thermal + magnetic pressures (eq. (12)) + p_tot_ll = p_ll + 0.5f0 * (B1_ll^2 + B2_ll^2 + B3_ll^2) + p_tot_rr = p_rr + 0.5f0 * (B1_rr^2 + B2_rr^2 + B3_rr^2) + + # Conserved variables + rho_v1_ll = u_ll[2] + rho_v2_ll = u_ll[3] + rho_v3_ll = u_ll[4] + + rho_v1_rr = u_rr[2] + rho_v2_rr = u_rr[3] + rho_v3_rr = u_rr[4] + + # Obtain left and right fluxes + f_ll = flux(u_ll, orientation, equations) + f_rr = flux(u_rr, orientation, equations) + + SsL, SsR = min_max_speed_einfeldt(u_ll, u_rr, orientation, equations) + sMu_L = SsL - v1_ll + sMu_R = SsR - v1_rr + if SsL >= 0 + f1 = f_ll[1] + f2 = f_ll[2] + f3 = f_ll[3] + f4 = f_ll[4] + f5 = f_ll[5] + f6 = f_ll[6] + f7 = f_ll[7] + f8 = f_ll[8] + elseif SsR <= 0 + f1 = f_rr[1] + f2 = f_rr[2] + f3 = f_rr[3] + f4 = f_rr[4] + f5 = f_rr[5] + f6 = f_rr[6] + f7 = f_rr[7] + f8 = f_rr[8] + else + # Compute the "HLLC-speed", eq. (14) from paper mentioned above + #= SStar = (rho_rr * v1_rr * sMu_R - rho_ll * v1_ll * sMu_L + p_tot_ll - p_tot_rr - B1_ll^2 + B1_rr^2 ) / (rho_rr * sMu_R - rho_ll * sMu_L) =# - # Simplification for 1D: B1 is constant - SStar = (rho_rr * v1_rr * sMu_R - rho_ll * v1_ll * sMu_L + p_tot_ll - p_tot_rr) / + # Simplification for 1D: B1 is constant + SStar = (rho_rr * v1_rr * sMu_R - rho_ll * v1_ll * sMu_L + p_tot_ll - p_tot_rr) / (rho_rr * sMu_R - rho_ll * sMu_L) - Sdiff = SsR - SsL + Sdiff = SsR - SsL - # Compute HLL values for vStar, BStar - # These correspond to eq. (28) and (30) from the referenced paper - # and the classic HLL intermediate state given by (2) - rho_HLL = (SsR * rho_rr - SsL * rho_ll - (f_rr[1] - f_ll[1])) / Sdiff + # Compute HLL values for vStar, BStar + # These correspond to eq. (28) and (30) from the referenced paper + # and the classic HLL intermediate state given by (2) + rho_HLL = (SsR * rho_rr - SsL * rho_ll - (f_rr[1] - f_ll[1])) / Sdiff - v1Star = (SsR * rho_v1_rr - SsL * rho_v1_ll - (f_rr[2] - f_ll[2])) / - (Sdiff * rho_HLL) - v2Star = (SsR * rho_v2_rr - SsL * rho_v2_ll - (f_rr[3] - f_ll[3])) / - (Sdiff * rho_HLL) - v3Star = (SsR * rho_v3_rr - SsL * rho_v3_ll - (f_rr[4] - f_ll[4])) / - (Sdiff * rho_HLL) + v1Star = (SsR * rho_v1_rr - SsL * rho_v1_ll - (f_rr[2] - f_ll[2])) / + (Sdiff * rho_HLL) + v2Star = (SsR * rho_v2_rr - SsL * rho_v2_ll - (f_rr[3] - f_ll[3])) / + (Sdiff * rho_HLL) + v3Star = (SsR * rho_v3_rr - SsL * rho_v3_ll - (f_rr[4] - f_ll[4])) / + (Sdiff * rho_HLL) - #B1Star = (SsR * B1_rr - SsL * B1_ll - (f_rr[6] - f_ll[6])) / Sdiff - # 1D B1 = constant => B1_ll = B1_rr = B1Star - B1Star = B1_ll + #B1Star = (SsR * B1_rr - SsL * B1_ll - (f_rr[6] - f_ll[6])) / Sdiff + # 1D B1 = constant => B1_ll = B1_rr = B1Star + B1Star = B1_ll + + B2Star = (SsR * B2_rr - SsL * B2_ll - (f_rr[7] - f_ll[7])) / Sdiff + B3Star = (SsR * B3_rr - SsL * B3_ll - (f_rr[8] - f_ll[8])) / Sdiff + if SsL <= SStar + SdiffStar = SsL - SStar + + densStar = rho_ll * sMu_L / SdiffStar # (19) + + mom_1_Star = densStar * SStar # (20) + mom_2_Star = densStar * v2_ll - + (B1Star * B2Star - B1_ll * B2_ll) / SdiffStar # (21) + mom_3_Star = densStar * v3_ll - + (B1Star * B3Star - B1_ll * B3_ll) / SdiffStar # (22) + + #p_tot_Star = rho_ll * sMu_L * (SStar - v1_ll) + p_tot_ll - B1_ll^2 + B1Star^2 # (17) + # 1D B1 = constant => B1_ll = B1_rr = B1Star + p_tot_Star = rho_ll * sMu_L * (SStar - v1_ll) + p_tot_ll # (17) + + enerStar = u_ll[5] * sMu_L / SdiffStar + + ( + p_tot_Star * SStar - p_tot_ll * v1_ll - ( + B1Star * + (B1Star * v1Star + B2Star * v2Star + B3Star * v3Star) - + B1_ll * (B1_ll * v1_ll + B2_ll * v2_ll + B3_ll * v3_ll) + ) + ) / + SdiffStar # (23) + + # Classic HLLC update (32) + f1 = f_ll[1] + SsL * (densStar - u_ll[1]) + f2 = f_ll[2] + SsL * (mom_1_Star - u_ll[2]) + f3 = f_ll[3] + SsL * (mom_2_Star - u_ll[3]) + f4 = f_ll[4] + SsL * (mom_3_Star - u_ll[4]) + f5 = f_ll[5] + SsL * (enerStar - u_ll[5]) + f6 = f_ll[6] + SsL * (B1Star - u_ll[6]) + f7 = f_ll[7] + SsL * (B2Star - u_ll[7]) + f8 = f_ll[8] + SsL * (B3Star - u_ll[8]) + else # SStar <= Ssr + SdiffStar = SsR - SStar + + densStar = rho_rr * sMu_R / SdiffStar # (19) + + mom_1_Star = densStar * SStar # (20) + mom_2_Star = densStar * v2_rr - + (B1Star * B2Star - B1_rr * B2_rr) / SdiffStar # (21) + mom_3_Star = densStar * v3_rr - + (B1Star * B3Star - B1_rr * B3_rr) / SdiffStar # (22) + + #p_tot_Star = rho_rr * sMu_R * (SStar - v1_rr) + p_tot_rr - B1_rr^2 + B1Star^2 # (17) + # 1D B1 = constant => B1_ll = B1_rr = B1Star + p_tot_Star = rho_rr * sMu_R * (SStar - v1_rr) + p_tot_rr # (17) + + enerStar = u_rr[5] * sMu_R / SdiffStar + + ( + p_tot_Star * SStar - p_tot_rr * v1_rr - ( + B1Star * + (B1Star * v1Star + B2Star * v2Star + B3Star * v3Star) - + B1_rr * (B1_rr * v1_rr + B2_rr * v2_rr + B3_rr * v3_rr) + ) + ) / + SdiffStar # (23) + + # Classic HLLC update (32) + f1 = f_rr[1] + SsR * (densStar - u_rr[1]) + f2 = f_rr[2] + SsR * (mom_1_Star - u_rr[2]) + f3 = f_rr[3] + SsR * (mom_2_Star - u_rr[3]) + f4 = f_rr[4] + SsR * (mom_3_Star - u_rr[4]) + f5 = f_rr[5] + SsR * (enerStar - u_rr[5]) + f6 = f_rr[6] + SsR * (B1Star - u_rr[6]) + f7 = f_rr[7] + SsR * (B2Star - u_rr[7]) + f8 = f_rr[8] + SsR * (B3Star - u_rr[8]) + end + end + return SVector(f1, f2, f3, f4, f5, f6, f7, f8) + end - B2Star = (SsR * B2_rr - SsL * B2_ll - (f_rr[7] - f_ll[7])) / Sdiff - B3Star = (SsR * B3_rr - SsL * B3_ll - (f_rr[8] - f_ll[8])) / Sdiff - if SsL <= SStar - SdiffStar = SsL - SStar + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations1D + ) + rho_ll, rho_v1_ll, _ = u_ll + rho_rr, rho_v1_rr, _ = u_rr + + # Calculate velocities (ignore orientation since it is always "1" in 1D) + # and fast magnetoacoustic wave speeds + # left + v_ll = rho_v1_ll / rho_ll + cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) + # right + v_rr = rho_v1_rr / rho_rr + cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_max = max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) + end - densStar = rho_ll * sMu_L / SdiffStar # (19) + # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations1D + ) + rho_ll, rho_v1_ll, _ = u_ll + rho_rr, rho_v1_rr, _ = u_rr - mom_1_Star = densStar * SStar # (20) - mom_2_Star = densStar * v2_ll - - (B1Star * B2Star - B1_ll * B2_ll) / SdiffStar # (21) - mom_3_Star = densStar * v3_ll - - (B1Star * B3Star - B1_ll * B3_ll) / SdiffStar # (22) + # Calculate primitive variables + v1_ll = rho_v1_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr - #p_tot_Star = rho_ll * sMu_L * (SStar - v1_ll) + p_tot_ll - B1_ll^2 + B1Star^2 # (17) - # 1D B1 = constant => B1_ll = B1_rr = B1Star - p_tot_Star = rho_ll * sMu_L * (SStar - v1_ll) + p_tot_ll # (17) - - enerStar = u_ll[5] * sMu_L / SdiffStar + - (p_tot_Star * SStar - p_tot_ll * v1_ll - (B1Star * - (B1Star * v1Star + B2Star * v2Star + B3Star * v3Star) - - B1_ll * (B1_ll * v1_ll + B2_ll * v2_ll + B3_ll * v3_ll))) / - SdiffStar # (23) - - # Classic HLLC update (32) - f1 = f_ll[1] + SsL * (densStar - u_ll[1]) - f2 = f_ll[2] + SsL * (mom_1_Star - u_ll[2]) - f3 = f_ll[3] + SsL * (mom_2_Star - u_ll[3]) - f4 = f_ll[4] + SsL * (mom_3_Star - u_ll[4]) - f5 = f_ll[5] + SsL * (enerStar - u_ll[5]) - f6 = f_ll[6] + SsL * (B1Star - u_ll[6]) - f7 = f_ll[7] + SsL * (B2Star - u_ll[7]) - f8 = f_ll[8] + SsL * (B3Star - u_ll[8]) - else # SStar <= Ssr - SdiffStar = SsR - SStar - - densStar = rho_rr * sMu_R / SdiffStar # (19) - - mom_1_Star = densStar * SStar # (20) - mom_2_Star = densStar * v2_rr - - (B1Star * B2Star - B1_rr * B2_rr) / SdiffStar # (21) - mom_3_Star = densStar * v3_rr - - (B1Star * B3Star - B1_rr * B3_rr) / SdiffStar # (22) - - #p_tot_Star = rho_rr * sMu_R * (SStar - v1_rr) + p_tot_rr - B1_rr^2 + B1Star^2 # (17) - # 1D B1 = constant => B1_ll = B1_rr = B1Star - p_tot_Star = rho_rr * sMu_R * (SStar - v1_rr) + p_tot_rr # (17) - - enerStar = u_rr[5] * sMu_R / SdiffStar + - (p_tot_Star * SStar - p_tot_rr * v1_rr - (B1Star * - (B1Star * v1Star + B2Star * v2Star + B3Star * v3Star) - - B1_rr * (B1_rr * v1_rr + B2_rr * v2_rr + B3_rr * v3_rr))) / - SdiffStar # (23) - - # Classic HLLC update (32) - f1 = f_rr[1] + SsR * (densStar - u_rr[1]) - f2 = f_rr[2] + SsR * (mom_1_Star - u_rr[2]) - f3 = f_rr[3] + SsR * (mom_2_Star - u_rr[3]) - f4 = f_rr[4] + SsR * (mom_3_Star - u_rr[4]) - f5 = f_rr[5] + SsR * (enerStar - u_rr[5]) - f6 = f_rr[6] + SsR * (B1Star - u_rr[6]) - f7 = f_rr[7] + SsR * (B2Star - u_rr[7]) - f8 = f_rr[8] + SsR * (B3Star - u_rr[8]) - end + λ_min = v1_ll - calc_fast_wavespeed(u_ll, orientation, equations) + λ_max = v1_rr + calc_fast_wavespeed(u_rr, orientation, equations) + + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations1D + ) + rho_ll, rho_v1_ll, _ = u_ll + rho_rr, rho_v1_rr, _ = u_rr + + # Calculate primitive variables + v1_ll = rho_v1_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + + # Approximate the left-most and right-most eigenvalues in the Riemann fan + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v1_ll - c_f_ll, v1_rr - c_f_rr) + λ_max = max(v1_ll + c_f_ll, v1_rr + c_f_rr) + + return λ_min, λ_max end - return SVector(f1, f2, f3, f4, f5, f6, f7, f8) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations1D) - rho_ll, rho_v1_ll, _ = u_ll - rho_rr, rho_v1_rr, _ = u_rr - - # Calculate velocities (ignore orientation since it is always "1" in 1D) - # and fast magnetoacoustic wave speeds - # left - v_ll = rho_v1_ll / rho_ll - cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) - # right - v_rr = rho_v1_rr / rho_rr - cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) - - λ_max = max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) -end - -# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations1D) - rho_ll, rho_v1_ll, _ = u_ll - rho_rr, rho_v1_rr, _ = u_rr - - # Calculate primitive variables - v1_ll = rho_v1_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - - λ_min = v1_ll - calc_fast_wavespeed(u_ll, orientation, equations) - λ_max = v1_rr + calc_fast_wavespeed(u_rr, orientation, equations) - - return λ_min, λ_max -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations1D) - rho_ll, rho_v1_ll, _ = u_ll - rho_rr, rho_v1_rr, _ = u_rr - - # Calculate primitive variables - v1_ll = rho_v1_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - - # Approximate the left-most and right-most eigenvalues in the Riemann fan - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - - λ_min = min(v1_ll - c_f_ll, v1_rr - c_f_rr) - λ_max = max(v1_ll + c_f_ll, v1_rr + c_f_rr) - - return λ_min, λ_max -end - -""" - min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, equations::IdealGlmMhdEquations1D) - -Calculate minimum and maximum wave speeds for HLL-type fluxes as in -- Li (2005) - An HLLC Riemann solver for magneto-hydrodynamics - [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020). -""" -@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations1D) - rho_ll, rho_v1_ll, _ = u_ll - rho_rr, rho_v1_rr, _ = u_rr - - # Calculate primitive variables - v1_ll = rho_v1_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - - # Approximate the left-most and right-most eigenvalues in the Riemann fan - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) - λ_min = min(v1_ll - c_f_ll, vel_roe - c_f_roe) - λ_max = max(v1_rr + c_f_rr, vel_roe + c_f_roe) - - return λ_min, λ_max -end - -@inline function max_abs_speeds(u, equations::IdealGlmMhdEquations1D) - rho, rho_v1, _ = u - v1 = rho_v1 / rho - cf_x_direction = calc_fast_wavespeed(u, 1, equations) - - return abs(v1) + cf_x_direction -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::IdealGlmMhdEquations1D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - p = (equations.gamma - 1) * (rho_e - - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3 - + B1 * B1 + B2 * B2 + B3 * B3)) - - return SVector(rho, v1, v2, v3, p, B1, B2, B3) -end - -# Convert conservative variables to entropy -@inline function cons2entropy(u, equations::IdealGlmMhdEquations1D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - v_square = v1^2 + v2^2 + v3^2 - p = (equations.gamma - 1) * - (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2)) - s = log(p) - equations.gamma * log(rho) - rho_p = rho / p - - w1 = (equations.gamma - s) / (equations.gamma - 1) - 0.5f0 * rho_p * v_square - w2 = rho_p * v1 - w3 = rho_p * v2 - w4 = rho_p * v3 - w5 = -rho_p - w6 = rho_p * B1 - w7 = rho_p * B2 - w8 = rho_p * B3 - - return SVector(w1, w2, w3, w4, w5, w6, w7, w8) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::IdealGlmMhdEquations1D) - rho, v1, v2, v3, p, B1, B2, B3 = prim - - rho_v1 = rho * v1 - rho_v2 = rho * v2 - rho_v3 = rho * v3 - rho_e = p / (equations.gamma - 1) + + + """ + min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, equations::IdealGlmMhdEquations1D) + + Calculate minimum and maximum wave speeds for HLL-type fluxes as in + - Li (2005) + An HLLC Riemann solver for magneto-hydrodynamics + [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020). + """ + @inline function min_max_speed_einfeldt( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations1D + ) + rho_ll, rho_v1_ll, _ = u_ll + rho_rr, rho_v1_rr, _ = u_rr + + # Calculate primitive variables + v1_ll = rho_v1_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + + # Approximate the left-most and right-most eigenvalues in the Riemann fan + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v1_ll - c_f_ll, vel_roe - c_f_roe) + λ_max = max(v1_rr + c_f_rr, vel_roe + c_f_roe) + + return λ_min, λ_max + end + + @inline function max_abs_speeds(u, equations::IdealGlmMhdEquations1D) + rho, rho_v1, _ = u + v1 = rho_v1 / rho + cf_x_direction = calc_fast_wavespeed(u, 1, equations) + + return abs(v1) + cf_x_direction + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::IdealGlmMhdEquations1D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + p = (equations.gamma - 1) * ( + rho_e - + 0.5f0 * ( + rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3 + + B1 * B1 + B2 * B2 + B3 * B3 + ) + ) + + return SVector(rho, v1, v2, v3, p, B1, B2, B3) + end + + # Convert conservative variables to entropy + @inline function cons2entropy(u, equations::IdealGlmMhdEquations1D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + v_square = v1^2 + v2^2 + v3^2 + p = (equations.gamma - 1) * + (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2)) + s = log(p) - equations.gamma * log(rho) + rho_p = rho / p + + w1 = (equations.gamma - s) / (equations.gamma - 1) - 0.5f0 * rho_p * v_square + w2 = rho_p * v1 + w3 = rho_p * v2 + w4 = rho_p * v3 + w5 = -rho_p + w6 = rho_p * B1 + w7 = rho_p * B2 + w8 = rho_p * B3 + + return SVector(w1, w2, w3, w4, w5, w6, w7, w8) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::IdealGlmMhdEquations1D) + rho, v1, v2, v3, p, B1, B2, B3 = prim + + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_v3 = rho * v3 + rho_e = p / (equations.gamma - 1) + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + 0.5f0 * (B1^2 + B2^2 + B3^2) - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3) -end - -@inline function density(u, equations::IdealGlmMhdEquations1D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - return rho -end - -@inline function pressure(u, equations::IdealGlmMhdEquations1D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho - - - 0.5f0 * (B1^2 + B2^2 + B3^2)) - return p -end - -@inline function density_pressure(u, equations::IdealGlmMhdEquations1D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho - - - 0.5f0 * (B1^2 + B2^2 + B3^2)) - return rho * p -end - -# Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue -@inline function calc_fast_wavespeed(cons, direction, equations::IdealGlmMhdEquations1D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = cons - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - v_mag = sqrt(v1^2 + v2^2 + v3^2) - p = (equations.gamma - 1) * - (rho_e - 0.5f0 * rho * v_mag^2 - 0.5f0 * (B1^2 + B2^2 + B3^2)) - a_square = equations.gamma * p / rho - sqrt_rho = sqrt(rho) - b1 = B1 / sqrt_rho - b2 = B2 / sqrt_rho - b3 = B3 / sqrt_rho - b_square = b1^2 + b2^2 + b3^2 - - c_f = sqrt(0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2)) - return c_f -end - -""" - calc_fast_wavespeed_roe(u_ll, u_rr, direction, equations::IdealGlmMhdEquations1D) - -Compute the fast magnetoacoustic wave speed using Roe averages -as given by -- Cargo and Gallice (1997) - Roe Matrices for Ideal MHD and Systematic Construction - of Roe Matrices for Systems of Conservation Laws - [DOI: 10.1006/jcph.1997.5773](https://doi.org/10.1006/jcph.1997.5773) -""" -@inline function calc_fast_wavespeed_roe(u_ll, u_rr, direction, - equations::IdealGlmMhdEquations1D) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr = u_rr - - # Calculate primitive variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 - mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 - p_ll = (equations.gamma - 1) * - (rho_e_ll - 0.5f0 * rho_ll * vel_norm_ll - 0.5f0 * mag_norm_ll) - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 - mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 - p_rr = (equations.gamma - 1) * - (rho_e_rr - 0.5f0 * rho_rr * vel_norm_rr - 0.5f0 * mag_norm_rr) - - # compute total pressure which is thermal + magnetic pressures - p_total_ll = p_ll + 0.5f0 * mag_norm_ll - p_total_rr = p_rr + 0.5f0 * mag_norm_rr - - # compute the Roe density averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) - inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) - rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add - rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add - # Roe averages - # velocities and magnetic fields - v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe - v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe - v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe - B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe - B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe - B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe - # enthalpy - H_ll = (rho_e_ll + p_total_ll) / rho_ll - H_rr = (rho_e_rr + p_total_rr) / rho_rr - H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe - # temporary variable see equations (4.12) in Cargo and Gallice - X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * - inv_sqrt_rho_add^2 - # averaged components needed to compute c_f, the fast magnetoacoustic wave speed - b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum - a_square_roe = ((2 - equations.gamma) * X + - (equations.gamma - 1) * - (H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - - b_square_roe)) # acoustic speed - # finally compute the average wave speed and set the output velocity - # Ignore orientation since it is always "1" in 1D - c_a_roe = B1_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed - a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - 4 * a_square_roe * c_a_roe) - c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) - - return v1_roe, c_f_roe -end - -# Calculate thermodynamic entropy for a conservative state `cons` -@inline function entropy_thermodynamic(cons, equations::IdealGlmMhdEquations1D) - # Pressure - p = (equations.gamma - 1) * - (cons[5] - 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] - - - 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2)) - - # Thermodynamic entropy - s = log(p) - equations.gamma * log(cons[1]) - - return s -end - -# Calculate mathematical entropy for a conservative state `cons` -@inline function entropy_math(cons, equations::IdealGlmMhdEquations1D) - S = -entropy_thermodynamic(cons, equations) * cons[1] / (equations.gamma - 1) - - return S -end - -# Default entropy is the mathematical entropy -@inline entropy(cons, equations::IdealGlmMhdEquations1D) = entropy_math(cons, equations) - -# Calculate total energy for a conservative state `cons` -@inline energy_total(cons, ::IdealGlmMhdEquations1D) = cons[5] - -# Calculate kinetic energy for a conservative state `cons` -@inline function energy_kinetic(cons, equations::IdealGlmMhdEquations1D) - return 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] -end - -# Calculate the magnetic energy for a conservative state `cons'. -# OBS! For non-dinmensional form of the ideal MHD magnetic pressure ≡ magnetic energy -@inline function energy_magnetic(cons, ::IdealGlmMhdEquations1D) - return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) -end - -# Calculate internal energy for a conservative state `cons` -@inline function energy_internal(cons, equations::IdealGlmMhdEquations1D) - return (energy_total(cons, equations) - - - energy_kinetic(cons, equations) - - - energy_magnetic(cons, equations)) -end - -# Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons' -@inline function cross_helicity(cons, ::IdealGlmMhdEquations1D) - return (cons[2] * cons[6] + cons[3] * cons[7] + cons[4] * cons[8]) / cons[1] -end + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3) + end + + @inline function density(u, equations::IdealGlmMhdEquations1D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u + return rho + end + + @inline function pressure(u, equations::IdealGlmMhdEquations1D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u + p = (equations.gamma - 1) * ( + rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho + - + 0.5f0 * (B1^2 + B2^2 + B3^2) + ) + return p + end + + @inline function density_pressure(u, equations::IdealGlmMhdEquations1D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u + p = (equations.gamma - 1) * ( + rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho + - + 0.5f0 * (B1^2 + B2^2 + B3^2) + ) + return rho * p + end + + # Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue + @inline function calc_fast_wavespeed(cons, direction, equations::IdealGlmMhdEquations1D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = cons + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + v_mag = sqrt(v1^2 + v2^2 + v3^2) + p = (equations.gamma - 1) * + (rho_e - 0.5f0 * rho * v_mag^2 - 0.5f0 * (B1^2 + B2^2 + B3^2)) + a_square = equations.gamma * p / rho + sqrt_rho = sqrt(rho) + b1 = B1 / sqrt_rho + b2 = B2 / sqrt_rho + b3 = B3 / sqrt_rho + b_square = b1^2 + b2^2 + b3^2 + + c_f = sqrt( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2) + ) + return c_f + end + + """ + calc_fast_wavespeed_roe(u_ll, u_rr, direction, equations::IdealGlmMhdEquations1D) + + Compute the fast magnetoacoustic wave speed using Roe averages + as given by + - Cargo and Gallice (1997) + Roe Matrices for Ideal MHD and Systematic Construction + of Roe Matrices for Systems of Conservation Laws + [DOI: 10.1006/jcph.1997.5773](https://doi.org/10.1006/jcph.1997.5773) + """ + @inline function calc_fast_wavespeed_roe( + u_ll, u_rr, direction, + equations::IdealGlmMhdEquations1D + ) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr = u_rr + + # Calculate primitive variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 + mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 + p_ll = (equations.gamma - 1) * + (rho_e_ll - 0.5f0 * rho_ll * vel_norm_ll - 0.5f0 * mag_norm_ll) + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 + mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 + p_rr = (equations.gamma - 1) * + (rho_e_rr - 0.5f0 * rho_rr * vel_norm_rr - 0.5f0 * mag_norm_rr) + + # compute total pressure which is thermal + magnetic pressures + p_total_ll = p_ll + 0.5f0 * mag_norm_ll + p_total_rr = p_rr + 0.5f0 * mag_norm_rr + + # compute the Roe density averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) + inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) + rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add + rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add + # Roe averages + # velocities and magnetic fields + v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe + v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe + v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe + B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe + B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe + B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe + # enthalpy + H_ll = (rho_e_ll + p_total_ll) / rho_ll + H_rr = (rho_e_rr + p_total_rr) / rho_rr + H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe + # temporary variable see equations (4.12) in Cargo and Gallice + X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * + inv_sqrt_rho_add^2 + # averaged components needed to compute c_f, the fast magnetoacoustic wave speed + b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum + a_square_roe = ( + (2 - equations.gamma) * X + + (equations.gamma - 1) * + ( + H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - + b_square_roe + ) + ) # acoustic speed + # finally compute the average wave speed and set the output velocity + # Ignore orientation since it is always "1" in 1D + c_a_roe = B1_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed + a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - 4 * a_square_roe * c_a_roe) + c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) + + return v1_roe, c_f_roe + end + + # Calculate thermodynamic entropy for a conservative state `cons` + @inline function entropy_thermodynamic(cons, equations::IdealGlmMhdEquations1D) + # Pressure + p = (equations.gamma - 1) * + ( + cons[5] - 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] + - + 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) + ) + + # Thermodynamic entropy + s = log(p) - equations.gamma * log(cons[1]) + + return s + end + + # Calculate mathematical entropy for a conservative state `cons` + @inline function entropy_math(cons, equations::IdealGlmMhdEquations1D) + S = -entropy_thermodynamic(cons, equations) * cons[1] / (equations.gamma - 1) + + return S + end + + # Default entropy is the mathematical entropy + @inline entropy(cons, equations::IdealGlmMhdEquations1D) = entropy_math(cons, equations) + + # Calculate total energy for a conservative state `cons` + @inline energy_total(cons, ::IdealGlmMhdEquations1D) = cons[5] + + # Calculate kinetic energy for a conservative state `cons` + @inline function energy_kinetic(cons, equations::IdealGlmMhdEquations1D) + return 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] + end + + # Calculate the magnetic energy for a conservative state `cons'. + # OBS! For non-dinmensional form of the ideal MHD magnetic pressure ≡ magnetic energy + @inline function energy_magnetic(cons, ::IdealGlmMhdEquations1D) + return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) + end + + # Calculate internal energy for a conservative state `cons` + @inline function energy_internal(cons, equations::IdealGlmMhdEquations1D) + return ( + energy_total(cons, equations) + - + energy_kinetic(cons, equations) + - + energy_magnetic(cons, equations) + ) + end + + # Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons' + @inline function cross_helicity(cons, ::IdealGlmMhdEquations1D) + return (cons[2] * cons[6] + cons[3] * cons[7] + cons[4] * cons[8]) / cons[1] + end end # @muladd diff --git a/src/equations/ideal_glm_mhd_2d.jl b/src/equations/ideal_glm_mhd_2d.jl index ab2a4b066a..c99b7c0c28 100644 --- a/src/equations/ideal_glm_mhd_2d.jl +++ b/src/equations/ideal_glm_mhd_2d.jl @@ -3,1418 +3,1580 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - IdealGlmMhdEquations2D(gamma) - -The ideal compressible GLM-MHD equations for an ideal gas with ratio of -specific heats `gamma` in two space dimensions. -""" -mutable struct IdealGlmMhdEquations2D{RealT <: Real} <: - AbstractIdealGlmMhdEquations{2, 9} - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - c_h::RealT # GLM cleaning speed - - function IdealGlmMhdEquations2D(gamma, c_h) - γ, inv_gamma_minus_one, c_h = promote(gamma, inv(gamma - 1), c_h) - new{typeof(γ)}(γ, inv_gamma_minus_one, c_h) + #! format: noindent + + @doc raw""" + IdealGlmMhdEquations2D(gamma) + + The ideal compressible GLM-MHD equations for an ideal gas with ratio of + specific heats `gamma` in two space dimensions. + """ + mutable struct IdealGlmMhdEquations2D{RealT <: Real} <: + AbstractIdealGlmMhdEquations{2, 9} + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + c_h::RealT # GLM cleaning speed + + function IdealGlmMhdEquations2D(gamma, c_h) + γ, inv_gamma_minus_one, c_h = promote(gamma, inv(gamma - 1), c_h) + new{typeof(γ)}(γ, inv_gamma_minus_one, c_h) + end end -end - -function IdealGlmMhdEquations2D(gamma; initial_c_h = convert(typeof(gamma), NaN)) - # Use `promote` to ensure that `gamma` and `initial_c_h` have the same type - IdealGlmMhdEquations2D(promote(gamma, initial_c_h)...) -end - -have_nonconservative_terms(::IdealGlmMhdEquations2D) = True() -n_nonconservative_terms(::IdealGlmMhdEquations2D) = 2 - -function varnames(::typeof(cons2cons), ::IdealGlmMhdEquations2D) - ("rho", "rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3", "psi") -end -function varnames(::typeof(cons2prim), ::IdealGlmMhdEquations2D) - ("rho", "v1", "v2", "v3", "p", "B1", "B2", "B3", "psi") -end -function default_analysis_integrals(::IdealGlmMhdEquations2D) - (entropy_timederivative, Val(:l2_divb), Val(:linf_divb)) -end - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::IdealGlmMhdEquations2D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equations::IdealGlmMhdEquations2D) - RealT = eltype(x) - rho = 1 - rho_v1 = convert(RealT, 0.1) - rho_v2 = -convert(RealT, 0.2) - rho_v3 = -0.5f0 - rho_e = 50 - B1 = 3 - B2 = -convert(RealT, 1.2) - B3 = 0.5f0 - psi = 0 - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi) -end - -""" - initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations2D) - -An Alfvén wave as smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations2D) - # smooth Alfvén wave test from Derigs et al. FLASH (2016) - # domain must be set to [0, 1/cos(α)] x [0, 1/sin(α)], γ = 5/3 - RealT = eltype(x) - alpha = 0.25f0 * convert(RealT, pi) - x_perp = x[1] * cos(alpha) + x[2] * sin(alpha) - B_perp = convert(RealT, 0.1) * sinpi(2 * x_perp) - rho = 1 - v1 = -B_perp * sin(alpha) - v2 = B_perp * cos(alpha) - v3 = convert(RealT, 0.1) * cospi(2 * x_perp) - p = convert(RealT, 0.1) - B1 = cos(alpha) + v1 - B2 = sin(alpha) + v2 - B3 = v3 - psi = 0 - return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3, psi), equations) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations2D) - -A weak blast wave adapted from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations2D) - # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Same discontinuity in the velocities but with magnetic fields - # Set up polar coordinates - RealT = eltype(x) - inicenter = (0, 0) - x_norm = x[1] - inicenter[1] - y_norm = x[2] - inicenter[2] - r = sqrt(x_norm^2 + y_norm^2) - phi = atan(y_norm, x_norm) - - # Calculate primitive variables - rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) - v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin(phi) - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - return prim2cons(SVector(rho, v1, v2, 0, p, 1, 1, 1, 0), equations) -end - -# Pre-defined source terms should be implemented as -# function source_terms_WHATEVER(u, x, t, equations::IdealGlmMhdEquations2D) - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, equations::IdealGlmMhdEquations2D) - 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 - kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) - p_over_gamma_minus_one = (rho_e - kin_en - mag_en - 0.5f0 * psi^2) - p = (equations.gamma - 1) * p_over_gamma_minus_one - if orientation == 1 - f1 = rho_v1 - f2 = rho_v1 * v1 + p + mag_en - B1^2 - f3 = rho_v1 * v2 - B1 * B2 - f4 = rho_v1 * v3 - B1 * B3 - f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v1 - - B1 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B1 - f6 = equations.c_h * psi - f7 = v1 * B2 - v2 * B1 - f8 = v1 * B3 - v3 * B1 - f9 = equations.c_h * B1 - else #if orientation == 2 - f1 = rho_v2 - f2 = rho_v2 * v1 - B2 * B1 - f3 = rho_v2 * v2 + p + mag_en - B2^2 - f4 = rho_v2 * v3 - B2 * B3 - f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v2 - - B2 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B2 - f6 = v2 * B1 - v1 * B2 - f7 = equations.c_h * psi - f8 = v2 * B3 - v3 * B2 - f9 = equations.c_h * B2 + + function IdealGlmMhdEquations2D(gamma; initial_c_h = convert(typeof(gamma), NaN)) + # Use `promote` to ensure that `gamma` and `initial_c_h` have the same type + IdealGlmMhdEquations2D(promote(gamma, initial_c_h)...) end - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -# Calculate 1D flux for a single point in the normal direction -# Note, this directional vector is not normalized -@inline function flux(u, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations2D) - 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 - kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) - p_over_gamma_minus_one = (rho_e - kin_en - mag_en - 0.5f0 * psi^2) - p = (equations.gamma - 1) * p_over_gamma_minus_one - - v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] - B_normal = B1 * normal_direction[1] + B2 * normal_direction[2] - rho_v_normal = rho * v_normal - - f1 = rho_v_normal - f2 = rho_v_normal * v1 - B1 * B_normal + (p + mag_en) * normal_direction[1] - f3 = rho_v_normal * v2 - B2 * B_normal + (p + mag_en) * normal_direction[2] - f4 = rho_v_normal * v3 - B3 * B_normal - f5 = ((kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v_normal - - - B_normal * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B_normal) - f6 = equations.c_h * psi * normal_direction[1] + - (v2 * B1 - v1 * B2) * normal_direction[2] - f7 = equations.c_h * psi * normal_direction[2] + - (v1 * B2 - v2 * B1) * normal_direction[1] - f8 = v_normal * B3 - v3 * B_normal - f9 = equations.c_h * B_normal - - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -""" - flux_nonconservative_powell(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations2D) - flux_nonconservative_powell(u_ll, u_rr, - normal_direction_ll ::AbstractVector, - normal_direction_average::AbstractVector, - equations::IdealGlmMhdEquations2D) + have_nonconservative_terms(::IdealGlmMhdEquations2D) = True() + n_nonconservative_terms(::IdealGlmMhdEquations2D) = 2 + + function varnames(::typeof(cons2cons), ::IdealGlmMhdEquations2D) + ("rho", "rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3", "psi") + end + function varnames(::typeof(cons2prim), ::IdealGlmMhdEquations2D) + ("rho", "v1", "v2", "v3", "p", "B1", "B2", "B3", "psi") + end + function default_analysis_integrals(::IdealGlmMhdEquations2D) + (entropy_timederivative, Val(:l2_divb), Val(:linf_divb)) + end -Non-symmetric two-point flux discretizing the nonconservative (source) term of -Powell and the Galilean nonconservative term associated with the GLM multiplier -of the [`IdealGlmMhdEquations2D`](@ref). - -On curvilinear meshes, this nonconservative flux depends on both the -contravariant vector (normal direction) at the current node and the averaged -one. This is different from numerical fluxes used to discretize conservative -terms. - -## References -- Marvin Bohm, Andrew R.Winters, Gregor J. Gassner, Dominik Derigs, - Florian Hindenlang, Joachim Saur - An entropy stable nodal discontinuous Galerkin method for the resistive MHD - equations. Part I: Theory and numerical verification - [DOI: 10.1016/j.jcp.2018.06.027](https://doi.org/10.1016/j.jcp.2018.06.027) -""" -@inline function flux_nonconservative_powell(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations2D) - 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 - - # 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}, 0, 0, 0, v_{1,2}) - if orientation == 1 - f = SVector(0, - B1_ll * B1_rr, - B2_ll * B1_rr, - B3_ll * B1_rr, - v_dot_B_ll * B1_rr + v1_ll * psi_ll * psi_rr, - v1_ll * B1_rr, - v2_ll * B1_rr, - v3_ll * B1_rr, - v1_ll * psi_rr) - else # orientation == 2 - f = SVector(0, - B1_ll * B2_rr, - B2_ll * B2_rr, - B3_ll * B2_rr, - v_dot_B_ll * B2_rr + v2_ll * psi_ll * psi_rr, - v1_ll * B2_rr, - v2_ll * B2_rr, - v3_ll * B2_rr, - v2_ll * psi_rr) + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::IdealGlmMhdEquations2D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equations::IdealGlmMhdEquations2D) + RealT = eltype(x) + rho = 1 + rho_v1 = convert(RealT, 0.1) + rho_v2 = -convert(RealT, 0.2) + rho_v3 = -0.5f0 + rho_e = 50 + B1 = 3 + B2 = -convert(RealT, 1.2) + B3 = 0.5f0 + psi = 0 + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi) end - return f -end - -@inline function flux_nonconservative_powell(u_ll, u_rr, - normal_direction_ll::AbstractVector, - normal_direction_average::AbstractVector, - equations::IdealGlmMhdEquations2D) - 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 - - # Note that `v_dot_n_ll` uses the `normal_direction_ll` (contravariant vector - # at the same node location) while `B_dot_n_rr` uses the averaged normal - # direction. The reason for this is that `v_dot_n_ll` depends only on the left - # state and multiplies some gradient while `B_dot_n_rr` is used to compute - # the divergence of B. - v_dot_n_ll = v1_ll * normal_direction_ll[1] + v2_ll * normal_direction_ll[2] - B_dot_n_rr = B1_rr * normal_direction_average[1] + - B2_rr * normal_direction_average[2] - - # 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}, 0, 0, 0, v_{1,2}) - f = SVector(0, - B1_ll * B_dot_n_rr, - B2_ll * B_dot_n_rr, - B3_ll * B_dot_n_rr, - v_dot_B_ll * B_dot_n_rr + v_dot_n_ll * psi_ll * psi_rr, - v1_ll * B_dot_n_rr, - v2_ll * B_dot_n_rr, - v3_ll * B_dot_n_rr, - v_dot_n_ll * psi_rr) - - return f -end - -""" - flux_nonconservative_powell_local_symmetric(u_ll, u_rr, - orientation::Integer, - equations::IdealGlmMhdEquations2D) - -Non-symmetric two-point flux discretizing the nonconservative (source) term of -Powell and the Galilean nonconservative term associated with the GLM multiplier -of the [`IdealGlmMhdEquations2D`](@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`) for conforming meshes but it yields different -results on non-conforming meshes(!). - -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_2d_subcell_limiters.jl. - -## References -- Rueda-Ramírez, Gassner (2023). A Flux-Differencing Formula for Split-Form Summation By Parts - Discretizations of Non-Conservative Systems. https://arxiv.org/pdf/2211.14009.pdf. -""" -@inline function flux_nonconservative_powell_local_symmetric(u_ll, u_rr, - orientation::Integer, - equations::IdealGlmMhdEquations2D) - 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 - - # 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}, 0, 0, 0, v_{1,2}) - psi_avg = (psi_ll + psi_rr) #* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code - if orientation == 1 - B1_avg = (B1_ll + B1_rr) #* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code - f = SVector(0, - B1_ll * B1_avg, - B2_ll * B1_avg, - B3_ll * B1_avg, - v_dot_B_ll * B1_avg + v1_ll * psi_ll * psi_avg, - v1_ll * B1_avg, - v2_ll * B1_avg, - v3_ll * B1_avg, - v1_ll * psi_avg) - else # orientation == 2 - B2_avg = (B2_ll + B2_rr) #* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code - f = SVector(0, - B1_ll * B2_avg, - B2_ll * B2_avg, - B3_ll * B2_avg, - v_dot_B_ll * B2_avg + v2_ll * psi_ll * psi_avg, - v1_ll * B2_avg, - v2_ll * B2_avg, - v3_ll * B2_avg, - v2_ll * psi_avg) + """ + initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations2D) + + An Alfvén wave as smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations2D) + # smooth Alfvén wave test from Derigs et al. FLASH (2016) + # domain must be set to [0, 1/cos(α)] x [0, 1/sin(α)], γ = 5/3 + RealT = eltype(x) + alpha = 0.25f0 * convert(RealT, pi) + x_perp = x[1] * cos(alpha) + x[2] * sin(alpha) + B_perp = convert(RealT, 0.1) * sinpi(2 * x_perp) + rho = 1 + v1 = -B_perp * sin(alpha) + v2 = B_perp * cos(alpha) + v3 = convert(RealT, 0.1) * cospi(2 * x_perp) + p = convert(RealT, 0.1) + B1 = cos(alpha) + v1 + B2 = sin(alpha) + v2 + B3 = v3 + psi = 0 + return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3, psi), equations) end - return f -end - -""" - flux_nonconservative_powell_local_symmetric(u_ll, orientation::Integer, - equations::IdealGlmMhdEquations2D, - 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. https://arxiv.org/pdf/2211.14009.pdf. -This function is used to compute the subcell fluxes in dg_2d_subcell_limiters.jl. -""" -@inline function flux_nonconservative_powell_local_symmetric(u_ll, orientation::Integer, - equations::IdealGlmMhdEquations2D, - 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) + """ + initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations2D) + + A weak blast wave adapted from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations2D) + # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Same discontinuity in the velocities but with magnetic fields + # Set up polar coordinates + RealT = eltype(x) + inicenter = (0, 0) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + phi = atan(y_norm, x_norm) + + # Calculate primitive variables + rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) + v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin(phi) + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + return prim2cons(SVector(rho, v1, v2, 0, p, 1, 1, 1, 0), equations) + end + + # Pre-defined source terms should be implemented as + # function source_terms_WHATEVER(u, x, t, equations::IdealGlmMhdEquations2D) + + # Calculate 1D flux in for a single point + @inline function flux(u, orientation::Integer, equations::IdealGlmMhdEquations2D) + 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 + kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) + p_over_gamma_minus_one = (rho_e - kin_en - mag_en - 0.5f0 * psi^2) + p = (equations.gamma - 1) * p_over_gamma_minus_one + if orientation == 1 + f1 = rho_v1 + f2 = rho_v1 * v1 + p + mag_en - B1^2 + f3 = rho_v1 * v2 - B1 * B2 + f4 = rho_v1 * v3 - B1 * B3 + f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v1 - + B1 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B1 + f6 = equations.c_h * psi + f7 = v1 * B2 - v2 * B1 + f8 = v1 * B3 - v3 * B1 + f9 = equations.c_h * B1 + else #if orientation == 2 + f1 = rho_v2 + f2 = rho_v2 * v1 - B2 * B1 + f3 = rho_v2 * v2 + p + mag_en - B2^2 + f4 = rho_v2 * v3 - B2 * B3 + f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v2 - + B2 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B2 + f6 = v2 * B1 - v1 * B2 + f7 = equations.c_h * psi + f8 = v2 * B3 - v3 * B2 + f9 = equations.c_h * B2 + end + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) + end + + # Calculate 1D flux for a single point in the normal direction + # Note, this directional vector is not normalized + @inline function flux( + u, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + 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 + kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) + p_over_gamma_minus_one = (rho_e - kin_en - mag_en - 0.5f0 * psi^2) + p = (equations.gamma - 1) * p_over_gamma_minus_one + + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + B_normal = B1 * normal_direction[1] + B2 * normal_direction[2] + rho_v_normal = rho * v_normal + + f1 = rho_v_normal + f2 = rho_v_normal * v1 - B1 * B_normal + (p + mag_en) * normal_direction[1] + f3 = rho_v_normal * v2 - B2 * B_normal + (p + mag_en) * normal_direction[2] + f4 = rho_v_normal * v3 - B3 * B_normal + f5 = ( + (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v_normal + - + B_normal * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B_normal + ) + f6 = equations.c_h * psi * normal_direction[1] + + (v2 * B1 - v1 * B2) * normal_direction[2] + f7 = equations.c_h * psi * normal_direction[2] + + (v1 * B2 - v2 * B1) * normal_direction[1] + f8 = v_normal * B3 - v3 * B_normal + f9 = equations.c_h * B_normal + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) + end + + """ + flux_nonconservative_powell(u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D) + flux_nonconservative_powell(u_ll, u_rr, + normal_direction_ll ::AbstractVector, + normal_direction_average::AbstractVector, + equations::IdealGlmMhdEquations2D) + + Non-symmetric two-point flux discretizing the nonconservative (source) term of + Powell and the Galilean nonconservative term associated with the GLM multiplier + of the [`IdealGlmMhdEquations2D`](@ref). + + On curvilinear meshes, this nonconservative flux depends on both the + contravariant vector (normal direction) at the current node and the averaged + one. This is different from numerical fluxes used to discretize conservative + terms. + + ## References + - Marvin Bohm, Andrew R.Winters, Gregor J. Gassner, Dominik Derigs, + Florian Hindenlang, Joachim Saur + An entropy stable nodal discontinuous Galerkin method for the resistive MHD + equations. Part I: Theory and numerical verification + [DOI: 10.1016/j.jcp.2018.06.027](https://doi.org/10.1016/j.jcp.2018.06.027) + """ + @inline function flux_nonconservative_powell( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + 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 - f = SVector(0, - B1_ll, - B2_ll, - B3_ll, - v_dot_B_ll, - v1_ll, - v2_ll, - v3_ll, - 0) - else #nonconservative_term ==2 + + # 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}, 0, 0, 0, v_{1,2}) if orientation == 1 - v1_ll = rho_v1_ll / rho_ll - f = SVector(0, - 0, - 0, - 0, - v1_ll * psi_ll, - 0, - 0, - 0, - v1_ll) - else #orientation == 2 - v2_ll = rho_v2_ll / rho_ll - f = SVector(0, - 0, - 0, - 0, - v2_ll * psi_ll, - 0, - 0, - 0, - v2_ll) + f = SVector( + 0, + B1_ll * B1_rr, + B2_ll * B1_rr, + B3_ll * B1_rr, + v_dot_B_ll * B1_rr + v1_ll * psi_ll * psi_rr, + v1_ll * B1_rr, + v2_ll * B1_rr, + v3_ll * B1_rr, + v1_ll * psi_rr + ) + else # orientation == 2 + f = SVector( + 0, + B1_ll * B2_rr, + B2_ll * B2_rr, + B3_ll * B2_rr, + v_dot_B_ll * B2_rr + v2_ll * psi_ll * psi_rr, + v1_ll * B2_rr, + v2_ll * B2_rr, + v3_ll * B2_rr, + v2_ll * psi_rr + ) end + + return f end - return f -end - -""" - flux_nonconservative_powell_local_symmetric(u_ll, orientation::Integer, - equations::IdealGlmMhdEquations2D, - 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. https://arxiv.org/pdf/2211.14009.pdf. -This function is used to compute the subcell fluxes in dg_2d_subcell_limiters.jl. -""" -@inline function flux_nonconservative_powell_local_symmetric(u_ll, u_rr, - orientation::Integer, - equations::IdealGlmMhdEquations2D, - 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 + + @inline function flux_nonconservative_powell( + u_ll, u_rr, + normal_direction_ll::AbstractVector, + normal_direction_average::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + 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 + + # Note that `v_dot_n_ll` uses the `normal_direction_ll` (contravariant vector + # at the same node location) while `B_dot_n_rr` uses the averaged normal + # direction. The reason for this is that `v_dot_n_ll` depends only on the left + # state and multiplies some gradient while `B_dot_n_rr` is used to compute + # the divergence of B. + v_dot_n_ll = v1_ll * normal_direction_ll[1] + v2_ll * normal_direction_ll[2] + B_dot_n_rr = B1_rr * normal_direction_average[1] + + B2_rr * normal_direction_average[2] + # 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}, 0, 0, 0, v_{1,2}) + f = SVector( + 0, + B1_ll * B_dot_n_rr, + B2_ll * B_dot_n_rr, + B3_ll * B_dot_n_rr, + v_dot_B_ll * B_dot_n_rr + v_dot_n_ll * psi_ll * psi_rr, + v1_ll * B_dot_n_rr, + v2_ll * B_dot_n_rr, + v3_ll * B_dot_n_rr, + v_dot_n_ll * psi_rr + ) + + return f + end + + """ + flux_nonconservative_powell_local_symmetric(u_ll, u_rr, + orientation::Integer, + equations::IdealGlmMhdEquations2D) + + Non-symmetric two-point flux discretizing the nonconservative (source) term of + Powell and the Galilean nonconservative term associated with the GLM multiplier + of the [`IdealGlmMhdEquations2D`](@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`) for conforming meshes but it yields different + results on non-conforming meshes(!). + + 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_2d_subcell_limiters.jl. + + ## References + - Rueda-Ramírez, Gassner (2023). A Flux-Differencing Formula for Split-Form Summation By Parts + Discretizations of Non-Conservative Systems. https://arxiv.org/pdf/2211.14009.pdf. + """ + @inline function flux_nonconservative_powell_local_symmetric( + u_ll, u_rr, + orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + 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 + + # 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}, 0, 0, 0, v_{1,2}) + psi_avg = (psi_ll + psi_rr) #* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code if orientation == 1 - B1_avg = (B1_ll + B1_rr)#* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code - f = SVector(0, - B1_avg, - B1_avg, - B1_avg, - B1_avg, - B1_avg, - B1_avg, - B1_avg, - 0) + B1_avg = (B1_ll + B1_rr) #* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code + f = SVector( + 0, + B1_ll * B1_avg, + B2_ll * B1_avg, + B3_ll * B1_avg, + v_dot_B_ll * B1_avg + v1_ll * psi_ll * psi_avg, + v1_ll * B1_avg, + v2_ll * B1_avg, + v3_ll * B1_avg, + v1_ll * psi_avg + ) else # orientation == 2 - B2_avg = (B2_ll + B2_rr)#* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code - f = SVector(0, - B2_avg, - B2_avg, - B2_avg, - B2_avg, - B2_avg, - B2_avg, - B2_avg, - 0) + B2_avg = (B2_ll + B2_rr) #* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code + f = SVector( + 0, + B1_ll * B2_avg, + B2_ll * B2_avg, + B3_ll * B2_avg, + v_dot_B_ll * B2_avg + v2_ll * psi_ll * psi_avg, + v1_ll * B2_avg, + v2_ll * B2_avg, + v3_ll * B2_avg, + v2_ll * psi_avg + ) end - else #nonconservative_term == 2 - # Galilean nonconservative term: (0, 0, 0, 0, ψ v_{1,2}, 0, 0, 0, v_{1,2}) - psi_avg = (psi_ll + psi_rr)#* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code - f = SVector(0, + + return f + end + + """ + flux_nonconservative_powell_local_symmetric(u_ll, orientation::Integer, + equations::IdealGlmMhdEquations2D, + 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. https://arxiv.org/pdf/2211.14009.pdf. + This function is used to compute the subcell fluxes in dg_2d_subcell_limiters.jl. + """ + @inline function flux_nonconservative_powell_local_symmetric( + u_ll, orientation::Integer, + equations::IdealGlmMhdEquations2D, + 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}) + if orientation == 1 + v1_ll = rho_v1_ll / rho_ll + f = SVector( + 0, + 0, + 0, 0, + v1_ll * psi_ll, 0, 0, - psi_avg, 0, + v1_ll + ) + else #orientation == 2 + v2_ll = rho_v2_ll / rho_ll + f = SVector( 0, 0, - psi_avg) + 0, + 0, + v2_ll * psi_ll, + 0, + 0, + 0, + v2_ll + ) + end + end + return f end - return f -end - -""" - flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations2D) - -Entropy conserving two-point flux by -- Derigs et al. (2018) - Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field - divergence diminishing ideal magnetohydrodynamics equations - [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) -""" -function flux_derigs_etal(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations2D) - # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) - 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 - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 - vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 - mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 - mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 - p_ll = (equations.gamma - 1) * - (rho_e_ll - 0.5f0 * rho_ll * vel_norm_ll - 0.5f0 * mag_norm_ll - - 0.5f0 * psi_ll^2) - p_rr = (equations.gamma - 1) * - (rho_e_rr - 0.5f0 * rho_rr * vel_norm_rr - 0.5f0 * mag_norm_rr - - 0.5f0 * psi_rr^2) - beta_ll = 0.5f0 * rho_ll / p_ll - beta_rr = 0.5f0 * rho_rr / p_rr - # for convenience store v⋅B - vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll - vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr - - # Compute the necessary mean values needed for either direction - rho_avg = 0.5f0 * (rho_ll + rho_rr) - rho_mean = ln_mean(rho_ll, rho_rr) - beta_mean = ln_mean(beta_ll, beta_rr) - beta_avg = 0.5f0 * (beta_ll + beta_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_mean = 0.5f0 * rho_avg / beta_avg - B1_avg = 0.5f0 * (B1_ll + B1_rr) - B2_avg = 0.5f0 * (B2_ll + B2_rr) - B3_avg = 0.5f0 * (B3_ll + B3_rr) - psi_avg = 0.5f0 * (psi_ll + psi_rr) - vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) - mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) - vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) - - # Calculate fluxes depending on orientation with specific direction averages - if orientation == 1 - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_mean + 0.5f0 * mag_norm_avg - B1_avg * B1_avg - f3 = f1 * v2_avg - B1_avg * B2_avg - f4 = f1 * v3_avg - B1_avg * B3_avg - f6 = equations.c_h * psi_avg - f7 = v1_avg * B2_avg - v2_avg * B1_avg - f8 = v1_avg * B3_avg - v3_avg * B1_avg - f9 = equations.c_h * B1_avg - # total energy flux is complicated and involves the previous eight components - psi_B1_avg = 0.5f0 * (B1_ll * psi_ll + B1_rr * psi_rr) - v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) - f5 = (f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + - f2 * v1_avg + f3 * v2_avg + - f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - - 0.5f0 * v1_mag_avg + - B1_avg * vel_dot_mag_avg - equations.c_h * psi_B1_avg) - else - f1 = rho_mean * v2_avg - f2 = f1 * v1_avg - B1_avg * B2_avg - f3 = f1 * v2_avg + p_mean + 0.5f0 * mag_norm_avg - B2_avg * B2_avg - f4 = f1 * v3_avg - B2_avg * B3_avg - f6 = v2_avg * B1_avg - v1_avg * B2_avg - f7 = equations.c_h * psi_avg - f8 = v2_avg * B3_avg - v3_avg * B2_avg - f9 = equations.c_h * B2_avg - # total energy flux is complicated and involves the previous eight components - psi_B2_avg = 0.5f0 * (B2_ll * psi_ll + B2_rr * psi_rr) - v2_mag_avg = 0.5f0 * (v2_ll * mag_norm_ll + v2_rr * mag_norm_rr) - f5 = (f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + - f2 * v1_avg + f3 * v2_avg + - f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - - 0.5f0 * v2_mag_avg + - B2_avg * vel_dot_mag_avg - equations.c_h * psi_B2_avg) + """ + flux_nonconservative_powell_local_symmetric(u_ll, orientation::Integer, + equations::IdealGlmMhdEquations2D, + 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. https://arxiv.org/pdf/2211.14009.pdf. + This function is used to compute the subcell fluxes in dg_2d_subcell_limiters.jl. + """ + @inline function flux_nonconservative_powell_local_symmetric( + u_ll, u_rr, + orientation::Integer, + equations::IdealGlmMhdEquations2D, + 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) + if orientation == 1 + B1_avg = (B1_ll + B1_rr) #* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code + f = SVector( + 0, + B1_avg, + B1_avg, + B1_avg, + B1_avg, + B1_avg, + B1_avg, + B1_avg, + 0 + ) + else # orientation == 2 + B2_avg = (B2_ll + B2_rr) #* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code + f = SVector( + 0, + B2_avg, + B2_avg, + B2_avg, + B2_avg, + B2_avg, + B2_avg, + B2_avg, + 0 + ) + end + else #nonconservative_term == 2 + # Galilean nonconservative term: (0, 0, 0, 0, ψ v_{1,2}, 0, 0, 0, v_{1,2}) + psi_avg = (psi_ll + psi_rr) #* 0.5 # The flux is already multiplied by 0.5 wherever it is used in the code + f = SVector( + 0, + 0, + 0, + 0, + psi_avg, + 0, + 0, + 0, + psi_avg + ) + end + + return f end - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -""" - flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, - equations::IdealGlmMhdEquations2D) - -Entropy conserving and kinetic energy preserving two-point flux of -Hindenlang and Gassner (2019), extending [`flux_ranocha`](@ref) to the MHD equations. - -## References -- Florian Hindenlang, Gregor Gassner (2019) - A new entropy conservative two-point flux for ideal MHD equations derived from - first principles. - Presented at HONOM 2019: European workshop on high order numerical methods - for evolutionary PDEs, theory and applications -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_hindenlang_gassner(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations2D) - # Unpack left and right states - rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim(u_ll, - equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim(u_rr, - equations) - - # Compute the necessary mean values needed for either direction - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - psi_avg = 0.5f0 * (psi_ll + psi_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) - - # Calculate fluxes depending on orientation with specific direction averages - if orientation == 1 - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_avg + magnetic_square_avg - - 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) - f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) - f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) - #f5 below - f6 = equations.c_h * psi_avg - f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) - f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) - f9 = equations.c_h * 0.5f0 * (B1_ll + B1_rr) - # total energy flux is complicated and involves the previous components - f5 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v1_rr + p_rr * v1_ll - + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) - + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) - - - (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) - - - (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll) - + - equations.c_h * (B1_ll * psi_rr + B1_rr * psi_ll))) - else # orientation == 2 - f1 = rho_mean * v2_avg - f2 = f1 * v1_avg - 0.5f0 * (B2_ll * B1_rr + B2_rr * B1_ll) - f3 = f1 * v2_avg + p_avg + magnetic_square_avg - - 0.5f0 * (B2_ll * B2_rr + B2_rr * B2_ll) - f4 = f1 * v3_avg - 0.5f0 * (B2_ll * B3_rr + B2_rr * B3_ll) + """ + flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations2D) + + Entropy conserving two-point flux by + - Derigs et al. (2018) + Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field + divergence diminishing ideal magnetohydrodynamics equations + [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) + """ + function flux_derigs_etal( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) + 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 + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 + vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 + mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 + mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 + p_ll = (equations.gamma - 1) * + ( + rho_e_ll - 0.5f0 * rho_ll * vel_norm_ll - 0.5f0 * mag_norm_ll - + 0.5f0 * psi_ll^2 + ) + p_rr = (equations.gamma - 1) * + ( + rho_e_rr - 0.5f0 * rho_rr * vel_norm_rr - 0.5f0 * mag_norm_rr - + 0.5f0 * psi_rr^2 + ) + beta_ll = 0.5f0 * rho_ll / p_ll + beta_rr = 0.5f0 * rho_rr / p_rr + # for convenience store v⋅B + vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll + vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr + + # Compute the necessary mean values needed for either direction + rho_avg = 0.5f0 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5f0 * (beta_ll + beta_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_mean = 0.5f0 * rho_avg / beta_avg + B1_avg = 0.5f0 * (B1_ll + B1_rr) + B2_avg = 0.5f0 * (B2_ll + B2_rr) + B3_avg = 0.5f0 * (B3_ll + B3_rr) + psi_avg = 0.5f0 * (psi_ll + psi_rr) + vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) + mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) + vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) + + # Calculate fluxes depending on orientation with specific direction averages + if orientation == 1 + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_mean + 0.5f0 * mag_norm_avg - B1_avg * B1_avg + f3 = f1 * v2_avg - B1_avg * B2_avg + f4 = f1 * v3_avg - B1_avg * B3_avg + f6 = equations.c_h * psi_avg + f7 = v1_avg * B2_avg - v2_avg * B1_avg + f8 = v1_avg * B3_avg - v3_avg * B1_avg + f9 = equations.c_h * B1_avg + # total energy flux is complicated and involves the previous eight components + psi_B1_avg = 0.5f0 * (B1_ll * psi_ll + B1_rr * psi_rr) + v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) + f5 = ( + f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + + f2 * v1_avg + f3 * v2_avg + + f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - + 0.5f0 * v1_mag_avg + + B1_avg * vel_dot_mag_avg - equations.c_h * psi_B1_avg + ) + else + f1 = rho_mean * v2_avg + f2 = f1 * v1_avg - B1_avg * B2_avg + f3 = f1 * v2_avg + p_mean + 0.5f0 * mag_norm_avg - B2_avg * B2_avg + f4 = f1 * v3_avg - B2_avg * B3_avg + f6 = v2_avg * B1_avg - v1_avg * B2_avg + f7 = equations.c_h * psi_avg + f8 = v2_avg * B3_avg - v3_avg * B2_avg + f9 = equations.c_h * B2_avg + # total energy flux is complicated and involves the previous eight components + psi_B2_avg = 0.5f0 * (B2_ll * psi_ll + B2_rr * psi_rr) + v2_mag_avg = 0.5f0 * (v2_ll * mag_norm_ll + v2_rr * mag_norm_rr) + f5 = ( + f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + + f2 * v1_avg + f3 * v2_avg + + f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - + 0.5f0 * v2_mag_avg + + B2_avg * vel_dot_mag_avg - equations.c_h * psi_B2_avg + ) + end + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) + end + + """ + flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, + equations::IdealGlmMhdEquations2D) + + Entropy conserving and kinetic energy preserving two-point flux of + Hindenlang and Gassner (2019), extending [`flux_ranocha`](@ref) to the MHD equations. + + ## References + - Florian Hindenlang, Gregor Gassner (2019) + A new entropy conservative two-point flux for ideal MHD equations derived from + first principles. + Presented at HONOM 2019: European workshop on high order numerical methods + for evolutionary PDEs, theory and applications + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_hindenlang_gassner( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + # Unpack left and right states + rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim( + u_ll, + equations + ) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim( + u_rr, + equations + ) + + # Compute the necessary mean values needed for either direction + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + psi_avg = 0.5f0 * (psi_ll + psi_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) + + # Calculate fluxes depending on orientation with specific direction averages + if orientation == 1 + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_avg + magnetic_square_avg - + 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) + f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) + f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) + #f5 below + f6 = equations.c_h * psi_avg + f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) + f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) + f9 = equations.c_h * 0.5f0 * (B1_ll + B1_rr) + # total energy flux is complicated and involves the previous components + f5 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v1_rr + p_rr * v1_ll + + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) + + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) + - + (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) + - + (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll) + + + equations.c_h * (B1_ll * psi_rr + B1_rr * psi_ll) + ) + ) + else # orientation == 2 + f1 = rho_mean * v2_avg + f2 = f1 * v1_avg - 0.5f0 * (B2_ll * B1_rr + B2_rr * B1_ll) + f3 = f1 * v2_avg + p_avg + magnetic_square_avg - + 0.5f0 * (B2_ll * B2_rr + B2_rr * B2_ll) + f4 = f1 * v3_avg - 0.5f0 * (B2_ll * B3_rr + B2_rr * B3_ll) + #f5 below + f6 = 0.5f0 * (v2_ll * B1_ll - v1_ll * B2_ll + v2_rr * B1_rr - v1_rr * B2_rr) + f7 = equations.c_h * psi_avg + f8 = 0.5f0 * (v2_ll * B3_ll - v3_ll * B2_ll + v2_rr * B3_rr - v3_rr * B2_rr) + f9 = equations.c_h * 0.5f0 * (B2_ll + B2_rr) + # total energy flux is complicated and involves the previous components + f5 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v2_rr + p_rr * v2_ll + + (v2_ll * B1_ll * B1_rr + v2_rr * B1_rr * B1_ll) + + (v2_ll * B3_ll * B3_rr + v2_rr * B3_rr * B3_ll) + - + (v1_ll * B2_ll * B1_rr + v1_rr * B2_rr * B1_ll) + - + (v3_ll * B2_ll * B3_rr + v3_rr * B2_rr * B3_ll) + + + equations.c_h * (B2_ll * psi_rr + B2_rr * psi_ll) + ) + ) + end + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) + end + + @inline function flux_hindenlang_gassner( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + # Unpack left and right states + rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim( + u_ll, + equations + ) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim( + u_rr, + equations + ) + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + B_dot_n_ll = B1_ll * normal_direction[1] + B2_ll * normal_direction[2] + B_dot_n_rr = B1_rr * normal_direction[1] + B2_rr * normal_direction[2] + + # Compute the necessary mean values needed for either direction + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + psi_avg = 0.5f0 * (psi_ll + psi_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) + + # Calculate fluxes depending on normal_direction + f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + f2 = ( + f1 * v1_avg + (p_avg + magnetic_square_avg) * normal_direction[1] + - + 0.5f0 * (B_dot_n_ll * B1_rr + B_dot_n_rr * B1_ll) + ) + f3 = ( + f1 * v2_avg + (p_avg + magnetic_square_avg) * normal_direction[2] + - + 0.5f0 * (B_dot_n_ll * B2_rr + B_dot_n_rr * B2_ll) + ) + f4 = ( + f1 * v3_avg + - + 0.5f0 * (B_dot_n_ll * B3_rr + B_dot_n_rr * B3_ll) + ) #f5 below - f6 = 0.5f0 * (v2_ll * B1_ll - v1_ll * B2_ll + v2_rr * B1_rr - v1_rr * B2_rr) - f7 = equations.c_h * psi_avg - f8 = 0.5f0 * (v2_ll * B3_ll - v3_ll * B2_ll + v2_rr * B3_rr - v3_rr * B2_rr) - f9 = equations.c_h * 0.5f0 * (B2_ll + B2_rr) + f6 = ( + equations.c_h * psi_avg * normal_direction[1] + + + 0.5f0 * ( + v_dot_n_ll * B1_ll - v1_ll * B_dot_n_ll + + v_dot_n_rr * B1_rr - v1_rr * B_dot_n_rr + ) + ) + f7 = ( + equations.c_h * psi_avg * normal_direction[2] + + + 0.5f0 * ( + v_dot_n_ll * B2_ll - v2_ll * B_dot_n_ll + + v_dot_n_rr * B2_rr - v2_rr * B_dot_n_rr + ) + ) + f8 = +0.5f0 * ( + v_dot_n_ll * B3_ll - v3_ll * B_dot_n_ll + + v_dot_n_rr * B3_rr - v3_rr * B_dot_n_rr + ) + f9 = equations.c_h * 0.5f0 * (B_dot_n_ll + B_dot_n_rr) # total energy flux is complicated and involves the previous components - f5 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v2_rr + p_rr * v2_ll - + (v2_ll * B1_ll * B1_rr + v2_rr * B1_rr * B1_ll) - + (v2_ll * B3_ll * B3_rr + v2_rr * B3_rr * B3_ll) - - - (v1_ll * B2_ll * B1_rr + v1_rr * B2_rr * B1_ll) - - - (v3_ll * B2_ll * B3_rr + v3_rr * B2_rr * B3_ll) - + - equations.c_h * (B2_ll * psi_rr + B2_rr * psi_ll))) + f5 = ( + f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v_dot_n_rr + p_rr * v_dot_n_ll + + (v_dot_n_ll * B1_ll * B1_rr + v_dot_n_rr * B1_rr * B1_ll) + + (v_dot_n_ll * B2_ll * B2_rr + v_dot_n_rr * B2_rr * B2_ll) + + (v_dot_n_ll * B3_ll * B3_rr + v_dot_n_rr * B3_rr * B3_ll) + - + (v1_ll * B_dot_n_ll * B1_rr + v1_rr * B_dot_n_rr * B1_ll) + - + (v2_ll * B_dot_n_ll * B2_rr + v2_rr * B_dot_n_rr * B2_ll) + - + (v3_ll * B_dot_n_ll * B3_rr + v3_rr * B_dot_n_rr * B3_ll) + + + equations.c_h * (B_dot_n_ll * psi_rr + B_dot_n_rr * psi_ll) + ) + ) + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) end - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -@inline function flux_hindenlang_gassner(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations2D) - # Unpack left and right states - rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim(u_ll, - equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim(u_rr, - equations) - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - B_dot_n_ll = B1_ll * normal_direction[1] + B2_ll * normal_direction[2] - B_dot_n_rr = B1_rr * normal_direction[1] + B2_rr * normal_direction[2] - - # Compute the necessary mean values needed for either direction - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - psi_avg = 0.5f0 * (psi_ll + psi_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) - - # Calculate fluxes depending on normal_direction - f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - f2 = (f1 * v1_avg + (p_avg + magnetic_square_avg) * normal_direction[1] - - - 0.5f0 * (B_dot_n_ll * B1_rr + B_dot_n_rr * B1_ll)) - f3 = (f1 * v2_avg + (p_avg + magnetic_square_avg) * normal_direction[2] - - - 0.5f0 * (B_dot_n_ll * B2_rr + B_dot_n_rr * B2_ll)) - f4 = (f1 * v3_avg - - - 0.5f0 * (B_dot_n_ll * B3_rr + B_dot_n_rr * B3_ll)) - #f5 below - f6 = (equations.c_h * psi_avg * normal_direction[1] - + - 0.5f0 * (v_dot_n_ll * B1_ll - v1_ll * B_dot_n_ll + - v_dot_n_rr * B1_rr - v1_rr * B_dot_n_rr)) - f7 = (equations.c_h * psi_avg * normal_direction[2] - + - 0.5f0 * (v_dot_n_ll * B2_ll - v2_ll * B_dot_n_ll + - v_dot_n_rr * B2_rr - v2_rr * B_dot_n_rr)) - f8 = +0.5f0 * (v_dot_n_ll * B3_ll - v3_ll * B_dot_n_ll + - v_dot_n_rr * B3_rr - v3_rr * B_dot_n_rr) - f9 = equations.c_h * 0.5f0 * (B_dot_n_ll + B_dot_n_rr) - # total energy flux is complicated and involves the previous components - f5 = (f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v_dot_n_rr + p_rr * v_dot_n_ll - + (v_dot_n_ll * B1_ll * B1_rr + v_dot_n_rr * B1_rr * B1_ll) - + (v_dot_n_ll * B2_ll * B2_rr + v_dot_n_rr * B2_rr * B2_ll) - + (v_dot_n_ll * B3_ll * B3_rr + v_dot_n_rr * B3_rr * B3_ll) - - - (v1_ll * B_dot_n_ll * B1_rr + v1_rr * B_dot_n_rr * B1_ll) - - - (v2_ll * B_dot_n_ll * B2_rr + v2_rr * B_dot_n_rr * B2_ll) - - - (v3_ll * B_dot_n_ll * B3_rr + v3_rr * B_dot_n_rr * B3_ll) - + - equations.c_h * (B_dot_n_ll * psi_rr + B_dot_n_rr * psi_ll))) - - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations2D) - rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr - - # Calculate the left/right velocities and fast magnetoacoustic wave speeds - if orientation == 1 - v_ll = rho_v1_ll / rho_ll - v_rr = rho_v1_rr / rho_rr - else # orientation == 2 - v_ll = rho_v2_ll / rho_ll - v_rr = rho_v2_rr / rho_rr + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + + # Calculate the left/right velocities and fast magnetoacoustic wave speeds + if orientation == 1 + v_ll = rho_v1_ll / rho_ll + v_rr = rho_v1_rr / rho_rr + else # orientation == 2 + v_ll = rho_v2_ll / rho_ll + v_rr = rho_v2_rr / rho_rr + end + cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) + cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) end - cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) - cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) - - return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) -end - -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations2D) - # return max(v_mag_ll, v_mag_rr) + max(cf_ll, cf_rr) - rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr - - # Calculate normal velocities and fast magnetoacoustic wave speeds - # left - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v_ll = (v1_ll * normal_direction[1] - + - v2_ll * normal_direction[2]) - cf_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) - # right - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v_rr = (v1_rr * normal_direction[1] - + - v2_rr * normal_direction[2]) - cf_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) - - # wave speeds already scaled by norm(normal_direction) in [`calc_fast_wavespeed`](@ref) - return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) -end - -# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations2D) - rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr - - # Calculate primitive velocity variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - - # Approximate the left-most and right-most eigenvalues in the Riemann fan - if orientation == 1 # x-direction - λ_min = v1_ll - calc_fast_wavespeed(u_ll, orientation, equations) - λ_max = v1_rr + calc_fast_wavespeed(u_rr, orientation, equations) - else # y-direction - λ_min = v2_ll - calc_fast_wavespeed(u_ll, orientation, equations) - λ_max = v2_rr + calc_fast_wavespeed(u_rr, orientation, equations) + + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + # return max(v_mag_ll, v_mag_rr) + max(cf_ll, cf_rr) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + + # Calculate normal velocities and fast magnetoacoustic wave speeds + # left + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v_ll = ( + v1_ll * normal_direction[1] + + + v2_ll * normal_direction[2] + ) + cf_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) + # right + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v_rr = ( + v1_rr * normal_direction[1] + + + v2_rr * normal_direction[2] + ) + cf_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) + + # wave speeds already scaled by norm(normal_direction) in [`calc_fast_wavespeed`](@ref) + return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) end - return λ_min, λ_max -end + # Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr -@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations2D) - rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll - # Calculate primitive velocity variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr + # Approximate the left-most and right-most eigenvalues in the Riemann fan + if orientation == 1 # x-direction + λ_min = v1_ll - calc_fast_wavespeed(u_ll, orientation, equations) + λ_max = v1_rr + calc_fast_wavespeed(u_rr, orientation, equations) + else # y-direction + λ_min = v2_ll - calc_fast_wavespeed(u_ll, orientation, equations) + λ_max = v2_rr + calc_fast_wavespeed(u_rr, orientation, equations) + end - v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) - v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) + return λ_min, λ_max + end + + @inline function min_max_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr - c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) - c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) + v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) + v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) - # Estimate the min/max eigenvalues in the normal direction - λ_min = min(v_normal_ll - c_f_ll, v_normal_rr - c_f_rr) - λ_max = max(v_normal_rr + c_f_rr, v_normal_rr + c_f_rr) + c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) + c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) - return λ_min, λ_max -end + # Estimate the min/max eigenvalues in the normal direction + λ_min = min(v_normal_ll - c_f_ll, v_normal_rr - c_f_rr) + λ_max = max(v_normal_rr + c_f_rr, v_normal_rr + c_f_rr) -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations2D) - rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll - # Calculate primitive velocity variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr + # Approximate the left-most and right-most eigenvalues in the Riemann fan + if orientation == 1 # x-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - # Approximate the left-most and right-most eigenvalues in the Riemann fan - if orientation == 1 # x-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + λ_min = min(v1_ll - c_f_ll, v1_rr - c_f_rr) + λ_max = max(v1_ll + c_f_ll, v1_rr + c_f_rr) + else # y-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - λ_min = min(v1_ll - c_f_ll, v1_rr - c_f_rr) - λ_max = max(v1_ll + c_f_ll, v1_rr + c_f_rr) - else # y-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + λ_min = min(v2_ll - c_f_ll, v2_rr - c_f_rr) + λ_max = max(v2_ll + c_f_ll, v1_rr + c_f_rr) + end - λ_min = min(v2_ll - c_f_ll, v2_rr - c_f_rr) - λ_max = max(v2_ll + c_f_ll, v1_rr + c_f_rr) + return λ_min, λ_max end - return λ_min, λ_max -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations2D) - rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr - - # Calculate primitive velocity variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - - v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) - v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) - - c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) - c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) - - # Estimate the min/max eigenvalues in the normal direction - λ_min = min(v_normal_ll - c_f_ll, v_normal_rr - c_f_rr) - λ_max = max(v_normal_ll + c_f_ll, v_normal_rr + c_f_rr) - - return λ_min, λ_max -end - -""" - min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, equations::IdealGlmMhdEquations2D) - -Calculate minimum and maximum wave speeds for HLL-type fluxes as in -- Li (2005) - An HLLC Riemann solver for magneto-hydrodynamics - [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020). -""" -@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations2D) - rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr - - # Calculate primitive velocity variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - - # Approximate the left-most and right-most eigenvalues in the Riemann fan - if orientation == 1 # x-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) - λ_min = min(v1_ll - c_f_ll, vel_roe - c_f_roe) - λ_max = max(v1_rr + c_f_rr, vel_roe + c_f_roe) - else # y-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) - λ_min = min(v2_ll - c_f_ll, vel_roe - c_f_roe) - λ_max = max(v2_rr + c_f_rr, vel_roe + c_f_roe) + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + + v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) + v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) + + c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) + c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) + + # Estimate the min/max eigenvalues in the normal direction + λ_min = min(v_normal_ll - c_f_ll, v_normal_rr - c_f_rr) + λ_max = max(v_normal_ll + c_f_ll, v_normal_rr + c_f_rr) + + return λ_min, λ_max end - return λ_min, λ_max -end - -@inline function min_max_speed_einfeldt(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations2D) - rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr - - # Calculate primitive velocity variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - - v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) - v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) - - c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) - c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) - v_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, normal_direction, equations) - - # Estimate the min/max eigenvalues in the normal direction - λ_min = min(v_normal_ll - c_f_ll, v_roe - c_f_roe) - λ_max = max(v_normal_rr + c_f_rr, v_roe + c_f_roe) - - return λ_min, λ_max -end - -# Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction -# has been normalized prior to this rotation of the state vector -@inline function rotate_to_x(u, normal_vector, equations::IdealGlmMhdEquations2D) - # cos and sin of the angle between the x-axis and the normalized normal_vector are - # the normalized vector's x and y coordinates respectively (see unit circle). - c = normal_vector[1] - s = normal_vector[2] - - # Apply the 2D rotation matrix with normal and tangent directions of the form - # [ 1 0 0 0 0 0 0 0 0; - # 0 n_1 n_2 0 0 0 0 0 0; - # 0 t_1 t_2 0 0 0 0 0 0; - # 0 0 0 1 0 0 0 0 0; - # 0 0 0 0 1 0 0 0 0; - # 0 0 0 0 0 n_1 n_2 0 0; - # 0 0 0 0 0 t_1 t_2 0 0; - # 0 0 0 0 0 0 0 1 0; - # 0 0 0 0 0 0 0 0 1 ] - # where t_1 = -n_2 and t_2 = n_1. - # Note for IdealGlmMhdEquations2D only the velocities and magnetic field variables rotate - - return SVector(u[1], - c * u[2] + s * u[3], - -s * u[2] + c * u[3], - u[4], - u[5], - c * u[6] + s * u[7], - -s * u[6] + c * u[7], - u[8], - u[9]) -end - -# Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction -# has been normalized prior to this back-rotation of the state vector -@inline function rotate_from_x(u, normal_vector, equations::IdealGlmMhdEquations2D) - # cos and sin of the angle between the x-axis and the normalized normal_vector are - # the normalized vector's x and y coordinates respectively (see unit circle). - c = normal_vector[1] - s = normal_vector[2] - - # Apply the 2D back-rotation matrix with normal and tangent directions of the form - # [ 1 0 0 0 0 0 0 0 0; - # 0 n_1 t_1 0 0 0 0 0 0; - # 0 n_2 t_2 0 0 0 0 0 0; - # 0 0 0 1 0 0 0 0 0; - # 0 0 0 0 1 0 0 0 0; - # 0 0 0 0 0 n_1 t_1 0 0; - # 0 0 0 0 0 n_2 t_2 0 0; - # 0 0 0 0 0 0 0 1 0; - # 0 0 0 0 0 0 0 0 1 ] - # where t_1 = -n_2 and t_2 = n_1. - # Note for IdealGlmMhdEquations2D the velocities and magnetic field variables back-rotate - - return SVector(u[1], - c * u[2] - s * u[3], - s * u[2] + c * u[3], - u[4], - u[5], - c * u[6] - s * u[7], - s * u[6] + c * u[7], - u[8], - u[9]) -end - -@inline function max_abs_speeds(u, equations::IdealGlmMhdEquations2D) - rho, rho_v1, rho_v2, rho_v3, _ = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - cf_x_direction = calc_fast_wavespeed(u, 1, equations) - cf_y_direction = calc_fast_wavespeed(u, 2, equations) - - return abs(v1) + cf_x_direction, abs(v2) + cf_y_direction -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::IdealGlmMhdEquations2D) - 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 - p = (equations.gamma - 1) * (rho_e - - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3 - + B1 * B1 + B2 * B2 + B3 * B3 - + psi * psi)) - - return SVector(rho, v1, v2, v3, p, B1, B2, B3, psi) -end - -# Convert conservative variables to entropy variables -@inline function cons2entropy(u, equations::IdealGlmMhdEquations2D) - 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 - p = (equations.gamma - 1) * - (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) - s = log(p) - equations.gamma * log(rho) - rho_p = rho / p - - w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - - 0.5f0 * rho_p * v_square - w2 = rho_p * v1 - w3 = rho_p * v2 - w4 = rho_p * v3 - w5 = -rho_p - w6 = rho_p * B1 - w7 = rho_p * B2 - w8 = rho_p * B3 - w9 = rho_p * psi - - return SVector(w1, w2, w3, w4, w5, w6, w7, w8, w9) -end - -# Convert entropy variables to conservative variables -@inline function entropy2cons(w, equations::IdealGlmMhdEquations2D) - w1, w2, w3, w4, w5, w6, w7, w8, w9 = w - - v1 = -w2 / w5 - v2 = -w3 / w5 - v3 = -w4 / w5 - - B1 = -w6 / w5 - B2 = -w7 / w5 - B3 = -w8 / w5 - psi = -w9 / w5 - - # This imitates what is done for compressible Euler 3D `entropy2cons`: we convert from - # the entropy variables for `-rho * s / (gamma - 1)` to the entropy variables for the entropy - # `-rho * s` used by Hughes, Franca, Mallet (1986). - @unpack gamma = equations - V1, V2, V3, V4, V5 = SVector(w1, w2, w3, w4, w5) * (gamma - 1) - s = gamma - V1 + (V2^2 + V3^2 + V4^2) / (2 * V5) - rho_iota = ((gamma - 1) / (-V5)^gamma)^(equations.inv_gamma_minus_one) * - exp(-s * equations.inv_gamma_minus_one) - rho = -rho_iota * V5 - p = -rho / w5 - - return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3, psi), equations) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::IdealGlmMhdEquations2D) - rho, v1, v2, v3, p, B1, B2, B3, psi = prim - - rho_v1 = rho * v1 - rho_v2 = rho * v2 - rho_v3 = rho * v3 - rho_e = p * equations.inv_gamma_minus_one + + """ + min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, equations::IdealGlmMhdEquations2D) + + Calculate minimum and maximum wave speeds for HLL-type fluxes as in + - Li (2005) + An HLLC Riemann solver for magneto-hydrodynamics + [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020). + """ + @inline function min_max_speed_einfeldt( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + + # Approximate the left-most and right-most eigenvalues in the Riemann fan + if orientation == 1 # x-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v1_ll - c_f_ll, vel_roe - c_f_roe) + λ_max = max(v1_rr + c_f_rr, vel_roe + c_f_roe) + else # y-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v2_ll - c_f_ll, vel_roe - c_f_roe) + λ_max = max(v2_rr + c_f_rr, vel_roe + c_f_roe) + end + + return λ_min, λ_max + end + + @inline function min_max_speed_einfeldt( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + rho_ll, rho_v1_ll, rho_v2_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + + v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) + v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) + + c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) + c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) + v_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, normal_direction, equations) + + # Estimate the min/max eigenvalues in the normal direction + λ_min = min(v_normal_ll - c_f_ll, v_roe - c_f_roe) + λ_max = max(v_normal_rr + c_f_rr, v_roe + c_f_roe) + + return λ_min, λ_max + end + + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction + # has been normalized prior to this rotation of the state vector + @inline function rotate_to_x(u, normal_vector, equations::IdealGlmMhdEquations2D) + # cos and sin of the angle between the x-axis and the normalized normal_vector are + # the normalized vector's x and y coordinates respectively (see unit circle). + c = normal_vector[1] + s = normal_vector[2] + + # Apply the 2D rotation matrix with normal and tangent directions of the form + # [ 1 0 0 0 0 0 0 0 0; + # 0 n_1 n_2 0 0 0 0 0 0; + # 0 t_1 t_2 0 0 0 0 0 0; + # 0 0 0 1 0 0 0 0 0; + # 0 0 0 0 1 0 0 0 0; + # 0 0 0 0 0 n_1 n_2 0 0; + # 0 0 0 0 0 t_1 t_2 0 0; + # 0 0 0 0 0 0 0 1 0; + # 0 0 0 0 0 0 0 0 1 ] + # where t_1 = -n_2 and t_2 = n_1. + # Note for IdealGlmMhdEquations2D only the velocities and magnetic field variables rotate + + return SVector( + u[1], + c * u[2] + s * u[3], + -s * u[2] + c * u[3], + u[4], + u[5], + c * u[6] + s * u[7], + -s * u[6] + c * u[7], + u[8], + u[9] + ) + end + + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the direction + # has been normalized prior to this back-rotation of the state vector + @inline function rotate_from_x(u, normal_vector, equations::IdealGlmMhdEquations2D) + # cos and sin of the angle between the x-axis and the normalized normal_vector are + # the normalized vector's x and y coordinates respectively (see unit circle). + c = normal_vector[1] + s = normal_vector[2] + + # Apply the 2D back-rotation matrix with normal and tangent directions of the form + # [ 1 0 0 0 0 0 0 0 0; + # 0 n_1 t_1 0 0 0 0 0 0; + # 0 n_2 t_2 0 0 0 0 0 0; + # 0 0 0 1 0 0 0 0 0; + # 0 0 0 0 1 0 0 0 0; + # 0 0 0 0 0 n_1 t_1 0 0; + # 0 0 0 0 0 n_2 t_2 0 0; + # 0 0 0 0 0 0 0 1 0; + # 0 0 0 0 0 0 0 0 1 ] + # where t_1 = -n_2 and t_2 = n_1. + # Note for IdealGlmMhdEquations2D the velocities and magnetic field variables back-rotate + + return SVector( + u[1], + c * u[2] - s * u[3], + s * u[2] + c * u[3], + u[4], + u[5], + c * u[6] - s * u[7], + s * u[6] + c * u[7], + u[8], + u[9] + ) + end + + @inline function max_abs_speeds(u, equations::IdealGlmMhdEquations2D) + rho, rho_v1, rho_v2, rho_v3, _ = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + cf_x_direction = calc_fast_wavespeed(u, 1, equations) + cf_y_direction = calc_fast_wavespeed(u, 2, equations) + + return abs(v1) + cf_x_direction, abs(v2) + cf_y_direction + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::IdealGlmMhdEquations2D) + 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 + p = (equations.gamma - 1) * ( + rho_e - + 0.5f0 * ( + rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3 + + B1 * B1 + B2 * B2 + B3 * B3 + + psi * psi + ) + ) + + return SVector(rho, v1, v2, v3, p, B1, B2, B3, psi) + end + + # Convert conservative variables to entropy variables + @inline function cons2entropy(u, equations::IdealGlmMhdEquations2D) + 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 + p = (equations.gamma - 1) * + (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) + s = log(p) - equations.gamma * log(rho) + rho_p = rho / p + + w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - + 0.5f0 * rho_p * v_square + w2 = rho_p * v1 + w3 = rho_p * v2 + w4 = rho_p * v3 + w5 = -rho_p + w6 = rho_p * B1 + w7 = rho_p * B2 + w8 = rho_p * B3 + w9 = rho_p * psi + + return SVector(w1, w2, w3, w4, w5, w6, w7, w8, w9) + end + + # Convert entropy variables to conservative variables + @inline function entropy2cons(w, equations::IdealGlmMhdEquations2D) + w1, w2, w3, w4, w5, w6, w7, w8, w9 = w + + v1 = -w2 / w5 + v2 = -w3 / w5 + v3 = -w4 / w5 + + B1 = -w6 / w5 + B2 = -w7 / w5 + B3 = -w8 / w5 + psi = -w9 / w5 + + # This imitates what is done for compressible Euler 3D `entropy2cons`: we convert from + # the entropy variables for `-rho * s / (gamma - 1)` to the entropy variables for the entropy + # `-rho * s` used by Hughes, Franca, Mallet (1986). + @unpack gamma = equations + V1, V2, V3, V4, V5 = SVector(w1, w2, w3, w4, w5) * (gamma - 1) + s = gamma - V1 + (V2^2 + V3^2 + V4^2) / (2 * V5) + rho_iota = ((gamma - 1) / (-V5)^gamma)^(equations.inv_gamma_minus_one) * + exp(-s * equations.inv_gamma_minus_one) + rho = -rho_iota * V5 + p = -rho / w5 + + return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3, psi), equations) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::IdealGlmMhdEquations2D) + rho, v1, v2, v3, p, B1, B2, B3, psi = prim + + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_v3 = rho * v3 + rho_e = p * equations.inv_gamma_minus_one + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + 0.5f0 * (B1^2 + B2^2 + B3^2) + 0.5f0 * psi^2 - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi) -end - -@inline function density(u, equations::IdealGlmMhdEquations2D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u - return rho -end - -@inline function pressure(u, equations::IdealGlmMhdEquations2D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho - - - 0.5f0 * (B1^2 + B2^2 + B3^2) - - - 0.5f0 * psi^2) - return p -end - -# Transformation from conservative variables u to d(p)/d(u) -@inline function gradient_conservative(::typeof(pressure), - u, equations::IdealGlmMhdEquations2D) - 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::IdealGlmMhdEquations2D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho - - - 0.5f0 * (B1^2 + B2^2 + B3^2) - - - 0.5f0 * psi^2) - return rho * p -end - -# Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue -@inline function calc_fast_wavespeed(cons, orientation::Integer, - equations::IdealGlmMhdEquations2D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) - p = (equations.gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) - a_square = equations.gamma * p / rho - sqrt_rho = sqrt(rho) - b1 = B1 / sqrt_rho - b2 = B2 / sqrt_rho - b3 = B3 / sqrt_rho - b_square = b1 * b1 + b2 * b2 + b3 * b3 - if orientation == 1 # x-direction - c_f = sqrt(0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2)) - else - c_f = sqrt(0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b2^2)) + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi) + end + + @inline function density(u, equations::IdealGlmMhdEquations2D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u + return rho + end + + @inline function pressure(u, equations::IdealGlmMhdEquations2D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u + p = (equations.gamma - 1) * ( + rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho + - + 0.5f0 * (B1^2 + B2^2 + B3^2) + - + 0.5f0 * psi^2 + ) + return p + end + + # Transformation from conservative variables u to d(p)/d(u) + @inline function gradient_conservative( + ::typeof(pressure), + u, equations::IdealGlmMhdEquations2D + ) + 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::IdealGlmMhdEquations2D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u + p = (equations.gamma - 1) * ( + rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho + - + 0.5f0 * (B1^2 + B2^2 + B3^2) + - + 0.5f0 * psi^2 + ) + return rho * p + end + + # Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue + @inline function calc_fast_wavespeed( + cons, orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) + p = (equations.gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) + a_square = equations.gamma * p / rho + sqrt_rho = sqrt(rho) + b1 = B1 / sqrt_rho + b2 = B2 / sqrt_rho + b3 = B3 / sqrt_rho + b_square = b1 * b1 + b2 * b2 + b3 * b3 + if orientation == 1 # x-direction + c_f = sqrt( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2) + ) + else + c_f = sqrt( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b2^2) + ) + end + return c_f + end + + @inline function calc_fast_wavespeed( + cons, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) + p = (equations.gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) + a_square = equations.gamma * p / rho + sqrt_rho = sqrt(rho) + b1 = B1 / sqrt_rho + b2 = B2 / sqrt_rho + b3 = B3 / sqrt_rho + b_square = b1 * b1 + b2 * b2 + b3 * b3 + norm_squared = ( + normal_direction[1] * normal_direction[1] + + normal_direction[2] * normal_direction[2] + ) + b_dot_n_squared = ( + b1 * normal_direction[1] + + b2 * normal_direction[2] + )^2 / norm_squared + + c_f = sqrt( + ( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b_dot_n_squared) + ) * + norm_squared + ) + return c_f + end + + """ + calc_fast_wavespeed_roe(u_ll, u_rr, orientation_or_normal_direction, equations::IdealGlmMhdEquations2D) + + Compute the fast magnetoacoustic wave speed using Roe averages + as given by + - Cargo and Gallice (1997) + Roe Matrices for Ideal MHD and Systematic Construction + of Roe Matrices for Systems of Conservation Laws + [DOI: 10.1006/jcph.1997.5773](https://doi.org/10.1006/jcph.1997.5773) + """ + @inline function calc_fast_wavespeed_roe( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations2D + ) + 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 + + # Calculate primitive variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + kin_en_ll = 0.5f0 * (rho_v1_ll * v1_ll + rho_v2_ll * v2_ll + rho_v3_ll * v3_ll) + mag_norm_ll = B1_ll * B1_ll + B2_ll * B2_ll + B3_ll * B3_ll + p_ll = (equations.gamma - 1) * + (rho_e_ll - kin_en_ll - 0.5f0 * mag_norm_ll - 0.5f0 * psi_ll^2) + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + kin_en_rr = 0.5f0 * (rho_v1_rr * v1_rr + rho_v2_rr * v2_rr + rho_v3_rr * v3_rr) + mag_norm_rr = B1_rr * B1_rr + B2_rr * B2_rr + B3_rr * B3_rr + p_rr = (equations.gamma - 1) * + (rho_e_rr - kin_en_rr - 0.5f0 * mag_norm_rr - 0.5f0 * psi_rr^2) + + # compute total pressure which is thermal + magnetic pressures + p_total_ll = p_ll + 0.5f0 * mag_norm_ll + p_total_rr = p_rr + 0.5f0 * mag_norm_rr + + # compute the Roe density averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) + inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) + rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add + rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add + # Roe averages + # velocities and magnetic fields + v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe + v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe + v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe + B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe + B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe + B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe + # enthalpy + H_ll = (rho_e_ll + p_total_ll) / rho_ll + H_rr = (rho_e_rr + p_total_rr) / rho_rr + H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe + # temporary variable see equation (4.12) in Cargo and Gallice + X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * + inv_sqrt_rho_add^2 + # averaged components needed to compute c_f, the fast magnetoacoustic wave speed + b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum + a_square_roe = ( + (2 - equations.gamma) * X + + (equations.gamma - 1) * + ( + H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - + b_square_roe + ) + ) # acoustic speed + # finally compute the average wave speed and set the output velocity (depends on orientation) + if orientation == 1 # x-direction + c_a_roe = B1_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed + a_star_roe = sqrt( + (a_square_roe + b_square_roe)^2 - + 4 * a_square_roe * c_a_roe + ) + c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) + vel_out_roe = v1_roe + else # y-direction + c_a_roe = B2_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed + a_star_roe = sqrt( + (a_square_roe + b_square_roe)^2 - + 4 * a_square_roe * c_a_roe + ) + c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) + vel_out_roe = v2_roe + end + + return vel_out_roe, c_f_roe + end + + @inline function calc_fast_wavespeed_roe( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations2D + ) + 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 + + # Calculate primitive variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + kin_en_ll = 0.5f0 * (rho_v1_ll * v1_ll + rho_v2_ll * v2_ll + rho_v3_ll * v3_ll) + mag_norm_ll = B1_ll * B1_ll + B2_ll * B2_ll + B3_ll * B3_ll + p_ll = (equations.gamma - 1) * + (rho_e_ll - kin_en_ll - 0.5f0 * mag_norm_ll - 0.5f0 * psi_ll^2) + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + kin_en_rr = 0.5f0 * (rho_v1_rr * v1_rr + rho_v2_rr * v2_rr + rho_v3_rr * v3_rr) + mag_norm_rr = B1_rr * B1_rr + B2_rr * B2_rr + B3_rr * B3_rr + p_rr = (equations.gamma - 1) * + (rho_e_rr - kin_en_rr - 0.5f0 * mag_norm_rr - 0.5f0 * psi_rr^2) + + # compute total pressure which is thermal + magnetic pressures + p_total_ll = p_ll + 0.5f0 * mag_norm_ll + p_total_rr = p_rr + 0.5f0 * mag_norm_rr + + # compute the Roe density averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) + inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) + rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add + rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add + # Roe averages + # velocities and magnetic fields + v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe + v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe + v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe + B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe + B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe + B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe + # enthalpy + H_ll = (rho_e_ll + p_total_ll) / rho_ll + H_rr = (rho_e_rr + p_total_rr) / rho_rr + H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe + # temporary variable see equation (4.12) in Cargo and Gallice + X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * + inv_sqrt_rho_add^2 + # averaged components needed to compute c_f, the fast magnetoacoustic wave speed + b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum + a_square_roe = ( + (2 - equations.gamma) * X + + (equations.gamma - 1) * + ( + H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - + b_square_roe + ) + ) # acoustic speed + + # finally compute the average wave speed and set the output velocity (depends on orientation) + norm_squared = ( + normal_direction[1] * normal_direction[1] + + normal_direction[2] * normal_direction[2] + ) + B_roe_dot_n_squared = ( + B1_roe * normal_direction[1] + + B2_roe * normal_direction[2] + )^2 / norm_squared + + c_a_roe = B_roe_dot_n_squared * inv_sqrt_rho_prod # (squared) Alfvén wave speed + a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - 4 * a_square_roe * c_a_roe) + c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe) * norm_squared) + vel_out_roe = ( + v1_roe * normal_direction[1] + + v2_roe * normal_direction[2] + ) + + return vel_out_roe, c_f_roe end - return c_f -end - -@inline function calc_fast_wavespeed(cons, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations2D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) - p = (equations.gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) - a_square = equations.gamma * p / rho - sqrt_rho = sqrt(rho) - b1 = B1 / sqrt_rho - b2 = B2 / sqrt_rho - b3 = B3 / sqrt_rho - b_square = b1 * b1 + b2 * b2 + b3 * b3 - norm_squared = (normal_direction[1] * normal_direction[1] + - normal_direction[2] * normal_direction[2]) - b_dot_n_squared = (b1 * normal_direction[1] + - b2 * normal_direction[2])^2 / norm_squared - - c_f = sqrt((0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b_dot_n_squared)) * - norm_squared) - return c_f -end - -""" - calc_fast_wavespeed_roe(u_ll, u_rr, orientation_or_normal_direction, equations::IdealGlmMhdEquations2D) - -Compute the fast magnetoacoustic wave speed using Roe averages -as given by -- Cargo and Gallice (1997) - Roe Matrices for Ideal MHD and Systematic Construction - of Roe Matrices for Systems of Conservation Laws - [DOI: 10.1006/jcph.1997.5773](https://doi.org/10.1006/jcph.1997.5773) -""" -@inline function calc_fast_wavespeed_roe(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations2D) - 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 - - # Calculate primitive variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - kin_en_ll = 0.5f0 * (rho_v1_ll * v1_ll + rho_v2_ll * v2_ll + rho_v3_ll * v3_ll) - mag_norm_ll = B1_ll * B1_ll + B2_ll * B2_ll + B3_ll * B3_ll - p_ll = (equations.gamma - 1) * - (rho_e_ll - kin_en_ll - 0.5f0 * mag_norm_ll - 0.5f0 * psi_ll^2) - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - kin_en_rr = 0.5f0 * (rho_v1_rr * v1_rr + rho_v2_rr * v2_rr + rho_v3_rr * v3_rr) - mag_norm_rr = B1_rr * B1_rr + B2_rr * B2_rr + B3_rr * B3_rr - p_rr = (equations.gamma - 1) * - (rho_e_rr - kin_en_rr - 0.5f0 * mag_norm_rr - 0.5f0 * psi_rr^2) - - # compute total pressure which is thermal + magnetic pressures - p_total_ll = p_ll + 0.5f0 * mag_norm_ll - p_total_rr = p_rr + 0.5f0 * mag_norm_rr - - # compute the Roe density averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) - inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) - rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add - rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add - # Roe averages - # velocities and magnetic fields - v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe - v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe - v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe - B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe - B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe - B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe - # enthalpy - H_ll = (rho_e_ll + p_total_ll) / rho_ll - H_rr = (rho_e_rr + p_total_rr) / rho_rr - H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe - # temporary variable see equation (4.12) in Cargo and Gallice - X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * - inv_sqrt_rho_add^2 - # averaged components needed to compute c_f, the fast magnetoacoustic wave speed - b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum - a_square_roe = ((2 - equations.gamma) * X + - (equations.gamma - 1) * - (H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - - b_square_roe)) # acoustic speed - # finally compute the average wave speed and set the output velocity (depends on orientation) - if orientation == 1 # x-direction - c_a_roe = B1_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed - a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - - 4 * a_square_roe * c_a_roe) - c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) - vel_out_roe = v1_roe - else # y-direction - c_a_roe = B2_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed - a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - - 4 * a_square_roe * c_a_roe) - c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) - vel_out_roe = v2_roe + + # Calculate thermodynamic entropy for a conservative state `cons` + @inline function entropy_thermodynamic(cons, equations::IdealGlmMhdEquations2D) + # Pressure + p = (equations.gamma - 1) * + ( + cons[5] - 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] + - + 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) + - + 0.5f0 * cons[9]^2 + ) + + # Thermodynamic entropy + s = log(p) - equations.gamma * log(cons[1]) + + return s end - return vel_out_roe, c_f_roe -end - -@inline function calc_fast_wavespeed_roe(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations2D) - 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 - - # Calculate primitive variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - kin_en_ll = 0.5f0 * (rho_v1_ll * v1_ll + rho_v2_ll * v2_ll + rho_v3_ll * v3_ll) - mag_norm_ll = B1_ll * B1_ll + B2_ll * B2_ll + B3_ll * B3_ll - p_ll = (equations.gamma - 1) * - (rho_e_ll - kin_en_ll - 0.5f0 * mag_norm_ll - 0.5f0 * psi_ll^2) - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - kin_en_rr = 0.5f0 * (rho_v1_rr * v1_rr + rho_v2_rr * v2_rr + rho_v3_rr * v3_rr) - mag_norm_rr = B1_rr * B1_rr + B2_rr * B2_rr + B3_rr * B3_rr - p_rr = (equations.gamma - 1) * - (rho_e_rr - kin_en_rr - 0.5f0 * mag_norm_rr - 0.5f0 * psi_rr^2) - - # compute total pressure which is thermal + magnetic pressures - p_total_ll = p_ll + 0.5f0 * mag_norm_ll - p_total_rr = p_rr + 0.5f0 * mag_norm_rr - - # compute the Roe density averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) - inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) - rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add - rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add - # Roe averages - # velocities and magnetic fields - v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe - v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe - v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe - B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe - B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe - B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe - # enthalpy - H_ll = (rho_e_ll + p_total_ll) / rho_ll - H_rr = (rho_e_rr + p_total_rr) / rho_rr - H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe - # temporary variable see equation (4.12) in Cargo and Gallice - X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * - inv_sqrt_rho_add^2 - # averaged components needed to compute c_f, the fast magnetoacoustic wave speed - b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum - a_square_roe = ((2 - equations.gamma) * X + - (equations.gamma - 1) * - (H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - - b_square_roe)) # acoustic speed - - # finally compute the average wave speed and set the output velocity (depends on orientation) - norm_squared = (normal_direction[1] * normal_direction[1] + - normal_direction[2] * normal_direction[2]) - B_roe_dot_n_squared = (B1_roe * normal_direction[1] + - B2_roe * normal_direction[2])^2 / norm_squared - - c_a_roe = B_roe_dot_n_squared * inv_sqrt_rho_prod # (squared) Alfvén wave speed - a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - 4 * a_square_roe * c_a_roe) - c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe) * norm_squared) - vel_out_roe = (v1_roe * normal_direction[1] + - v2_roe * normal_direction[2]) - - return vel_out_roe, c_f_roe -end - -# Calculate thermodynamic entropy for a conservative state `cons` -@inline function entropy_thermodynamic(cons, equations::IdealGlmMhdEquations2D) - # Pressure - p = (equations.gamma - 1) * - (cons[5] - 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] - - - 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) - - - 0.5f0 * cons[9]^2) - - # Thermodynamic entropy - s = log(p) - equations.gamma * log(cons[1]) - - return s -end - -# Calculate mathematical entropy for a conservative state `cons` -@inline function entropy_math(cons, equations::IdealGlmMhdEquations2D) - S = -entropy_thermodynamic(cons, equations) * cons[1] * - equations.inv_gamma_minus_one - - return S -end - -# Default entropy is the mathematical entropy -@inline entropy(cons, equations::IdealGlmMhdEquations2D) = entropy_math(cons, equations) - -# Calculate total energy for a conservative state `cons` -@inline energy_total(cons, ::IdealGlmMhdEquations2D) = cons[5] - -# Calculate kinetic energy for a conservative state `cons` -@inline function energy_kinetic(cons, equations::IdealGlmMhdEquations2D) - return 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] -end - -# Calculate the magnetic energy for a conservative state `cons'. -# OBS! For non-dinmensional form of the ideal MHD magnetic pressure ≡ magnetic energy -@inline function energy_magnetic(cons, ::IdealGlmMhdEquations2D) - return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) -end - -# Calculate internal energy for a conservative state `cons` -@inline function energy_internal(cons, equations::IdealGlmMhdEquations2D) - return (energy_total(cons, equations) - - - energy_kinetic(cons, equations) - - - energy_magnetic(cons, equations) - - - cons[9]^2 / 2) -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 - return false + # Calculate mathematical entropy for a conservative state `cons` + @inline function entropy_math(cons, equations::IdealGlmMhdEquations2D) + S = -entropy_thermodynamic(cons, equations) * cons[1] * + equations.inv_gamma_minus_one + + return S end - return true -end -# Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons' -@inline function cross_helicity(cons, ::IdealGlmMhdEquations2D) - return (cons[2] * cons[6] + cons[3] * cons[7] + cons[4] * cons[8]) / cons[1] -end + # Default entropy is the mathematical entropy + @inline entropy(cons, equations::IdealGlmMhdEquations2D) = entropy_math(cons, equations) + + # Calculate total energy for a conservative state `cons` + @inline energy_total(cons, ::IdealGlmMhdEquations2D) = cons[5] + + # Calculate kinetic energy for a conservative state `cons` + @inline function energy_kinetic(cons, equations::IdealGlmMhdEquations2D) + return 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] + end + + # Calculate the magnetic energy for a conservative state `cons'. + # OBS! For non-dinmensional form of the ideal MHD magnetic pressure ≡ magnetic energy + @inline function energy_magnetic(cons, ::IdealGlmMhdEquations2D) + return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) + end + + # Calculate internal energy for a conservative state `cons` + @inline function energy_internal(cons, equations::IdealGlmMhdEquations2D) + return ( + energy_total(cons, equations) + - + energy_kinetic(cons, equations) + - + energy_magnetic(cons, equations) + - + cons[9]^2 / 2 + ) + 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 + return false + end + return true + end + + # Calculate the cross helicity (\vec{v}⋅\vec{B}) for a conservative state `cons' + @inline function cross_helicity(cons, ::IdealGlmMhdEquations2D) + return (cons[2] * cons[6] + cons[3] * cons[7] + cons[4] * cons[8]) / cons[1] + end end # @muladd diff --git a/src/equations/ideal_glm_mhd_3d.jl b/src/equations/ideal_glm_mhd_3d.jl index 2ffaa57524..f901c23495 100644 --- a/src/equations/ideal_glm_mhd_3d.jl +++ b/src/equations/ideal_glm_mhd_3d.jl @@ -3,1299 +3,1473 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - IdealGlmMhdEquations3D(gamma) - -The ideal compressible GLM-MHD equations for an ideal gas with ratio of -specific heats `gamma` in three space dimensions. -""" -mutable struct IdealGlmMhdEquations3D{RealT <: Real} <: - AbstractIdealGlmMhdEquations{3, 9} - gamma::RealT # ratio of specific heats - inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications - c_h::RealT # GLM cleaning speed - - function IdealGlmMhdEquations3D(gamma, c_h) - γ, inv_gamma_minus_one, c_h = promote(gamma, inv(gamma - 1), c_h) - new{typeof(γ)}(γ, inv_gamma_minus_one, c_h) + #! format: noindent + + @doc raw""" + IdealGlmMhdEquations3D(gamma) + + The ideal compressible GLM-MHD equations for an ideal gas with ratio of + specific heats `gamma` in three space dimensions. + """ + mutable struct IdealGlmMhdEquations3D{RealT <: Real} <: + AbstractIdealGlmMhdEquations{3, 9} + gamma::RealT # ratio of specific heats + inv_gamma_minus_one::RealT # = inv(gamma - 1); can be used to write slow divisions as fast multiplications + c_h::RealT # GLM cleaning speed + + function IdealGlmMhdEquations3D(gamma, c_h) + γ, inv_gamma_minus_one, c_h = promote(gamma, inv(gamma - 1), c_h) + new{typeof(γ)}(γ, inv_gamma_minus_one, c_h) + end end -end - -function IdealGlmMhdEquations3D(gamma; initial_c_h = convert(typeof(gamma), NaN)) - # Use `promote` to ensure that `gamma` and `initial_c_h` have the same type - IdealGlmMhdEquations3D(promote(gamma, initial_c_h)...) -end - -have_nonconservative_terms(::IdealGlmMhdEquations3D) = True() -function varnames(::typeof(cons2cons), ::IdealGlmMhdEquations3D) - ("rho", "rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3", "psi") -end -function varnames(::typeof(cons2prim), ::IdealGlmMhdEquations3D) - ("rho", "v1", "v2", "v3", "p", "B1", "B2", "B3", "psi") -end -function default_analysis_integrals(::IdealGlmMhdEquations3D) - (entropy_timederivative, Val(:l2_divb), Val(:linf_divb)) -end - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::IdealGlmMhdEquations3D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equations::IdealGlmMhdEquations3D) - RealT = eltype(x) - rho = 1 - rho_v1 = convert(RealT, 0.1) - rho_v2 = -convert(RealT, 0.2) - rho_v3 = -0.5f0 - rho_e = 50 - B1 = 3 - B2 = -convert(RealT, 1.2) - B3 = 0.5f0 - psi = 0 - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi) -end - -""" - initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations3D) - -An Alfvén wave as smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations3D) - # Alfvén wave in three space dimensions - # Altmann thesis http://dx.doi.org/10.18419/opus-3895 - # domain must be set to [-1, 1]^3, γ = 5/3 - RealT = eltype(x) - p = 1 - omega = 2 * convert(RealT, pi) # may be multiplied by frequency - # r: length-variable = length of computational domain - r = convert(RealT, 2) - # e: epsilon = 0.2 - e = convert(RealT, 0.2) - nx = 1 / sqrt(r^2 + 1) - ny = r / sqrt(r^2 + 1) - sqr = 1 - Va = omega / (ny * sqr) - phi_alv = omega / ny * (nx * (x[1] - 0.5f0 * r) + ny * (x[2] - 0.5f0 * r)) - Va * t - - rho = 1 - v1 = -e * ny * cos(phi_alv) / rho - v2 = e * nx * cos(phi_alv) / rho - v3 = e * sin(phi_alv) / rho - B1 = nx - rho * v1 * sqr - B2 = ny - rho * v2 * sqr - B3 = -rho * v3 * sqr - psi = 0 - - return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3, psi), equations) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations3D) - -A weak blast wave adapted from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations3D) - # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Same discontinuity in the velocities but with magnetic fields - # Set up polar coordinates - RealT = eltype(x) - inicenter = (0, 0, 0) - 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) - phi = atan(y_norm, x_norm) - theta = iszero(r) ? zero(RealT) : acos(z_norm / r) - - # Calculate primitive variables - rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) * sin(theta) - v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin(phi) * sin(theta) - v3 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(theta) - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - return prim2cons(SVector(rho, v1, v2, v3, p, 1, 1, 1, 0), equations) -end - -# Pre-defined source terms should be implemented as -# function source_terms_WHATEVER(u, x, t, equations::IdealGlmMhdEquations3D) - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, 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 - kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) - p_over_gamma_minus_one = (rho_e - kin_en - mag_en - 0.5f0 * psi^2) - p = (equations.gamma - 1) * p_over_gamma_minus_one - if orientation == 1 - f1 = rho_v1 - f2 = rho_v1 * v1 + p + mag_en - B1^2 - f3 = rho_v1 * v2 - B1 * B2 - f4 = rho_v1 * v3 - B1 * B3 - f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v1 - - B1 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B1 - f6 = equations.c_h * psi - f7 = v1 * B2 - v2 * B1 - f8 = v1 * B3 - v3 * B1 - f9 = equations.c_h * B1 - elseif orientation == 2 - f1 = rho_v2 - f2 = rho_v2 * v1 - B2 * B1 - f3 = rho_v2 * v2 + p + mag_en - B2^2 - f4 = rho_v2 * v3 - B2 * B3 - f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v2 - - B2 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B2 - f6 = v2 * B1 - v1 * B2 - f7 = equations.c_h * psi - f8 = v2 * B3 - v3 * B2 - f9 = equations.c_h * B2 - else - f1 = rho_v3 - f2 = rho_v3 * v1 - B3 * B1 - f3 = rho_v3 * v2 - B3 * B2 - f4 = rho_v3 * v3 + p + mag_en - B3^2 - f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v3 - - B3 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B3 - f6 = v3 * B1 - v1 * B3 - f7 = v3 * B2 - v2 * B3 - f8 = equations.c_h * psi - f9 = equations.c_h * B3 + + function IdealGlmMhdEquations3D(gamma; initial_c_h = convert(typeof(gamma), NaN)) + # Use `promote` to ensure that `gamma` and `initial_c_h` have the same type + IdealGlmMhdEquations3D(promote(gamma, initial_c_h)...) end - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -# Calculate 1D flux for a single point in the normal direction -# Note, this directional vector is not normalized -@inline function flux(u, normal_direction::AbstractVector, - 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 - kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) - p_over_gamma_minus_one = (rho_e - kin_en - mag_en - 0.5f0 * psi^2) - p = (equations.gamma - 1) * p_over_gamma_minus_one - - v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + - v3 * normal_direction[3] - B_normal = B1 * normal_direction[1] + B2 * normal_direction[2] + - B3 * normal_direction[3] - rho_v_normal = rho * v_normal - - f1 = rho_v_normal - f2 = rho_v_normal * v1 - B1 * B_normal + (p + mag_en) * normal_direction[1] - f3 = rho_v_normal * v2 - B2 * B_normal + (p + mag_en) * normal_direction[2] - f4 = rho_v_normal * v3 - B3 * B_normal + (p + mag_en) * normal_direction[3] - f5 = ((kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v_normal - - - B_normal * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B_normal) - f6 = (equations.c_h * psi * normal_direction[1] + - (v2 * B1 - v1 * B2) * normal_direction[2] + - (v3 * B1 - v1 * B3) * normal_direction[3]) - f7 = ((v1 * B2 - v2 * B1) * normal_direction[1] + - equations.c_h * psi * normal_direction[2] + - (v3 * B2 - v2 * B3) * normal_direction[3]) - f8 = ((v1 * B3 - v3 * B1) * normal_direction[1] + - (v2 * B3 - v3 * B2) * normal_direction[2] + - equations.c_h * psi * normal_direction[3]) - f9 = equations.c_h * B_normal - - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -""" - flux_nonconservative_powell(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations3D) - flux_nonconservative_powell(u_ll, u_rr, - normal_direction_ll ::AbstractVector, - normal_direction_average::AbstractVector, - equations::IdealGlmMhdEquations3D) + have_nonconservative_terms(::IdealGlmMhdEquations3D) = True() + function varnames(::typeof(cons2cons), ::IdealGlmMhdEquations3D) + ("rho", "rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3", "psi") + end + function varnames(::typeof(cons2prim), ::IdealGlmMhdEquations3D) + ("rho", "v1", "v2", "v3", "p", "B1", "B2", "B3", "psi") + end + function default_analysis_integrals(::IdealGlmMhdEquations3D) + (entropy_timederivative, Val(:l2_divb), Val(:linf_divb)) + end -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, this nonconservative flux depends on both the -contravariant vector (normal direction) at the current node and the averaged -one. This is different from numerical fluxes used to discretize conservative -terms. - -## References -- Marvin Bohm, Andrew R.Winters, Gregor J. Gassner, Dominik Derigs, - Florian Hindenlang, Joachim Saur - An entropy stable nodal discontinuous Galerkin method for the resistive MHD - equations. Part I: Theory and numerical verification - [DOI: 10.1016/j.jcp.2018.06.027](https://doi.org/10.1016/j.jcp.2018.06.027) -""" -@inline function flux_nonconservative_powell(u_ll, u_rr, orientation::Integer, - 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 - - # 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}) - if orientation == 1 - f = SVector(0, - B1_ll * B1_rr, - B2_ll * B1_rr, - B3_ll * B1_rr, - v_dot_B_ll * B1_rr + v1_ll * psi_ll * psi_rr, - v1_ll * B1_rr, - v2_ll * B1_rr, - v3_ll * B1_rr, - v1_ll * psi_rr) - elseif orientation == 2 - f = SVector(0, - B1_ll * B2_rr, - B2_ll * B2_rr, - B3_ll * B2_rr, - v_dot_B_ll * B2_rr + v2_ll * psi_ll * psi_rr, - v1_ll * B2_rr, - v2_ll * B2_rr, - v3_ll * B2_rr, - v2_ll * psi_rr) - else # orientation == 3 - f = SVector(0, - B1_ll * B3_rr, - B2_ll * B3_rr, - B3_ll * B3_rr, - v_dot_B_ll * B3_rr + v3_ll * psi_ll * psi_rr, - v1_ll * B3_rr, - v2_ll * B3_rr, - v3_ll * B3_rr, - v3_ll * psi_rr) + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::IdealGlmMhdEquations3D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equations::IdealGlmMhdEquations3D) + RealT = eltype(x) + rho = 1 + rho_v1 = convert(RealT, 0.1) + rho_v2 = -convert(RealT, 0.2) + rho_v3 = -0.5f0 + rho_e = 50 + B1 = 3 + B2 = -convert(RealT, 1.2) + B3 = 0.5f0 + psi = 0 + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi) end - return f -end - -@inline function flux_nonconservative_powell(u_ll, u_rr, - normal_direction_ll::AbstractVector, - normal_direction_average::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 - - # Note that `v_dot_n_ll` uses the `normal_direction_ll` (contravariant vector - # at the same node location) while `B_dot_n_rr` uses the averaged normal - # direction. The reason for this is that `v_dot_n_ll` depends only on the left - # state and multiplies some gradient while `B_dot_n_rr` is used to compute - # the divergence of B. - v_dot_n_ll = v1_ll * normal_direction_ll[1] + v2_ll * normal_direction_ll[2] + - v3_ll * normal_direction_ll[3] - B_dot_n_rr = B1_rr * normal_direction_average[1] + - B2_rr * normal_direction_average[2] + - B3_rr * normal_direction_average[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_rr, - B2_ll * B_dot_n_rr, - B3_ll * B_dot_n_rr, - v_dot_B_ll * B_dot_n_rr + v_dot_n_ll * psi_ll * psi_rr, - v1_ll * B_dot_n_rr, - v2_ll * B_dot_n_rr, - v3_ll * B_dot_n_rr, - v_dot_n_ll * psi_rr) - - return f -end - -""" - flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations3D) - -Entropy conserving two-point flux by -- Derigs et al. (2018) - Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field - divergence diminishing ideal magnetohydrodynamics equations - [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) -""" -function flux_derigs_etal(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations3D) - # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) - rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim(u_ll, - equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim(u_rr, - equations) - - vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 - vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 - mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 - mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 - beta_ll = 0.5f0 * rho_ll / p_ll - beta_rr = 0.5f0 * rho_rr / p_rr - # for convenience store v⋅B - vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll - vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr - - # Compute the necessary mean values needed for either direction - rho_avg = 0.5f0 * (rho_ll + rho_rr) - rho_mean = ln_mean(rho_ll, rho_rr) - beta_mean = ln_mean(beta_ll, beta_rr) - beta_avg = 0.5f0 * (beta_ll + beta_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_mean = 0.5f0 * rho_avg / beta_avg - B1_avg = 0.5f0 * (B1_ll + B1_rr) - B2_avg = 0.5f0 * (B2_ll + B2_rr) - B3_avg = 0.5f0 * (B3_ll + B3_rr) - psi_avg = 0.5f0 * (psi_ll + psi_rr) - vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) - mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) - vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) - - # Calculate fluxes depending on orientation with specific direction averages - if orientation == 1 - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_mean + 0.5f0 * mag_norm_avg - B1_avg * B1_avg - f3 = f1 * v2_avg - B1_avg * B2_avg - f4 = f1 * v3_avg - B1_avg * B3_avg - f6 = equations.c_h * psi_avg - f7 = v1_avg * B2_avg - v2_avg * B1_avg - f8 = v1_avg * B3_avg - v3_avg * B1_avg - f9 = equations.c_h * B1_avg - # total energy flux is complicated and involves the previous eight components - psi_B1_avg = 0.5f0 * (B1_ll * psi_ll + B1_rr * psi_rr) - v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) - f5 = (f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + - f2 * v1_avg + f3 * v2_avg + - f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - - 0.5f0 * v1_mag_avg + - B1_avg * vel_dot_mag_avg - equations.c_h * psi_B1_avg) - elseif orientation == 2 - f1 = rho_mean * v2_avg - f2 = f1 * v1_avg - B2_avg * B1_avg - f3 = f1 * v2_avg + p_mean + 0.5f0 * mag_norm_avg - B2_avg * B2_avg - f4 = f1 * v3_avg - B2_avg * B3_avg - f6 = v2_avg * B1_avg - v1_avg * B2_avg - f7 = equations.c_h * psi_avg - f8 = v2_avg * B3_avg - v3_avg * B2_avg - f9 = equations.c_h * B2_avg - # total energy flux is complicated and involves the previous eight components - psi_B2_avg = 0.5f0 * (B2_ll * psi_ll + B2_rr * psi_rr) - v2_mag_avg = 0.5f0 * (v2_ll * mag_norm_ll + v2_rr * mag_norm_rr) - f5 = (f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + - f2 * v1_avg + f3 * v2_avg + - f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - - 0.5f0 * v2_mag_avg + - B2_avg * vel_dot_mag_avg - equations.c_h * psi_B2_avg) - else - f1 = rho_mean * v3_avg - f2 = f1 * v1_avg - B3_avg * B1_avg - f3 = f1 * v2_avg - B3_avg * B2_avg - f4 = f1 * v3_avg + p_mean + 0.5f0 * mag_norm_avg - B3_avg * B3_avg - f6 = v3_avg * B1_avg - v1_avg * B3_avg - f7 = v3_avg * B2_avg - v2_avg * B3_avg - f8 = equations.c_h * psi_avg - f9 = equations.c_h * B3_avg - # total energy flux is complicated and involves the previous eight components - psi_B3_avg = 0.5f0 * (B3_ll * psi_ll + B3_rr * psi_rr) - v3_mag_avg = 0.5f0 * (v3_ll * mag_norm_ll + v3_rr * mag_norm_rr) - f5 = (f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + - f2 * v1_avg + f3 * v2_avg + - f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - - 0.5f0 * v3_mag_avg + - B3_avg * vel_dot_mag_avg - equations.c_h * psi_B3_avg) + """ + initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations3D) + + An Alfvén wave as smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test(x, t, equations::IdealGlmMhdEquations3D) + # Alfvén wave in three space dimensions + # Altmann thesis http://dx.doi.org/10.18419/opus-3895 + # domain must be set to [-1, 1]^3, γ = 5/3 + RealT = eltype(x) + p = 1 + omega = 2 * convert(RealT, pi) # may be multiplied by frequency + # r: length-variable = length of computational domain + r = convert(RealT, 2) + # e: epsilon = 0.2 + e = convert(RealT, 0.2) + nx = 1 / sqrt(r^2 + 1) + ny = r / sqrt(r^2 + 1) + sqr = 1 + Va = omega / (ny * sqr) + phi_alv = omega / ny * (nx * (x[1] - 0.5f0 * r) + ny * (x[2] - 0.5f0 * r)) - Va * t + + rho = 1 + v1 = -e * ny * cos(phi_alv) / rho + v2 = e * nx * cos(phi_alv) / rho + v3 = e * sin(phi_alv) / rho + B1 = nx - rho * v1 * sqr + B2 = ny - rho * v2 * sqr + B3 = -rho * v3 * sqr + psi = 0 + + return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3, psi), equations) end - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -""" - flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, - equations::IdealGlmMhdEquations3D) - -Entropy conserving and kinetic energy preserving two-point flux of -Hindenlang and Gassner (2019), extending [`flux_ranocha`](@ref) to the MHD equations. - -## References -- Florian Hindenlang, Gregor Gassner (2019) - A new entropy conservative two-point flux for ideal MHD equations derived from - first principles. - Presented at HONOM 2019: European workshop on high order numerical methods - for evolutionary PDEs, theory and applications -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_hindenlang_gassner(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations3D) - # Unpack left and right states - rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim(u_ll, - equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim(u_rr, - equations) - - # Compute the necessary mean values needed for either direction - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - psi_avg = 0.5f0 * (psi_ll + psi_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) - - # Calculate fluxes depending on orientation with specific direction averages - if orientation == 1 - f1 = rho_mean * v1_avg - f2 = f1 * v1_avg + p_avg + magnetic_square_avg - - 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) - f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) - f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) - #f5 below - f6 = equations.c_h * psi_avg - f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) - f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) - f9 = equations.c_h * 0.5f0 * (B1_ll + B1_rr) - # total energy flux is complicated and involves the previous components - f5 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v1_rr + p_rr * v1_ll - + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) - + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) - - - (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) - - - (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll) - + - equations.c_h * (B1_ll * psi_rr + B1_rr * psi_ll))) - elseif orientation == 2 - f1 = rho_mean * v2_avg - f2 = f1 * v1_avg - 0.5f0 * (B2_ll * B1_rr + B2_rr * B1_ll) - f3 = f1 * v2_avg + p_avg + magnetic_square_avg - - 0.5f0 * (B2_ll * B2_rr + B2_rr * B2_ll) - f4 = f1 * v3_avg - 0.5f0 * (B2_ll * B3_rr + B2_rr * B3_ll) - #f5 below - f6 = 0.5f0 * (v2_ll * B1_ll - v1_ll * B2_ll + v2_rr * B1_rr - v1_rr * B2_rr) - f7 = equations.c_h * psi_avg - f8 = 0.5f0 * (v2_ll * B3_ll - v3_ll * B2_ll + v2_rr * B3_rr - v3_rr * B2_rr) - f9 = equations.c_h * 0.5f0 * (B2_ll + B2_rr) - # total energy flux is complicated and involves the previous components - f5 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v2_rr + p_rr * v2_ll - + (v2_ll * B1_ll * B1_rr + v2_rr * B1_rr * B1_ll) - + (v2_ll * B3_ll * B3_rr + v2_rr * B3_rr * B3_ll) - - - (v1_ll * B2_ll * B1_rr + v1_rr * B2_rr * B1_ll) - - - (v3_ll * B2_ll * B3_rr + v3_rr * B2_rr * B3_ll) - + - equations.c_h * (B2_ll * psi_rr + B2_rr * psi_ll))) - else # orientation == 3 - f1 = rho_mean * v3_avg - f2 = f1 * v1_avg - 0.5f0 * (B3_ll * B1_rr + B3_rr * B1_ll) - f3 = f1 * v2_avg - 0.5f0 * (B3_ll * B2_rr + B3_rr * B2_ll) - f4 = f1 * v3_avg + p_avg + magnetic_square_avg - - 0.5f0 * (B3_ll * B3_rr + B3_rr * B3_ll) + """ + initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations3D) + + A weak blast wave adapted from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdEquations3D) + # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Same discontinuity in the velocities but with magnetic fields + # Set up polar coordinates + RealT = eltype(x) + inicenter = (0, 0, 0) + 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) + phi = atan(y_norm, x_norm) + theta = iszero(r) ? zero(RealT) : acos(z_norm / r) + + # Calculate primitive variables + rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) * sin(theta) + v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin(phi) * sin(theta) + v3 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(theta) + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + return prim2cons(SVector(rho, v1, v2, v3, p, 1, 1, 1, 0), equations) + end + + # Pre-defined source terms should be implemented as + # function source_terms_WHATEVER(u, x, t, equations::IdealGlmMhdEquations3D) + + # Calculate 1D flux in for a single point + @inline function flux(u, orientation::Integer, 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 + kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) + p_over_gamma_minus_one = (rho_e - kin_en - mag_en - 0.5f0 * psi^2) + p = (equations.gamma - 1) * p_over_gamma_minus_one + if orientation == 1 + f1 = rho_v1 + f2 = rho_v1 * v1 + p + mag_en - B1^2 + f3 = rho_v1 * v2 - B1 * B2 + f4 = rho_v1 * v3 - B1 * B3 + f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v1 - + B1 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B1 + f6 = equations.c_h * psi + f7 = v1 * B2 - v2 * B1 + f8 = v1 * B3 - v3 * B1 + f9 = equations.c_h * B1 + elseif orientation == 2 + f1 = rho_v2 + f2 = rho_v2 * v1 - B2 * B1 + f3 = rho_v2 * v2 + p + mag_en - B2^2 + f4 = rho_v2 * v3 - B2 * B3 + f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v2 - + B2 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B2 + f6 = v2 * B1 - v1 * B2 + f7 = equations.c_h * psi + f8 = v2 * B3 - v3 * B2 + f9 = equations.c_h * B2 + else + f1 = rho_v3 + f2 = rho_v3 * v1 - B3 * B1 + f3 = rho_v3 * v2 - B3 * B2 + f4 = rho_v3 * v3 + p + mag_en - B3^2 + f5 = (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v3 - + B3 * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B3 + f6 = v3 * B1 - v1 * B3 + f7 = v3 * B2 - v2 * B3 + f8 = equations.c_h * psi + f9 = equations.c_h * B3 + end + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) + end + + # Calculate 1D flux for a single point in the normal direction + # Note, this directional vector is not normalized + @inline function flux( + u, normal_direction::AbstractVector, + 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 + kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) + p_over_gamma_minus_one = (rho_e - kin_en - mag_en - 0.5f0 * psi^2) + p = (equations.gamma - 1) * p_over_gamma_minus_one + + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + + v3 * normal_direction[3] + B_normal = B1 * normal_direction[1] + B2 * normal_direction[2] + + B3 * normal_direction[3] + rho_v_normal = rho * v_normal + + f1 = rho_v_normal + f2 = rho_v_normal * v1 - B1 * B_normal + (p + mag_en) * normal_direction[1] + f3 = rho_v_normal * v2 - B2 * B_normal + (p + mag_en) * normal_direction[2] + f4 = rho_v_normal * v3 - B3 * B_normal + (p + mag_en) * normal_direction[3] + f5 = ( + (kin_en + equations.gamma * p_over_gamma_minus_one + 2 * mag_en) * v_normal + - + B_normal * (v1 * B1 + v2 * B2 + v3 * B3) + equations.c_h * psi * B_normal + ) + f6 = ( + equations.c_h * psi * normal_direction[1] + + (v2 * B1 - v1 * B2) * normal_direction[2] + + (v3 * B1 - v1 * B3) * normal_direction[3] + ) + f7 = ( + (v1 * B2 - v2 * B1) * normal_direction[1] + + equations.c_h * psi * normal_direction[2] + + (v3 * B2 - v2 * B3) * normal_direction[3] + ) + f8 = ( + (v1 * B3 - v3 * B1) * normal_direction[1] + + (v2 * B3 - v3 * B2) * normal_direction[2] + + equations.c_h * psi * normal_direction[3] + ) + f9 = equations.c_h * B_normal + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) + end + + """ + flux_nonconservative_powell(u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations3D) + flux_nonconservative_powell(u_ll, u_rr, + normal_direction_ll ::AbstractVector, + normal_direction_average::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). + + On curvilinear meshes, this nonconservative flux depends on both the + contravariant vector (normal direction) at the current node and the averaged + one. This is different from numerical fluxes used to discretize conservative + terms. + + ## References + - Marvin Bohm, Andrew R.Winters, Gregor J. Gassner, Dominik Derigs, + Florian Hindenlang, Joachim Saur + An entropy stable nodal discontinuous Galerkin method for the resistive MHD + equations. Part I: Theory and numerical verification + [DOI: 10.1016/j.jcp.2018.06.027](https://doi.org/10.1016/j.jcp.2018.06.027) + """ + @inline function flux_nonconservative_powell( + u_ll, u_rr, orientation::Integer, + 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 + + # 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}) + if orientation == 1 + f = SVector( + 0, + B1_ll * B1_rr, + B2_ll * B1_rr, + B3_ll * B1_rr, + v_dot_B_ll * B1_rr + v1_ll * psi_ll * psi_rr, + v1_ll * B1_rr, + v2_ll * B1_rr, + v3_ll * B1_rr, + v1_ll * psi_rr + ) + elseif orientation == 2 + f = SVector( + 0, + B1_ll * B2_rr, + B2_ll * B2_rr, + B3_ll * B2_rr, + v_dot_B_ll * B2_rr + v2_ll * psi_ll * psi_rr, + v1_ll * B2_rr, + v2_ll * B2_rr, + v3_ll * B2_rr, + v2_ll * psi_rr + ) + else # orientation == 3 + f = SVector( + 0, + B1_ll * B3_rr, + B2_ll * B3_rr, + B3_ll * B3_rr, + v_dot_B_ll * B3_rr + v3_ll * psi_ll * psi_rr, + v1_ll * B3_rr, + v2_ll * B3_rr, + v3_ll * B3_rr, + v3_ll * psi_rr + ) + end + + return f + end + + @inline function flux_nonconservative_powell( + u_ll, u_rr, + normal_direction_ll::AbstractVector, + normal_direction_average::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 + + # Note that `v_dot_n_ll` uses the `normal_direction_ll` (contravariant vector + # at the same node location) while `B_dot_n_rr` uses the averaged normal + # direction. The reason for this is that `v_dot_n_ll` depends only on the left + # state and multiplies some gradient while `B_dot_n_rr` is used to compute + # the divergence of B. + v_dot_n_ll = v1_ll * normal_direction_ll[1] + v2_ll * normal_direction_ll[2] + + v3_ll * normal_direction_ll[3] + B_dot_n_rr = B1_rr * normal_direction_average[1] + + B2_rr * normal_direction_average[2] + + B3_rr * normal_direction_average[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_rr, + B2_ll * B_dot_n_rr, + B3_ll * B_dot_n_rr, + v_dot_B_ll * B_dot_n_rr + v_dot_n_ll * psi_ll * psi_rr, + v1_ll * B_dot_n_rr, + v2_ll * B_dot_n_rr, + v3_ll * B_dot_n_rr, + v_dot_n_ll * psi_rr + ) + + return f + end + + """ + flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations3D) + + Entropy conserving two-point flux by + - Derigs et al. (2018) + Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field + divergence diminishing ideal magnetohydrodynamics equations + [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) + """ + function flux_derigs_etal( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations3D + ) + # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) + rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim( + u_ll, + equations + ) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim( + u_rr, + equations + ) + + vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 + vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 + mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 + mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 + beta_ll = 0.5f0 * rho_ll / p_ll + beta_rr = 0.5f0 * rho_rr / p_rr + # for convenience store v⋅B + vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll + vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr + + # Compute the necessary mean values needed for either direction + rho_avg = 0.5f0 * (rho_ll + rho_rr) + rho_mean = ln_mean(rho_ll, rho_rr) + beta_mean = ln_mean(beta_ll, beta_rr) + beta_avg = 0.5f0 * (beta_ll + beta_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_mean = 0.5f0 * rho_avg / beta_avg + B1_avg = 0.5f0 * (B1_ll + B1_rr) + B2_avg = 0.5f0 * (B2_ll + B2_rr) + B3_avg = 0.5f0 * (B3_ll + B3_rr) + psi_avg = 0.5f0 * (psi_ll + psi_rr) + vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) + mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) + vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) + + # Calculate fluxes depending on orientation with specific direction averages + if orientation == 1 + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_mean + 0.5f0 * mag_norm_avg - B1_avg * B1_avg + f3 = f1 * v2_avg - B1_avg * B2_avg + f4 = f1 * v3_avg - B1_avg * B3_avg + f6 = equations.c_h * psi_avg + f7 = v1_avg * B2_avg - v2_avg * B1_avg + f8 = v1_avg * B3_avg - v3_avg * B1_avg + f9 = equations.c_h * B1_avg + # total energy flux is complicated and involves the previous eight components + psi_B1_avg = 0.5f0 * (B1_ll * psi_ll + B1_rr * psi_rr) + v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) + f5 = ( + f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + + f2 * v1_avg + f3 * v2_avg + + f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - + 0.5f0 * v1_mag_avg + + B1_avg * vel_dot_mag_avg - equations.c_h * psi_B1_avg + ) + elseif orientation == 2 + f1 = rho_mean * v2_avg + f2 = f1 * v1_avg - B2_avg * B1_avg + f3 = f1 * v2_avg + p_mean + 0.5f0 * mag_norm_avg - B2_avg * B2_avg + f4 = f1 * v3_avg - B2_avg * B3_avg + f6 = v2_avg * B1_avg - v1_avg * B2_avg + f7 = equations.c_h * psi_avg + f8 = v2_avg * B3_avg - v3_avg * B2_avg + f9 = equations.c_h * B2_avg + # total energy flux is complicated and involves the previous eight components + psi_B2_avg = 0.5f0 * (B2_ll * psi_ll + B2_rr * psi_rr) + v2_mag_avg = 0.5f0 * (v2_ll * mag_norm_ll + v2_rr * mag_norm_rr) + f5 = ( + f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + + f2 * v1_avg + f3 * v2_avg + + f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - + 0.5f0 * v2_mag_avg + + B2_avg * vel_dot_mag_avg - equations.c_h * psi_B2_avg + ) + else + f1 = rho_mean * v3_avg + f2 = f1 * v1_avg - B3_avg * B1_avg + f3 = f1 * v2_avg - B3_avg * B2_avg + f4 = f1 * v3_avg + p_mean + 0.5f0 * mag_norm_avg - B3_avg * B3_avg + f6 = v3_avg * B1_avg - v1_avg * B3_avg + f7 = v3_avg * B2_avg - v2_avg * B3_avg + f8 = equations.c_h * psi_avg + f9 = equations.c_h * B3_avg + # total energy flux is complicated and involves the previous eight components + psi_B3_avg = 0.5f0 * (B3_ll * psi_ll + B3_rr * psi_rr) + v3_mag_avg = 0.5f0 * (v3_ll * mag_norm_ll + v3_rr * mag_norm_rr) + f5 = ( + f1 * 0.5f0 * (1 / (equations.gamma - 1) / beta_mean - vel_norm_avg) + + f2 * v1_avg + f3 * v2_avg + + f4 * v3_avg + f6 * B1_avg + f7 * B2_avg + f8 * B3_avg + f9 * psi_avg - + 0.5f0 * v3_mag_avg + + B3_avg * vel_dot_mag_avg - equations.c_h * psi_B3_avg + ) + end + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) + end + + """ + flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, + equations::IdealGlmMhdEquations3D) + + Entropy conserving and kinetic energy preserving two-point flux of + Hindenlang and Gassner (2019), extending [`flux_ranocha`](@ref) to the MHD equations. + + ## References + - Florian Hindenlang, Gregor Gassner (2019) + A new entropy conservative two-point flux for ideal MHD equations derived from + first principles. + Presented at HONOM 2019: European workshop on high order numerical methods + for evolutionary PDEs, theory and applications + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_hindenlang_gassner( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations3D + ) + # Unpack left and right states + rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim( + u_ll, + equations + ) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim( + u_rr, + equations + ) + + # Compute the necessary mean values needed for either direction + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + psi_avg = 0.5f0 * (psi_ll + psi_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) + + # Calculate fluxes depending on orientation with specific direction averages + if orientation == 1 + f1 = rho_mean * v1_avg + f2 = f1 * v1_avg + p_avg + magnetic_square_avg - + 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) + f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) + f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) + #f5 below + f6 = equations.c_h * psi_avg + f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) + f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) + f9 = equations.c_h * 0.5f0 * (B1_ll + B1_rr) + # total energy flux is complicated and involves the previous components + f5 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v1_rr + p_rr * v1_ll + + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) + + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) + - + (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) + - + (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll) + + + equations.c_h * (B1_ll * psi_rr + B1_rr * psi_ll) + ) + ) + elseif orientation == 2 + f1 = rho_mean * v2_avg + f2 = f1 * v1_avg - 0.5f0 * (B2_ll * B1_rr + B2_rr * B1_ll) + f3 = f1 * v2_avg + p_avg + magnetic_square_avg - + 0.5f0 * (B2_ll * B2_rr + B2_rr * B2_ll) + f4 = f1 * v3_avg - 0.5f0 * (B2_ll * B3_rr + B2_rr * B3_ll) + #f5 below + f6 = 0.5f0 * (v2_ll * B1_ll - v1_ll * B2_ll + v2_rr * B1_rr - v1_rr * B2_rr) + f7 = equations.c_h * psi_avg + f8 = 0.5f0 * (v2_ll * B3_ll - v3_ll * B2_ll + v2_rr * B3_rr - v3_rr * B2_rr) + f9 = equations.c_h * 0.5f0 * (B2_ll + B2_rr) + # total energy flux is complicated and involves the previous components + f5 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v2_rr + p_rr * v2_ll + + (v2_ll * B1_ll * B1_rr + v2_rr * B1_rr * B1_ll) + + (v2_ll * B3_ll * B3_rr + v2_rr * B3_rr * B3_ll) + - + (v1_ll * B2_ll * B1_rr + v1_rr * B2_rr * B1_ll) + - + (v3_ll * B2_ll * B3_rr + v3_rr * B2_rr * B3_ll) + + + equations.c_h * (B2_ll * psi_rr + B2_rr * psi_ll) + ) + ) + else # orientation == 3 + f1 = rho_mean * v3_avg + f2 = f1 * v1_avg - 0.5f0 * (B3_ll * B1_rr + B3_rr * B1_ll) + f3 = f1 * v2_avg - 0.5f0 * (B3_ll * B2_rr + B3_rr * B2_ll) + f4 = f1 * v3_avg + p_avg + magnetic_square_avg - + 0.5f0 * (B3_ll * B3_rr + B3_rr * B3_ll) + #f5 below + f6 = 0.5f0 * (v3_ll * B1_ll - v1_ll * B3_ll + v3_rr * B1_rr - v1_rr * B3_rr) + f7 = 0.5f0 * (v3_ll * B2_ll - v2_ll * B3_ll + v3_rr * B2_rr - v2_rr * B3_rr) + f8 = equations.c_h * psi_avg + f9 = equations.c_h * 0.5f0 * (B3_ll + B3_rr) + # total energy flux is complicated and involves the previous components + f5 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v3_rr + p_rr * v3_ll + + (v3_ll * B1_ll * B1_rr + v3_rr * B1_rr * B1_ll) + + (v3_ll * B2_ll * B2_rr + v3_rr * B2_rr * B2_ll) + - + (v1_ll * B3_ll * B1_rr + v1_rr * B3_rr * B1_ll) + - + (v2_ll * B3_ll * B2_rr + v2_rr * B3_rr * B2_ll) + + + equations.c_h * (B3_ll * psi_rr + B3_rr * psi_ll) + ) + ) + end + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) + end + + @inline function flux_hindenlang_gassner( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations3D + ) + # Unpack left and right states + rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim( + u_ll, + equations + ) + rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim( + u_rr, + equations + ) + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + B_dot_n_ll = B1_ll * normal_direction[1] + B2_ll * normal_direction[2] + + B3_ll * normal_direction[3] + B_dot_n_rr = B1_rr * normal_direction[1] + B2_rr * normal_direction[2] + + B3_rr * normal_direction[3] + + # Compute the necessary mean values needed for either direction + rho_mean = ln_mean(rho_ll, rho_rr) + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + psi_avg = 0.5f0 * (psi_ll + psi_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) + + # Calculate fluxes depending on normal_direction + f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + f2 = ( + f1 * v1_avg + (p_avg + magnetic_square_avg) * normal_direction[1] + - + 0.5f0 * (B_dot_n_ll * B1_rr + B_dot_n_rr * B1_ll) + ) + f3 = ( + f1 * v2_avg + (p_avg + magnetic_square_avg) * normal_direction[2] + - + 0.5f0 * (B_dot_n_ll * B2_rr + B_dot_n_rr * B2_ll) + ) + f4 = ( + f1 * v3_avg + (p_avg + magnetic_square_avg) * normal_direction[3] + - + 0.5f0 * (B_dot_n_ll * B3_rr + B_dot_n_rr * B3_ll) + ) #f5 below - f6 = 0.5f0 * (v3_ll * B1_ll - v1_ll * B3_ll + v3_rr * B1_rr - v1_rr * B3_rr) - f7 = 0.5f0 * (v3_ll * B2_ll - v2_ll * B3_ll + v3_rr * B2_rr - v2_rr * B3_rr) - f8 = equations.c_h * psi_avg - f9 = equations.c_h * 0.5f0 * (B3_ll + B3_rr) + f6 = ( + equations.c_h * psi_avg * normal_direction[1] + + + 0.5f0 * ( + v_dot_n_ll * B1_ll - v1_ll * B_dot_n_ll + + v_dot_n_rr * B1_rr - v1_rr * B_dot_n_rr + ) + ) + f7 = ( + equations.c_h * psi_avg * normal_direction[2] + + + 0.5f0 * ( + v_dot_n_ll * B2_ll - v2_ll * B_dot_n_ll + + v_dot_n_rr * B2_rr - v2_rr * B_dot_n_rr + ) + ) + f8 = ( + equations.c_h * psi_avg * normal_direction[3] + + + 0.5f0 * ( + v_dot_n_ll * B3_ll - v3_ll * B_dot_n_ll + + v_dot_n_rr * B3_rr - v3_rr * B_dot_n_rr + ) + ) + f9 = equations.c_h * 0.5f0 * (B_dot_n_ll + B_dot_n_rr) # total energy flux is complicated and involves the previous components - f5 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v3_rr + p_rr * v3_ll - + (v3_ll * B1_ll * B1_rr + v3_rr * B1_rr * B1_ll) - + (v3_ll * B2_ll * B2_rr + v3_rr * B2_rr * B2_ll) - - - (v1_ll * B3_ll * B1_rr + v1_rr * B3_rr * B1_ll) - - - (v2_ll * B3_ll * B2_rr + v2_rr * B3_rr * B2_ll) - + - equations.c_h * (B3_ll * psi_rr + B3_rr * psi_ll))) + f5 = ( + f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v_dot_n_rr + p_rr * v_dot_n_ll + + (v_dot_n_ll * B1_ll * B1_rr + v_dot_n_rr * B1_rr * B1_ll) + + (v_dot_n_ll * B2_ll * B2_rr + v_dot_n_rr * B2_rr * B2_ll) + + (v_dot_n_ll * B3_ll * B3_rr + v_dot_n_rr * B3_rr * B3_ll) + - + (v1_ll * B_dot_n_ll * B1_rr + v1_rr * B_dot_n_rr * B1_ll) + - + (v2_ll * B_dot_n_ll * B2_rr + v2_rr * B_dot_n_rr * B2_ll) + - + (v3_ll * B_dot_n_ll * B3_rr + v3_rr * B_dot_n_rr * B3_ll) + + + equations.c_h * (B_dot_n_ll * psi_rr + B_dot_n_rr * psi_ll) + ) + ) + + return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations3D + ) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate the left/right velocities and fast magnetoacoustic wave speeds + if orientation == 1 + v_ll = rho_v1_ll / rho_ll + v_rr = rho_v1_rr / rho_rr + elseif orientation == 2 + v_ll = rho_v2_ll / rho_ll + v_rr = rho_v2_rr / rho_rr + else # orientation == 3 + v_ll = rho_v3_ll / rho_ll + v_rr = rho_v3_rr / rho_rr + end + cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) + cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations3D + ) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate normal velocities and fast magnetoacoustic wave speeds + # left + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + v_ll = ( + v1_ll * normal_direction[1] + + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + ) + cf_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) + # right + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + v_rr = ( + v1_rr * normal_direction[1] + + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + ) + cf_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) + + # wave speeds already scaled by norm(normal_direction) in [`calc_fast_wavespeed`](@ref) + return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) + end + + # Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations3D + ) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate primitive variables and speed of sound + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + + # Approximate the left-most and right-most eigenvalues in the Riemann fan + if orientation == 1 # x-direction + λ_min = v1_ll - calc_fast_wavespeed(u_ll, orientation, equations) + λ_max = v1_rr + calc_fast_wavespeed(u_rr, orientation, equations) + elseif orientation == 2 # y-direction + λ_min = v2_ll - calc_fast_wavespeed(u_ll, orientation, equations) + λ_max = v2_rr + calc_fast_wavespeed(u_rr, orientation, equations) + else # z-direction + λ_min = v3_ll - calc_fast_wavespeed(u_ll, orientation, equations) + λ_max = v3_rr + calc_fast_wavespeed(u_rr, orientation, equations) + end + + return λ_min, λ_max + end + + @inline function min_max_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations3D + ) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + + v_normal_ll = ( + v1_ll * normal_direction[1] + + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + ) + v_normal_rr = ( + v1_rr * normal_direction[1] + + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + ) + + # Estimate the min/max eigenvalues in the normal direction + λ_min = v_normal_ll - calc_fast_wavespeed(u_ll, normal_direction, equations) + λ_max = v_normal_rr + calc_fast_wavespeed(u_rr, normal_direction, equations) + + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations3D + ) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate primitive variables and speed of sound + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + + # Approximate the left-most and right-most eigenvalues in the Riemann fan + if orientation == 1 # x-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v1_ll - c_f_ll, v1_rr - c_f_rr) + λ_max = max(v1_ll + c_f_ll, v1_rr + c_f_rr) + elseif orientation == 2 # y-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v2_ll - c_f_ll, v2_rr - c_f_rr) + λ_max = max(v2_ll + c_f_ll, v2_rr + c_f_rr) + else # z-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_min = min(v3_ll - c_f_ll, v3_rr - c_f_rr) + λ_max = max(v3_ll + c_f_ll, v3_rr + c_f_rr) + end + + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations3D + ) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + + v_normal_ll = ( + v1_ll * normal_direction[1] + + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + ) + v_normal_rr = ( + v1_rr * normal_direction[1] + + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + ) + + c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) + c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) + + # Estimate the min/max eigenvalues in the normal direction + λ_min = min(v_normal_ll - c_f_ll, v_normal_rr - c_f_rr) + λ_max = max(v_normal_ll + c_f_ll, v_normal_rr + c_f_rr) + + return λ_min, λ_max + end + + """ + min_max_speed_einfeldt(u_ll, u_rr, orientation_or_normal_direction, equations::IdealGlmMhdEquations3D) + + Calculate minimum and maximum wave speeds for HLL-type fluxes as in + - Li (2005) + An HLLC Riemann solver for magneto-hydrodynamics + [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020) + """ + @inline function min_max_speed_einfeldt( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdEquations3D + ) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate primitive variables and speed of sound + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + + # Approximate the left-most and right-most eigenvalues in the Riemann fan + if orientation == 1 # x-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v1_ll - c_f_ll, vel_roe - c_f_roe) + λ_max = max(v1_rr + c_f_rr, vel_roe + c_f_roe) + elseif orientation == 2 # y-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v2_ll - c_f_ll, vel_roe - c_f_roe) + λ_max = max(v2_rr + c_f_rr, vel_roe + c_f_roe) + else # z-direction + c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) + c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) + vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v3_ll - c_f_ll, vel_roe - c_f_roe) + λ_max = max(v3_rr + c_f_rr, vel_roe + c_f_roe) + end + + return λ_min, λ_max + end + + @inline function min_max_speed_einfeldt( + u_ll, u_rr, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations3D + ) + rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll + rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr + + # Calculate primitive velocity variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + + v_normal_ll = ( + v1_ll * normal_direction[1] + + v2_ll * normal_direction[2] + + v3_ll * normal_direction[3] + ) + v_normal_rr = ( + v1_rr * normal_direction[1] + + v2_rr * normal_direction[2] + + v3_rr * normal_direction[3] + ) + + c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) + c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) + v_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, normal_direction, equations) + + # Estimate the min/max eigenvalues in the normal direction + λ_min = min(v_normal_ll - c_f_ll, v_roe - c_f_roe) + λ_max = max(v_normal_rr + c_f_rr, v_roe + c_f_roe) + + return λ_min, λ_max end - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -@inline function flux_hindenlang_gassner(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations3D) - # Unpack left and right states - rho_ll, v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim(u_ll, - equations) - rho_rr, v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim(u_rr, - equations) - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + - v3_ll * normal_direction[3] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + - v3_rr * normal_direction[3] - B_dot_n_ll = B1_ll * normal_direction[1] + B2_ll * normal_direction[2] + - B3_ll * normal_direction[3] - B_dot_n_rr = B1_rr * normal_direction[1] + B2_rr * normal_direction[2] + - B3_rr * normal_direction[3] - - # Compute the necessary mean values needed for either direction - rho_mean = ln_mean(rho_ll, rho_rr) - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - psi_avg = 0.5f0 * (psi_ll + psi_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) - - # Calculate fluxes depending on normal_direction - f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - f2 = (f1 * v1_avg + (p_avg + magnetic_square_avg) * normal_direction[1] - - - 0.5f0 * (B_dot_n_ll * B1_rr + B_dot_n_rr * B1_ll)) - f3 = (f1 * v2_avg + (p_avg + magnetic_square_avg) * normal_direction[2] - - - 0.5f0 * (B_dot_n_ll * B2_rr + B_dot_n_rr * B2_ll)) - f4 = (f1 * v3_avg + (p_avg + magnetic_square_avg) * normal_direction[3] - - - 0.5f0 * (B_dot_n_ll * B3_rr + B_dot_n_rr * B3_ll)) - #f5 below - f6 = (equations.c_h * psi_avg * normal_direction[1] - + - 0.5f0 * (v_dot_n_ll * B1_ll - v1_ll * B_dot_n_ll + - v_dot_n_rr * B1_rr - v1_rr * B_dot_n_rr)) - f7 = (equations.c_h * psi_avg * normal_direction[2] - + - 0.5f0 * (v_dot_n_ll * B2_ll - v2_ll * B_dot_n_ll + - v_dot_n_rr * B2_rr - v2_rr * B_dot_n_rr)) - f8 = (equations.c_h * psi_avg * normal_direction[3] - + - 0.5f0 * (v_dot_n_ll * B3_ll - v3_ll * B_dot_n_ll + - v_dot_n_rr * B3_rr - v3_rr * B_dot_n_rr)) - f9 = equations.c_h * 0.5f0 * (B_dot_n_ll + B_dot_n_rr) - # total energy flux is complicated and involves the previous components - f5 = (f1 * (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v_dot_n_rr + p_rr * v_dot_n_ll - + (v_dot_n_ll * B1_ll * B1_rr + v_dot_n_rr * B1_rr * B1_ll) - + (v_dot_n_ll * B2_ll * B2_rr + v_dot_n_rr * B2_rr * B2_ll) - + (v_dot_n_ll * B3_ll * B3_rr + v_dot_n_rr * B3_rr * B3_ll) - - - (v1_ll * B_dot_n_ll * B1_rr + v1_rr * B_dot_n_rr * B1_ll) - - - (v2_ll * B_dot_n_ll * B2_rr + v2_rr * B_dot_n_rr * B2_ll) - - - (v3_ll * B_dot_n_ll * B3_rr + v3_rr * B_dot_n_rr * B3_ll) - + - equations.c_h * (B_dot_n_ll * psi_rr + B_dot_n_rr * psi_ll))) - - return SVector(f1, f2, f3, f4, f5, f6, f7, f8, f9) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations3D) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr - - # Calculate the left/right velocities and fast magnetoacoustic wave speeds - if orientation == 1 - v_ll = rho_v1_ll / rho_ll - v_rr = rho_v1_rr / rho_rr - elseif orientation == 2 - v_ll = rho_v2_ll / rho_ll - v_rr = rho_v2_rr / rho_rr - else # orientation == 3 - v_ll = rho_v3_ll / rho_ll - v_rr = rho_v3_rr / rho_rr + # Rotate normal vector to x-axis; normal, tangent1 and tangent2 need to be orthonormal + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the directions + # has been normalized prior to this rotation of the state vector + # Note, for ideal GLM-MHD only the velocities and magnetic field variables rotate + @inline function rotate_to_x( + u, normal_vector, tangent1, tangent2, + equations::IdealGlmMhdEquations3D + ) + # Multiply with [ 1 0 0 0 0 0 0 0 0; + # 0 ― normal_vector ― 0 0 0 0 0; + # 0 ― tangent1 ― 0 0 0 0 0; + # 0 ― tangent2 ― 0 0 0 0 0; + # 0 0 0 0 1 0 0 0 0; + # 0 0 0 0 0 ― normal_vector ― 0; + # 0 0 0 0 0 ― tangent1 ― 0; + # 0 0 0 0 0 ― tangent2 ― 0; + # 0 0 0 0 0 0 0 0 1 ] + return SVector( + u[1], + normal_vector[1] * u[2] + normal_vector[2] * u[3] + + normal_vector[3] * u[4], + tangent1[1] * u[2] + tangent1[2] * u[3] + tangent1[3] * u[4], + tangent2[1] * u[2] + tangent2[2] * u[3] + tangent2[3] * u[4], + u[5], + normal_vector[1] * u[6] + normal_vector[2] * u[7] + + normal_vector[3] * u[8], + tangent1[1] * u[6] + tangent1[2] * u[7] + tangent1[3] * u[8], + tangent2[1] * u[6] + tangent2[2] * u[7] + tangent2[3] * u[8], + u[9] + ) end - cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) - cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) - - return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) -end - -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations3D) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr - - # Calculate normal velocities and fast magnetoacoustic wave speeds - # left - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - v_ll = (v1_ll * normal_direction[1] - + v2_ll * normal_direction[2] - + v3_ll * normal_direction[3]) - cf_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) - # right - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - v_rr = (v1_rr * normal_direction[1] - + v2_rr * normal_direction[2] - + v3_rr * normal_direction[3]) - cf_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) - - # wave speeds already scaled by norm(normal_direction) in [`calc_fast_wavespeed`](@ref) - return max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) -end - -# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations3D) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr - - # Calculate primitive variables and speed of sound - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - - # Approximate the left-most and right-most eigenvalues in the Riemann fan - if orientation == 1 # x-direction - λ_min = v1_ll - calc_fast_wavespeed(u_ll, orientation, equations) - λ_max = v1_rr + calc_fast_wavespeed(u_rr, orientation, equations) - elseif orientation == 2 # y-direction - λ_min = v2_ll - calc_fast_wavespeed(u_ll, orientation, equations) - λ_max = v2_rr + calc_fast_wavespeed(u_rr, orientation, equations) - else # z-direction - λ_min = v3_ll - calc_fast_wavespeed(u_ll, orientation, equations) - λ_max = v3_rr + calc_fast_wavespeed(u_rr, orientation, equations) + + # Rotate x-axis to normal vector; normal, tangent1 and tangent2 need to be orthonormal + # Called inside `FluxRotated` in `numerical_fluxes.jl` so the directions + # has been normalized prior to this back-rotation of the state vector + # Note, for ideal GLM-MHD only the velocities and magnetic field variables back-rotate + @inline function rotate_from_x( + u, normal_vector, tangent1, tangent2, + equations::IdealGlmMhdEquations3D + ) + # Multiply with [ 1 0 0 0 0 0 0 0 0; + # 0 | | | 0 0 0 0 0; + # 0 normal_vector tangent1 tangent2 0 0 0 0 0; + # 0 | | | 0 0 0 0 0; + # 0 0 0 0 1 0 0 0 0; + # 0 0 0 0 0 | | | 0; + # 0 0 0 0 0 normal_vector tangent1 tangent2 0; + # 0 0 0 0 0 | | | 0; + # 0 0 0 0 0 0 0 0 1 ] + return SVector( + u[1], + normal_vector[1] * u[2] + tangent1[1] * u[3] + tangent2[1] * u[4], + normal_vector[2] * u[2] + tangent1[2] * u[3] + tangent2[2] * u[4], + normal_vector[3] * u[2] + tangent1[3] * u[3] + tangent2[3] * u[4], + u[5], + normal_vector[1] * u[6] + tangent1[1] * u[7] + tangent2[1] * u[8], + normal_vector[2] * u[6] + tangent1[2] * u[7] + tangent2[2] * u[8], + normal_vector[3] * u[6] + tangent1[3] * u[7] + tangent2[3] * u[8], + u[9] + ) end - return λ_min, λ_max -end - -@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations3D) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr - - # Calculate primitive velocity variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - - v_normal_ll = (v1_ll * normal_direction[1] + - v2_ll * normal_direction[2] + - v3_ll * normal_direction[3]) - v_normal_rr = (v1_rr * normal_direction[1] + - v2_rr * normal_direction[2] + - v3_rr * normal_direction[3]) - - # Estimate the min/max eigenvalues in the normal direction - λ_min = v_normal_ll - calc_fast_wavespeed(u_ll, normal_direction, equations) - λ_max = v_normal_rr + calc_fast_wavespeed(u_rr, normal_direction, equations) - - return λ_min, λ_max -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations3D) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr - - # Calculate primitive variables and speed of sound - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - - # Approximate the left-most and right-most eigenvalues in the Riemann fan - if orientation == 1 # x-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - - λ_min = min(v1_ll - c_f_ll, v1_rr - c_f_rr) - λ_max = max(v1_ll + c_f_ll, v1_rr + c_f_rr) - elseif orientation == 2 # y-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - - λ_min = min(v2_ll - c_f_ll, v2_rr - c_f_rr) - λ_max = max(v2_ll + c_f_ll, v2_rr + c_f_rr) - else # z-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - - λ_min = min(v3_ll - c_f_ll, v3_rr - c_f_rr) - λ_max = max(v3_ll + c_f_ll, v3_rr + c_f_rr) + @inline function max_abs_speeds(u, equations::IdealGlmMhdEquations3D) + rho, rho_v1, rho_v2, rho_v3, _ = u + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + cf_x_direction = calc_fast_wavespeed(u, 1, equations) + cf_y_direction = calc_fast_wavespeed(u, 2, equations) + cf_z_direction = calc_fast_wavespeed(u, 3, equations) + + return abs(v1) + cf_x_direction, abs(v2) + cf_y_direction, abs(v3) + cf_z_direction end - return λ_min, λ_max -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations3D) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr - - # Calculate primitive velocity variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - - v_normal_ll = (v1_ll * normal_direction[1] + - v2_ll * normal_direction[2] + - v3_ll * normal_direction[3]) - v_normal_rr = (v1_rr * normal_direction[1] + - v2_rr * normal_direction[2] + - v3_rr * normal_direction[3]) - - c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) - c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) - - # Estimate the min/max eigenvalues in the normal direction - λ_min = min(v_normal_ll - c_f_ll, v_normal_rr - c_f_rr) - λ_max = max(v_normal_ll + c_f_ll, v_normal_rr + c_f_rr) - - return λ_min, λ_max -end - -""" - min_max_speed_einfeldt(u_ll, u_rr, orientation_or_normal_direction, equations::IdealGlmMhdEquations3D) - -Calculate minimum and maximum wave speeds for HLL-type fluxes as in -- Li (2005) - An HLLC Riemann solver for magneto-hydrodynamics - [DOI: 10.1016/j.jcp.2004.08.020](https://doi.org/10.1016/j.jcp.2004.08.020) -""" -@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdEquations3D) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr - - # Calculate primitive variables and speed of sound - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - - # Approximate the left-most and right-most eigenvalues in the Riemann fan - if orientation == 1 # x-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) - λ_min = min(v1_ll - c_f_ll, vel_roe - c_f_roe) - λ_max = max(v1_rr + c_f_rr, vel_roe + c_f_roe) - elseif orientation == 2 # y-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) - λ_min = min(v2_ll - c_f_ll, vel_roe - c_f_roe) - λ_max = max(v2_rr + c_f_rr, vel_roe + c_f_roe) - else # z-direction - c_f_ll = calc_fast_wavespeed(u_ll, orientation, equations) - c_f_rr = calc_fast_wavespeed(u_rr, orientation, equations) - vel_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, orientation, equations) - λ_min = min(v3_ll - c_f_ll, vel_roe - c_f_roe) - λ_max = max(v3_rr + c_f_rr, vel_roe + c_f_roe) + # Convert conservative variables to primitive + @inline function cons2prim(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 + p = (equations.gamma - 1) * ( + rho_e - + 0.5f0 * ( + rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3 + + B1 * B1 + B2 * B2 + B3 * B3 + + psi * psi + ) + ) + + return SVector(rho, v1, v2, v3, p, B1, B2, B3, psi) end - return λ_min, λ_max -end - -@inline function min_max_speed_einfeldt(u_ll, u_rr, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations3D) - rho_ll, rho_v1_ll, rho_v2_ll, rho_v3_ll, _ = u_ll - rho_rr, rho_v1_rr, rho_v2_rr, rho_v3_rr, _ = u_rr - - # Calculate primitive velocity variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - - v_normal_ll = (v1_ll * normal_direction[1] + - v2_ll * normal_direction[2] + - v3_ll * normal_direction[3]) - v_normal_rr = (v1_rr * normal_direction[1] + - v2_rr * normal_direction[2] + - v3_rr * normal_direction[3]) - - c_f_ll = calc_fast_wavespeed(u_ll, normal_direction, equations) - c_f_rr = calc_fast_wavespeed(u_rr, normal_direction, equations) - v_roe, c_f_roe = calc_fast_wavespeed_roe(u_ll, u_rr, normal_direction, equations) - - # Estimate the min/max eigenvalues in the normal direction - λ_min = min(v_normal_ll - c_f_ll, v_roe - c_f_roe) - λ_max = max(v_normal_rr + c_f_rr, v_roe + c_f_roe) - - return λ_min, λ_max -end - -# Rotate normal vector to x-axis; normal, tangent1 and tangent2 need to be orthonormal -# Called inside `FluxRotated` in `numerical_fluxes.jl` so the directions -# has been normalized prior to this rotation of the state vector -# Note, for ideal GLM-MHD only the velocities and magnetic field variables rotate -@inline function rotate_to_x(u, normal_vector, tangent1, tangent2, - equations::IdealGlmMhdEquations3D) - # Multiply with [ 1 0 0 0 0 0 0 0 0; - # 0 ― normal_vector ― 0 0 0 0 0; - # 0 ― tangent1 ― 0 0 0 0 0; - # 0 ― tangent2 ― 0 0 0 0 0; - # 0 0 0 0 1 0 0 0 0; - # 0 0 0 0 0 ― normal_vector ― 0; - # 0 0 0 0 0 ― tangent1 ― 0; - # 0 0 0 0 0 ― tangent2 ― 0; - # 0 0 0 0 0 0 0 0 1 ] - return SVector(u[1], - normal_vector[1] * u[2] + normal_vector[2] * u[3] + - normal_vector[3] * u[4], - tangent1[1] * u[2] + tangent1[2] * u[3] + tangent1[3] * u[4], - tangent2[1] * u[2] + tangent2[2] * u[3] + tangent2[3] * u[4], - u[5], - normal_vector[1] * u[6] + normal_vector[2] * u[7] + - normal_vector[3] * u[8], - tangent1[1] * u[6] + tangent1[2] * u[7] + tangent1[3] * u[8], - tangent2[1] * u[6] + tangent2[2] * u[7] + tangent2[3] * u[8], - u[9]) -end - -# Rotate x-axis to normal vector; normal, tangent1 and tangent2 need to be orthonormal -# Called inside `FluxRotated` in `numerical_fluxes.jl` so the directions -# has been normalized prior to this back-rotation of the state vector -# Note, for ideal GLM-MHD only the velocities and magnetic field variables back-rotate -@inline function rotate_from_x(u, normal_vector, tangent1, tangent2, - equations::IdealGlmMhdEquations3D) - # Multiply with [ 1 0 0 0 0 0 0 0 0; - # 0 | | | 0 0 0 0 0; - # 0 normal_vector tangent1 tangent2 0 0 0 0 0; - # 0 | | | 0 0 0 0 0; - # 0 0 0 0 1 0 0 0 0; - # 0 0 0 0 0 | | | 0; - # 0 0 0 0 0 normal_vector tangent1 tangent2 0; - # 0 0 0 0 0 | | | 0; - # 0 0 0 0 0 0 0 0 1 ] - return SVector(u[1], - normal_vector[1] * u[2] + tangent1[1] * u[3] + tangent2[1] * u[4], - normal_vector[2] * u[2] + tangent1[2] * u[3] + tangent2[2] * u[4], - normal_vector[3] * u[2] + tangent1[3] * u[3] + tangent2[3] * u[4], - u[5], - normal_vector[1] * u[6] + tangent1[1] * u[7] + tangent2[1] * u[8], - normal_vector[2] * u[6] + tangent1[2] * u[7] + tangent2[2] * u[8], - normal_vector[3] * u[6] + tangent1[3] * u[7] + tangent2[3] * u[8], - u[9]) -end - -@inline function max_abs_speeds(u, equations::IdealGlmMhdEquations3D) - rho, rho_v1, rho_v2, rho_v3, _ = u - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - cf_x_direction = calc_fast_wavespeed(u, 1, equations) - cf_y_direction = calc_fast_wavespeed(u, 2, equations) - cf_z_direction = calc_fast_wavespeed(u, 3, equations) - - return abs(v1) + cf_x_direction, abs(v2) + cf_y_direction, abs(v3) + cf_z_direction -end - -# Convert conservative variables to primitive -@inline function cons2prim(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 - p = (equations.gamma - 1) * (rho_e - - 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3 - + B1 * B1 + B2 * B2 + B3 * B3 - + psi * psi)) - - return SVector(rho, v1, v2, v3, p, B1, B2, B3, psi) -end - -# Convert conservative variables to entropy -@inline function cons2entropy(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 - p = (equations.gamma - 1) * - (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) - s = log(p) - equations.gamma * log(rho) - rho_p = rho / p - - w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - - 0.5f0 * rho_p * v_square - w2 = rho_p * v1 - w3 = rho_p * v2 - w4 = rho_p * v3 - w5 = -rho_p - w6 = rho_p * B1 - w7 = rho_p * B2 - w8 = rho_p * B3 - w9 = rho_p * psi - - return SVector(w1, w2, w3, w4, w5, w6, w7, w8, w9) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::IdealGlmMhdEquations3D) - rho, v1, v2, v3, p, B1, B2, B3, psi = prim - - rho_v1 = rho * v1 - rho_v2 = rho * v2 - rho_v3 = rho * v3 - rho_e = p * equations.inv_gamma_minus_one + + # Convert conservative variables to entropy + @inline function cons2entropy(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 + p = (equations.gamma - 1) * + (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) + s = log(p) - equations.gamma * log(rho) + rho_p = rho / p + + w1 = (equations.gamma - s) * equations.inv_gamma_minus_one - + 0.5f0 * rho_p * v_square + w2 = rho_p * v1 + w3 = rho_p * v2 + w4 = rho_p * v3 + w5 = -rho_p + w6 = rho_p * B1 + w7 = rho_p * B2 + w8 = rho_p * B3 + w9 = rho_p * psi + + return SVector(w1, w2, w3, w4, w5, w6, w7, w8, w9) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::IdealGlmMhdEquations3D) + rho, v1, v2, v3, p, B1, B2, B3, psi = prim + + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_v3 = rho * v3 + rho_e = p * equations.inv_gamma_minus_one + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + 0.5f0 * (B1^2 + B2^2 + B3^2) + 0.5f0 * psi^2 - return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi) -end - -@inline function density(u, equations::IdealGlmMhdEquations3D) - return u[1] -end - -@inline function pressure(u, equations::IdealGlmMhdEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho - - - 0.5f0 * (B1^2 + B2^2 + B3^2) - - - 0.5f0 * psi^2) - return p -end - -@inline function density_pressure(u, equations::IdealGlmMhdEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u - p = (equations.gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho - - - 0.5f0 * (B1^2 + B2^2 + B3^2) - - - 0.5f0 * psi^2) - return rho * p -end - -# Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue -@inline function calc_fast_wavespeed(cons, orientation::Integer, - equations::IdealGlmMhdEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) - p = (equations.gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) - a_square = equations.gamma * p / rho - sqrt_rho = sqrt(rho) - b1 = B1 / sqrt_rho - b2 = B2 / sqrt_rho - b3 = B3 / sqrt_rho - b_square = b1 * b1 + b2 * b2 + b3 * b3 - if orientation == 1 # x-direction - c_f = sqrt(0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2)) - elseif orientation == 2 # y-direction - c_f = sqrt(0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b2^2)) - else # z-direction - c_f = sqrt(0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b3^2)) + return SVector(rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi) end - return c_f -end - -@inline function calc_fast_wavespeed(cons, normal_direction::AbstractVector, - equations::IdealGlmMhdEquations3D) - rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) - mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) - p = (equations.gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) - a_square = equations.gamma * p / rho - sqrt_rho = sqrt(rho) - b1 = B1 / sqrt_rho - b2 = B2 / sqrt_rho - b3 = B3 / sqrt_rho - b_square = b1 * b1 + b2 * b2 + b3 * b3 - norm_squared = (normal_direction[1] * normal_direction[1] + - normal_direction[2] * normal_direction[2] + - normal_direction[3] * normal_direction[3]) - b_dot_n_squared = (b1 * normal_direction[1] + - b2 * normal_direction[2] + - b3 * normal_direction[3])^2 / norm_squared - - c_f = sqrt((0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b_dot_n_squared)) * - norm_squared) - return c_f -end - -""" - calc_fast_wavespeed_roe(u_ll, u_rr, orientation_or_normal_direction, equations::IdealGlmMhdEquations3D) - -Compute the fast magnetoacoustic wave speed using Roe averages as given by -- Cargo and Gallice (1997) - Roe Matrices for Ideal MHD and Systematic Construction - of Roe Matrices for Systems of Conservation Laws - [DOI: 10.1006/jcph.1997.5773](https://doi.org/10.1006/jcph.1997.5773) -""" -@inline function calc_fast_wavespeed_roe(u_ll, u_rr, orientation::Integer, - 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 - - # Calculate primitive variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - kin_en_ll = 0.5f0 * (rho_v1_ll * v1_ll + rho_v2_ll * v2_ll + rho_v3_ll * v3_ll) - mag_norm_ll = B1_ll * B1_ll + B2_ll * B2_ll + B3_ll * B3_ll - p_ll = (equations.gamma - 1) * - (rho_e_ll - kin_en_ll - 0.5f0 * mag_norm_ll - 0.5f0 * psi_ll^2) - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - kin_en_rr = 0.5f0 * (rho_v1_rr * v1_rr + rho_v2_rr * v2_rr + rho_v3_rr * v3_rr) - mag_norm_rr = B1_rr * B1_rr + B2_rr * B2_rr + B3_rr * B3_rr - p_rr = (equations.gamma - 1) * - (rho_e_rr - kin_en_rr - 0.5f0 * mag_norm_rr - 0.5f0 * psi_rr^2) - - # compute total pressure which is thermal + magnetic pressures - p_total_ll = p_ll + 0.5f0 * mag_norm_ll - p_total_rr = p_rr + 0.5f0 * mag_norm_rr - - # compute the Roe density averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) - inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) - rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add - rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add - # Roe averages - # velocities and magnetic fields - v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe - v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe - v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe - B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe - B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe - B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe - # enthalpy - H_ll = (rho_e_ll + p_total_ll) / rho_ll - H_rr = (rho_e_rr + p_total_rr) / rho_rr - H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe - # temporary variable see equation (4.12) in Cargo and Gallice - X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * - inv_sqrt_rho_add^2 - # averaged components needed to compute c_f, the fast magnetoacoustic wave speed - b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum - a_square_roe = ((2 - equations.gamma) * X + - (equations.gamma - 1) * - (H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - - b_square_roe)) # acoustic speed - # finally compute the average wave speed and set the output velocity (depends on orientation) - if orientation == 1 # x-direction - c_a_roe = B1_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed - a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - - 4 * a_square_roe * c_a_roe) - c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) - vel_out_roe = v1_roe - elseif orientation == 2 # y-direction - c_a_roe = B2_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed - a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - - 4 * a_square_roe * c_a_roe) - c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) - vel_out_roe = v2_roe - else # z-direction - c_a_roe = B3_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed - a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - - 4 * a_square_roe * c_a_roe) - c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) - vel_out_roe = v3_roe + + @inline function density(u, equations::IdealGlmMhdEquations3D) + return u[1] + end + + @inline function pressure(u, equations::IdealGlmMhdEquations3D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u + p = (equations.gamma - 1) * ( + rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho + - + 0.5f0 * (B1^2 + B2^2 + B3^2) + - + 0.5f0 * psi^2 + ) + return p + end + + @inline function density_pressure(u, equations::IdealGlmMhdEquations3D) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u + p = (equations.gamma - 1) * ( + rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho + - + 0.5f0 * (B1^2 + B2^2 + B3^2) + - + 0.5f0 * psi^2 + ) + return rho * p + end + + # Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue + @inline function calc_fast_wavespeed( + cons, orientation::Integer, + equations::IdealGlmMhdEquations3D + ) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) + p = (equations.gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) + a_square = equations.gamma * p / rho + sqrt_rho = sqrt(rho) + b1 = B1 / sqrt_rho + b2 = B2 / sqrt_rho + b3 = B3 / sqrt_rho + b_square = b1 * b1 + b2 * b2 + b3 * b3 + if orientation == 1 # x-direction + c_f = sqrt( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2) + ) + elseif orientation == 2 # y-direction + c_f = sqrt( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b2^2) + ) + else # z-direction + c_f = sqrt( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b3^2) + ) + end + return c_f + end + + @inline function calc_fast_wavespeed( + cons, normal_direction::AbstractVector, + equations::IdealGlmMhdEquations3D + ) + rho, rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + kin_en = 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + mag_en = 0.5f0 * (B1 * B1 + B2 * B2 + B3 * B3) + p = (equations.gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) + a_square = equations.gamma * p / rho + sqrt_rho = sqrt(rho) + b1 = B1 / sqrt_rho + b2 = B2 / sqrt_rho + b3 = B3 / sqrt_rho + b_square = b1 * b1 + b2 * b2 + b3 * b3 + norm_squared = ( + normal_direction[1] * normal_direction[1] + + normal_direction[2] * normal_direction[2] + + normal_direction[3] * normal_direction[3] + ) + b_dot_n_squared = ( + b1 * normal_direction[1] + + b2 * normal_direction[2] + + b3 * normal_direction[3] + )^2 / norm_squared + + c_f = sqrt( + ( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b_dot_n_squared) + ) * + norm_squared + ) + return c_f end - return vel_out_roe, c_f_roe -end - -@inline function calc_fast_wavespeed_roe(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 - - # Calculate primitive variables - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - kin_en_ll = 0.5f0 * (rho_v1_ll * v1_ll + rho_v2_ll * v2_ll + rho_v3_ll * v3_ll) - mag_norm_ll = B1_ll * B1_ll + B2_ll * B2_ll + B3_ll * B3_ll - p_ll = (equations.gamma - 1) * - (rho_e_ll - kin_en_ll - 0.5f0 * mag_norm_ll - 0.5f0 * psi_ll^2) - - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - kin_en_rr = 0.5f0 * (rho_v1_rr * v1_rr + rho_v2_rr * v2_rr + rho_v3_rr * v3_rr) - mag_norm_rr = B1_rr * B1_rr + B2_rr * B2_rr + B3_rr * B3_rr - p_rr = (equations.gamma - 1) * - (rho_e_rr - kin_en_rr - 0.5f0 * mag_norm_rr - 0.5f0 * psi_rr^2) - - # compute total pressure which is thermal + magnetic pressures - p_total_ll = p_ll + 0.5f0 * mag_norm_ll - p_total_rr = p_rr + 0.5f0 * mag_norm_rr - - # compute the Roe density averages - sqrt_rho_ll = sqrt(rho_ll) - sqrt_rho_rr = sqrt(rho_rr) - inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) - inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) - rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add - rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add - # Roe averages - # velocities and magnetic fields - v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe - v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe - v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe - B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe - B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe - B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe - # enthalpy - H_ll = (rho_e_ll + p_total_ll) / rho_ll - H_rr = (rho_e_rr + p_total_rr) / rho_rr - H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe - # temporary variable see equation (4.12) in Cargo and Gallice - X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * - inv_sqrt_rho_add^2 - # averaged components needed to compute c_f, the fast magnetoacoustic wave speed - b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum - a_square_roe = ((2 - equations.gamma) * X + - (equations.gamma - 1) * - (H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - - b_square_roe)) # acoustic speed - - # finally compute the average wave speed and set the output velocity (depends on orientation) - norm_squared = (normal_direction[1] * normal_direction[1] + - normal_direction[2] * normal_direction[2] + - normal_direction[3] * normal_direction[3]) - B_roe_dot_n_squared = (B1_roe * normal_direction[1] + - B2_roe * normal_direction[2] + - B3_roe * normal_direction[3])^2 / norm_squared - - c_a_roe = B_roe_dot_n_squared * inv_sqrt_rho_prod # (squared) Alfvén wave speed - a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - 4 * a_square_roe * c_a_roe) - c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe) * norm_squared) - vel_out_roe = (v1_roe * normal_direction[1] + - v2_roe * normal_direction[2] + - v3_roe * normal_direction[3]) - - return vel_out_roe, c_f_roe -end - -# Calculate thermodynamic entropy for a conservative state `cons` -@inline function entropy_thermodynamic(cons, equations::IdealGlmMhdEquations3D) - # Pressure - p = (equations.gamma - 1) * - (cons[5] - 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] - - - 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) - - - 0.5f0 * cons[9]^2) - - # Thermodynamic entropy - s = log(p) - equations.gamma * log(cons[1]) - - return s -end - -# Calculate mathematical entropy for a conservative state `cons` -@inline function entropy_math(cons, equations::IdealGlmMhdEquations3D) - S = -entropy_thermodynamic(cons, equations) * cons[1] * - equations.inv_gamma_minus_one - - return S -end - -# Default entropy is the mathematical entropy -@inline entropy(cons, equations::IdealGlmMhdEquations3D) = entropy_math(cons, equations) - -# Calculate total energy for a conservative state `cons` -@inline energy_total(cons, ::IdealGlmMhdEquations3D) = cons[5] - -# Calculate kinetic energy for a conservative state `cons` -@inline function energy_kinetic(cons, equations::IdealGlmMhdEquations3D) - return 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] -end - -# Calculate the magnetic energy for a conservative state `cons'. -# OBS! For non-dinmensional form of the ideal MHD magnetic pressure ≡ magnetic energy -@inline function energy_magnetic(cons, ::IdealGlmMhdEquations3D) - return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) -end - -# Calculate internal energy for a conservative state `cons` -@inline function energy_internal(cons, equations::IdealGlmMhdEquations3D) - return (energy_total(cons, equations) - - - energy_kinetic(cons, equations) - - - energy_magnetic(cons, equations) - - - cons[9]^2 / 2) -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] -end + """ + calc_fast_wavespeed_roe(u_ll, u_rr, orientation_or_normal_direction, equations::IdealGlmMhdEquations3D) + + Compute the fast magnetoacoustic wave speed using Roe averages as given by + - Cargo and Gallice (1997) + Roe Matrices for Ideal MHD and Systematic Construction + of Roe Matrices for Systems of Conservation Laws + [DOI: 10.1006/jcph.1997.5773](https://doi.org/10.1006/jcph.1997.5773) + """ + @inline function calc_fast_wavespeed_roe( + u_ll, u_rr, orientation::Integer, + 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 + + # Calculate primitive variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + kin_en_ll = 0.5f0 * (rho_v1_ll * v1_ll + rho_v2_ll * v2_ll + rho_v3_ll * v3_ll) + mag_norm_ll = B1_ll * B1_ll + B2_ll * B2_ll + B3_ll * B3_ll + p_ll = (equations.gamma - 1) * + (rho_e_ll - kin_en_ll - 0.5f0 * mag_norm_ll - 0.5f0 * psi_ll^2) + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + kin_en_rr = 0.5f0 * (rho_v1_rr * v1_rr + rho_v2_rr * v2_rr + rho_v3_rr * v3_rr) + mag_norm_rr = B1_rr * B1_rr + B2_rr * B2_rr + B3_rr * B3_rr + p_rr = (equations.gamma - 1) * + (rho_e_rr - kin_en_rr - 0.5f0 * mag_norm_rr - 0.5f0 * psi_rr^2) + + # compute total pressure which is thermal + magnetic pressures + p_total_ll = p_ll + 0.5f0 * mag_norm_ll + p_total_rr = p_rr + 0.5f0 * mag_norm_rr + + # compute the Roe density averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) + inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) + rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add + rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add + # Roe averages + # velocities and magnetic fields + v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe + v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe + v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe + B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe + B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe + B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe + # enthalpy + H_ll = (rho_e_ll + p_total_ll) / rho_ll + H_rr = (rho_e_rr + p_total_rr) / rho_rr + H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe + # temporary variable see equation (4.12) in Cargo and Gallice + X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * + inv_sqrt_rho_add^2 + # averaged components needed to compute c_f, the fast magnetoacoustic wave speed + b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum + a_square_roe = ( + (2 - equations.gamma) * X + + (equations.gamma - 1) * + ( + H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - + b_square_roe + ) + ) # acoustic speed + # finally compute the average wave speed and set the output velocity (depends on orientation) + if orientation == 1 # x-direction + c_a_roe = B1_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed + a_star_roe = sqrt( + (a_square_roe + b_square_roe)^2 - + 4 * a_square_roe * c_a_roe + ) + c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) + vel_out_roe = v1_roe + elseif orientation == 2 # y-direction + c_a_roe = B2_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed + a_star_roe = sqrt( + (a_square_roe + b_square_roe)^2 - + 4 * a_square_roe * c_a_roe + ) + c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) + vel_out_roe = v2_roe + else # z-direction + c_a_roe = B3_roe^2 * inv_sqrt_rho_prod # (squared) Alfvén wave speed + a_star_roe = sqrt( + (a_square_roe + b_square_roe)^2 - + 4 * a_square_roe * c_a_roe + ) + c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe)) + vel_out_roe = v3_roe + end + + return vel_out_roe, c_f_roe + end + + @inline function calc_fast_wavespeed_roe( + 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 + + # Calculate primitive variables + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + kin_en_ll = 0.5f0 * (rho_v1_ll * v1_ll + rho_v2_ll * v2_ll + rho_v3_ll * v3_ll) + mag_norm_ll = B1_ll * B1_ll + B2_ll * B2_ll + B3_ll * B3_ll + p_ll = (equations.gamma - 1) * + (rho_e_ll - kin_en_ll - 0.5f0 * mag_norm_ll - 0.5f0 * psi_ll^2) + + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + kin_en_rr = 0.5f0 * (rho_v1_rr * v1_rr + rho_v2_rr * v2_rr + rho_v3_rr * v3_rr) + mag_norm_rr = B1_rr * B1_rr + B2_rr * B2_rr + B3_rr * B3_rr + p_rr = (equations.gamma - 1) * + (rho_e_rr - kin_en_rr - 0.5f0 * mag_norm_rr - 0.5f0 * psi_rr^2) + + # compute total pressure which is thermal + magnetic pressures + p_total_ll = p_ll + 0.5f0 * mag_norm_ll + p_total_rr = p_rr + 0.5f0 * mag_norm_rr + + # compute the Roe density averages + sqrt_rho_ll = sqrt(rho_ll) + sqrt_rho_rr = sqrt(rho_rr) + inv_sqrt_rho_add = 1 / (sqrt_rho_ll + sqrt_rho_rr) + inv_sqrt_rho_prod = 1 / (sqrt_rho_ll * sqrt_rho_rr) + rho_ll_roe = sqrt_rho_ll * inv_sqrt_rho_add + rho_rr_roe = sqrt_rho_rr * inv_sqrt_rho_add + # Roe averages + # velocities and magnetic fields + v1_roe = v1_ll * rho_ll_roe + v1_rr * rho_rr_roe + v2_roe = v2_ll * rho_ll_roe + v2_rr * rho_rr_roe + v3_roe = v3_ll * rho_ll_roe + v3_rr * rho_rr_roe + B1_roe = B1_ll * rho_ll_roe + B1_rr * rho_rr_roe + B2_roe = B2_ll * rho_ll_roe + B2_rr * rho_rr_roe + B3_roe = B3_ll * rho_ll_roe + B3_rr * rho_rr_roe + # enthalpy + H_ll = (rho_e_ll + p_total_ll) / rho_ll + H_rr = (rho_e_rr + p_total_rr) / rho_rr + H_roe = H_ll * rho_ll_roe + H_rr * rho_rr_roe + # temporary variable see equation (4.12) in Cargo and Gallice + X = 0.5f0 * ((B1_ll - B1_rr)^2 + (B2_ll - B2_rr)^2 + (B3_ll - B3_rr)^2) * + inv_sqrt_rho_add^2 + # averaged components needed to compute c_f, the fast magnetoacoustic wave speed + b_square_roe = (B1_roe^2 + B2_roe^2 + B3_roe^2) * inv_sqrt_rho_prod # scaled magnectic sum + a_square_roe = ( + (2 - equations.gamma) * X + + (equations.gamma - 1) * + ( + H_roe - 0.5f0 * (v1_roe^2 + v2_roe^2 + v3_roe^2) - + b_square_roe + ) + ) # acoustic speed + + # finally compute the average wave speed and set the output velocity (depends on orientation) + norm_squared = ( + normal_direction[1] * normal_direction[1] + + normal_direction[2] * normal_direction[2] + + normal_direction[3] * normal_direction[3] + ) + B_roe_dot_n_squared = ( + B1_roe * normal_direction[1] + + B2_roe * normal_direction[2] + + B3_roe * normal_direction[3] + )^2 / norm_squared + + c_a_roe = B_roe_dot_n_squared * inv_sqrt_rho_prod # (squared) Alfvén wave speed + a_star_roe = sqrt((a_square_roe + b_square_roe)^2 - 4 * a_square_roe * c_a_roe) + c_f_roe = sqrt(0.5f0 * (a_square_roe + b_square_roe + a_star_roe) * norm_squared) + vel_out_roe = ( + v1_roe * normal_direction[1] + + v2_roe * normal_direction[2] + + v3_roe * normal_direction[3] + ) + + return vel_out_roe, c_f_roe + end + + # Calculate thermodynamic entropy for a conservative state `cons` + @inline function entropy_thermodynamic(cons, equations::IdealGlmMhdEquations3D) + # Pressure + p = (equations.gamma - 1) * + ( + cons[5] - 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] + - + 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) + - + 0.5f0 * cons[9]^2 + ) + + # Thermodynamic entropy + s = log(p) - equations.gamma * log(cons[1]) + + return s + end + + # Calculate mathematical entropy for a conservative state `cons` + @inline function entropy_math(cons, equations::IdealGlmMhdEquations3D) + S = -entropy_thermodynamic(cons, equations) * cons[1] * + equations.inv_gamma_minus_one + + return S + end + + # Default entropy is the mathematical entropy + @inline entropy(cons, equations::IdealGlmMhdEquations3D) = entropy_math(cons, equations) + + # Calculate total energy for a conservative state `cons` + @inline energy_total(cons, ::IdealGlmMhdEquations3D) = cons[5] + + # Calculate kinetic energy for a conservative state `cons` + @inline function energy_kinetic(cons, equations::IdealGlmMhdEquations3D) + return 0.5f0 * (cons[2]^2 + cons[3]^2 + cons[4]^2) / cons[1] + end + + # Calculate the magnetic energy for a conservative state `cons'. + # OBS! For non-dinmensional form of the ideal MHD magnetic pressure ≡ magnetic energy + @inline function energy_magnetic(cons, ::IdealGlmMhdEquations3D) + return 0.5f0 * (cons[6]^2 + cons[7]^2 + cons[8]^2) + end + + # Calculate internal energy for a conservative state `cons` + @inline function energy_internal(cons, equations::IdealGlmMhdEquations3D) + return ( + energy_total(cons, equations) + - + energy_kinetic(cons, equations) + - + energy_magnetic(cons, equations) + - + cons[9]^2 / 2 + ) + 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] + end end # @muladd diff --git a/src/equations/ideal_glm_mhd_multicomponent_1d.jl b/src/equations/ideal_glm_mhd_multicomponent_1d.jl index b2ed06e53e..a12eeb9a88 100644 --- a/src/equations/ideal_glm_mhd_multicomponent_1d.jl +++ b/src/equations/ideal_glm_mhd_multicomponent_1d.jl @@ -3,552 +3,624 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - IdealGlmMhdMulticomponentEquations1D - -The ideal compressible multicomponent GLM-MHD equations in one space dimension. -""" -mutable struct IdealGlmMhdMulticomponentEquations1D{NVARS, NCOMP, RealT <: Real} <: - AbstractIdealGlmMhdMulticomponentEquations{1, NVARS, NCOMP} - gammas::SVector{NCOMP, RealT} - gas_constants::SVector{NCOMP, RealT} - cv::SVector{NCOMP, RealT} - cp::SVector{NCOMP, RealT} - - function IdealGlmMhdMulticomponentEquations1D{NVARS, NCOMP, RealT}(gammas::SVector{NCOMP, - RealT}, - gas_constants::SVector{NCOMP, - RealT}) where { - NVARS, - NCOMP, - RealT <: - Real - } - NCOMP >= 1 || - throw(DimensionMismatch("`gammas` and `gas_constants` have to be filled with at least one value")) - - cv = gas_constants ./ (gammas .- 1) - cp = gas_constants + gas_constants ./ (gammas .- 1) - - new(gammas, gas_constants, cv, cp) + #! format: noindent + + @doc raw""" + IdealGlmMhdMulticomponentEquations1D + + The ideal compressible multicomponent GLM-MHD equations in one space dimension. + """ + mutable struct IdealGlmMhdMulticomponentEquations1D{NVARS, NCOMP, RealT <: Real} <: + AbstractIdealGlmMhdMulticomponentEquations{1, NVARS, NCOMP} + gammas::SVector{NCOMP, RealT} + gas_constants::SVector{NCOMP, RealT} + cv::SVector{NCOMP, RealT} + cp::SVector{NCOMP, RealT} + + function IdealGlmMhdMulticomponentEquations1D{NVARS, NCOMP, RealT}( + gammas::SVector{ + NCOMP, + RealT, + }, + gas_constants::SVector{ + NCOMP, + RealT, + } + ) where { + NVARS, + NCOMP, + RealT <: + Real, + } + NCOMP >= 1 || + throw(DimensionMismatch("`gammas` and `gas_constants` have to be filled with at least one value")) + + cv = gas_constants ./ (gammas .- 1) + cp = gas_constants + gas_constants ./ (gammas .- 1) + + new(gammas, gas_constants, cv, cp) + end end -end - -function IdealGlmMhdMulticomponentEquations1D(; gammas, gas_constants) - _gammas = promote(gammas...) - _gas_constants = promote(gas_constants...) - RealT = promote_type(eltype(_gammas), eltype(_gas_constants)) - - NVARS = length(_gammas) + 7 - NCOMP = length(_gammas) - - __gammas = SVector(map(RealT, _gammas)) - __gas_constants = SVector(map(RealT, _gas_constants)) - - return IdealGlmMhdMulticomponentEquations1D{NVARS, NCOMP, RealT}(__gammas, - __gas_constants) -end - -@inline function Base.real(::IdealGlmMhdMulticomponentEquations1D{NVARS, NCOMP, RealT}) where { - NVARS, - NCOMP, - RealT - } - RealT -end - -have_nonconservative_terms(::IdealGlmMhdMulticomponentEquations1D) = False() - -function varnames(::typeof(cons2cons), equations::IdealGlmMhdMulticomponentEquations1D) - cons = ("rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3") - rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) - return (cons..., rhos...) -end - -function varnames(::typeof(cons2prim), equations::IdealGlmMhdMulticomponentEquations1D) - prim = ("v1", "v2", "v3", "p", "B1", "B2", "B3") - rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) - return (prim..., rhos...) -end - -""" - initial_condition_convergence_test(x, t, equations::IdealGlmMhdMulticomponentEquations1D) - -An Alfvén wave as smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, - equations::IdealGlmMhdMulticomponentEquations1D) - # smooth Alfvén wave test from Derigs et al. FLASH (2016) - # domain must be set to [0, 1], γ = 5/3 - - RealT = eltype(x) - rho = 1 - prim_rho = SVector{ncomponents(equations), real(equations)}(2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - rho - for i in eachcomponent(equations)) - v1 = 0 - # TODO: sincospi - si, co = sincos(2 * convert(RealT, pi) * x[1]) - v2 = convert(RealT, 0.1) * si - v3 = convert(RealT, 0.1) * co - p = convert(RealT, 0.1) - B1 = 1 - B2 = v2 - B3 = v3 - prim_other = SVector(v1, v2, v3, p, B1, B2, B3) - - return prim2cons(vcat(prim_other, prim_rho), equations) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdMulticomponentEquations1D) - -A weak blast wave adapted from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, - equations::IdealGlmMhdMulticomponentEquations1D) - # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Same discontinuity in the velocities but with magnetic fields - # Set up polar coordinates - RealT = eltype(x) - inicenter = (0) - x_norm = x[1] - inicenter[1] - r = sqrt(x_norm^2) - phi = atan(x_norm) - - # Calculate primitive variables - if r > 0.5f0 - rho = one(RealT) - prim_rho = SVector{ncomponents(equations), real(equations)}(2^(i - 1) * - (1 - 2) / (1 - - 2^ncomponents(equations)) * - rho - for i in eachcomponent(equations)) - else - rho = convert(RealT, 1.1691) - prim_rho = SVector{ncomponents(equations), real(equations)}(2^(i - 1) * - (1 - 2) / (1 - - 2^ncomponents(equations)) * - rho - for i in eachcomponent(equations)) + + function IdealGlmMhdMulticomponentEquations1D(; gammas, gas_constants) + _gammas = promote(gammas...) + _gas_constants = promote(gas_constants...) + RealT = promote_type(eltype(_gammas), eltype(_gas_constants)) + + NVARS = length(_gammas) + 7 + NCOMP = length(_gammas) + + __gammas = SVector(map(RealT, _gammas)) + __gas_constants = SVector(map(RealT, _gas_constants)) + + return IdealGlmMhdMulticomponentEquations1D{NVARS, NCOMP, RealT}( + __gammas, + __gas_constants + ) end - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - prim_other = SVector(v1, 0, 0, p, 1, 1, 1) - - return prim2cons(vcat(prim_other, prim_rho), equations) -end - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations1D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - - rho = density(u, equations) - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - kin_en = 0.5f0 * rho * (v1^2 + v2^2 + v3^2) - mag_en = 0.5f0 * (B1^2 + B2^2 + B3^2) - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - kin_en - mag_en) - - f_rho = densities(u, v1, equations) - f1 = rho_v1 * v1 + p + mag_en - B1^2 - f2 = rho_v1 * v2 - B1 * B2 - f3 = rho_v1 * v3 - B1 * B3 - f4 = (kin_en + gamma * p / (gamma - 1) + 2 * mag_en) * v1 - - B1 * (v1 * B1 + v2 * B2 + v3 * B3) - f5 = 0 - f6 = v1 * B2 - v2 * B1 - f7 = v1 * B3 - v3 * B1 - - f_other = SVector(f1, f2, f3, f4, f5, f6, f7) - - return vcat(f_other, f_rho) -end - -""" - flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations1D) - -Entropy conserving two-point flux adapted by -- Derigs et al. (2018) - Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field - divergence diminishing ideal magnetohydrodynamics equations for multicomponent - [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) -""" -function flux_derigs_etal(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations1D) - # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) - rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll = u_ll - rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr = u_rr - @unpack gammas, gas_constants, cv = equations - - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - gamma_ll = totalgamma(u_ll, equations) - gamma_rr = totalgamma(u_rr, equations) - - rhok_mean = SVector{ncomponents(equations), real(equations)}(ln_mean(u_ll[i + 7], - u_rr[i + 7]) - for i in eachcomponent(equations)) - rhok_avg = SVector{ncomponents(equations), real(equations)}(0.5f0 * (u_ll[i + 7] + - u_rr[i + 7]) - for i in eachcomponent(equations)) - - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 - vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 - mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 - mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 - # for convenience store v⋅B - vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll - vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr - - # Compute the necessary mean values needed for either direction - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - v_sum = v1_avg + v2_avg + v3_avg - B1_avg = 0.5f0 * (B1_ll + B1_rr) - B2_avg = 0.5f0 * (B2_ll + B2_rr) - B3_avg = 0.5f0 * (B3_ll + B3_rr) - vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) - mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) - vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) - - RealT = eltype(u_ll) - enth = zero(RealT) - help1_ll = zero(RealT) - help1_rr = zero(RealT) - - for i in eachcomponent(equations) - enth += rhok_avg[i] * gas_constants[i] - help1_ll += u_ll[i + 7] * cv[i] - help1_rr += u_rr[i + 7] * cv[i] + + @inline function Base.real(::IdealGlmMhdMulticomponentEquations1D{NVARS, NCOMP, RealT}) where { + NVARS, + NCOMP, + RealT, + } + RealT end - T_ll = (rho_e_ll - 0.5f0 * rho_ll * (vel_norm_ll) - 0.5f0 * mag_norm_ll) / help1_ll - T_rr = (rho_e_rr - 0.5f0 * rho_rr * (vel_norm_rr) - 0.5f0 * mag_norm_rr) / help1_rr - T = 0.5f0 * (1 / T_ll + 1 / T_rr) - T_log = ln_mean(1 / T_ll, 1 / T_rr) + have_nonconservative_terms(::IdealGlmMhdMulticomponentEquations1D) = False() - # Calculate fluxes depending on orientation with specific direction averages - help1 = zero(RealT) - help2 = zero(RealT) + function varnames(::typeof(cons2cons), equations::IdealGlmMhdMulticomponentEquations1D) + cons = ("rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3") + rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) + return (cons..., rhos...) + end + + function varnames(::typeof(cons2prim), equations::IdealGlmMhdMulticomponentEquations1D) + prim = ("v1", "v2", "v3", "p", "B1", "B2", "B3") + rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) + return (prim..., rhos...) + end - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v1_avg - for i in eachcomponent(equations)) - for i in eachcomponent(equations) - help1 += f_rho[i] * cv[i] - help2 += f_rho[i] + """ + initial_condition_convergence_test(x, t, equations::IdealGlmMhdMulticomponentEquations1D) + + An Alfvén wave as smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test( + x, t, + equations::IdealGlmMhdMulticomponentEquations1D + ) + # smooth Alfvén wave test from Derigs et al. FLASH (2016) + # domain must be set to [0, 1], γ = 5/3 + + RealT = eltype(x) + rho = 1 + prim_rho = SVector{ncomponents(equations), real(equations)}( + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + rho + for i in eachcomponent(equations) + ) + v1 = 0 + # TODO: sincospi + si, co = sincos(2 * convert(RealT, pi) * x[1]) + v2 = convert(RealT, 0.1) * si + v3 = convert(RealT, 0.1) * co + p = convert(RealT, 0.1) + B1 = 1 + B2 = v2 + B3 = v3 + prim_other = SVector(v1, v2, v3, p, B1, B2, B3) + + return prim2cons(vcat(prim_other, prim_rho), equations) end - f1 = help2 * v1_avg + enth / T + 0.5f0 * mag_norm_avg - B1_avg * B1_avg - f2 = help2 * v2_avg - B1_avg * B2_avg - f3 = help2 * v3_avg - B1_avg * B3_avg - f5 = 0 - f6 = v1_avg * B2_avg - v2_avg * B1_avg - f7 = v1_avg * B3_avg - v3_avg * B1_avg - - # total energy flux is complicated and involves the previous eight components - v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) - - f4 = (help1 / T_log) - 0.5f0 * (vel_norm_avg) * (help2) + f1 * v1_avg + - f2 * v2_avg + - f3 * v3_avg + - f5 * B1_avg + f6 * B2_avg + f7 * B3_avg - 0.5f0 * v1_mag_avg + - B1_avg * vel_dot_mag_avg - - f_other = SVector(f1, f2, f3, f4, f5, f6, f7) - - return vcat(f_other, f_rho) -end - -""" - flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, - equations::IdealGlmMhdMulticomponentEquations1D) - -Adaption of the entropy conserving and kinetic energy preserving two-point flux of -Hindenlang (2019), extending [`flux_ranocha`](@ref) to the MHD equations. -## References -- Florian Hindenlang, Gregor Gassner (2019) - A new entropy conservative two-point flux for ideal MHD equations derived from - first principles. - Presented at HONOM 2019: European workshop on high order numerical methods - for evolutionary PDEs, theory and applications -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_hindenlang_gassner(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations1D) - # Unpack left and right states - v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll = cons2prim(u_ll, equations) - v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr = cons2prim(u_rr, equations) - - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - # Compute the necessary mean values needed for either direction - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) - - inv_gamma_minus_one = 1 / (totalgamma(0.5f0 * (u_ll + u_rr), equations) - 1) - - rhok_mean = SVector{ncomponents(equations), real(equations)}(ln_mean(u_ll[i + 7], - u_rr[i + 7]) - for i in eachcomponent(equations)) - rhok_avg = SVector{ncomponents(equations), real(equations)}(0.5f0 * (u_ll[i + 7] + - u_rr[i + 7]) - for i in eachcomponent(equations)) - - RealT = eltype(u_ll) - f1 = zero(RealT) - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v1_avg - for i in eachcomponent(equations)) - for i in eachcomponent(equations) - f1 += f_rho[i] + + """ + initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdMulticomponentEquations1D) + + A weak blast wave adapted from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave( + x, t, + equations::IdealGlmMhdMulticomponentEquations1D + ) + # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Same discontinuity in the velocities but with magnetic fields + # Set up polar coordinates + RealT = eltype(x) + inicenter = (0) + x_norm = x[1] - inicenter[1] + r = sqrt(x_norm^2) + phi = atan(x_norm) + + # Calculate primitive variables + if r > 0.5f0 + rho = one(RealT) + prim_rho = SVector{ncomponents(equations), real(equations)}( + 2^(i - 1) * + (1 - 2) / ( + 1 - + 2^ncomponents(equations) + ) * + rho + for i in eachcomponent(equations) + ) + else + rho = convert(RealT, 1.1691) + prim_rho = SVector{ncomponents(equations), real(equations)}( + 2^(i - 1) * + (1 - 2) / ( + 1 - + 2^ncomponents(equations) + ) * + rho + for i in eachcomponent(equations) + ) + end + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + prim_other = SVector(v1, 0, 0, p, 1, 1, 1) + + return prim2cons(vcat(prim_other, prim_rho), equations) + end + + # Calculate 1D flux in for a single point + @inline function flux( + u, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations1D + ) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u + + rho = density(u, equations) + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + kin_en = 0.5f0 * rho * (v1^2 + v2^2 + v3^2) + mag_en = 0.5f0 * (B1^2 + B2^2 + B3^2) + gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - kin_en - mag_en) + + f_rho = densities(u, v1, equations) + f1 = rho_v1 * v1 + p + mag_en - B1^2 + f2 = rho_v1 * v2 - B1 * B2 + f3 = rho_v1 * v3 - B1 * B3 + f4 = (kin_en + gamma * p / (gamma - 1) + 2 * mag_en) * v1 - + B1 * (v1 * B1 + v2 * B2 + v3 * B3) + f5 = 0 + f6 = v1 * B2 - v2 * B1 + f7 = v1 * B3 - v3 * B1 + + f_other = SVector(f1, f2, f3, f4, f5, f6, f7) + + return vcat(f_other, f_rho) + end + + """ + flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdEquations1D) + + Entropy conserving two-point flux adapted by + - Derigs et al. (2018) + Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field + divergence diminishing ideal magnetohydrodynamics equations for multicomponent + [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) + """ + function flux_derigs_etal( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations1D + ) + # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) + rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll = u_ll + rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr = u_rr + @unpack gammas, gas_constants, cv = equations + + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + gamma_ll = totalgamma(u_ll, equations) + gamma_rr = totalgamma(u_rr, equations) + + rhok_mean = SVector{ncomponents(equations), real(equations)}( + ln_mean( + u_ll[i + 7], + u_rr[i + 7] + ) + for i in eachcomponent(equations) + ) + rhok_avg = SVector{ncomponents(equations), real(equations)}( + 0.5f0 * ( + u_ll[i + 7] + + u_rr[i + 7] + ) + for i in eachcomponent(equations) + ) + + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 + vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 + mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 + mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 + # for convenience store v⋅B + vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll + vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr + + # Compute the necessary mean values needed for either direction + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + v_sum = v1_avg + v2_avg + v3_avg + B1_avg = 0.5f0 * (B1_ll + B1_rr) + B2_avg = 0.5f0 * (B2_ll + B2_rr) + B3_avg = 0.5f0 * (B3_ll + B3_rr) + vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) + mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) + vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) + + RealT = eltype(u_ll) + enth = zero(RealT) + help1_ll = zero(RealT) + help1_rr = zero(RealT) + + for i in eachcomponent(equations) + enth += rhok_avg[i] * gas_constants[i] + help1_ll += u_ll[i + 7] * cv[i] + help1_rr += u_rr[i + 7] * cv[i] + end + + T_ll = (rho_e_ll - 0.5f0 * rho_ll * (vel_norm_ll) - 0.5f0 * mag_norm_ll) / help1_ll + T_rr = (rho_e_rr - 0.5f0 * rho_rr * (vel_norm_rr) - 0.5f0 * mag_norm_rr) / help1_rr + T = 0.5f0 * (1 / T_ll + 1 / T_rr) + T_log = ln_mean(1 / T_ll, 1 / T_rr) + + # Calculate fluxes depending on orientation with specific direction averages + help1 = zero(RealT) + help2 = zero(RealT) + + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v1_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + help1 += f_rho[i] * cv[i] + help2 += f_rho[i] + end + f1 = help2 * v1_avg + enth / T + 0.5f0 * mag_norm_avg - B1_avg * B1_avg + f2 = help2 * v2_avg - B1_avg * B2_avg + f3 = help2 * v3_avg - B1_avg * B3_avg + f5 = 0 + f6 = v1_avg * B2_avg - v2_avg * B1_avg + f7 = v1_avg * B3_avg - v3_avg * B1_avg + + # total energy flux is complicated and involves the previous eight components + v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) + + f4 = (help1 / T_log) - 0.5f0 * (vel_norm_avg) * (help2) + f1 * v1_avg + + f2 * v2_avg + + f3 * v3_avg + + f5 * B1_avg + f6 * B2_avg + f7 * B3_avg - 0.5f0 * v1_mag_avg + + B1_avg * vel_dot_mag_avg + + f_other = SVector(f1, f2, f3, f4, f5, f6, f7) + + return vcat(f_other, f_rho) + end + + """ + flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, + equations::IdealGlmMhdMulticomponentEquations1D) + + Adaption of the entropy conserving and kinetic energy preserving two-point flux of + Hindenlang (2019), extending [`flux_ranocha`](@ref) to the MHD equations. + ## References + - Florian Hindenlang, Gregor Gassner (2019) + A new entropy conservative two-point flux for ideal MHD equations derived from + first principles. + Presented at HONOM 2019: European workshop on high order numerical methods + for evolutionary PDEs, theory and applications + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_hindenlang_gassner( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations1D + ) + # Unpack left and right states + v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll = cons2prim(u_ll, equations) + v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr = cons2prim(u_rr, equations) + + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + # Compute the necessary mean values needed for either direction + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) + + inv_gamma_minus_one = 1 / (totalgamma(0.5f0 * (u_ll + u_rr), equations) - 1) + + rhok_mean = SVector{ncomponents(equations), real(equations)}( + ln_mean( + u_ll[i + 7], + u_rr[i + 7] + ) + for i in eachcomponent(equations) + ) + rhok_avg = SVector{ncomponents(equations), real(equations)}( + 0.5f0 * ( + u_ll[i + 7] + + u_rr[i + 7] + ) + for i in eachcomponent(equations) + ) + + RealT = eltype(u_ll) + f1 = zero(RealT) + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v1_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + f1 += f_rho[i] + end + + # Calculate fluxes depending on orientation with specific direction averages + f2 = f1 * v1_avg + p_avg + magnetic_square_avg - + 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) + f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) + f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) + # f5 below + f6 = 0 + f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) + f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) + # total energy flux is complicated and involves the previous components + f5 = ( + f1 * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v1_rr + p_rr * v1_ll + + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) + + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) + - + (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) + - + (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll) + ) + ) + + f_other = SVector(f2, f3, f4, f5, f6, f7, f8) + + return vcat(f_other, f_rho) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations1D + ) + rho_v1_ll, _ = u_ll + rho_v1_rr, _ = u_rr + + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + # Calculate velocities (ignore orientation since it is always "1" in 1D) + # and fast magnetoacoustic wave speeds + # left + v_ll = rho_v1_ll / rho_ll + cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) + # right + v_rr = rho_v1_rr / rho_rr + cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_max = max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) end - # Calculate fluxes depending on orientation with specific direction averages - f2 = f1 * v1_avg + p_avg + magnetic_square_avg - - 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) - f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) - f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) - # f5 below - f6 = 0 - f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) - f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) - # total energy flux is complicated and involves the previous components - f5 = (f1 * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v1_rr + p_rr * v1_ll - + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) - + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) - - - (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) - - - (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll))) - - f_other = SVector(f2, f3, f4, f5, f6, f7, f8) - - return vcat(f_other, f_rho) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations1D) - rho_v1_ll, _ = u_ll - rho_v1_rr, _ = u_rr - - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - # Calculate velocities (ignore orientation since it is always "1" in 1D) - # and fast magnetoacoustic wave speeds - # left - v_ll = rho_v1_ll / rho_ll - cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) - # right - v_rr = rho_v1_rr / rho_rr - cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) - - λ_max = max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) -end - -@inline function max_abs_speeds(u, equations::IdealGlmMhdMulticomponentEquations1D) - rho_v1, _ = u - - rho = density(u, equations) - - v1 = rho_v1 / rho - - cf_x_direction = calc_fast_wavespeed(u, 1, equations) - - return (abs(v1) + cf_x_direction,) -end - -# Convert conservative variables to primitive -function cons2prim(u, equations::IdealGlmMhdMulticomponentEquations1D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - - prim_rho = SVector{ncomponents(equations), real(equations)}(u[i + 7] - for i in eachcomponent(equations)) - rho = density(u, equations) - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - - gamma = totalgamma(u, equations) - - p = (gamma - 1) * - (rho_e - 0.5f0 * rho * (v1^2 + v2^2 + v3^2) - 0.5f0 * (B1^2 + B2^2 + B3^2)) - prim_other = SVector(v1, v2, v3, p, B1, B2, B3) - - return vcat(prim_other, prim_rho) -end - -# Convert conservative variables to entropy -@inline function cons2entropy(u, equations::IdealGlmMhdMulticomponentEquations1D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - @unpack cv, gammas, gas_constants = equations - - rho = density(u, equations) - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - v_square = v1^2 + v2^2 + v3^2 - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2)) - s = log(p) - gamma * log(rho) - rho_p = rho / p - - # Multicomponent stuff - RealT = eltype(u) - help1 = zero(RealT) - - for i in eachcomponent(equations) - help1 += u[i + 7] * cv[i] + @inline function max_abs_speeds(u, equations::IdealGlmMhdMulticomponentEquations1D) + rho_v1, _ = u + + rho = density(u, equations) + + v1 = rho_v1 / rho + + cf_x_direction = calc_fast_wavespeed(u, 1, equations) + + return (abs(v1) + cf_x_direction,) end - T = (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2)) / (help1) + # Convert conservative variables to primitive + function cons2prim(u, equations::IdealGlmMhdMulticomponentEquations1D) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - entrop_rho = SVector{ncomponents(equations), real(equations)}(-1 * - (cv[i] * log(T) - - gas_constants[i] * - log(u[i + 7])) + - gas_constants[i] + - cv[i] - - (v_square / (2 * T)) - for i in eachcomponent(equations)) + prim_rho = SVector{ncomponents(equations), real(equations)}( + u[i + 7] + for i in eachcomponent(equations) + ) + rho = density(u, equations) - w1 = v1 / T - w2 = v2 / T - w3 = v3 / T - w4 = -1 / T - w5 = B1 / T - w6 = B2 / T - w7 = B3 / T + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho - entrop_other = SVector(w1, w2, w3, w4, w5, w6, w7) + gamma = totalgamma(u, equations) - return vcat(entrop_other, entrop_rho) -end + p = (gamma - 1) * + (rho_e - 0.5f0 * rho * (v1^2 + v2^2 + v3^2) - 0.5f0 * (B1^2 + B2^2 + B3^2)) + prim_other = SVector(v1, v2, v3, p, B1, B2, B3) -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::IdealGlmMhdMulticomponentEquations1D) - v1, v2, v3, p, B1, B2, B3 = prim + return vcat(prim_other, prim_rho) + end - cons_rho = SVector{ncomponents(equations), real(equations)}(prim[i + 7] - for i in eachcomponent(equations)) - rho = density(prim, equations) + # Convert conservative variables to entropy + @inline function cons2entropy(u, equations::IdealGlmMhdMulticomponentEquations1D) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u + @unpack cv, gammas, gas_constants = equations + + rho = density(u, equations) + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + v_square = v1^2 + v2^2 + v3^2 + gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2)) + s = log(p) - gamma * log(rho) + rho_p = rho / p + + # Multicomponent stuff + RealT = eltype(u) + help1 = zero(RealT) + + for i in eachcomponent(equations) + help1 += u[i + 7] * cv[i] + end + + T = (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2)) / (help1) + + entrop_rho = SVector{ncomponents(equations), real(equations)}( + -1 * + ( + cv[i] * log(T) - + gas_constants[i] * + log(u[i + 7]) + ) + + gas_constants[i] + + cv[i] - + (v_square / (2 * T)) + for i in eachcomponent(equations) + ) + + w1 = v1 / T + w2 = v2 / T + w3 = v3 / T + w4 = -1 / T + w5 = B1 / T + w6 = B2 / T + w7 = B3 / T + + entrop_other = SVector(w1, w2, w3, w4, w5, w6, w7) + + return vcat(entrop_other, entrop_rho) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::IdealGlmMhdMulticomponentEquations1D) + v1, v2, v3, p, B1, B2, B3 = prim - rho_v1 = rho * v1 - rho_v2 = rho * v2 - rho_v3 = rho * v3 + cons_rho = SVector{ncomponents(equations), real(equations)}( + prim[i + 7] + for i in eachcomponent(equations) + ) + rho = density(prim, equations) - gamma = totalgamma(prim, equations) - rho_e = p / (gamma - 1) + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_v3 = rho * v3 + + gamma = totalgamma(prim, equations) + rho_e = p / (gamma - 1) + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + 0.5f0 * (B1^2 + B2^2 + B3^2) - cons_other = SVector(rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3) - - return vcat(cons_other, cons_rho) -end - -@inline function density_pressure(u, equations::IdealGlmMhdMulticomponentEquations1D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u - rho = density(u, equations) - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho - - - 0.5f0 * (B1^2 + B2^2 + B3^2)) - return rho * p -end - -# Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue -@inline function calc_fast_wavespeed(cons, direction, - equations::IdealGlmMhdMulticomponentEquations1D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = cons - rho = density(cons, equations) - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - v_mag = sqrt(v1^2 + v2^2 + v3^2) - gamma = totalgamma(cons, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * rho * v_mag^2 - 0.5f0 * (B1^2 + B2^2 + B3^2)) - a_square = gamma * p / rho - sqrt_rho = sqrt(rho) - b1 = B1 / sqrt_rho - b2 = B2 / sqrt_rho - b3 = B3 / sqrt_rho - b_square = b1^2 + b2^2 + b3^2 - - c_f = sqrt(0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2)) - - return c_f -end - -@inline function density(u, equations::IdealGlmMhdMulticomponentEquations1D) - RealT = eltype(u) - rho = zero(RealT) - - for i in eachcomponent(equations) - rho += u[i + 7] + cons_other = SVector(rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3) + + return vcat(cons_other, cons_rho) end - return rho -end + @inline function density_pressure(u, equations::IdealGlmMhdMulticomponentEquations1D) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = u + rho = density(u, equations) + gamma = totalgamma(u, equations) + p = (gamma - 1) * ( + rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho + - + 0.5f0 * (B1^2 + B2^2 + B3^2) + ) + return rho * p + end + + # Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue + @inline function calc_fast_wavespeed( + cons, direction, + equations::IdealGlmMhdMulticomponentEquations1D + ) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3 = cons + rho = density(cons, equations) + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + v_mag = sqrt(v1^2 + v2^2 + v3^2) + gamma = totalgamma(cons, equations) + p = (gamma - 1) * (rho_e - 0.5f0 * rho * v_mag^2 - 0.5f0 * (B1^2 + B2^2 + B3^2)) + a_square = gamma * p / rho + sqrt_rho = sqrt(rho) + b1 = B1 / sqrt_rho + b2 = B2 / sqrt_rho + b3 = B3 / sqrt_rho + b_square = b1^2 + b2^2 + b3^2 + + c_f = sqrt( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2) + ) + + return c_f + end -@inline function totalgamma(u, equations::IdealGlmMhdMulticomponentEquations1D) - @unpack cv, gammas = equations + @inline function density(u, equations::IdealGlmMhdMulticomponentEquations1D) + RealT = eltype(u) + rho = zero(RealT) - RealT = eltype(u) - help1 = zero(RealT) - help2 = zero(RealT) + for i in eachcomponent(equations) + rho += u[i + 7] + end - for i in eachcomponent(equations) - help1 += u[i + 7] * cv[i] * gammas[i] - help2 += u[i + 7] * cv[i] + return rho end - return help1 / help2 -end + @inline function totalgamma(u, equations::IdealGlmMhdMulticomponentEquations1D) + @unpack cv, gammas = equations + + RealT = eltype(u) + help1 = zero(RealT) + help2 = zero(RealT) -@inline function densities(u, v, equations::IdealGlmMhdMulticomponentEquations1D) - return SVector{ncomponents(equations), real(equations)}(u[i + 7] * v - for i in eachcomponent(equations)) -end + for i in eachcomponent(equations) + help1 += u[i + 7] * cv[i] * gammas[i] + help2 += u[i + 7] * cv[i] + end + + return help1 / help2 + end + + @inline function densities(u, v, equations::IdealGlmMhdMulticomponentEquations1D) + return SVector{ncomponents(equations), real(equations)}( + u[i + 7] * v + for i in eachcomponent(equations) + ) + end end # @muladd diff --git a/src/equations/ideal_glm_mhd_multicomponent_2d.jl b/src/equations/ideal_glm_mhd_multicomponent_2d.jl index 3aab048bd9..f20802b098 100644 --- a/src/equations/ideal_glm_mhd_multicomponent_2d.jl +++ b/src/equations/ideal_glm_mhd_multicomponent_2d.jl @@ -3,719 +3,817 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - IdealGlmMhdMulticomponentEquations2D - -The ideal compressible multicomponent GLM-MHD equations in two space dimensions. -""" -mutable struct IdealGlmMhdMulticomponentEquations2D{NVARS, NCOMP, RealT <: Real} <: - AbstractIdealGlmMhdMulticomponentEquations{2, NVARS, NCOMP} - gammas::SVector{NCOMP, RealT} - gas_constants::SVector{NCOMP, RealT} - cv::SVector{NCOMP, RealT} - cp::SVector{NCOMP, RealT} - c_h::RealT # GLM cleaning speed - - function IdealGlmMhdMulticomponentEquations2D{NVARS, NCOMP, RealT}(gammas::SVector{NCOMP, - RealT}, - gas_constants::SVector{NCOMP, - RealT}) where { - NVARS, - NCOMP, - RealT <: - Real - } - NCOMP >= 1 || - throw(DimensionMismatch("`gammas` and `gas_constants` have to be filled with at least one value")) - - cv = gas_constants ./ (gammas .- 1) - cp = gas_constants + gas_constants ./ (gammas .- 1) - c_h = convert(eltype(gammas), NaN) - - new(gammas, gas_constants, cv, cp, c_h) + #! format: noindent + + @doc raw""" + IdealGlmMhdMulticomponentEquations2D + + The ideal compressible multicomponent GLM-MHD equations in two space dimensions. + """ + mutable struct IdealGlmMhdMulticomponentEquations2D{NVARS, NCOMP, RealT <: Real} <: + AbstractIdealGlmMhdMulticomponentEquations{2, NVARS, NCOMP} + gammas::SVector{NCOMP, RealT} + gas_constants::SVector{NCOMP, RealT} + cv::SVector{NCOMP, RealT} + cp::SVector{NCOMP, RealT} + c_h::RealT # GLM cleaning speed + + function IdealGlmMhdMulticomponentEquations2D{NVARS, NCOMP, RealT}( + gammas::SVector{ + NCOMP, + RealT, + }, + gas_constants::SVector{ + NCOMP, + RealT, + } + ) where { + NVARS, + NCOMP, + RealT <: + Real, + } + NCOMP >= 1 || + throw(DimensionMismatch("`gammas` and `gas_constants` have to be filled with at least one value")) + + cv = gas_constants ./ (gammas .- 1) + cp = gas_constants + gas_constants ./ (gammas .- 1) + c_h = convert(eltype(gammas), NaN) + + new(gammas, gas_constants, cv, cp, c_h) + end end -end - -function IdealGlmMhdMulticomponentEquations2D(; gammas, gas_constants) - _gammas = promote(gammas...) - _gas_constants = promote(gas_constants...) - RealT = promote_type(eltype(_gammas), eltype(_gas_constants)) - - NVARS = length(_gammas) + 8 - NCOMP = length(_gammas) - - __gammas = SVector(map(RealT, _gammas)) - __gas_constants = SVector(map(RealT, _gas_constants)) - - return IdealGlmMhdMulticomponentEquations2D{NVARS, NCOMP, RealT}(__gammas, - __gas_constants) -end - -@inline function Base.real(::IdealGlmMhdMulticomponentEquations2D{NVARS, NCOMP, RealT}) where { - NVARS, - NCOMP, - RealT - } - RealT -end - -have_nonconservative_terms(::IdealGlmMhdMulticomponentEquations2D) = True() - -function varnames(::typeof(cons2cons), equations::IdealGlmMhdMulticomponentEquations2D) - cons = ("rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3", "psi") - rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) - return (cons..., rhos...) -end - -function varnames(::typeof(cons2prim), equations::IdealGlmMhdMulticomponentEquations2D) - prim = ("v1", "v2", "v3", "p", "B1", "B2", "B3", "psi") - rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) - return (prim..., rhos...) -end - -function default_analysis_integrals(::IdealGlmMhdMulticomponentEquations2D) - (entropy_timederivative, Val(:l2_divb), Val(:linf_divb)) -end - -""" - initial_condition_convergence_test(x, t, equations::IdealGlmMhdMulticomponentEquations2D) - -An Alfvén wave as smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, - equations::IdealGlmMhdMulticomponentEquations2D) - # smooth Alfvén wave test from Derigs et al. FLASH (2016) - # domain must be set to [0, 1/cos(α)] x [0, 1/sin(α)], γ = 5/3 - RealT = eltype(x) - alpha = 0.25f0 * convert(RealT, pi) - x_perp = x[1] * cos(alpha) + x[2] * sin(alpha) - B_perp = convert(RealT, 0.1) * sinpi(2 * x_perp) - rho = 1 - prim_rho = SVector{ncomponents(equations), real(equations)}(2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - rho - for i in eachcomponent(equations)) - - v1 = -B_perp * sin(alpha) - v2 = B_perp * cos(alpha) - v3 = convert(RealT, 0.1) * cospi(2 * x_perp) - p = convert(RealT, 0.1) - B1 = cos(alpha) + v1 - B2 = sin(alpha) + v2 - B3 = v3 - psi = 0 - prim_other = SVector(v1, v2, v3, p, B1, B2, B3, psi) - - return prim2cons(vcat(prim_other, prim_rho), equations) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdMulticomponentEquations2D) - -A weak blast wave adapted from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, - equations::IdealGlmMhdMulticomponentEquations2D) - # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Same discontinuity in the velocities but with magnetic fields - # Set up polar coordinates - RealT = eltype(x) - inicenter = SVector(0, 0) - x_norm = x[1] - inicenter[1] - y_norm = x[2] - inicenter[2] - r = sqrt(x_norm^2 + y_norm^2) - phi = atan(y_norm, x_norm) - sin_phi, cos_phi = sincos(phi) - - prim_rho = SVector{ncomponents(equations), real(equations)}(r > 0.5f0 ? - 2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - one(RealT) : - 2^(i - 1) * (1 - 2) / - (1 - - 2^ncomponents(equations)) * - convert(RealT, 1.1691) - for i in eachcomponent(equations)) - - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi - v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin_phi - p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) - - prim_other = SVector(v1, v2, 0, p, 1, 1, 1, 0) - - return prim2cons(vcat(prim_other, prim_rho), equations) -end - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations2D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u - @unpack c_h = equations - - rho = density(u, equations) - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - kin_en = 0.5f0 * rho * (v1^2 + v2^2 + v3^2) - mag_en = 0.5f0 * (B1^2 + B2^2 + B3^2) - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) - - if orientation == 1 - f_rho = densities(u, v1, equations) - f1 = rho_v1 * v1 + p + mag_en - B1^2 - f2 = rho_v1 * v2 - B1 * B2 - f3 = rho_v1 * v3 - B1 * B3 - f4 = (kin_en + gamma * p / (gamma - 1) + 2 * mag_en) * v1 - - B1 * (v1 * B1 + v2 * B2 + v3 * B3) + c_h * psi * B1 - f5 = c_h * psi - f6 = v1 * B2 - v2 * B1 - f7 = v1 * B3 - v3 * B1 - f8 = c_h * B1 - else # orientation == 2 - f_rho = densities(u, v2, equations) - f1 = rho_v2 * v1 - B1 * B2 - f2 = rho_v2 * v2 + p + mag_en - B2^2 - f3 = rho_v2 * v3 - B2 * B3 - f4 = (kin_en + gamma * p / (gamma - 1) + 2 * mag_en) * v2 - - B2 * (v1 * B1 + v2 * B2 + v3 * B3) + c_h * psi * B2 - f5 = v2 * B1 - v1 * B2 - f6 = c_h * psi - f7 = v2 * B3 - v3 * B2 - f8 = c_h * B2 + + function IdealGlmMhdMulticomponentEquations2D(; gammas, gas_constants) + _gammas = promote(gammas...) + _gas_constants = promote(gas_constants...) + RealT = promote_type(eltype(_gammas), eltype(_gas_constants)) + + NVARS = length(_gammas) + 8 + NCOMP = length(_gammas) + + __gammas = SVector(map(RealT, _gammas)) + __gas_constants = SVector(map(RealT, _gas_constants)) + + return IdealGlmMhdMulticomponentEquations2D{NVARS, NCOMP, RealT}( + __gammas, + __gas_constants + ) end - f_other = SVector(f1, f2, f3, f4, f5, f6, f7, f8) + @inline function Base.real(::IdealGlmMhdMulticomponentEquations2D{NVARS, NCOMP, RealT}) where { + NVARS, + NCOMP, + RealT, + } + RealT + end - return vcat(f_other, f_rho) -end + have_nonconservative_terms(::IdealGlmMhdMulticomponentEquations2D) = True() -""" - flux_nonconservative_powell(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations2D) + function varnames(::typeof(cons2cons), equations::IdealGlmMhdMulticomponentEquations2D) + cons = ("rho_v1", "rho_v2", "rho_v3", "rho_e", "B1", "B2", "B3", "psi") + rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) + return (cons..., rhos...) + end -Non-symmetric two-point flux discretizing the nonconservative (source) term of -Powell and the Galilean nonconservative term associated with the GLM multiplier -of the [`IdealGlmMhdMulticomponentEquations2D`](@ref). - -## References -- Marvin Bohm, Andrew R.Winters, Gregor J. Gassner, Dominik Derigs, - Florian Hindenlang, Joachim Saur - An entropy stable nodal discontinuous Galerkin method for the resistive MHD - equations. Part I: Theory and numerical verification - [DOI: 10.1016/j.jcp.2018.06.027](https://doi.org/10.1016/j.jcp.2018.06.027) -""" -@inline function flux_nonconservative_powell(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations2D) - rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll, psi_ll = u_ll - rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr, psi_rr = u_rr - - rho_ll = density(u_ll, equations) - - 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 - - # 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}, 0, 0, 0, v_{1,2}) - # Note that the order of conserved variables is changed compared to the - # standard GLM MHD equations, i.e., the densities are moved to the end - # Here, we compute the non-density components at first and append zero density - # components afterwards - zero_densities = SVector{ncomponents(equations), real(equations)}(ntuple(_ -> zero(real(equations)), - Val(ncomponents(equations)))) - if orientation == 1 - f = SVector(B1_ll * B1_rr, - B2_ll * B1_rr, - B3_ll * B1_rr, - v_dot_B_ll * B1_rr + v1_ll * psi_ll * psi_rr, - v1_ll * B1_rr, - v2_ll * B1_rr, - v3_ll * B1_rr, - v1_ll * psi_rr) - else # orientation == 2 - f = SVector(B1_ll * B2_rr, - B2_ll * B2_rr, - B3_ll * B2_rr, - v_dot_B_ll * B2_rr + v2_ll * psi_ll * psi_rr, - v1_ll * B2_rr, - v2_ll * B2_rr, - v3_ll * B2_rr, - v2_ll * psi_rr) + function varnames(::typeof(cons2prim), equations::IdealGlmMhdMulticomponentEquations2D) + prim = ("v1", "v2", "v3", "p", "B1", "B2", "B3", "psi") + rhos = ntuple(n -> "rho" * string(n), Val(ncomponents(equations))) + return (prim..., rhos...) end - return vcat(f, zero_densities) -end - -""" - flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdMulticomponentEquations2D) - -Entropy conserving two-point flux adapted by -- Derigs et al. (2018) - Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field - divergence diminishing ideal magnetohydrodynamics equations for multicomponent - [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) -""" -function flux_derigs_etal(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations2D) - # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) - rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll, psi_ll = u_ll - rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr, psi_rr = u_rr - @unpack gammas, gas_constants, cv, c_h = equations - - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - gamma_ll = totalgamma(u_ll, equations) - gamma_rr = totalgamma(u_rr, equations) - - rhok_mean = SVector{ncomponents(equations), real(equations)}(ln_mean(u_ll[i + 8], - u_rr[i + 8]) - for i in eachcomponent(equations)) - rhok_avg = SVector{ncomponents(equations), real(equations)}(0.5f0 * (u_ll[i + 8] + - u_rr[i + 8]) - for i in eachcomponent(equations)) - - v1_ll = rho_v1_ll / rho_ll - v2_ll = rho_v2_ll / rho_ll - v3_ll = rho_v3_ll / rho_ll - v1_rr = rho_v1_rr / rho_rr - v2_rr = rho_v2_rr / rho_rr - v3_rr = rho_v3_rr / rho_rr - v1_sq = 0.5f0 * (v1_ll^2 + v1_rr^2) - v2_sq = 0.5f0 * (v2_ll^2 + v2_rr^2) - v3_sq = 0.5f0 * (v3_ll^2 + v3_rr^2) - v_sq = v1_sq + v2_sq + v3_sq - B1_sq = 0.5f0 * (B1_ll^2 + B1_rr^2) - B2_sq = 0.5f0 * (B2_ll^2 + B2_rr^2) - B3_sq = 0.5f0 * (B3_ll^2 + B3_rr^2) - B_sq = B1_sq + B2_sq + B3_sq - vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 - vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 - mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 - mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 - # for convenience store v⋅B - vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll - vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr - - # Compute the necessary mean values needed for either direction - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - v_sum = v1_avg + v2_avg + v3_avg - B1_avg = 0.5f0 * (B1_ll + B1_rr) - B2_avg = 0.5f0 * (B2_ll + B2_rr) - B3_avg = 0.5f0 * (B3_ll + B3_rr) - psi_avg = 0.5f0 * (psi_ll + psi_rr) - vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) - mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) - vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) - - RealT = eltype(u_ll) - enth = zero(RealT) - help1_ll = zero(RealT) - help1_rr = zero(RealT) - - for i in eachcomponent(equations) - enth += rhok_avg[i] * gas_constants[i] - help1_ll += u_ll[i + 8] * cv[i] - help1_rr += u_rr[i + 8] * cv[i] + function default_analysis_integrals(::IdealGlmMhdMulticomponentEquations2D) + (entropy_timederivative, Val(:l2_divb), Val(:linf_divb)) end - T_ll = (rho_e_ll - 0.5f0 * rho_ll * (vel_norm_ll) - 0.5f0 * mag_norm_ll - - 0.5f0 * psi_ll^2) / help1_ll - T_rr = (rho_e_rr - 0.5f0 * rho_rr * (vel_norm_rr) - 0.5f0 * mag_norm_rr - - 0.5f0 * psi_rr^2) / help1_rr - T = 0.5f0 * (1 / T_ll + 1 / T_rr) - T_log = ln_mean(1 / T_ll, 1 / T_rr) - - # Calculate fluxes depending on orientation with specific direction averages - help1 = zero(RealT) - help2 = zero(RealT) - if orientation == 1 - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v1_avg - for i in eachcomponent(equations)) - for i in eachcomponent(equations) - help1 += f_rho[i] * cv[i] - help2 += f_rho[i] + """ + initial_condition_convergence_test(x, t, equations::IdealGlmMhdMulticomponentEquations2D) + + An Alfvén wave as smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test( + x, t, + equations::IdealGlmMhdMulticomponentEquations2D + ) + # smooth Alfvén wave test from Derigs et al. FLASH (2016) + # domain must be set to [0, 1/cos(α)] x [0, 1/sin(α)], γ = 5/3 + RealT = eltype(x) + alpha = 0.25f0 * convert(RealT, pi) + x_perp = x[1] * cos(alpha) + x[2] * sin(alpha) + B_perp = convert(RealT, 0.1) * sinpi(2 * x_perp) + rho = 1 + prim_rho = SVector{ncomponents(equations), real(equations)}( + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + rho + for i in eachcomponent(equations) + ) + + v1 = -B_perp * sin(alpha) + v2 = B_perp * cos(alpha) + v3 = convert(RealT, 0.1) * cospi(2 * x_perp) + p = convert(RealT, 0.1) + B1 = cos(alpha) + v1 + B2 = sin(alpha) + v2 + B3 = v3 + psi = 0 + prim_other = SVector(v1, v2, v3, p, B1, B2, B3, psi) + + return prim2cons(vcat(prim_other, prim_rho), equations) + end + + """ + initial_condition_weak_blast_wave(x, t, equations::IdealGlmMhdMulticomponentEquations2D) + + A weak blast wave adapted from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave( + x, t, + equations::IdealGlmMhdMulticomponentEquations2D + ) + # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Same discontinuity in the velocities but with magnetic fields + # Set up polar coordinates + RealT = eltype(x) + inicenter = SVector(0, 0) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + phi = atan(y_norm, x_norm) + sin_phi, cos_phi = sincos(phi) + + prim_rho = SVector{ncomponents(equations), real(equations)}( + r > 0.5f0 ? + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + one(RealT) : + 2^(i - 1) * (1 - 2) / + ( + 1 - + 2^ncomponents(equations) + ) * + convert(RealT, 1.1691) + for i in eachcomponent(equations) + ) + + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi + v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin_phi + p = r > 0.5f0 ? one(RealT) : convert(RealT, 1.245) + + prim_other = SVector(v1, v2, 0, p, 1, 1, 1, 0) + + return prim2cons(vcat(prim_other, prim_rho), equations) + end + + # Calculate 1D flux in for a single point + @inline function flux( + u, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations2D + ) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u + @unpack c_h = equations + + rho = density(u, equations) + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + kin_en = 0.5f0 * rho * (v1^2 + v2^2 + v3^2) + mag_en = 0.5f0 * (B1^2 + B2^2 + B3^2) + gamma = totalgamma(u, equations) + p = (gamma - 1) * (rho_e - kin_en - mag_en - 0.5f0 * psi^2) + + if orientation == 1 + f_rho = densities(u, v1, equations) + f1 = rho_v1 * v1 + p + mag_en - B1^2 + f2 = rho_v1 * v2 - B1 * B2 + f3 = rho_v1 * v3 - B1 * B3 + f4 = (kin_en + gamma * p / (gamma - 1) + 2 * mag_en) * v1 - + B1 * (v1 * B1 + v2 * B2 + v3 * B3) + c_h * psi * B1 + f5 = c_h * psi + f6 = v1 * B2 - v2 * B1 + f7 = v1 * B3 - v3 * B1 + f8 = c_h * B1 + else # orientation == 2 + f_rho = densities(u, v2, equations) + f1 = rho_v2 * v1 - B1 * B2 + f2 = rho_v2 * v2 + p + mag_en - B2^2 + f3 = rho_v2 * v3 - B2 * B3 + f4 = (kin_en + gamma * p / (gamma - 1) + 2 * mag_en) * v2 - + B2 * (v1 * B1 + v2 * B2 + v3 * B3) + c_h * psi * B2 + f5 = v2 * B1 - v1 * B2 + f6 = c_h * psi + f7 = v2 * B3 - v3 * B2 + f8 = c_h * B2 end - f1 = help2 * v1_avg + enth / T + 0.5f0 * mag_norm_avg - B1_avg * B1_avg - f2 = help2 * v2_avg - B1_avg * B2_avg - f3 = help2 * v3_avg - B1_avg * B3_avg - f5 = c_h * psi_avg - f6 = v1_avg * B2_avg - v2_avg * B1_avg - f7 = v1_avg * B3_avg - v3_avg * B1_avg - f8 = c_h * B1_avg - # total energy flux is complicated and involves the previous eight components - psi_B1_avg = 0.5f0 * (B1_ll * psi_ll + B1_rr * psi_rr) - v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) - - f4 = (help1 / T_log) - 0.5f0 * (vel_norm_avg) * (help2) + f1 * v1_avg + - f2 * v2_avg + f3 * v3_avg + - f5 * B1_avg + f6 * B2_avg + f7 * B3_avg + f8 * psi_avg - - 0.5f0 * v1_mag_avg + - B1_avg * vel_dot_mag_avg - c_h * psi_B1_avg - - else - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v2_avg - for i in eachcomponent(equations)) - for i in eachcomponent(equations) - help1 += f_rho[i] * cv[i] - help2 += f_rho[i] + + f_other = SVector(f1, f2, f3, f4, f5, f6, f7, f8) + + return vcat(f_other, f_rho) + end + + """ + flux_nonconservative_powell(u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations2D) + + Non-symmetric two-point flux discretizing the nonconservative (source) term of + Powell and the Galilean nonconservative term associated with the GLM multiplier + of the [`IdealGlmMhdMulticomponentEquations2D`](@ref). + + ## References + - Marvin Bohm, Andrew R.Winters, Gregor J. Gassner, Dominik Derigs, + Florian Hindenlang, Joachim Saur + An entropy stable nodal discontinuous Galerkin method for the resistive MHD + equations. Part I: Theory and numerical verification + [DOI: 10.1016/j.jcp.2018.06.027](https://doi.org/10.1016/j.jcp.2018.06.027) + """ + @inline function flux_nonconservative_powell( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations2D + ) + rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll, psi_ll = u_ll + rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr, psi_rr = u_rr + + rho_ll = density(u_ll, equations) + + 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 + + # 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}, 0, 0, 0, v_{1,2}) + # Note that the order of conserved variables is changed compared to the + # standard GLM MHD equations, i.e., the densities are moved to the end + # Here, we compute the non-density components at first and append zero density + # components afterwards + zero_densities = SVector{ncomponents(equations), real(equations)}( + ntuple( + _ -> zero(real(equations)), + Val(ncomponents(equations)) + ) + ) + if orientation == 1 + f = SVector( + B1_ll * B1_rr, + B2_ll * B1_rr, + B3_ll * B1_rr, + v_dot_B_ll * B1_rr + v1_ll * psi_ll * psi_rr, + v1_ll * B1_rr, + v2_ll * B1_rr, + v3_ll * B1_rr, + v1_ll * psi_rr + ) + else # orientation == 2 + f = SVector( + B1_ll * B2_rr, + B2_ll * B2_rr, + B3_ll * B2_rr, + v_dot_B_ll * B2_rr + v2_ll * psi_ll * psi_rr, + v1_ll * B2_rr, + v2_ll * B2_rr, + v3_ll * B2_rr, + v2_ll * psi_rr + ) end - f1 = help2 * v1_avg - B1_avg * B2_avg - f2 = help2 * v2_avg + enth / T + 0.5f0 * mag_norm_avg - B2_avg * B2_avg - f3 = help2 * v3_avg - B2_avg * B3_avg - f5 = v2_avg * B1_avg - v1_avg * B2_avg - f6 = c_h * psi_avg - f7 = v2_avg * B3_avg - v3_avg * B2_avg - f8 = c_h * B2_avg - - # total energy flux is complicated and involves the previous eight components - psi_B2_avg = 0.5f0 * (B2_ll * psi_ll + B2_rr * psi_rr) - v2_mag_avg = 0.5f0 * (v2_ll * mag_norm_ll + v2_rr * mag_norm_rr) - - f4 = (help1 / T_log) - 0.5f0 * (vel_norm_avg) * (help2) + f1 * v1_avg + - f2 * v2_avg + f3 * v3_avg + - f5 * B1_avg + f6 * B2_avg + f7 * B3_avg + f8 * psi_avg - - 0.5f0 * v2_mag_avg + - B2_avg * vel_dot_mag_avg - c_h * psi_B2_avg + + return vcat(f, zero_densities) end - f_other = SVector(f1, f2, f3, f4, f5, f6, f7, f8) - - return vcat(f_other, f_rho) -end - -""" - flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, - equations::IdealGlmMhdMulticomponentEquations2D) - -Adaption of the entropy conserving and kinetic energy preserving two-point flux of -Hindenlang (2019), extending [`flux_ranocha`](@ref) to the MHD equations. -## References -- Florian Hindenlang, Gregor Gassner (2019) - A new entropy conservative two-point flux for ideal MHD equations derived from - first principles. - Presented at HONOM 2019: European workshop on high order numerical methods - for evolutionary PDEs, theory and applications -- Hendrik Ranocha (2018) - Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods - for Hyperbolic Balance Laws - [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) -- Hendrik Ranocha (2020) - Entropy Conserving and Kinetic Energy Preserving Numerical Methods for - the Euler Equations Using Summation-by-Parts Operators - [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) -""" -@inline function flux_hindenlang_gassner(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations2D) - # Unpack left and right states - v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim(u_ll, equations) - v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim(u_rr, equations) - - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) - - # Compute the necessary mean values needed for either direction - # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` - # in exact arithmetic since - # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) - # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) - inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - v3_avg = 0.5f0 * (v3_ll + v3_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - psi_avg = 0.5f0 * (psi_ll + psi_rr) - velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) - magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) - - inv_gamma_minus_one = 1 / (totalgamma(0.5f0 * (u_ll + u_rr), equations) - 1) - - rhok_mean = SVector{ncomponents(equations), real(equations)}(ln_mean(u_ll[i + 8], - u_rr[i + 8]) - for i in eachcomponent(equations)) - rhok_avg = SVector{ncomponents(equations), real(equations)}(0.5f0 * (u_ll[i + 8] + - u_rr[i + 8]) - for i in eachcomponent(equations)) - - RealT = eltype(u_ll) - if orientation == 1 - f1 = zero(RealT) - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v1_avg - for i in eachcomponent(equations)) + """ + flux_derigs_etal(u_ll, u_rr, orientation, equations::IdealGlmMhdMulticomponentEquations2D) + + Entropy conserving two-point flux adapted by + - Derigs et al. (2018) + Ideal GLM-MHD: About the entropy consistent nine-wave magnetic field + divergence diminishing ideal magnetohydrodynamics equations for multicomponent + [DOI: 10.1016/j.jcp.2018.03.002](https://doi.org/10.1016/j.jcp.2018.03.002) + """ + function flux_derigs_etal( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations2D + ) + # Unpack left and right states to get velocities, pressure, and inverse temperature (called beta) + rho_v1_ll, rho_v2_ll, rho_v3_ll, rho_e_ll, B1_ll, B2_ll, B3_ll, psi_ll = u_ll + rho_v1_rr, rho_v2_rr, rho_v3_rr, rho_e_rr, B1_rr, B2_rr, B3_rr, psi_rr = u_rr + @unpack gammas, gas_constants, cv, c_h = equations + + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + gamma_ll = totalgamma(u_ll, equations) + gamma_rr = totalgamma(u_rr, equations) + + rhok_mean = SVector{ncomponents(equations), real(equations)}( + ln_mean( + u_ll[i + 8], + u_rr[i + 8] + ) + for i in eachcomponent(equations) + ) + rhok_avg = SVector{ncomponents(equations), real(equations)}( + 0.5f0 * ( + u_ll[i + 8] + + u_rr[i + 8] + ) + for i in eachcomponent(equations) + ) + + v1_ll = rho_v1_ll / rho_ll + v2_ll = rho_v2_ll / rho_ll + v3_ll = rho_v3_ll / rho_ll + v1_rr = rho_v1_rr / rho_rr + v2_rr = rho_v2_rr / rho_rr + v3_rr = rho_v3_rr / rho_rr + v1_sq = 0.5f0 * (v1_ll^2 + v1_rr^2) + v2_sq = 0.5f0 * (v2_ll^2 + v2_rr^2) + v3_sq = 0.5f0 * (v3_ll^2 + v3_rr^2) + v_sq = v1_sq + v2_sq + v3_sq + B1_sq = 0.5f0 * (B1_ll^2 + B1_rr^2) + B2_sq = 0.5f0 * (B2_ll^2 + B2_rr^2) + B3_sq = 0.5f0 * (B3_ll^2 + B3_rr^2) + B_sq = B1_sq + B2_sq + B3_sq + vel_norm_ll = v1_ll^2 + v2_ll^2 + v3_ll^2 + vel_norm_rr = v1_rr^2 + v2_rr^2 + v3_rr^2 + mag_norm_ll = B1_ll^2 + B2_ll^2 + B3_ll^2 + mag_norm_rr = B1_rr^2 + B2_rr^2 + B3_rr^2 + # for convenience store v⋅B + vel_dot_mag_ll = v1_ll * B1_ll + v2_ll * B2_ll + v3_ll * B3_ll + vel_dot_mag_rr = v1_rr * B1_rr + v2_rr * B2_rr + v3_rr * B3_rr + + # Compute the necessary mean values needed for either direction + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + v_sum = v1_avg + v2_avg + v3_avg + B1_avg = 0.5f0 * (B1_ll + B1_rr) + B2_avg = 0.5f0 * (B2_ll + B2_rr) + B3_avg = 0.5f0 * (B3_ll + B3_rr) + psi_avg = 0.5f0 * (psi_ll + psi_rr) + vel_norm_avg = 0.5f0 * (vel_norm_ll + vel_norm_rr) + mag_norm_avg = 0.5f0 * (mag_norm_ll + mag_norm_rr) + vel_dot_mag_avg = 0.5f0 * (vel_dot_mag_ll + vel_dot_mag_rr) + + RealT = eltype(u_ll) + enth = zero(RealT) + help1_ll = zero(RealT) + help1_rr = zero(RealT) + for i in eachcomponent(equations) - f1 += f_rho[i] + enth += rhok_avg[i] * gas_constants[i] + help1_ll += u_ll[i + 8] * cv[i] + help1_rr += u_rr[i + 8] * cv[i] end + T_ll = ( + rho_e_ll - 0.5f0 * rho_ll * (vel_norm_ll) - 0.5f0 * mag_norm_ll - + 0.5f0 * psi_ll^2 + ) / help1_ll + T_rr = ( + rho_e_rr - 0.5f0 * rho_rr * (vel_norm_rr) - 0.5f0 * mag_norm_rr - + 0.5f0 * psi_rr^2 + ) / help1_rr + T = 0.5f0 * (1 / T_ll + 1 / T_rr) + T_log = ln_mean(1 / T_ll, 1 / T_rr) + # Calculate fluxes depending on orientation with specific direction averages - f2 = f1 * v1_avg + p_avg + magnetic_square_avg - - 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) - f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) - f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) - # f5 below - f6 = f6 = equations.c_h * psi_avg - f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) - f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) - f9 = equations.c_h * 0.5f0 * (B1_ll + B1_rr) - # total energy flux is complicated and involves the previous components - f5 = (f1 * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v1_rr + p_rr * v1_ll - + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) - + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) - - - (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) - - - (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll) - + - equations.c_h * (B1_ll * psi_rr + B1_rr * psi_ll))) - else - f1 = zero(RealT) - f_rho = SVector{ncomponents(equations), real(equations)}(rhok_mean[i] * v2_avg - for i in eachcomponent(equations)) - for i in eachcomponent(equations) - f1 += f_rho[i] + help1 = zero(RealT) + help2 = zero(RealT) + if orientation == 1 + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v1_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + help1 += f_rho[i] * cv[i] + help2 += f_rho[i] + end + f1 = help2 * v1_avg + enth / T + 0.5f0 * mag_norm_avg - B1_avg * B1_avg + f2 = help2 * v2_avg - B1_avg * B2_avg + f3 = help2 * v3_avg - B1_avg * B3_avg + f5 = c_h * psi_avg + f6 = v1_avg * B2_avg - v2_avg * B1_avg + f7 = v1_avg * B3_avg - v3_avg * B1_avg + f8 = c_h * B1_avg + # total energy flux is complicated and involves the previous eight components + psi_B1_avg = 0.5f0 * (B1_ll * psi_ll + B1_rr * psi_rr) + v1_mag_avg = 0.5f0 * (v1_ll * mag_norm_ll + v1_rr * mag_norm_rr) + + f4 = (help1 / T_log) - 0.5f0 * (vel_norm_avg) * (help2) + f1 * v1_avg + + f2 * v2_avg + f3 * v3_avg + + f5 * B1_avg + f6 * B2_avg + f7 * B3_avg + f8 * psi_avg - + 0.5f0 * v1_mag_avg + + B1_avg * vel_dot_mag_avg - c_h * psi_B1_avg + + else + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v2_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + help1 += f_rho[i] * cv[i] + help2 += f_rho[i] + end + f1 = help2 * v1_avg - B1_avg * B2_avg + f2 = help2 * v2_avg + enth / T + 0.5f0 * mag_norm_avg - B2_avg * B2_avg + f3 = help2 * v3_avg - B2_avg * B3_avg + f5 = v2_avg * B1_avg - v1_avg * B2_avg + f6 = c_h * psi_avg + f7 = v2_avg * B3_avg - v3_avg * B2_avg + f8 = c_h * B2_avg + + # total energy flux is complicated and involves the previous eight components + psi_B2_avg = 0.5f0 * (B2_ll * psi_ll + B2_rr * psi_rr) + v2_mag_avg = 0.5f0 * (v2_ll * mag_norm_ll + v2_rr * mag_norm_rr) + + f4 = (help1 / T_log) - 0.5f0 * (vel_norm_avg) * (help2) + f1 * v1_avg + + f2 * v2_avg + f3 * v3_avg + + f5 * B1_avg + f6 * B2_avg + f7 * B3_avg + f8 * psi_avg - + 0.5f0 * v2_mag_avg + + B2_avg * vel_dot_mag_avg - c_h * psi_B2_avg end - # Calculate fluxes depending on orientation with specific direction averages - f2 = f1 * v1_avg - 0.5f0 * (B2_ll * B1_rr + B2_rr * B1_ll) - f3 = f1 * v2_avg + p_avg + magnetic_square_avg - - 0.5f0 * (B2_ll * B2_rr + B2_rr * B2_ll) - f4 = f1 * v3_avg - 0.5f0 * (B2_ll * B3_rr + B2_rr * B3_ll) - #f5 below - f6 = 0.5f0 * (v2_ll * B1_ll - v1_ll * B2_ll + v2_rr * B1_rr - v1_rr * B2_rr) - f7 = equations.c_h * psi_avg - f8 = 0.5f0 * (v2_ll * B3_ll - v3_ll * B2_ll + v2_rr * B3_rr - v3_rr * B2_rr) - f9 = equations.c_h * 0.5f0 * (B2_ll + B2_rr) - # total energy flux is complicated and involves the previous components - f5 = (f1 * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) - + - 0.5f0 * (+p_ll * v2_rr + p_rr * v2_ll - + (v2_ll * B1_ll * B1_rr + v2_rr * B1_rr * B1_ll) - + (v2_ll * B3_ll * B3_rr + v2_rr * B3_rr * B3_ll) - - - (v1_ll * B2_ll * B1_rr + v1_rr * B2_rr * B1_ll) - - - (v3_ll * B2_ll * B3_rr + v3_rr * B2_rr * B3_ll) - + - equations.c_h * (B2_ll * psi_rr + B2_rr * psi_ll))) + f_other = SVector(f1, f2, f3, f4, f5, f6, f7, f8) + + return vcat(f_other, f_rho) + end + + """ + flux_hindenlang_gassner(u_ll, u_rr, orientation_or_normal_direction, + equations::IdealGlmMhdMulticomponentEquations2D) + + Adaption of the entropy conserving and kinetic energy preserving two-point flux of + Hindenlang (2019), extending [`flux_ranocha`](@ref) to the MHD equations. + ## References + - Florian Hindenlang, Gregor Gassner (2019) + A new entropy conservative two-point flux for ideal MHD equations derived from + first principles. + Presented at HONOM 2019: European workshop on high order numerical methods + for evolutionary PDEs, theory and applications + - Hendrik Ranocha (2018) + Generalised Summation-by-Parts Operators and Entropy Stability of Numerical Methods + for Hyperbolic Balance Laws + [PhD thesis, TU Braunschweig](https://cuvillier.de/en/shop/publications/7743) + - Hendrik Ranocha (2020) + Entropy Conserving and Kinetic Energy Preserving Numerical Methods for + the Euler Equations Using Summation-by-Parts Operators + [Proceedings of ICOSAHOM 2018](https://doi.org/10.1007/978-3-030-39647-3_42) + """ + @inline function flux_hindenlang_gassner( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations2D + ) + # Unpack left and right states + v1_ll, v2_ll, v3_ll, p_ll, B1_ll, B2_ll, B3_ll, psi_ll = cons2prim(u_ll, equations) + v1_rr, v2_rr, v3_rr, p_rr, B1_rr, B2_rr, B3_rr, psi_rr = cons2prim(u_rr, equations) + + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + # Compute the necessary mean values needed for either direction + # Algebraically equivalent to `inv_ln_mean(rho_ll / p_ll, rho_rr / p_rr)` + # in exact arithmetic since + # log((ϱₗ/pₗ) / (ϱᵣ/pᵣ)) / (ϱₗ/pₗ - ϱᵣ/pᵣ) + # = pₗ pᵣ log((ϱₗ pᵣ) / (ϱᵣ pₗ)) / (ϱₗ pᵣ - ϱᵣ pₗ) + inv_rho_p_mean = p_ll * p_rr * inv_ln_mean(rho_ll * p_rr, rho_rr * p_ll) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + v3_avg = 0.5f0 * (v3_ll + v3_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + psi_avg = 0.5f0 * (psi_ll + psi_rr) + velocity_square_avg = 0.5f0 * (v1_ll * v1_rr + v2_ll * v2_rr + v3_ll * v3_rr) + magnetic_square_avg = 0.5f0 * (B1_ll * B1_rr + B2_ll * B2_rr + B3_ll * B3_rr) + + inv_gamma_minus_one = 1 / (totalgamma(0.5f0 * (u_ll + u_rr), equations) - 1) + + rhok_mean = SVector{ncomponents(equations), real(equations)}( + ln_mean( + u_ll[i + 8], + u_rr[i + 8] + ) + for i in eachcomponent(equations) + ) + rhok_avg = SVector{ncomponents(equations), real(equations)}( + 0.5f0 * ( + u_ll[i + 8] + + u_rr[i + 8] + ) + for i in eachcomponent(equations) + ) + + RealT = eltype(u_ll) + if orientation == 1 + f1 = zero(RealT) + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v1_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + f1 += f_rho[i] + end + + # Calculate fluxes depending on orientation with specific direction averages + f2 = f1 * v1_avg + p_avg + magnetic_square_avg - + 0.5f0 * (B1_ll * B1_rr + B1_rr * B1_ll) + f3 = f1 * v2_avg - 0.5f0 * (B1_ll * B2_rr + B1_rr * B2_ll) + f4 = f1 * v3_avg - 0.5f0 * (B1_ll * B3_rr + B1_rr * B3_ll) + # f5 below + f6 = f6 = equations.c_h * psi_avg + f7 = 0.5f0 * (v1_ll * B2_ll - v2_ll * B1_ll + v1_rr * B2_rr - v2_rr * B1_rr) + f8 = 0.5f0 * (v1_ll * B3_ll - v3_ll * B1_ll + v1_rr * B3_rr - v3_rr * B1_rr) + f9 = equations.c_h * 0.5f0 * (B1_ll + B1_rr) + # total energy flux is complicated and involves the previous components + f5 = ( + f1 * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v1_rr + p_rr * v1_ll + + (v1_ll * B2_ll * B2_rr + v1_rr * B2_rr * B2_ll) + + (v1_ll * B3_ll * B3_rr + v1_rr * B3_rr * B3_ll) + - + (v2_ll * B1_ll * B2_rr + v2_rr * B1_rr * B2_ll) + - + (v3_ll * B1_ll * B3_rr + v3_rr * B1_rr * B3_ll) + + + equations.c_h * (B1_ll * psi_rr + B1_rr * psi_ll) + ) + ) + else + f1 = zero(RealT) + f_rho = SVector{ncomponents(equations), real(equations)}( + rhok_mean[i] * v2_avg + for i in eachcomponent(equations) + ) + for i in eachcomponent(equations) + f1 += f_rho[i] + end + + # Calculate fluxes depending on orientation with specific direction averages + f2 = f1 * v1_avg - 0.5f0 * (B2_ll * B1_rr + B2_rr * B1_ll) + f3 = f1 * v2_avg + p_avg + magnetic_square_avg - + 0.5f0 * (B2_ll * B2_rr + B2_rr * B2_ll) + f4 = f1 * v3_avg - 0.5f0 * (B2_ll * B3_rr + B2_rr * B3_ll) + #f5 below + f6 = 0.5f0 * (v2_ll * B1_ll - v1_ll * B2_ll + v2_rr * B1_rr - v1_rr * B2_rr) + f7 = equations.c_h * psi_avg + f8 = 0.5f0 * (v2_ll * B3_ll - v3_ll * B2_ll + v2_rr * B3_rr - v3_rr * B2_rr) + f9 = equations.c_h * 0.5f0 * (B2_ll + B2_rr) + # total energy flux is complicated and involves the previous components + f5 = ( + f1 * (velocity_square_avg + inv_rho_p_mean * inv_gamma_minus_one) + + + 0.5f0 * ( + +p_ll * v2_rr + p_rr * v2_ll + + (v2_ll * B1_ll * B1_rr + v2_rr * B1_rr * B1_ll) + + (v2_ll * B3_ll * B3_rr + v2_rr * B3_rr * B3_ll) + - + (v1_ll * B2_ll * B1_rr + v1_rr * B2_rr * B1_ll) + - + (v3_ll * B2_ll * B3_rr + v3_rr * B2_rr * B3_ll) + + + equations.c_h * (B2_ll * psi_rr + B2_rr * psi_ll) + ) + ) + end + + f_other = SVector(f2, f3, f4, f5, f6, f7, f8, f9) + + return vcat(f_other, f_rho) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::IdealGlmMhdMulticomponentEquations2D + ) + rho_v1_ll, rho_v2_ll, _ = u_ll + rho_v1_rr, rho_v2_rr, _ = u_rr + + rho_ll = density(u_ll, equations) + rho_rr = density(u_rr, equations) + + # Calculate velocities and fast magnetoacoustic wave speeds + if orientation == 1 + v_ll = rho_v1_ll / rho_ll + v_rr = rho_v1_rr / rho_rr + else # orientation == 2 + v_ll = rho_v2_ll / rho_ll + v_rr = rho_v2_rr / rho_rr + end + cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) + cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) + + λ_max = max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) end - f_other = SVector(f2, f3, f4, f5, f6, f7, f8, f9) + @inline function max_abs_speeds(u, equations::IdealGlmMhdMulticomponentEquations2D) + rho_v1, rho_v2, _ = u - return vcat(f_other, f_rho) -end + rho = density(u, equations) -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::IdealGlmMhdMulticomponentEquations2D) - rho_v1_ll, rho_v2_ll, _ = u_ll - rho_v1_rr, rho_v2_rr, _ = u_rr + v1 = rho_v1 / rho + v2 = rho_v2 / rho - rho_ll = density(u_ll, equations) - rho_rr = density(u_rr, equations) + cf_x_direction = calc_fast_wavespeed(u, 1, equations) + cf_y_direction = calc_fast_wavespeed(u, 2, equations) - # Calculate velocities and fast magnetoacoustic wave speeds - if orientation == 1 - v_ll = rho_v1_ll / rho_ll - v_rr = rho_v1_rr / rho_rr - else # orientation == 2 - v_ll = rho_v2_ll / rho_ll - v_rr = rho_v2_rr / rho_rr + return (abs(v1) + cf_x_direction, abs(v2) + cf_y_direction) end - cf_ll = calc_fast_wavespeed(u_ll, orientation, equations) - cf_rr = calc_fast_wavespeed(u_rr, orientation, equations) - - λ_max = max(abs(v_ll), abs(v_rr)) + max(cf_ll, cf_rr) -end - -@inline function max_abs_speeds(u, equations::IdealGlmMhdMulticomponentEquations2D) - rho_v1, rho_v2, _ = u - - rho = density(u, equations) - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - - cf_x_direction = calc_fast_wavespeed(u, 1, equations) - cf_y_direction = calc_fast_wavespeed(u, 2, equations) - - return (abs(v1) + cf_x_direction, abs(v2) + cf_y_direction) -end - -@inline function density_pressure(u, equations::IdealGlmMhdMulticomponentEquations2D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u - rho = density(u, equations) - gamma = totalgamma(u, equations) - p = (gamma - 1) * (rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho - - - 0.5f0 * (B1^2 + B2^2 + B3^2) - - - 0.5f0 * psi^2) - return rho * p -end - -# Convert conservative variables to primitive -function cons2prim(u, equations::IdealGlmMhdMulticomponentEquations2D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u - - prim_rho = SVector{ncomponents(equations), real(equations)}(u[i + 8] - for i in eachcomponent(equations)) - rho = density(u, equations) - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - - gamma = totalgamma(u, equations) - - p = (gamma - 1) * - (rho_e - 0.5f0 * rho * (v1^2 + v2^2 + v3^2) - 0.5f0 * (B1^2 + B2^2 + B3^2) - - 0.5f0 * psi^2) - prim_other = SVector(v1, v2, v3, p, B1, B2, B3, psi) - - return vcat(prim_other, prim_rho) -end - -# Convert conservative variables to entropy -@inline function cons2entropy(u, equations::IdealGlmMhdMulticomponentEquations2D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u - @unpack cv, gammas, gas_constants = equations - - rho = density(u, equations) - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - v_square = v1^2 + v2^2 + v3^2 - gamma = totalgamma(u, equations) - p = (gamma - 1) * - (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) - s = log(p) - gamma * log(rho) - rho_p = rho / p - - # Multicomponent stuff - help1 = zero(v1) - - for i in eachcomponent(equations) - help1 += u[i + 8] * cv[i] + + @inline function density_pressure(u, equations::IdealGlmMhdMulticomponentEquations2D) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u + rho = density(u, equations) + gamma = totalgamma(u, equations) + p = (gamma - 1) * ( + rho_e - 0.5f0 * (rho_v1^2 + rho_v2^2 + rho_v3^2) / rho + - + 0.5f0 * (B1^2 + B2^2 + B3^2) + - + 0.5f0 * psi^2 + ) + return rho * p end - T = (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) / - (help1) - - entrop_rho = SVector{ncomponents(equations), real(equations)}(-1 * - (cv[i] * log(T) - - gas_constants[i] * - log(u[i + 8])) + - gas_constants[i] + - cv[i] - - (v_square / (2 * T)) - for i in eachcomponent(equations)) - - w1 = v1 / T - w2 = v2 / T - w3 = v3 / T - w4 = -1 / T - w5 = B1 / T - w6 = B2 / T - w7 = B3 / T - w8 = psi / T - - entrop_other = SVector(w1, w2, w3, w4, w5, w6, w7, w8) - - return vcat(entrop_other, entrop_rho) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::IdealGlmMhdMulticomponentEquations2D) - v1, v2, v3, p, B1, B2, B3, psi = prim - - cons_rho = SVector{ncomponents(equations), real(equations)}(prim[i + 8] - for i in eachcomponent(equations)) - rho = density(prim, equations) - - rho_v1 = rho * v1 - rho_v2 = rho * v2 - rho_v3 = rho * v3 - - gamma = totalgamma(prim, equations) - rho_e = p / (gamma - 1) + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + - 0.5f0 * (B1^2 + B2^2 + B3^2) + 0.5f0 * psi^2 + # Convert conservative variables to primitive + function cons2prim(u, equations::IdealGlmMhdMulticomponentEquations2D) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u + + prim_rho = SVector{ncomponents(equations), real(equations)}( + u[i + 8] + for i in eachcomponent(equations) + ) + rho = density(u, equations) - cons_other = SVector(rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, - psi) - - return vcat(cons_other, cons_rho) -end - -# Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue -@inline function calc_fast_wavespeed(cons, direction, - equations::IdealGlmMhdMulticomponentEquations2D) - rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons - rho = density(cons, equations) - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - v_mag = sqrt(v1^2 + v2^2 + v3^2) - gamma = totalgamma(cons, equations) - p = (gamma - 1) * - (rho_e - 0.5f0 * rho * v_mag^2 - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) - a_square = gamma * p / rho - sqrt_rho = sqrt(rho) - b1 = B1 / sqrt_rho - b2 = B2 / sqrt_rho - b3 = B3 / sqrt_rho - b_square = b1^2 + b2^2 + b3^2 - if direction == 1 # x-direction - c_f = sqrt(0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2)) - else - c_f = sqrt(0.5f0 * (a_square + b_square) + - 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b2^2)) + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + + gamma = totalgamma(u, equations) + + p = (gamma - 1) * + ( + rho_e - 0.5f0 * rho * (v1^2 + v2^2 + v3^2) - 0.5f0 * (B1^2 + B2^2 + B3^2) - + 0.5f0 * psi^2 + ) + prim_other = SVector(v1, v2, v3, p, B1, B2, B3, psi) + + return vcat(prim_other, prim_rho) end - return c_f -end -@inline function density(u, equations::IdealGlmMhdMulticomponentEquations2D) - RealT = eltype(u) - rho = zero(RealT) + # Convert conservative variables to entropy + @inline function cons2entropy(u, equations::IdealGlmMhdMulticomponentEquations2D) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = u + @unpack cv, gammas, gas_constants = equations + + rho = density(u, equations) - for i in eachcomponent(equations) - rho += u[i + 8] + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + v_square = v1^2 + v2^2 + v3^2 + gamma = totalgamma(u, equations) + p = (gamma - 1) * + (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) + s = log(p) - gamma * log(rho) + rho_p = rho / p + + # Multicomponent stuff + help1 = zero(v1) + + for i in eachcomponent(equations) + help1 += u[i + 8] * cv[i] + end + + T = (rho_e - 0.5f0 * rho * v_square - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) / + (help1) + + entrop_rho = SVector{ncomponents(equations), real(equations)}( + -1 * + ( + cv[i] * log(T) - + gas_constants[i] * + log(u[i + 8]) + ) + + gas_constants[i] + + cv[i] - + (v_square / (2 * T)) + for i in eachcomponent(equations) + ) + + w1 = v1 / T + w2 = v2 / T + w3 = v3 / T + w4 = -1 / T + w5 = B1 / T + w6 = B2 / T + w7 = B3 / T + w8 = psi / T + + entrop_other = SVector(w1, w2, w3, w4, w5, w6, w7, w8) + + return vcat(entrop_other, entrop_rho) end - return rho -end + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::IdealGlmMhdMulticomponentEquations2D) + v1, v2, v3, p, B1, B2, B3, psi = prim + + cons_rho = SVector{ncomponents(equations), real(equations)}( + prim[i + 8] + for i in eachcomponent(equations) + ) + rho = density(prim, equations) -@inline function totalgamma(u, equations::IdealGlmMhdMulticomponentEquations2D) - @unpack cv, gammas = equations + rho_v1 = rho * v1 + rho_v2 = rho * v2 + rho_v3 = rho * v3 + + gamma = totalgamma(prim, equations) + rho_e = p / (gamma - 1) + 0.5f0 * (rho_v1 * v1 + rho_v2 * v2 + rho_v3 * v3) + + 0.5f0 * (B1^2 + B2^2 + B3^2) + 0.5f0 * psi^2 - RealT = eltype(u) - help1 = zero(RealT) - help2 = zero(RealT) + cons_other = SVector( + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, + psi + ) - for i in eachcomponent(equations) - help1 += u[i + 8] * cv[i] * gammas[i] - help2 += u[i + 8] * cv[i] + return vcat(cons_other, cons_rho) end - return help1 / help2 -end + # Compute the fastest wave speed for ideal MHD equations: c_f, the fast magnetoacoustic eigenvalue + @inline function calc_fast_wavespeed( + cons, direction, + equations::IdealGlmMhdMulticomponentEquations2D + ) + rho_v1, rho_v2, rho_v3, rho_e, B1, B2, B3, psi = cons + rho = density(cons, equations) + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + v_mag = sqrt(v1^2 + v2^2 + v3^2) + gamma = totalgamma(cons, equations) + p = (gamma - 1) * + (rho_e - 0.5f0 * rho * v_mag^2 - 0.5f0 * (B1^2 + B2^2 + B3^2) - 0.5f0 * psi^2) + a_square = gamma * p / rho + sqrt_rho = sqrt(rho) + b1 = B1 / sqrt_rho + b2 = B2 / sqrt_rho + b3 = B3 / sqrt_rho + b_square = b1^2 + b2^2 + b3^2 + if direction == 1 # x-direction + c_f = sqrt( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b1^2) + ) + else + c_f = sqrt( + 0.5f0 * (a_square + b_square) + + 0.5f0 * sqrt((a_square + b_square)^2 - 4 * a_square * b2^2) + ) + end + return c_f + end + + @inline function density(u, equations::IdealGlmMhdMulticomponentEquations2D) + RealT = eltype(u) + rho = zero(RealT) + + for i in eachcomponent(equations) + rho += u[i + 8] + end + + return rho + end -@inline function densities(u, v, equations::IdealGlmMhdMulticomponentEquations2D) - return SVector{ncomponents(equations), real(equations)}(u[i + 8] * v - for i in eachcomponent(equations)) -end + @inline function totalgamma(u, equations::IdealGlmMhdMulticomponentEquations2D) + @unpack cv, gammas = equations + + RealT = eltype(u) + help1 = zero(RealT) + help2 = zero(RealT) + + for i in eachcomponent(equations) + help1 += u[i + 8] * cv[i] * gammas[i] + help2 += u[i + 8] * cv[i] + end + + return help1 / help2 + end + + @inline function densities(u, v, equations::IdealGlmMhdMulticomponentEquations2D) + return SVector{ncomponents(equations), real(equations)}( + u[i + 8] * v + for i in eachcomponent(equations) + ) + end end # @muladd diff --git a/src/equations/inviscid_burgers_1d.jl b/src/equations/inviscid_burgers_1d.jl index afc6f9999c..470edc23da 100644 --- a/src/equations/inviscid_burgers_1d.jl +++ b/src/equations/inviscid_burgers_1d.jl @@ -3,183 +3,197 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - InviscidBurgersEquation1D - -The inviscid Burgers' equation -```math -\partial_t u + \frac{1}{2} \partial_1 u^2 = 0 -``` -in one space dimension. -""" -struct InviscidBurgersEquation1D <: AbstractInviscidBurgersEquation{1, 1} end - -varnames(::typeof(cons2cons), ::InviscidBurgersEquation1D) = ("scalar",) -varnames(::typeof(cons2prim), ::InviscidBurgersEquation1D) = ("scalar",) - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::InviscidBurgersEquation1D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equation::InviscidBurgersEquation1D) - RealT = eltype(x) - return SVector(RealT(2)) -end - -""" - initial_condition_convergence_test(x, t, equations::InviscidBurgersEquation1D) - -A smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, equation::InviscidBurgersEquation1D) - RealT = eltype(x) - c = 2 - A = 1 - L = 1 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - scalar = c + A * sin(omega * (x[1] - t)) - - return SVector(scalar) -end - -""" - source_terms_convergence_test(u, x, t, equations::InviscidBurgersEquation1D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::InviscidBurgersEquation1D) - # Same settings as in `initial_condition` - RealT = eltype(x) - c = 2 - A = 1 - L = 1 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - du = omega * A * cos(omega * (x[1] - t)) * (c - 1 + A * sin(omega * (x[1] - t))) - - return SVector(du) -end - -# Pre-defined source terms should be implemented as -# function source_terms_WHATEVER(u, x, t, equations::InviscidBurgersEquation1D) - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, equation::InviscidBurgersEquation1D) - return SVector(0.5f0 * u[1]^2) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::InviscidBurgersEquation1D) - u_L = u_ll[1] - u_R = u_rr[1] - - λ_max = max(abs(u_L), abs(u_R)) -end - -# Calculate minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::InviscidBurgersEquation1D) - u_L = u_ll[1] - u_R = u_rr[1] - - λ_min = min(u_L, u_R) - λ_max = max(u_L, u_R) - - return λ_min, λ_max -end - -@inline function max_abs_speeds(u, equation::InviscidBurgersEquation1D) - return (abs(u[1]),) -end - -# (Symmetric) Entropy Conserving flux -function flux_ec(u_ll, u_rr, orientation, equation::InviscidBurgersEquation1D) - u_L = u_ll[1] - u_R = u_rr[1] - - return SVector((u_L^2 + u_L * u_R + u_R^2) / 6) -end - -# See https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf , -# section 4.1.5 and especially equation (4.16). -function flux_godunov(u_ll, u_rr, orientation, equation::InviscidBurgersEquation1D) - u_L = u_ll[1] - u_R = u_rr[1] - - return SVector(0.5f0 * max(max(u_L, 0)^2, min(u_R, 0)^2)) -end - -# See https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf , -# section 4.2.5 and especially equation (4.34). -function flux_engquist_osher(u_ll, u_rr, orientation, - equation::InviscidBurgersEquation1D) - u_L = u_ll[1] - u_R = u_rr[1] - - return SVector(0.5f0 * (max(u_L, 0)^2 + min(u_R, 0)^2)) -end - -""" - splitting_lax_friedrichs(u, orientation::Integer, - equations::InviscidBurgersEquation1D) - splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} - orientation::Integer, - equations::InviscidBurgersEquation1D) - -Naive local Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ u)` -and `f⁻ = 0.5 (f - λ u)` where `λ = abs(u)`. - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. -""" -@inline function splitting_lax_friedrichs(u, orientation::Integer, - equations::InviscidBurgersEquation1D) - fm = splitting_lax_friedrichs(u, Val{:minus}(), orientation, equations) - fp = splitting_lax_friedrichs(u, Val{:plus}(), orientation, equations) - return fm, fp -end - -@inline function splitting_lax_friedrichs(u, ::Val{:plus}, orientation::Integer, - equations::InviscidBurgersEquation1D) - f = 0.5f0 * u[1]^2 - lambda = abs(u[1]) - return SVector(0.5f0 * (f + lambda * u[1])) -end - -@inline function splitting_lax_friedrichs(u, ::Val{:minus}, orientation::Integer, - equations::InviscidBurgersEquation1D) - f = 0.5f0 * u[1]^2 - lambda = abs(u[1]) - return SVector(0.5f0 * (f - lambda * u[1])) -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equation::InviscidBurgersEquation1D) = u - -# Convert conservative variables to entropy variables -@inline cons2entropy(u, equation::InviscidBurgersEquation1D) = u -@inline entropy2cons(u, equation::InviscidBurgersEquation1D) = u - -# Calculate entropy for a conservative state `cons` -@inline entropy(u::Real, ::InviscidBurgersEquation1D) = 0.5f0 * u^2 -@inline entropy(u, equation::InviscidBurgersEquation1D) = entropy(u[1], equation) - -# Calculate total energy for a conservative state `cons` -@inline energy_total(u::Real, ::InviscidBurgersEquation1D) = 0.5f0 * u^2 -@inline function energy_total(u, equation::InviscidBurgersEquation1D) - energy_total(u[1], equation) -end + #! format: noindent + + @doc raw""" + InviscidBurgersEquation1D + + The inviscid Burgers' equation + ```math + \partial_t u + \frac{1}{2} \partial_1 u^2 = 0 + ``` + in one space dimension. + """ + struct InviscidBurgersEquation1D <: AbstractInviscidBurgersEquation{1, 1} end + + varnames(::typeof(cons2cons), ::InviscidBurgersEquation1D) = ("scalar",) + varnames(::typeof(cons2prim), ::InviscidBurgersEquation1D) = ("scalar",) + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::InviscidBurgersEquation1D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equation::InviscidBurgersEquation1D) + RealT = eltype(x) + return SVector(RealT(2)) + end + + """ + initial_condition_convergence_test(x, t, equations::InviscidBurgersEquation1D) + + A smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test(x, t, equation::InviscidBurgersEquation1D) + RealT = eltype(x) + c = 2 + A = 1 + L = 1 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + scalar = c + A * sin(omega * (x[1] - t)) + + return SVector(scalar) + end + + """ + source_terms_convergence_test(u, x, t, equations::InviscidBurgersEquation1D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::InviscidBurgersEquation1D + ) + # Same settings as in `initial_condition` + RealT = eltype(x) + c = 2 + A = 1 + L = 1 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + du = omega * A * cos(omega * (x[1] - t)) * (c - 1 + A * sin(omega * (x[1] - t))) + + return SVector(du) + end + + # Pre-defined source terms should be implemented as + # function source_terms_WHATEVER(u, x, t, equations::InviscidBurgersEquation1D) + + # Calculate 1D flux in for a single point + @inline function flux(u, orientation::Integer, equation::InviscidBurgersEquation1D) + return SVector(0.5f0 * u[1]^2) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::InviscidBurgersEquation1D + ) + u_L = u_ll[1] + u_R = u_rr[1] + + λ_max = max(abs(u_L), abs(u_R)) + end + + # Calculate minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::InviscidBurgersEquation1D + ) + u_L = u_ll[1] + u_R = u_rr[1] + + λ_min = min(u_L, u_R) + λ_max = max(u_L, u_R) + + return λ_min, λ_max + end + + @inline function max_abs_speeds(u, equation::InviscidBurgersEquation1D) + return (abs(u[1]),) + end + + # (Symmetric) Entropy Conserving flux + function flux_ec(u_ll, u_rr, orientation, equation::InviscidBurgersEquation1D) + u_L = u_ll[1] + u_R = u_rr[1] + + return SVector((u_L^2 + u_L * u_R + u_R^2) / 6) + end + + # See https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf , + # section 4.1.5 and especially equation (4.16). + function flux_godunov(u_ll, u_rr, orientation, equation::InviscidBurgersEquation1D) + u_L = u_ll[1] + u_R = u_rr[1] + + return SVector(0.5f0 * max(max(u_L, 0)^2, min(u_R, 0)^2)) + end + + # See https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf , + # section 4.2.5 and especially equation (4.34). + function flux_engquist_osher( + u_ll, u_rr, orientation, + equation::InviscidBurgersEquation1D + ) + u_L = u_ll[1] + u_R = u_rr[1] + + return SVector(0.5f0 * (max(u_L, 0)^2 + min(u_R, 0)^2)) + end + + """ + splitting_lax_friedrichs(u, orientation::Integer, + equations::InviscidBurgersEquation1D) + splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, + equations::InviscidBurgersEquation1D) + + Naive local Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ u)` + and `f⁻ = 0.5 (f - λ u)` where `λ = abs(u)`. + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + """ + @inline function splitting_lax_friedrichs( + u, orientation::Integer, + equations::InviscidBurgersEquation1D + ) + fm = splitting_lax_friedrichs(u, Val{:minus}(), orientation, equations) + fp = splitting_lax_friedrichs(u, Val{:plus}(), orientation, equations) + return fm, fp + end + + @inline function splitting_lax_friedrichs( + u, ::Val{:plus}, orientation::Integer, + equations::InviscidBurgersEquation1D + ) + f = 0.5f0 * u[1]^2 + lambda = abs(u[1]) + return SVector(0.5f0 * (f + lambda * u[1])) + end + + @inline function splitting_lax_friedrichs( + u, ::Val{:minus}, orientation::Integer, + equations::InviscidBurgersEquation1D + ) + f = 0.5f0 * u[1]^2 + lambda = abs(u[1]) + return SVector(0.5f0 * (f - lambda * u[1])) + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equation::InviscidBurgersEquation1D) = u + + # Convert conservative variables to entropy variables + @inline cons2entropy(u, equation::InviscidBurgersEquation1D) = u + @inline entropy2cons(u, equation::InviscidBurgersEquation1D) = u + + # Calculate entropy for a conservative state `cons` + @inline entropy(u::Real, ::InviscidBurgersEquation1D) = 0.5f0 * u^2 + @inline entropy(u, equation::InviscidBurgersEquation1D) = entropy(u[1], equation) + + # Calculate total energy for a conservative state `cons` + @inline energy_total(u::Real, ::InviscidBurgersEquation1D) = 0.5f0 * u^2 + @inline function energy_total(u, equation::InviscidBurgersEquation1D) + energy_total(u[1], equation) + end end # @muladd diff --git a/src/equations/laplace_diffusion_1d.jl b/src/equations/laplace_diffusion_1d.jl index 64a72ef3a1..875871d369 100644 --- a/src/equations/laplace_diffusion_1d.jl +++ b/src/equations/laplace_diffusion_1d.jl @@ -10,8 +10,10 @@ struct LaplaceDiffusion1D{E, N, T} <: AbstractLaplaceDiffusion{1, N} end function LaplaceDiffusion1D(diffusivity, equations_hyperbolic) - LaplaceDiffusion1D{typeof(equations_hyperbolic), nvariables(equations_hyperbolic), - typeof(diffusivity)}(diffusivity, equations_hyperbolic) + LaplaceDiffusion1D{ + typeof(equations_hyperbolic), nvariables(equations_hyperbolic), + typeof(diffusivity), + }(diffusivity, equations_hyperbolic) end function varnames(variable_mapping, equations_parabolic::LaplaceDiffusion1D) @@ -26,34 +28,42 @@ end # Dirichlet and Neumann boundary conditions for use with parabolic solvers in weak form. # Note that these are general, so they apply to LaplaceDiffusion in any spatial dimension. -@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Gradient, - equations_parabolic::AbstractLaplaceDiffusion) +@inline function (boundary_condition::BoundaryConditionDirichlet)( + flux_inner, u_inner, + normal::AbstractVector, + x, t, + operator_type::Gradient, + equations_parabolic::AbstractLaplaceDiffusion + ) return boundary_condition.boundary_value_function(x, t, equations_parabolic) end -@inline function (boundary_condition::BoundaryConditionDirichlet)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Divergence, - equations_parabolic::AbstractLaplaceDiffusion) +@inline function (boundary_condition::BoundaryConditionDirichlet)( + flux_inner, u_inner, + normal::AbstractVector, + x, t, + operator_type::Divergence, + equations_parabolic::AbstractLaplaceDiffusion + ) return flux_inner end -@inline function (boundary_condition::BoundaryConditionNeumann)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Divergence, - equations_parabolic::AbstractLaplaceDiffusion) +@inline function (boundary_condition::BoundaryConditionNeumann)( + flux_inner, u_inner, + normal::AbstractVector, + x, t, + operator_type::Divergence, + equations_parabolic::AbstractLaplaceDiffusion + ) return boundary_condition.boundary_normal_flux_function(x, t, equations_parabolic) end -@inline function (boundary_condition::BoundaryConditionNeumann)(flux_inner, u_inner, - normal::AbstractVector, - x, t, - operator_type::Gradient, - equations_parabolic::AbstractLaplaceDiffusion) +@inline function (boundary_condition::BoundaryConditionNeumann)( + flux_inner, u_inner, + normal::AbstractVector, + x, t, + operator_type::Gradient, + equations_parabolic::AbstractLaplaceDiffusion + ) return flux_inner end diff --git a/src/equations/laplace_diffusion_2d.jl b/src/equations/laplace_diffusion_2d.jl index 5de989849b..780918183e 100644 --- a/src/equations/laplace_diffusion_2d.jl +++ b/src/equations/laplace_diffusion_2d.jl @@ -10,8 +10,10 @@ struct LaplaceDiffusion2D{E, N, T} <: AbstractLaplaceDiffusion{2, N} end function LaplaceDiffusion2D(diffusivity, equations_hyperbolic) - LaplaceDiffusion2D{typeof(equations_hyperbolic), nvariables(equations_hyperbolic), - typeof(diffusivity)}(diffusivity, equations_hyperbolic) + LaplaceDiffusion2D{ + typeof(equations_hyperbolic), nvariables(equations_hyperbolic), + typeof(diffusivity), + }(diffusivity, equations_hyperbolic) end function varnames(variable_mapping, equations_parabolic::LaplaceDiffusion2D) @@ -30,8 +32,10 @@ end # TODO: parabolic; should this remain in the equations file, be moved to solvers, or live in the elixir? # The penalization depends on the solver, but also depends explicitly on physical parameters, # and would probably need to be specialized for every different equation. -function penalty(u_outer, u_inner, inv_h, equations_parabolic::LaplaceDiffusion2D, - dg::ViscousFormulationLocalDG) +function penalty( + u_outer, u_inner, inv_h, equations_parabolic::LaplaceDiffusion2D, + dg::ViscousFormulationLocalDG + ) return dg.penalty_parameter * (u_outer - u_inner) * equations_parabolic.diffusivity end diff --git a/src/equations/laplace_diffusion_3d.jl b/src/equations/laplace_diffusion_3d.jl index fbd3d27725..3396213023 100644 --- a/src/equations/laplace_diffusion_3d.jl +++ b/src/equations/laplace_diffusion_3d.jl @@ -10,8 +10,10 @@ struct LaplaceDiffusion3D{E, N, T} <: AbstractLaplaceDiffusion{3, N} end function LaplaceDiffusion3D(diffusivity, equations_hyperbolic) - LaplaceDiffusion3D{typeof(equations_hyperbolic), nvariables(equations_hyperbolic), - typeof(diffusivity)}(diffusivity, equations_hyperbolic) + LaplaceDiffusion3D{ + typeof(equations_hyperbolic), nvariables(equations_hyperbolic), + typeof(diffusivity), + }(diffusivity, equations_hyperbolic) end function varnames(variable_mapping, equations_parabolic::LaplaceDiffusion3D) @@ -33,8 +35,10 @@ end # TODO: parabolic; should this remain in the equations file, be moved to solvers, or live in the elixir? # The penalization depends on the solver, but also depends explicitly on physical parameters, # and would probably need to be specialized for every different equation. -function penalty(u_outer, u_inner, inv_h, equations_parabolic::LaplaceDiffusion3D, - dg::ViscousFormulationLocalDG) +function penalty( + u_outer, u_inner, inv_h, equations_parabolic::LaplaceDiffusion3D, + dg::ViscousFormulationLocalDG + ) return dg.penalty_parameter * (u_outer - u_inner) * equations_parabolic.diffusivity end diff --git a/src/equations/lattice_boltzmann_2d.jl b/src/equations/lattice_boltzmann_2d.jl index bad417d9c8..4132376c82 100644 --- a/src/equations/lattice_boltzmann_2d.jl +++ b/src/equations/lattice_boltzmann_2d.jl @@ -3,382 +3,404 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - LatticeBoltzmannEquations2D(; Ma, Re, collision_op=collision_bgk, - c=1, L=1, rho0=1, u0=nothing, nu=nothing) - -The Lattice-Boltzmann equations -```math -\partial_t u_\alpha + v_{\alpha,1} \partial_1 u_\alpha + v_{\alpha,2} \partial_2 u_\alpha = 0 -``` -in two space dimensions for the D2Q9 scheme. - -The characteristic Mach number and Reynolds numbers are specified as `Ma` and `Re`. By the -default, the collision operator `collision_op` is set to the BGK model. `c`, `L`, and `rho0` -specify the mean thermal molecular velocity, the characteristic length, and the reference density, -respectively. They can usually be left to the default values. If desired, instead of the Mach -number, one can set the macroscopic reference velocity `u0` directly (`Ma` needs to be set to -`nothing` in this case). Likewise, instead of the Reynolds number one can specify the kinematic -viscosity `nu` directly (in this case, `Re` needs to be set to `nothing`). - - -The nine discrete velocity directions of the D2Q9 scheme are sorted as follows [4]: -``` - 6 2 5 y - ┌───┼───┐ │ - │ │ │ - 3 ┼ 9 ┼ 1 ──── x - │ │ ╱ - └───┼───┘ ╱ - 7 4 8 z -``` -Note that usually the velocities are numbered from `0` to `8`, where `0` corresponds to the zero -velocity. Due to Julia using 1-based indexing, here we use indices from `1` to `9`, where `1` -through `8` correspond to the velocity directions in [4] and `9` is the zero velocity. - -The corresponding opposite directions are: -* 1 ←→ 3 -* 2 ←→ 4 -* 3 ←→ 1 -* 4 ←→ 2 -* 5 ←→ 7 -* 6 ←→ 8 -* 7 ←→ 5 -* 8 ←→ 6 -* 9 ←→ 9 - -The main sources for the base implementation were -1. Misun Min, Taehun Lee, **A spectral-element discontinuous Galerkin lattice Boltzmann method for - nearly incompressible flows**, J Comput Phys 230(1), 2011 - [doi:10.1016/j.jcp.2010.09.024](https://doi.org/10.1016/j.jcp.2010.09.024) -2. Karsten Golly, **Anwendung der Lattice-Boltzmann Discontinuous Galerkin Spectral Element Method - (LB-DGSEM) auf laminare und turbulente nahezu inkompressible Strömungen im dreidimensionalen - Raum**, Master Thesis, University of Cologne, 2018. -3. Dieter Hänel, **Molekulare Gasdynamik**, Springer-Verlag Berlin Heidelberg, 2004 - [doi:10.1007/3-540-35047-0](https://doi.org/10.1007/3-540-35047-0) -4. Dieter Krüger et al., **The Lattice Boltzmann Method**, Springer International Publishing, 2017 - [doi:10.1007/978-3-319-44649-3](https://doi.org/10.1007/978-3-319-44649-3) -""" -struct LatticeBoltzmannEquations2D{RealT <: Real, CollisionOp} <: - AbstractLatticeBoltzmannEquations{2, 9} - c::RealT # mean thermal molecular velocity - c_s::RealT # isothermal speed of sound - rho0::RealT # macroscopic reference density - - Ma::RealT # characteristic Mach number - u0::RealT # macroscopic reference velocity - - Re::RealT # characteristic Reynolds number - L::RealT # reference length - nu::RealT # kinematic viscosity - - weights::SVector{9, RealT} # weighting factors for the equilibrium distribution - v_alpha1::SVector{9, RealT} # discrete molecular velocity components in x-direction - v_alpha2::SVector{9, RealT} # discrete molecular velocity components in y-direction - - collision_op::CollisionOp # collision operator for the collision kernel -end - -function LatticeBoltzmannEquations2D(; Ma, Re, collision_op = collision_bgk, - c = 1, L = 1, rho0 = 1, u0 = nothing, nu = nothing) - # Sanity check that exactly one of Ma, u0 is not `nothing` - if isnothing(Ma) && isnothing(u0) - error("Mach number `Ma` and reference speed `u0` may not both be `nothing`") - elseif !isnothing(Ma) && !isnothing(u0) - error("Mach number `Ma` and reference speed `u0` may not both be set") + #! format: noindent + + @doc raw""" + LatticeBoltzmannEquations2D(; Ma, Re, collision_op=collision_bgk, + c=1, L=1, rho0=1, u0=nothing, nu=nothing) + + The Lattice-Boltzmann equations + ```math + \partial_t u_\alpha + v_{\alpha,1} \partial_1 u_\alpha + v_{\alpha,2} \partial_2 u_\alpha = 0 + ``` + in two space dimensions for the D2Q9 scheme. + + The characteristic Mach number and Reynolds numbers are specified as `Ma` and `Re`. By the + default, the collision operator `collision_op` is set to the BGK model. `c`, `L`, and `rho0` + specify the mean thermal molecular velocity, the characteristic length, and the reference density, + respectively. They can usually be left to the default values. If desired, instead of the Mach + number, one can set the macroscopic reference velocity `u0` directly (`Ma` needs to be set to + `nothing` in this case). Likewise, instead of the Reynolds number one can specify the kinematic + viscosity `nu` directly (in this case, `Re` needs to be set to `nothing`). + + + The nine discrete velocity directions of the D2Q9 scheme are sorted as follows [4]: + ``` + 6 2 5 y + ┌───┼───┐ │ + │ │ │ + 3 ┼ 9 ┼ 1 ──── x + │ │ ╱ + └───┼───┘ ╱ + 7 4 8 z + ``` + Note that usually the velocities are numbered from `0` to `8`, where `0` corresponds to the zero + velocity. Due to Julia using 1-based indexing, here we use indices from `1` to `9`, where `1` + through `8` correspond to the velocity directions in [4] and `9` is the zero velocity. + + The corresponding opposite directions are: + * 1 ←→ 3 + * 2 ←→ 4 + * 3 ←→ 1 + * 4 ←→ 2 + * 5 ←→ 7 + * 6 ←→ 8 + * 7 ←→ 5 + * 8 ←→ 6 + * 9 ←→ 9 + + The main sources for the base implementation were + 1. Misun Min, Taehun Lee, **A spectral-element discontinuous Galerkin lattice Boltzmann method for + nearly incompressible flows**, J Comput Phys 230(1), 2011 + [doi:10.1016/j.jcp.2010.09.024](https://doi.org/10.1016/j.jcp.2010.09.024) + 2. Karsten Golly, **Anwendung der Lattice-Boltzmann Discontinuous Galerkin Spectral Element Method + (LB-DGSEM) auf laminare und turbulente nahezu inkompressible Strömungen im dreidimensionalen + Raum**, Master Thesis, University of Cologne, 2018. + 3. Dieter Hänel, **Molekulare Gasdynamik**, Springer-Verlag Berlin Heidelberg, 2004 + [doi:10.1007/3-540-35047-0](https://doi.org/10.1007/3-540-35047-0) + 4. Dieter Krüger et al., **The Lattice Boltzmann Method**, Springer International Publishing, 2017 + [doi:10.1007/978-3-319-44649-3](https://doi.org/10.1007/978-3-319-44649-3) + """ + struct LatticeBoltzmannEquations2D{RealT <: Real, CollisionOp} <: + AbstractLatticeBoltzmannEquations{2, 9} + c::RealT # mean thermal molecular velocity + c_s::RealT # isothermal speed of sound + rho0::RealT # macroscopic reference density + + Ma::RealT # characteristic Mach number + u0::RealT # macroscopic reference velocity + + Re::RealT # characteristic Reynolds number + L::RealT # reference length + nu::RealT # kinematic viscosity + + weights::SVector{9, RealT} # weighting factors for the equilibrium distribution + v_alpha1::SVector{9, RealT} # discrete molecular velocity components in x-direction + v_alpha2::SVector{9, RealT} # discrete molecular velocity components in y-direction + + collision_op::CollisionOp # collision operator for the collision kernel end - # Sanity check that exactly one of Re, nu is not `nothing` - if isnothing(Re) && isnothing(nu) - error("Reynolds number `Re` and visocsity `nu` may not both be `nothing`") - elseif !isnothing(Re) && !isnothing(nu) - error("Reynolds number `Re` and visocsity `nu` may not both be set") + function LatticeBoltzmannEquations2D(; + Ma, Re, collision_op = collision_bgk, + c = 1, L = 1, rho0 = 1, u0 = nothing, nu = nothing + ) + # Sanity check that exactly one of Ma, u0 is not `nothing` + if isnothing(Ma) && isnothing(u0) + error("Mach number `Ma` and reference speed `u0` may not both be `nothing`") + elseif !isnothing(Ma) && !isnothing(u0) + error("Mach number `Ma` and reference speed `u0` may not both be set") + end + + # Sanity check that exactly one of Re, nu is not `nothing` + if isnothing(Re) && isnothing(nu) + error("Reynolds number `Re` and visocsity `nu` may not both be `nothing`") + elseif !isnothing(Re) && !isnothing(nu) + error("Reynolds number `Re` and visocsity `nu` may not both be set") + end + + # Calculate isothermal speed of sound + # The relation between the isothermal speed of sound `c_s` and the mean thermal molecular velocity + # `c` depends on the used phase space discretization, and is valid for D2Q9 (and others). For + # details, see, e.g., [3] in the docstring above. + # c_s = c / sqrt(3) + + # Calculate missing quantities + if isnothing(Ma) + RealT = eltype(u0) + c_s = c / sqrt(convert(RealT, 3)) + Ma = u0 / c_s + elseif isnothing(u0) + RealT = eltype(Ma) + c_s = c / sqrt(convert(RealT, 3)) + u0 = Ma * c_s + end + if isnothing(Re) + Re = u0 * L / nu + elseif isnothing(nu) + nu = u0 * L / Re + end + + # Promote to common data type + Ma, Re, c, L, rho0, u0, nu = promote(Ma, Re, c, L, rho0, u0, nu) + + # Source for weights and speeds: [4] in the docstring above + weights = SVector{9, RealT}( + 1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 36, 1 / 36, 1 / 36, + 1 / 36, 4 / 9 + ) + v_alpha1 = SVector{9, RealT}(c, 0, -c, 0, c, -c, -c, c, 0) + v_alpha2 = SVector{9, RealT}(0, c, 0, -c, c, c, -c, -c, 0) + + LatticeBoltzmannEquations2D( + c, c_s, rho0, Ma, u0, Re, L, nu, + weights, v_alpha1, v_alpha2, + collision_op + ) end - # Calculate isothermal speed of sound - # The relation between the isothermal speed of sound `c_s` and the mean thermal molecular velocity - # `c` depends on the used phase space discretization, and is valid for D2Q9 (and others). For - # details, see, e.g., [3] in the docstring above. - # c_s = c / sqrt(3) - - # Calculate missing quantities - if isnothing(Ma) - RealT = eltype(u0) - c_s = c / sqrt(convert(RealT, 3)) - Ma = u0 / c_s - elseif isnothing(u0) - RealT = eltype(Ma) - c_s = c / sqrt(convert(RealT, 3)) - u0 = Ma * c_s + function varnames(::typeof(cons2cons), equations::LatticeBoltzmannEquations2D) + ntuple(v -> "pdf" * string(v), nvariables(equations)) end - if isnothing(Re) - Re = u0 * L / nu - elseif isnothing(nu) - nu = u0 * L / Re + function varnames(::typeof(cons2prim), equations::LatticeBoltzmannEquations2D) + varnames(cons2cons, equations) end - # Promote to common data type - Ma, Re, c, L, rho0, u0, nu = promote(Ma, Re, c, L, rho0, u0, nu) - - # Source for weights and speeds: [4] in the docstring above - weights = SVector{9, RealT}(1 / 9, 1 / 9, 1 / 9, 1 / 9, 1 / 36, 1 / 36, 1 / 36, - 1 / 36, 4 / 9) - v_alpha1 = SVector{9, RealT}(c, 0, -c, 0, c, -c, -c, c, 0) - v_alpha2 = SVector{9, RealT}(0, c, 0, -c, c, c, -c, -c, 0) - - LatticeBoltzmannEquations2D(c, c_s, rho0, Ma, u0, Re, L, nu, - weights, v_alpha1, v_alpha2, - collision_op) -end - -function varnames(::typeof(cons2cons), equations::LatticeBoltzmannEquations2D) - ntuple(v -> "pdf" * string(v), nvariables(equations)) -end -function varnames(::typeof(cons2prim), equations::LatticeBoltzmannEquations2D) - varnames(cons2cons, equations) -end - -# Convert conservative variables to macroscopic -@inline function cons2macroscopic(u, equations::LatticeBoltzmannEquations2D) - rho = density(u, equations) - v1, v2 = velocity(u, equations) - p = pressure(u, equations) - return SVector(rho, v1, v2, p) -end -function varnames(::typeof(cons2macroscopic), ::LatticeBoltzmannEquations2D) - ("rho", "v1", "v2", "p") -end - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::LatticeBoltzmannEquations2D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equations::LatticeBoltzmannEquations2D) - @unpack u0 = equations - - RealT = eltype(x) - rho = convert(RealT, pi) - v1 = u0 - v2 = u0 - - return equilibrium_distribution(rho, v1, v2, equations) -end - -""" - boundary_condition_noslip_wall(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::LatticeBoltzmannEquations2D) - -No-slip wall boundary condition using the bounce-back approach. -""" -@inline function boundary_condition_noslip_wall(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::LatticeBoltzmannEquations2D) - # For LBM no-slip wall boundary conditions, we set the boundary state to - # - the inner state for outgoing particle distribution functions - # - the *opposite* inner state for all other particle distribution functions - # See the list of (opposite) directions in the docstring of `LatticeBoltzmannEquations2D`. - if direction == 1 # boundary in -x direction - pdf1 = u_inner[3] - pdf2 = u_inner[4] - pdf3 = u_inner[3] # outgoing - pdf4 = u_inner[2] - pdf5 = u_inner[7] - pdf6 = u_inner[6] # outgoing - pdf7 = u_inner[7] # outgoing - pdf8 = u_inner[6] - pdf9 = u_inner[9] - elseif direction == 2 # boundary in +x direction - pdf1 = u_inner[1] # outgoing - pdf2 = u_inner[4] - pdf3 = u_inner[1] - pdf4 = u_inner[2] - pdf5 = u_inner[5] # outgoing - pdf6 = u_inner[8] - pdf7 = u_inner[5] - pdf8 = u_inner[8] # outgoing - pdf9 = u_inner[9] - elseif direction == 3 # boundary in -y direction - pdf1 = u_inner[3] - pdf2 = u_inner[4] - pdf3 = u_inner[1] - pdf4 = u_inner[4] # outgoing - pdf5 = u_inner[7] - pdf6 = u_inner[8] - pdf7 = u_inner[7] # outgoing - pdf8 = u_inner[8] # outgoing - pdf9 = u_inner[9] - else # boundary in +y direction - pdf1 = u_inner[3] - pdf2 = u_inner[2] # outgoing - pdf3 = u_inner[1] - pdf4 = u_inner[2] - pdf5 = u_inner[5] # outgoing - pdf6 = u_inner[6] # outgoing - pdf7 = u_inner[5] - pdf8 = u_inner[6] - pdf9 = u_inner[9] + # Convert conservative variables to macroscopic + @inline function cons2macroscopic(u, equations::LatticeBoltzmannEquations2D) + rho = density(u, equations) + v1, v2 = velocity(u, equations) + p = pressure(u, equations) + return SVector(rho, v1, v2, p) end - u_boundary = SVector(pdf1, pdf2, pdf3, pdf4, pdf5, pdf6, pdf7, pdf8, pdf9) + function varnames(::typeof(cons2macroscopic), ::LatticeBoltzmannEquations2D) + ("rho", "v1", "v2", "p") + end + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::LatticeBoltzmannEquations2D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equations::LatticeBoltzmannEquations2D) + @unpack u0 = equations + + RealT = eltype(x) + rho = convert(RealT, pi) + v1 = u0 + v2 = u0 - # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + return equilibrium_distribution(rho, v1, v2, equations) end - return flux -end + """ + boundary_condition_noslip_wall(u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LatticeBoltzmannEquations2D) + + No-slip wall boundary condition using the bounce-back approach. + """ + @inline function boundary_condition_noslip_wall( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LatticeBoltzmannEquations2D + ) + # For LBM no-slip wall boundary conditions, we set the boundary state to + # - the inner state for outgoing particle distribution functions + # - the *opposite* inner state for all other particle distribution functions + # See the list of (opposite) directions in the docstring of `LatticeBoltzmannEquations2D`. + if direction == 1 # boundary in -x direction + pdf1 = u_inner[3] + pdf2 = u_inner[4] + pdf3 = u_inner[3] # outgoing + pdf4 = u_inner[2] + pdf5 = u_inner[7] + pdf6 = u_inner[6] # outgoing + pdf7 = u_inner[7] # outgoing + pdf8 = u_inner[6] + pdf9 = u_inner[9] + elseif direction == 2 # boundary in +x direction + pdf1 = u_inner[1] # outgoing + pdf2 = u_inner[4] + pdf3 = u_inner[1] + pdf4 = u_inner[2] + pdf5 = u_inner[5] # outgoing + pdf6 = u_inner[8] + pdf7 = u_inner[5] + pdf8 = u_inner[8] # outgoing + pdf9 = u_inner[9] + elseif direction == 3 # boundary in -y direction + pdf1 = u_inner[3] + pdf2 = u_inner[4] + pdf3 = u_inner[1] + pdf4 = u_inner[4] # outgoing + pdf5 = u_inner[7] + pdf6 = u_inner[8] + pdf7 = u_inner[7] # outgoing + pdf8 = u_inner[8] # outgoing + pdf9 = u_inner[9] + else # boundary in +y direction + pdf1 = u_inner[3] + pdf2 = u_inner[2] # outgoing + pdf3 = u_inner[1] + pdf4 = u_inner[2] + pdf5 = u_inner[5] # outgoing + pdf6 = u_inner[6] # outgoing + pdf7 = u_inner[5] + pdf8 = u_inner[6] + pdf9 = u_inner[9] + end + u_boundary = SVector(pdf1, pdf2, pdf3, pdf4, pdf5, pdf6, pdf7, pdf8, pdf9) + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end -# Pre-defined source terms should be implemented as -# function source_terms_WHATEVER(u, x, t, equations::LatticeBoltzmannEquations2D) + # Pre-defined source terms should be implemented as + # function source_terms_WHATEVER(u, x, t, equations::LatticeBoltzmannEquations2D) + + # Calculate 1D flux in for a single point + @inline function flux(u, orientation::Integer, equations::LatticeBoltzmannEquations2D) + if orientation == 1 + v_alpha = equations.v_alpha1 + else + v_alpha = equations.v_alpha2 + end + return v_alpha .* u + end -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, equations::LatticeBoltzmannEquations2D) - if orientation == 1 - v_alpha = equations.v_alpha1 - else - v_alpha = equations.v_alpha2 + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + # @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations::LatticeBoltzmannEquations2D) + # λ_max = + # end + + @inline function flux_godunov( + u_ll, u_rr, orientation::Integer, + equations::LatticeBoltzmannEquations2D + ) + if orientation == 1 + v_alpha = equations.v_alpha1 + else + v_alpha = equations.v_alpha2 + end + return 0.5f0 * (v_alpha .* (u_ll + u_rr) - abs.(v_alpha) .* (u_rr - u_ll)) end - return v_alpha .* u -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -# @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations::LatticeBoltzmannEquations2D) -# λ_max = -# end - -@inline function flux_godunov(u_ll, u_rr, orientation::Integer, - equations::LatticeBoltzmannEquations2D) - if orientation == 1 - v_alpha = equations.v_alpha1 - else - v_alpha = equations.v_alpha2 + + """ + density(p::Real, equations::LatticeBoltzmannEquations2D) + density(u, equations::LatticeBoltzmannEquations2D) + + Calculate the macroscopic density from the pressure `p` or the particle distribution functions `u`. + """ + @inline density(p::Real, equations::LatticeBoltzmannEquations2D) = p / equations.c_s^2 + @inline density(u, equations::LatticeBoltzmannEquations2D) = sum(u) + + """ + velocity(u, orientation, equations::LatticeBoltzmannEquations2D) + + Calculate the macroscopic velocity for the given `orientation` (1 -> x, 2 -> y) from the + particle distribution functions `u`. + """ + @inline function velocity( + u, orientation::Integer, + equations::LatticeBoltzmannEquations2D + ) + if orientation == 1 + v_alpha = equations.v_alpha1 + else + v_alpha = equations.v_alpha2 + end + + return dot(v_alpha, u) / density(u, equations) end - return 0.5f0 * (v_alpha .* (u_ll + u_rr) - abs.(v_alpha) .* (u_rr - u_ll)) -end - -""" - density(p::Real, equations::LatticeBoltzmannEquations2D) - density(u, equations::LatticeBoltzmannEquations2D) - -Calculate the macroscopic density from the pressure `p` or the particle distribution functions `u`. -""" -@inline density(p::Real, equations::LatticeBoltzmannEquations2D) = p / equations.c_s^2 -@inline density(u, equations::LatticeBoltzmannEquations2D) = sum(u) - -""" - velocity(u, orientation, equations::LatticeBoltzmannEquations2D) - -Calculate the macroscopic velocity for the given `orientation` (1 -> x, 2 -> y) from the -particle distribution functions `u`. -""" -@inline function velocity(u, orientation::Integer, - equations::LatticeBoltzmannEquations2D) - if orientation == 1 - v_alpha = equations.v_alpha1 - else - v_alpha = equations.v_alpha2 + + """ + velocity(u, equations::LatticeBoltzmannEquations2D) + + Calculate the macroscopic velocity vector from the particle distribution functions `u`. + """ + @inline function velocity(u, equations::LatticeBoltzmannEquations2D) + @unpack v_alpha1, v_alpha2 = equations + rho = density(u, equations) + + return SVector( + dot(v_alpha1, u) / rho, + dot(v_alpha2, u) / rho + ) end - return dot(v_alpha, u) / density(u, equations) -end - -""" - velocity(u, equations::LatticeBoltzmannEquations2D) - -Calculate the macroscopic velocity vector from the particle distribution functions `u`. -""" -@inline function velocity(u, equations::LatticeBoltzmannEquations2D) - @unpack v_alpha1, v_alpha2 = equations - rho = density(u, equations) - - return SVector(dot(v_alpha1, u) / rho, - dot(v_alpha2, u) / rho) -end - -""" - pressure(rho::Real, equations::LatticeBoltzmannEquations2D) - pressure(u, equations::LatticeBoltzmannEquations2D) - -Calculate the macroscopic pressure from the density `rho` or the particle distribution functions -`u`. -""" -@inline function pressure(rho::Real, equations::LatticeBoltzmannEquations2D) - rho * equations.c_s^2 -end -@inline function pressure(u, equations::LatticeBoltzmannEquations2D) - pressure(density(u, equations), equations) -end - -""" - equilibrium_distribution(alpha, rho, v1, v2, equations::LatticeBoltzmannEquations2D) - -Calculate the local equilibrium distribution for the distribution function with index `alpha` and -given the macroscopic state defined by `rho`, `v1`, `v2`. -""" -@inline function equilibrium_distribution(alpha, rho, v1, v2, - equations::LatticeBoltzmannEquations2D) - @unpack weights, c_s, v_alpha1, v_alpha2 = equations - - va_v = v_alpha1[alpha] * v1 + v_alpha2[alpha] * v2 - cs_squared = c_s^2 - v_squared = v1^2 + v2^2 - - return weights[alpha] * rho * - (1 + va_v / cs_squared - + va_v^2 / (2 * cs_squared^2) - - - v_squared / (2 * cs_squared)) -end - -@inline function equilibrium_distribution(rho, v1, v2, - equations::LatticeBoltzmannEquations2D) - return SVector(equilibrium_distribution(1, rho, v1, v2, equations), - equilibrium_distribution(2, rho, v1, v2, equations), - equilibrium_distribution(3, rho, v1, v2, equations), - equilibrium_distribution(4, rho, v1, v2, equations), - equilibrium_distribution(5, rho, v1, v2, equations), - equilibrium_distribution(6, rho, v1, v2, equations), - equilibrium_distribution(7, rho, v1, v2, equations), - equilibrium_distribution(8, rho, v1, v2, equations), - equilibrium_distribution(9, rho, v1, v2, equations)) -end - -function equilibrium_distribution(u, equations::LatticeBoltzmannEquations2D) - rho = density(u, equations) - v1, v2 = velocity(u, equations) - - return equilibrium_distribution(rho, v1, v2, equations) -end - -""" - collision_bgk(u, dt, equations::LatticeBoltzmannEquations2D) - -Collision operator for the Bhatnagar, Gross, and Krook (BGK) model. -""" -@inline function collision_bgk(u, dt, equations::LatticeBoltzmannEquations2D) - @unpack c_s, nu = equations - tau = nu / (c_s^2 * dt) - return -(u - equilibrium_distribution(u, equations)) / (tau + 0.5f0) -end - -@inline have_constant_speed(::LatticeBoltzmannEquations2D) = True() - -@inline function max_abs_speeds(equations::LatticeBoltzmannEquations2D) - @unpack c = equations - - return c, c -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equations::LatticeBoltzmannEquations2D) = u - -# Convert conservative variables to entropy variables -@inline cons2entropy(u, equations::LatticeBoltzmannEquations2D) = u + """ + pressure(rho::Real, equations::LatticeBoltzmannEquations2D) + pressure(u, equations::LatticeBoltzmannEquations2D) + + Calculate the macroscopic pressure from the density `rho` or the particle distribution functions + `u`. + """ + @inline function pressure(rho::Real, equations::LatticeBoltzmannEquations2D) + rho * equations.c_s^2 + end + @inline function pressure(u, equations::LatticeBoltzmannEquations2D) + pressure(density(u, equations), equations) + end + + """ + equilibrium_distribution(alpha, rho, v1, v2, equations::LatticeBoltzmannEquations2D) + + Calculate the local equilibrium distribution for the distribution function with index `alpha` and + given the macroscopic state defined by `rho`, `v1`, `v2`. + """ + @inline function equilibrium_distribution( + alpha, rho, v1, v2, + equations::LatticeBoltzmannEquations2D + ) + @unpack weights, c_s, v_alpha1, v_alpha2 = equations + + va_v = v_alpha1[alpha] * v1 + v_alpha2[alpha] * v2 + cs_squared = c_s^2 + v_squared = v1^2 + v2^2 + + return weights[alpha] * rho * + ( + 1 + va_v / cs_squared + + va_v^2 / (2 * cs_squared^2) + - + v_squared / (2 * cs_squared) + ) + end + + @inline function equilibrium_distribution( + rho, v1, v2, + equations::LatticeBoltzmannEquations2D + ) + return SVector( + equilibrium_distribution(1, rho, v1, v2, equations), + equilibrium_distribution(2, rho, v1, v2, equations), + equilibrium_distribution(3, rho, v1, v2, equations), + equilibrium_distribution(4, rho, v1, v2, equations), + equilibrium_distribution(5, rho, v1, v2, equations), + equilibrium_distribution(6, rho, v1, v2, equations), + equilibrium_distribution(7, rho, v1, v2, equations), + equilibrium_distribution(8, rho, v1, v2, equations), + equilibrium_distribution(9, rho, v1, v2, equations) + ) + end + + function equilibrium_distribution(u, equations::LatticeBoltzmannEquations2D) + rho = density(u, equations) + v1, v2 = velocity(u, equations) + + return equilibrium_distribution(rho, v1, v2, equations) + end + + """ + collision_bgk(u, dt, equations::LatticeBoltzmannEquations2D) + + Collision operator for the Bhatnagar, Gross, and Krook (BGK) model. + """ + @inline function collision_bgk(u, dt, equations::LatticeBoltzmannEquations2D) + @unpack c_s, nu = equations + tau = nu / (c_s^2 * dt) + return -(u - equilibrium_distribution(u, equations)) / (tau + 0.5f0) + end + + @inline have_constant_speed(::LatticeBoltzmannEquations2D) = True() + + @inline function max_abs_speeds(equations::LatticeBoltzmannEquations2D) + @unpack c = equations + + return c, c + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equations::LatticeBoltzmannEquations2D) = u + + # Convert conservative variables to entropy variables + @inline cons2entropy(u, equations::LatticeBoltzmannEquations2D) = u end # @muladd diff --git a/src/equations/lattice_boltzmann_3d.jl b/src/equations/lattice_boltzmann_3d.jl index bf4c365e0f..e2b663bc34 100644 --- a/src/equations/lattice_boltzmann_3d.jl +++ b/src/equations/lattice_boltzmann_3d.jl @@ -3,409 +3,437 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - LatticeBoltzmannEquations3D(; Ma, Re, collision_op=collision_bgk, - c=1, L=1, rho0=1, u0=nothing, nu=nothing) - -The Lattice-Boltzmann equations -```math -\partial_t u_\alpha + v_{\alpha,1} \partial_1 u_\alpha + v_{\alpha,2} \partial_2 u_\alpha + v_{\alpha,3} \partial_3 u_\alpha = 0 -``` -in three space dimensions for the D3Q27 scheme. - -The characteristic Mach number and Reynolds numbers are specified as `Ma` and `Re`. By the -default, the collision operator `collision_op` is set to the BGK model. `c`, `L`, and `rho0` -specify the mean thermal molecular velocity, the characteristic length, and the reference density, -respectively. They can usually be left to the default values. If desired, instead of the Mach -number, one can set the macroscopic reference velocity `u0` directly (`Ma` needs to be set to -`nothing` in this case). Likewise, instead of the Reynolds number one can specify the kinematic -viscosity `nu` directly (in this case, `Re` needs to be set to `nothing`). - - -The twenty-seven discrete velocity directions of the D3Q27 scheme are sorted as follows [4]: -* plane at `z = -1`: - ``` - 24 17 21 y - ┌───┼───┐ │ - │ │ │ - 10 ┼ 6 ┼ 15 ──── x - │ │ ╱ - └───┼───┘ ╱ - 20 12 26 z - ``` -* plane at `z = 0`: - ``` - 14 3 7 y - ┌───┼───┐ │ - │ │ │ - 2 ┼ 27 ┼ 1 ──── x - │ │ ╱ - └───┼───┘ ╱ - 8 4 13 z - ``` -* plane at `z = +1`: - ``` - 25 11 19 y - ┌───┼───┐ │ - │ │ │ - 16 ┼ 5 ┼ 9 ──── x - │ │ ╱ - └───┼───┘ ╱ - 22 18 23 z - ``` -Note that usually the velocities are numbered from `0` to `26`, where `0` corresponds to the zero -velocity. Due to Julia using 1-based indexing, here we use indices from `1` to `27`, where `1` -through `26` correspond to the velocity directions in [4] and `27` is the zero velocity. - -The corresponding opposite directions are: -* 1 ←→ 2 -* 2 ←→ 1 -* 3 ←→ 4 -* 4 ←→ 3 -* 5 ←→ 6 -* 6 ←→ 5 -* 7 ←→ 8 -* 8 ←→ 7 -* 9 ←→ 10 -* 10 ←→ 9 -* 11 ←→ 12 -* 12 ←→ 11 -* 13 ←→ 14 -* 14 ←→ 13 -* 15 ←→ 16 -* 16 ←→ 15 -* 17 ←→ 18 -* 18 ←→ 17 -* 19 ←→ 20 -* 20 ←→ 19 -* 21 ←→ 22 -* 22 ←→ 21 -* 23 ←→ 24 -* 24 ←→ 23 -* 25 ←→ 26 -* 26 ←→ 25 -* 27 ←→ 27 - -The main sources for the base implementation were -1. Misun Min, Taehun Lee, **A spectral-element discontinuous Galerkin lattice Boltzmann method for - nearly incompressible flows**, J Comput Phys 230(1), 2011 - [doi:10.1016/j.jcp.2010.09.024](https://doi.org/10.1016/j.jcp.2010.09.024) -2. Karsten Golly, **Anwendung der Lattice-Boltzmann Discontinuous Galerkin Spectral Element Method - (LB-DGSEM) auf laminare und turbulente nahezu inkompressible Strömungen im dreidimensionalen - Raum**, Master Thesis, University of Cologne, 2018. -3. Dieter Hänel, **Molekulare Gasdynamik**, Springer-Verlag Berlin Heidelberg, 2004 - [doi:10.1007/3-540-35047-0](https://doi.org/10.1007/3-540-35047-0) -4. Dieter Krüger et al., **The Lattice Boltzmann Method**, Springer International Publishing, 2017 - [doi:10.1007/978-3-319-44649-3](https://doi.org/10.1007/978-3-319-44649-3) -""" -struct LatticeBoltzmannEquations3D{RealT <: Real, CollisionOp} <: - AbstractLatticeBoltzmannEquations{3, 27} - c::RealT # mean thermal molecular velocity - c_s::RealT # isothermal speed of sound - rho0::RealT # macroscopic reference density - - Ma::RealT # characteristic Mach number - u0::RealT # macroscopic reference velocity - - Re::RealT # characteristic Reynolds number - L::RealT # reference length - nu::RealT # kinematic viscosity - - weights::SVector{27, RealT} # weighting factors for the equilibrium distribution - v_alpha1::SVector{27, RealT} # discrete molecular velocity components in x-direction - v_alpha2::SVector{27, RealT} # discrete molecular velocity components in y-direction - v_alpha3::SVector{27, RealT} # discrete molecular velocity components in z-direction - - collision_op::CollisionOp # collision operator for the collision kernel -end - -function LatticeBoltzmannEquations3D(; Ma, Re, collision_op = collision_bgk, - c = 1, L = 1, rho0 = 1, u0 = nothing, nu = nothing) - # Sanity check that exactly one of Ma, u0 is not `nothing` - if isnothing(Ma) && isnothing(u0) - error("Mach number `Ma` and reference speed `u0` may not both be `nothing`") - elseif !isnothing(Ma) && !isnothing(u0) - error("Mach number `Ma` and reference speed `u0` may not both be set") + #! format: noindent + + @doc raw""" + LatticeBoltzmannEquations3D(; Ma, Re, collision_op=collision_bgk, + c=1, L=1, rho0=1, u0=nothing, nu=nothing) + + The Lattice-Boltzmann equations + ```math + \partial_t u_\alpha + v_{\alpha,1} \partial_1 u_\alpha + v_{\alpha,2} \partial_2 u_\alpha + v_{\alpha,3} \partial_3 u_\alpha = 0 + ``` + in three space dimensions for the D3Q27 scheme. + + The characteristic Mach number and Reynolds numbers are specified as `Ma` and `Re`. By the + default, the collision operator `collision_op` is set to the BGK model. `c`, `L`, and `rho0` + specify the mean thermal molecular velocity, the characteristic length, and the reference density, + respectively. They can usually be left to the default values. If desired, instead of the Mach + number, one can set the macroscopic reference velocity `u0` directly (`Ma` needs to be set to + `nothing` in this case). Likewise, instead of the Reynolds number one can specify the kinematic + viscosity `nu` directly (in this case, `Re` needs to be set to `nothing`). + + + The twenty-seven discrete velocity directions of the D3Q27 scheme are sorted as follows [4]: + * plane at `z = -1`: + ``` + 24 17 21 y + ┌───┼───┐ │ + │ │ │ + 10 ┼ 6 ┼ 15 ──── x + │ │ ╱ + └───┼───┘ ╱ + 20 12 26 z + ``` + * plane at `z = 0`: + ``` + 14 3 7 y + ┌───┼───┐ │ + │ │ │ + 2 ┼ 27 ┼ 1 ──── x + │ │ ╱ + └───┼───┘ ╱ + 8 4 13 z + ``` + * plane at `z = +1`: + ``` + 25 11 19 y + ┌───┼───┐ │ + │ │ │ + 16 ┼ 5 ┼ 9 ──── x + │ │ ╱ + └───┼───┘ ╱ + 22 18 23 z + ``` + Note that usually the velocities are numbered from `0` to `26`, where `0` corresponds to the zero + velocity. Due to Julia using 1-based indexing, here we use indices from `1` to `27`, where `1` + through `26` correspond to the velocity directions in [4] and `27` is the zero velocity. + + The corresponding opposite directions are: + * 1 ←→ 2 + * 2 ←→ 1 + * 3 ←→ 4 + * 4 ←→ 3 + * 5 ←→ 6 + * 6 ←→ 5 + * 7 ←→ 8 + * 8 ←→ 7 + * 9 ←→ 10 + * 10 ←→ 9 + * 11 ←→ 12 + * 12 ←→ 11 + * 13 ←→ 14 + * 14 ←→ 13 + * 15 ←→ 16 + * 16 ←→ 15 + * 17 ←→ 18 + * 18 ←→ 17 + * 19 ←→ 20 + * 20 ←→ 19 + * 21 ←→ 22 + * 22 ←→ 21 + * 23 ←→ 24 + * 24 ←→ 23 + * 25 ←→ 26 + * 26 ←→ 25 + * 27 ←→ 27 + + The main sources for the base implementation were + 1. Misun Min, Taehun Lee, **A spectral-element discontinuous Galerkin lattice Boltzmann method for + nearly incompressible flows**, J Comput Phys 230(1), 2011 + [doi:10.1016/j.jcp.2010.09.024](https://doi.org/10.1016/j.jcp.2010.09.024) + 2. Karsten Golly, **Anwendung der Lattice-Boltzmann Discontinuous Galerkin Spectral Element Method + (LB-DGSEM) auf laminare und turbulente nahezu inkompressible Strömungen im dreidimensionalen + Raum**, Master Thesis, University of Cologne, 2018. + 3. Dieter Hänel, **Molekulare Gasdynamik**, Springer-Verlag Berlin Heidelberg, 2004 + [doi:10.1007/3-540-35047-0](https://doi.org/10.1007/3-540-35047-0) + 4. Dieter Krüger et al., **The Lattice Boltzmann Method**, Springer International Publishing, 2017 + [doi:10.1007/978-3-319-44649-3](https://doi.org/10.1007/978-3-319-44649-3) + """ + struct LatticeBoltzmannEquations3D{RealT <: Real, CollisionOp} <: + AbstractLatticeBoltzmannEquations{3, 27} + c::RealT # mean thermal molecular velocity + c_s::RealT # isothermal speed of sound + rho0::RealT # macroscopic reference density + + Ma::RealT # characteristic Mach number + u0::RealT # macroscopic reference velocity + + Re::RealT # characteristic Reynolds number + L::RealT # reference length + nu::RealT # kinematic viscosity + + weights::SVector{27, RealT} # weighting factors for the equilibrium distribution + v_alpha1::SVector{27, RealT} # discrete molecular velocity components in x-direction + v_alpha2::SVector{27, RealT} # discrete molecular velocity components in y-direction + v_alpha3::SVector{27, RealT} # discrete molecular velocity components in z-direction + + collision_op::CollisionOp # collision operator for the collision kernel end - # Sanity check that exactly one of Re, nu is not `nothing` - if isnothing(Re) && isnothing(nu) - error("Reynolds number `Re` and visocsity `nu` may not both be `nothing`") - elseif !isnothing(Re) && !isnothing(nu) - error("Reynolds number `Re` and visocsity `nu` may not both be set") + function LatticeBoltzmannEquations3D(; + Ma, Re, collision_op = collision_bgk, + c = 1, L = 1, rho0 = 1, u0 = nothing, nu = nothing + ) + # Sanity check that exactly one of Ma, u0 is not `nothing` + if isnothing(Ma) && isnothing(u0) + error("Mach number `Ma` and reference speed `u0` may not both be `nothing`") + elseif !isnothing(Ma) && !isnothing(u0) + error("Mach number `Ma` and reference speed `u0` may not both be set") + end + + # Sanity check that exactly one of Re, nu is not `nothing` + if isnothing(Re) && isnothing(nu) + error("Reynolds number `Re` and visocsity `nu` may not both be `nothing`") + elseif !isnothing(Re) && !isnothing(nu) + error("Reynolds number `Re` and visocsity `nu` may not both be set") + end + + # Calculate isothermal speed of sound + # The relation between the isothermal speed of sound `c_s` and the mean thermal molecular velocity + # `c` depends on the used phase space discretization, and is valid for D3Q27 (and others). For + # details, see, e.g., [3] in the docstring above. + # c_s = c / sqrt(3) + + # Calculate missing quantities + if isnothing(Ma) + RealT = eltype(u0) + c_s = c / sqrt(convert(RealT, 3)) + Ma = u0 / c_s + elseif isnothing(u0) + RealT = eltype(Ma) + c_s = c / sqrt(convert(RealT, 3)) + u0 = Ma * c_s + end + if isnothing(Re) + Re = u0 * L / nu + elseif isnothing(nu) + nu = u0 * L / Re + end + + # Promote to common data type + Ma, Re, c, L, rho0, u0, nu = promote(Ma, Re, c, L, rho0, u0, nu) + + # Source for weights and speeds: [4] in docstring above + weights = SVector{27, RealT}( + 2 / 27, 2 / 27, 2 / 27, 2 / 27, 2 / 27, 2 / 27, 1 / 54, + 1 / 54, + 1 / 54, + 1 / 54, 1 / 54, 1 / 54, 1 / 54, 1 / 54, 1 / 54, 1 / 54, + 1 / 54, + 1 / 54, + 1 / 216, 1 / 216, 1 / 216, 1 / 216, 1 / 216, 1 / 216, + 1 / 216, + 1 / 216, 8 / 27 + ) + v_alpha1 = SVector{27, RealT}( + c, -c, 0, 0, 0, 0, c, -c, c, + -c, 0, 0, c, -c, c, -c, 0, 0, + c, -c, c, -c, c, -c, -c, c, 0 + ) + v_alpha2 = SVector{27, RealT}( + 0, 0, c, -c, 0, 0, c, -c, 0, + 0, c, -c, -c, c, 0, 0, c, -c, + c, -c, c, -c, -c, c, c, -c, 0 + ) + v_alpha3 = SVector{27, RealT}( + 0, 0, 0, 0, c, -c, 0, 0, c, + -c, c, -c, 0, 0, -c, c, -c, c, + c, -c, -c, c, c, -c, c, -c, 0 + ) + + LatticeBoltzmannEquations3D( + c, c_s, rho0, Ma, u0, Re, L, nu, + weights, v_alpha1, v_alpha2, v_alpha3, + collision_op + ) end - # Calculate isothermal speed of sound - # The relation between the isothermal speed of sound `c_s` and the mean thermal molecular velocity - # `c` depends on the used phase space discretization, and is valid for D3Q27 (and others). For - # details, see, e.g., [3] in the docstring above. - # c_s = c / sqrt(3) - - # Calculate missing quantities - if isnothing(Ma) - RealT = eltype(u0) - c_s = c / sqrt(convert(RealT, 3)) - Ma = u0 / c_s - elseif isnothing(u0) - RealT = eltype(Ma) - c_s = c / sqrt(convert(RealT, 3)) - u0 = Ma * c_s + function varnames(::typeof(cons2cons), equations::LatticeBoltzmannEquations3D) + ntuple(v -> "pdf" * string(v), Val(nvariables(equations))) end - if isnothing(Re) - Re = u0 * L / nu - elseif isnothing(nu) - nu = u0 * L / Re + function varnames(::typeof(cons2prim), equations::LatticeBoltzmannEquations3D) + varnames(cons2cons, equations) end - # Promote to common data type - Ma, Re, c, L, rho0, u0, nu = promote(Ma, Re, c, L, rho0, u0, nu) - - # Source for weights and speeds: [4] in docstring above - weights = SVector{27, RealT}(2 / 27, 2 / 27, 2 / 27, 2 / 27, 2 / 27, 2 / 27, 1 / 54, - 1 / 54, - 1 / 54, - 1 / 54, 1 / 54, 1 / 54, 1 / 54, 1 / 54, 1 / 54, 1 / 54, - 1 / 54, - 1 / 54, - 1 / 216, 1 / 216, 1 / 216, 1 / 216, 1 / 216, 1 / 216, - 1 / 216, - 1 / 216, 8 / 27) - v_alpha1 = SVector{27, RealT}(c, -c, 0, 0, 0, 0, c, -c, c, - -c, 0, 0, c, -c, c, -c, 0, 0, - c, -c, c, -c, c, -c, -c, c, 0) - v_alpha2 = SVector{27, RealT}(0, 0, c, -c, 0, 0, c, -c, 0, - 0, c, -c, -c, c, 0, 0, c, -c, - c, -c, c, -c, -c, c, c, -c, 0) - v_alpha3 = SVector{27, RealT}(0, 0, 0, 0, c, -c, 0, 0, c, - -c, c, -c, 0, 0, -c, c, -c, c, - c, -c, -c, c, c, -c, c, -c, 0) - - LatticeBoltzmannEquations3D(c, c_s, rho0, Ma, u0, Re, L, nu, - weights, v_alpha1, v_alpha2, v_alpha3, - collision_op) -end - -function varnames(::typeof(cons2cons), equations::LatticeBoltzmannEquations3D) - ntuple(v -> "pdf" * string(v), Val(nvariables(equations))) -end -function varnames(::typeof(cons2prim), equations::LatticeBoltzmannEquations3D) - varnames(cons2cons, equations) -end - -# Convert conservative variables to macroscopic -@inline function cons2macroscopic(u, equations::LatticeBoltzmannEquations3D) - rho = density(u, equations) - v1, v2, v3 = velocity(u, equations) - p = pressure(u, equations) - return SVector(rho, v1, v2, v3, p) -end -function varnames(::typeof(cons2macroscopic), ::LatticeBoltzmannEquations3D) - ("rho", "v1", "v2", "v3", "p") -end - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::LatticeBoltzmannEquations3D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equations::LatticeBoltzmannEquations3D) - @unpack u0 = equations - - RealT = eltype(x) - rho = convert(RealT, pi) - v1 = u0 - v2 = u0 - v3 = u0 - - return equilibrium_distribution(rho, v1, v2, v3, equations) -end - -# Pre-defined source terms should be implemented as -# function source_terms_WHATEVER(u, x, t, equations::LatticeBoltzmannEquations3D) - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, equations::LatticeBoltzmannEquations3D) - if orientation == 1 # x-direction - v_alpha = equations.v_alpha1 - elseif orientation == 2 # y-direction - v_alpha = equations.v_alpha2 - else # z-direction - v_alpha = equations.v_alpha3 + # Convert conservative variables to macroscopic + @inline function cons2macroscopic(u, equations::LatticeBoltzmannEquations3D) + rho = density(u, equations) + v1, v2, v3 = velocity(u, equations) + p = pressure(u, equations) + return SVector(rho, v1, v2, v3, p) end - return v_alpha .* u -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -# @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations::LatticeBoltzmannEquations3D) -# λ_max = -# end - -@inline function flux_godunov(u_ll, u_rr, orientation::Integer, - equations::LatticeBoltzmannEquations3D) - if orientation == 1 # x-direction - v_alpha = equations.v_alpha1 - elseif orientation == 2 # y-direction - v_alpha = equations.v_alpha2 - else # z-direction - v_alpha = equations.v_alpha3 + function varnames(::typeof(cons2macroscopic), ::LatticeBoltzmannEquations3D) + ("rho", "v1", "v2", "v3", "p") end - return 0.5f0 * (v_alpha .* (u_ll + u_rr) - abs.(v_alpha) .* (u_rr - u_ll)) -end - -""" - density(p::Real, equations::LatticeBoltzmannEquations3D) - density(u, equations::LatticeBoltzmannEquations3D) - -Calculate the macroscopic density from the pressure `p` or the particle distribution functions `u`. -""" -@inline density(p::Real, equations::LatticeBoltzmannEquations3D) = p / equations.c_s^2 -@inline density(u, equations::LatticeBoltzmannEquations3D) = sum(u) - -""" - velocity(u, orientation, equations::LatticeBoltzmannEquations3D) - -Calculate the macroscopic velocity for the given `orientation` (1 -> x, 2 -> y, 3 -> z) from the -particle distribution functions `u`. -""" -@inline function velocity(u, orientation::Integer, - equations::LatticeBoltzmannEquations3D) - if orientation == 1 # x-direction - v_alpha = equations.v_alpha1 - elseif orientation == 2 # y-direction - v_alpha = equations.v_alpha2 - else # z-direction - v_alpha = equations.v_alpha3 + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::LatticeBoltzmannEquations3D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equations::LatticeBoltzmannEquations3D) + @unpack u0 = equations + + RealT = eltype(x) + rho = convert(RealT, pi) + v1 = u0 + v2 = u0 + v3 = u0 + + return equilibrium_distribution(rho, v1, v2, v3, equations) + end + + # Pre-defined source terms should be implemented as + # function source_terms_WHATEVER(u, x, t, equations::LatticeBoltzmannEquations3D) + + # Calculate 1D flux in for a single point + @inline function flux(u, orientation::Integer, equations::LatticeBoltzmannEquations3D) + if orientation == 1 # x-direction + v_alpha = equations.v_alpha1 + elseif orientation == 2 # y-direction + v_alpha = equations.v_alpha2 + else # z-direction + v_alpha = equations.v_alpha3 + end + return v_alpha .* u + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + # @inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations::LatticeBoltzmannEquations3D) + # λ_max = + # end + + @inline function flux_godunov( + u_ll, u_rr, orientation::Integer, + equations::LatticeBoltzmannEquations3D + ) + if orientation == 1 # x-direction + v_alpha = equations.v_alpha1 + elseif orientation == 2 # y-direction + v_alpha = equations.v_alpha2 + else # z-direction + v_alpha = equations.v_alpha3 + end + return 0.5f0 * (v_alpha .* (u_ll + u_rr) - abs.(v_alpha) .* (u_rr - u_ll)) + end + + """ + density(p::Real, equations::LatticeBoltzmannEquations3D) + density(u, equations::LatticeBoltzmannEquations3D) + + Calculate the macroscopic density from the pressure `p` or the particle distribution functions `u`. + """ + @inline density(p::Real, equations::LatticeBoltzmannEquations3D) = p / equations.c_s^2 + @inline density(u, equations::LatticeBoltzmannEquations3D) = sum(u) + + """ + velocity(u, orientation, equations::LatticeBoltzmannEquations3D) + + Calculate the macroscopic velocity for the given `orientation` (1 -> x, 2 -> y, 3 -> z) from the + particle distribution functions `u`. + """ + @inline function velocity( + u, orientation::Integer, + equations::LatticeBoltzmannEquations3D + ) + if orientation == 1 # x-direction + v_alpha = equations.v_alpha1 + elseif orientation == 2 # y-direction + v_alpha = equations.v_alpha2 + else # z-direction + v_alpha = equations.v_alpha3 + end + + return dot(v_alpha, u) / density(u, equations) + end + + """ + velocity(u, equations::LatticeBoltzmannEquations3D) + + Calculate the macroscopic velocity vector from the particle distribution functions `u`. + """ + @inline function velocity(u, equations::LatticeBoltzmannEquations3D) + @unpack v_alpha1, v_alpha2, v_alpha3 = equations + rho = density(u, equations) + + return SVector( + dot(v_alpha1, u) / rho, + dot(v_alpha2, u) / rho, + dot(v_alpha3, u) / rho + ) + end + + """ + pressure(rho::Real, equations::LatticeBoltzmannEquations3D) + pressure(u, equations::LatticeBoltzmannEquations3D) + + Calculate the macroscopic pressure from the density `rho` or the particle distribution functions + `u`. + """ + @inline function pressure(rho::Real, equations::LatticeBoltzmannEquations3D) + rho * equations.c_s^2 + end + @inline function pressure(u, equations::LatticeBoltzmannEquations3D) + pressure(density(u, equations), equations) + end + + """ + equilibrium_distribution(alpha, rho, v1, v2, v3, equations::LatticeBoltzmannEquations3D) + + Calculate the local equilibrium distribution for the distribution function with index `alpha` and + given the macroscopic state defined by `rho`, `v1`, `v2`, `v3`. + """ + @inline function equilibrium_distribution( + alpha, rho, v1, v2, v3, + equations::LatticeBoltzmannEquations3D + ) + @unpack weights, c_s, v_alpha1, v_alpha2, v_alpha3 = equations + + va_v = v_alpha1[alpha] * v1 + v_alpha2[alpha] * v2 + v_alpha3[alpha] * v3 + cs_squared = c_s^2 + v_squared = v1^2 + v2^2 + v3^2 + + return weights[alpha] * rho * + ( + 1 + va_v / cs_squared + + va_v^2 / (2 * cs_squared^2) + - + v_squared / (2 * cs_squared) + ) + end + + @inline function equilibrium_distribution( + rho, v1, v2, v3, + equations::LatticeBoltzmannEquations3D + ) + return SVector( + equilibrium_distribution(1, rho, v1, v2, v3, equations), + equilibrium_distribution(2, rho, v1, v2, v3, equations), + equilibrium_distribution(3, rho, v1, v2, v3, equations), + equilibrium_distribution(4, rho, v1, v2, v3, equations), + equilibrium_distribution(5, rho, v1, v2, v3, equations), + equilibrium_distribution(6, rho, v1, v2, v3, equations), + equilibrium_distribution(7, rho, v1, v2, v3, equations), + equilibrium_distribution(8, rho, v1, v2, v3, equations), + equilibrium_distribution(9, rho, v1, v2, v3, equations), + equilibrium_distribution(10, rho, v1, v2, v3, equations), + equilibrium_distribution(11, rho, v1, v2, v3, equations), + equilibrium_distribution(12, rho, v1, v2, v3, equations), + equilibrium_distribution(13, rho, v1, v2, v3, equations), + equilibrium_distribution(14, rho, v1, v2, v3, equations), + equilibrium_distribution(15, rho, v1, v2, v3, equations), + equilibrium_distribution(16, rho, v1, v2, v3, equations), + equilibrium_distribution(17, rho, v1, v2, v3, equations), + equilibrium_distribution(18, rho, v1, v2, v3, equations), + equilibrium_distribution(19, rho, v1, v2, v3, equations), + equilibrium_distribution(20, rho, v1, v2, v3, equations), + equilibrium_distribution(21, rho, v1, v2, v3, equations), + equilibrium_distribution(22, rho, v1, v2, v3, equations), + equilibrium_distribution(23, rho, v1, v2, v3, equations), + equilibrium_distribution(24, rho, v1, v2, v3, equations), + equilibrium_distribution(25, rho, v1, v2, v3, equations), + equilibrium_distribution(26, rho, v1, v2, v3, equations), + equilibrium_distribution(27, rho, v1, v2, v3, equations) + ) + end + + function equilibrium_distribution(u, equations::LatticeBoltzmannEquations3D) + rho = density(u, equations) + v1, v2, v3 = velocity(u, equations) + + return equilibrium_distribution(rho, v1, v2, v3, equations) end - return dot(v_alpha, u) / density(u, equations) -end - -""" - velocity(u, equations::LatticeBoltzmannEquations3D) - -Calculate the macroscopic velocity vector from the particle distribution functions `u`. -""" -@inline function velocity(u, equations::LatticeBoltzmannEquations3D) - @unpack v_alpha1, v_alpha2, v_alpha3 = equations - rho = density(u, equations) - - return SVector(dot(v_alpha1, u) / rho, - dot(v_alpha2, u) / rho, - dot(v_alpha3, u) / rho) -end - -""" - pressure(rho::Real, equations::LatticeBoltzmannEquations3D) - pressure(u, equations::LatticeBoltzmannEquations3D) - -Calculate the macroscopic pressure from the density `rho` or the particle distribution functions -`u`. -""" -@inline function pressure(rho::Real, equations::LatticeBoltzmannEquations3D) - rho * equations.c_s^2 -end -@inline function pressure(u, equations::LatticeBoltzmannEquations3D) - pressure(density(u, equations), equations) -end - -""" - equilibrium_distribution(alpha, rho, v1, v2, v3, equations::LatticeBoltzmannEquations3D) - -Calculate the local equilibrium distribution for the distribution function with index `alpha` and -given the macroscopic state defined by `rho`, `v1`, `v2`, `v3`. -""" -@inline function equilibrium_distribution(alpha, rho, v1, v2, v3, - equations::LatticeBoltzmannEquations3D) - @unpack weights, c_s, v_alpha1, v_alpha2, v_alpha3 = equations - - va_v = v_alpha1[alpha] * v1 + v_alpha2[alpha] * v2 + v_alpha3[alpha] * v3 - cs_squared = c_s^2 - v_squared = v1^2 + v2^2 + v3^2 - - return weights[alpha] * rho * - (1 + va_v / cs_squared - + va_v^2 / (2 * cs_squared^2) - - - v_squared / (2 * cs_squared)) -end - -@inline function equilibrium_distribution(rho, v1, v2, v3, - equations::LatticeBoltzmannEquations3D) - return SVector(equilibrium_distribution(1, rho, v1, v2, v3, equations), - equilibrium_distribution(2, rho, v1, v2, v3, equations), - equilibrium_distribution(3, rho, v1, v2, v3, equations), - equilibrium_distribution(4, rho, v1, v2, v3, equations), - equilibrium_distribution(5, rho, v1, v2, v3, equations), - equilibrium_distribution(6, rho, v1, v2, v3, equations), - equilibrium_distribution(7, rho, v1, v2, v3, equations), - equilibrium_distribution(8, rho, v1, v2, v3, equations), - equilibrium_distribution(9, rho, v1, v2, v3, equations), - equilibrium_distribution(10, rho, v1, v2, v3, equations), - equilibrium_distribution(11, rho, v1, v2, v3, equations), - equilibrium_distribution(12, rho, v1, v2, v3, equations), - equilibrium_distribution(13, rho, v1, v2, v3, equations), - equilibrium_distribution(14, rho, v1, v2, v3, equations), - equilibrium_distribution(15, rho, v1, v2, v3, equations), - equilibrium_distribution(16, rho, v1, v2, v3, equations), - equilibrium_distribution(17, rho, v1, v2, v3, equations), - equilibrium_distribution(18, rho, v1, v2, v3, equations), - equilibrium_distribution(19, rho, v1, v2, v3, equations), - equilibrium_distribution(20, rho, v1, v2, v3, equations), - equilibrium_distribution(21, rho, v1, v2, v3, equations), - equilibrium_distribution(22, rho, v1, v2, v3, equations), - equilibrium_distribution(23, rho, v1, v2, v3, equations), - equilibrium_distribution(24, rho, v1, v2, v3, equations), - equilibrium_distribution(25, rho, v1, v2, v3, equations), - equilibrium_distribution(26, rho, v1, v2, v3, equations), - equilibrium_distribution(27, rho, v1, v2, v3, equations)) -end - -function equilibrium_distribution(u, equations::LatticeBoltzmannEquations3D) - rho = density(u, equations) - v1, v2, v3 = velocity(u, equations) - - return equilibrium_distribution(rho, v1, v2, v3, equations) -end - -""" - collision_bgk(u, dt, equations::LatticeBoltzmannEquations3D) - -Collision operator for the Bhatnagar, Gross, and Krook (BGK) model. -""" -@inline function collision_bgk(u, dt, equations::LatticeBoltzmannEquations3D) - @unpack c_s, nu = equations - tau = nu / (c_s^2 * dt) - return -(u - equilibrium_distribution(u, equations)) / (tau + 0.5f0) -end - -@inline have_constant_speed(::LatticeBoltzmannEquations3D) = True() - -@inline function max_abs_speeds(equations::LatticeBoltzmannEquations3D) - @unpack c = equations - - return c, c, c -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equations::LatticeBoltzmannEquations3D) = u - -# Convert conservative variables to entropy variables -@inline cons2entropy(u, equations::LatticeBoltzmannEquations3D) = u - -# Calculate kinetic energy for a conservative state `u` -@inline function energy_kinetic(u, equations::LatticeBoltzmannEquations3D) - rho = density(u, equations) - v1, v2, v3 = velocity(u, equations) - - return 0.5f0 * (v1^2 + v2^2 + v3^2) / rho / equations.rho0 -end - -# Calculate nondimensionalized kinetic energy for a conservative state `u` -@inline function energy_kinetic_nondimensional(u, - equations::LatticeBoltzmannEquations3D) - return energy_kinetic(u, equations) / equations.u0^2 -end + """ + collision_bgk(u, dt, equations::LatticeBoltzmannEquations3D) + + Collision operator for the Bhatnagar, Gross, and Krook (BGK) model. + """ + @inline function collision_bgk(u, dt, equations::LatticeBoltzmannEquations3D) + @unpack c_s, nu = equations + tau = nu / (c_s^2 * dt) + return -(u - equilibrium_distribution(u, equations)) / (tau + 0.5f0) + end + + @inline have_constant_speed(::LatticeBoltzmannEquations3D) = True() + + @inline function max_abs_speeds(equations::LatticeBoltzmannEquations3D) + @unpack c = equations + + return c, c, c + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equations::LatticeBoltzmannEquations3D) = u + + # Convert conservative variables to entropy variables + @inline cons2entropy(u, equations::LatticeBoltzmannEquations3D) = u + + # Calculate kinetic energy for a conservative state `u` + @inline function energy_kinetic(u, equations::LatticeBoltzmannEquations3D) + rho = density(u, equations) + v1, v2, v3 = velocity(u, equations) + + return 0.5f0 * (v1^2 + v2^2 + v3^2) / rho / equations.rho0 + end + + # Calculate nondimensionalized kinetic energy for a conservative state `u` + @inline function energy_kinetic_nondimensional( + u, + equations::LatticeBoltzmannEquations3D + ) + return energy_kinetic(u, equations) / equations.u0^2 + end end # @muladd diff --git a/src/equations/linear_scalar_advection_1d.jl b/src/equations/linear_scalar_advection_1d.jl index 743d2df870..e4af02da09 100644 --- a/src/equations/linear_scalar_advection_1d.jl +++ b/src/equations/linear_scalar_advection_1d.jl @@ -3,230 +3,252 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - LinearScalarAdvectionEquation1D - -The linear scalar advection equation -```math -\partial_t u + a \partial_1 u = 0 -``` -in one space dimension with constant velocity `a`. -""" -struct LinearScalarAdvectionEquation1D{RealT <: Real} <: - AbstractLinearScalarAdvectionEquation{1, 1} - advection_velocity::SVector{1, RealT} -end - -function LinearScalarAdvectionEquation1D(a::Real) - LinearScalarAdvectionEquation1D(SVector(a)) -end - -varnames(::typeof(cons2cons), ::LinearScalarAdvectionEquation1D) = ("scalar",) -varnames(::typeof(cons2prim), ::LinearScalarAdvectionEquation1D) = ("scalar",) - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::LinearScalarAdvectionEquation1D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equation::LinearScalarAdvectionEquation1D) - # Store translated coordinate for easy use of exact solution - RealT = eltype(x) - x_trans = x - equation.advection_velocity * t - - return SVector(RealT(2)) -end - -""" - initial_condition_convergence_test(x, t, equations::LinearScalarAdvectionEquation1D) - -A smooth initial condition used for convergence tests -(in combination with [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) -in non-periodic domains). -""" -function initial_condition_convergence_test(x, t, - equation::LinearScalarAdvectionEquation1D) - # Store translated coordinate for easy use of exact solution - RealT = eltype(x) - x_trans = x - equation.advection_velocity * t - - c = 1 - A = 0.5f0 - L = 2 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - scalar = c + A * sin(omega * sum(x_trans)) - return SVector(scalar) -end - -""" - initial_condition_gauss(x, t, equations::LinearScalarAdvectionEquation1D) - -A Gaussian pulse used together with -[`BoundaryConditionDirichlet(initial_condition_gauss)`](@ref). -""" -function initial_condition_gauss(x, t, equation::LinearScalarAdvectionEquation1D) - # Store translated coordinate for easy use of exact solution - x_trans = x - equation.advection_velocity * t - - scalar = exp(-(x_trans[1]^2)) - return SVector(scalar) -end - -""" - initial_condition_sin(x, t, equations::LinearScalarAdvectionEquation1D) - -A sine wave in the conserved variable. -""" -function initial_condition_sin(x, t, equation::LinearScalarAdvectionEquation1D) - # Store translated coordinate for easy use of exact solution - x_trans = x - equation.advection_velocity * t - - scalar = sinpi(2 * x_trans[1]) - return SVector(scalar) -end - -""" - initial_condition_linear_x(x, t, equations::LinearScalarAdvectionEquation1D) - -A linear function of `x[1]` used together with -[`boundary_condition_linear_x`](@ref). -""" -function initial_condition_linear_x(x, t, equation::LinearScalarAdvectionEquation1D) - # Store translated coordinate for easy use of exact solution - x_trans = x - equation.advection_velocity * t - - return SVector(x_trans[1]) -end - -""" - boundary_condition_linear_x(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation1D) - -Boundary conditions for -[`initial_condition_linear_x`](@ref). -""" -function boundary_condition_linear_x(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation1D) - u_boundary = initial_condition_linear_x(x, t, equation) - - # Calculate boundary flux - if direction == 2 # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equation) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equation) - end - - return flux -end - -# Pre-defined source terms should be implemented as -# function source_terms_WHATEVER(u, x, t, equations::LinearScalarAdvectionEquation1D) - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, - equation::LinearScalarAdvectionEquation1D) - a = equation.advection_velocity[orientation] - return a * u -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Int, - equation::LinearScalarAdvectionEquation1D) - λ_max = abs(equation.advection_velocity[orientation]) -end - -# Essentially first order upwind, see e.g. -# https://math.stackexchange.com/a/4355076/805029 -function flux_godunov(u_ll, u_rr, orientation::Int, - equation::LinearScalarAdvectionEquation1D) - u_L = u_ll[1] - u_R = u_rr[1] - - v_normal = equation.advection_velocity[orientation] - if v_normal >= 0 - return SVector(v_normal * u_L) - else - return SVector(v_normal * u_R) - end -end - -# See https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf , -# section 4.2.5 and especially equation (4.33). -function flux_engquist_osher(u_ll, u_rr, orientation::Int, - equation::LinearScalarAdvectionEquation1D) - u_L = u_ll[1] - u_R = u_rr[1] - - return SVector(0.5f0 * (flux(u_L, orientation, equation) + + #! format: noindent + + @doc raw""" + LinearScalarAdvectionEquation1D + + The linear scalar advection equation + ```math + \partial_t u + a \partial_1 u = 0 + ``` + in one space dimension with constant velocity `a`. + """ + struct LinearScalarAdvectionEquation1D{RealT <: Real} <: + AbstractLinearScalarAdvectionEquation{1, 1} + advection_velocity::SVector{1, RealT} + end + + function LinearScalarAdvectionEquation1D(a::Real) + LinearScalarAdvectionEquation1D(SVector(a)) + end + + varnames(::typeof(cons2cons), ::LinearScalarAdvectionEquation1D) = ("scalar",) + varnames(::typeof(cons2prim), ::LinearScalarAdvectionEquation1D) = ("scalar",) + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::LinearScalarAdvectionEquation1D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equation::LinearScalarAdvectionEquation1D) + # Store translated coordinate for easy use of exact solution + RealT = eltype(x) + x_trans = x - equation.advection_velocity * t + + return SVector(RealT(2)) + end + + """ + initial_condition_convergence_test(x, t, equations::LinearScalarAdvectionEquation1D) + + A smooth initial condition used for convergence tests + (in combination with [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) + in non-periodic domains). + """ + function initial_condition_convergence_test( + x, t, + equation::LinearScalarAdvectionEquation1D + ) + # Store translated coordinate for easy use of exact solution + RealT = eltype(x) + x_trans = x - equation.advection_velocity * t + + c = 1 + A = 0.5f0 + L = 2 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + scalar = c + A * sin(omega * sum(x_trans)) + return SVector(scalar) + end + + """ + initial_condition_gauss(x, t, equations::LinearScalarAdvectionEquation1D) + + A Gaussian pulse used together with + [`BoundaryConditionDirichlet(initial_condition_gauss)`](@ref). + """ + function initial_condition_gauss(x, t, equation::LinearScalarAdvectionEquation1D) + # Store translated coordinate for easy use of exact solution + x_trans = x - equation.advection_velocity * t + + scalar = exp(-(x_trans[1]^2)) + return SVector(scalar) + end + + """ + initial_condition_sin(x, t, equations::LinearScalarAdvectionEquation1D) + + A sine wave in the conserved variable. + """ + function initial_condition_sin(x, t, equation::LinearScalarAdvectionEquation1D) + # Store translated coordinate for easy use of exact solution + x_trans = x - equation.advection_velocity * t + + scalar = sinpi(2 * x_trans[1]) + return SVector(scalar) + end + + """ + initial_condition_linear_x(x, t, equations::LinearScalarAdvectionEquation1D) + + A linear function of `x[1]` used together with + [`boundary_condition_linear_x`](@ref). + """ + function initial_condition_linear_x(x, t, equation::LinearScalarAdvectionEquation1D) + # Store translated coordinate for easy use of exact solution + x_trans = x - equation.advection_velocity * t + + return SVector(x_trans[1]) + end + + """ + boundary_condition_linear_x(u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation1D) + + Boundary conditions for + [`initial_condition_linear_x`](@ref). + """ + function boundary_condition_linear_x( + u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation1D + ) + u_boundary = initial_condition_linear_x(x, t, equation) + + # Calculate boundary flux + if direction == 2 # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equation) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equation) + end + + return flux + end + + # Pre-defined source terms should be implemented as + # function source_terms_WHATEVER(u, x, t, equations::LinearScalarAdvectionEquation1D) + + # Calculate 1D flux in for a single point + @inline function flux( + u, orientation::Integer, + equation::LinearScalarAdvectionEquation1D + ) + a = equation.advection_velocity[orientation] + return a * u + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Int, + equation::LinearScalarAdvectionEquation1D + ) + λ_max = abs(equation.advection_velocity[orientation]) + end + + # Essentially first order upwind, see e.g. + # https://math.stackexchange.com/a/4355076/805029 + function flux_godunov( + u_ll, u_rr, orientation::Int, + equation::LinearScalarAdvectionEquation1D + ) + u_L = u_ll[1] + u_R = u_rr[1] + + v_normal = equation.advection_velocity[orientation] + if v_normal >= 0 + return SVector(v_normal * u_L) + else + return SVector(v_normal * u_R) + end + end + + # See https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf , + # section 4.2.5 and especially equation (4.33). + function flux_engquist_osher( + u_ll, u_rr, orientation::Int, + equation::LinearScalarAdvectionEquation1D + ) + u_L = u_ll[1] + u_R = u_rr[1] + + return SVector( + 0.5f0 * ( + flux(u_L, orientation, equation) + flux(u_R, orientation, equation) - - abs(equation.advection_velocity[orientation]) * (u_R - u_L))) -end - -@inline have_constant_speed(::LinearScalarAdvectionEquation1D) = True() - -@inline function max_abs_speeds(equation::LinearScalarAdvectionEquation1D) - return abs.(equation.advection_velocity) -end - -""" - splitting_lax_friedrichs(u, orientation::Integer, - equations::LinearScalarAdvectionEquation1D) - splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} - orientation::Integer, - equations::LinearScalarAdvectionEquation1D) - -Naive local Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ u)` -and `f⁻ = 0.5 (f - λ u)` where `λ` is the absolute value of the advection -velocity. - -Returns a tuple of the fluxes "minus" (associated with waves going into the -negative axis direction) and "plus" (associated with waves going into the -positive axis direction). If only one of the fluxes is required, use the -function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. -""" -@inline function splitting_lax_friedrichs(u, orientation::Integer, - equations::LinearScalarAdvectionEquation1D) - fm = splitting_lax_friedrichs(u, Val{:minus}(), orientation, equations) - fp = splitting_lax_friedrichs(u, Val{:plus}(), orientation, equations) - return fm, fp -end - -@inline function splitting_lax_friedrichs(u, ::Val{:plus}, orientation::Integer, - equations::LinearScalarAdvectionEquation1D) - RealT = eltype(u) - a = equations.advection_velocity[1] - return a > 0 ? flux(u, orientation, equations) : SVector(zero(RealT)) -end - -@inline function splitting_lax_friedrichs(u, ::Val{:minus}, orientation::Integer, - equations::LinearScalarAdvectionEquation1D) - RealT = eltype(u) - a = equations.advection_velocity[1] - return a < 0 ? flux(u, orientation, equations) : SVector(zero(RealT)) -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equation::LinearScalarAdvectionEquation1D) = u - -# Convert conservative variables to entropy variables -@inline cons2entropy(u, equation::LinearScalarAdvectionEquation1D) = u - -# Calculate entropy for a conservative state `cons` -@inline entropy(u::Real, ::LinearScalarAdvectionEquation1D) = 0.5f0 * u^2 -@inline entropy(u, equation::LinearScalarAdvectionEquation1D) = entropy(u[1], equation) - -# Calculate total energy for a conservative state `cons` -@inline energy_total(u::Real, ::LinearScalarAdvectionEquation1D) = 0.5f0 * u^2 -@inline function energy_total(u, equation::LinearScalarAdvectionEquation1D) - energy_total(u[1], equation) -end + abs(equation.advection_velocity[orientation]) * (u_R - u_L) + ) + ) + end + + @inline have_constant_speed(::LinearScalarAdvectionEquation1D) = True() + + @inline function max_abs_speeds(equation::LinearScalarAdvectionEquation1D) + return abs.(equation.advection_velocity) + end + + """ + splitting_lax_friedrichs(u, orientation::Integer, + equations::LinearScalarAdvectionEquation1D) + splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, + equations::LinearScalarAdvectionEquation1D) + + Naive local Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ u)` + and `f⁻ = 0.5 (f - λ u)` where `λ` is the absolute value of the advection + velocity. + + Returns a tuple of the fluxes "minus" (associated with waves going into the + negative axis direction) and "plus" (associated with waves going into the + positive axis direction). If only one of the fluxes is required, use the + function signature with argument `which` set to `Val{:minus}()` or `Val{:plus}()`. + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + """ + @inline function splitting_lax_friedrichs( + u, orientation::Integer, + equations::LinearScalarAdvectionEquation1D + ) + fm = splitting_lax_friedrichs(u, Val{:minus}(), orientation, equations) + fp = splitting_lax_friedrichs(u, Val{:plus}(), orientation, equations) + return fm, fp + end + + @inline function splitting_lax_friedrichs( + u, ::Val{:plus}, orientation::Integer, + equations::LinearScalarAdvectionEquation1D + ) + RealT = eltype(u) + a = equations.advection_velocity[1] + return a > 0 ? flux(u, orientation, equations) : SVector(zero(RealT)) + end + + @inline function splitting_lax_friedrichs( + u, ::Val{:minus}, orientation::Integer, + equations::LinearScalarAdvectionEquation1D + ) + RealT = eltype(u) + a = equations.advection_velocity[1] + return a < 0 ? flux(u, orientation, equations) : SVector(zero(RealT)) + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equation::LinearScalarAdvectionEquation1D) = u + + # Convert conservative variables to entropy variables + @inline cons2entropy(u, equation::LinearScalarAdvectionEquation1D) = u + + # Calculate entropy for a conservative state `cons` + @inline entropy(u::Real, ::LinearScalarAdvectionEquation1D) = 0.5f0 * u^2 + @inline entropy(u, equation::LinearScalarAdvectionEquation1D) = entropy(u[1], equation) + + # Calculate total energy for a conservative state `cons` + @inline energy_total(u::Real, ::LinearScalarAdvectionEquation1D) = 0.5f0 * u^2 + @inline function energy_total(u, equation::LinearScalarAdvectionEquation1D) + energy_total(u[1], equation) + end end # @muladd diff --git a/src/equations/linear_scalar_advection_2d.jl b/src/equations/linear_scalar_advection_2d.jl index 5e4f8463f5..8caea14ed0 100644 --- a/src/equations/linear_scalar_advection_2d.jl +++ b/src/equations/linear_scalar_advection_2d.jl @@ -3,291 +3,313 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - LinearScalarAdvectionEquation2D - -The linear scalar advection equation -```math -\partial_t u + a_1 \partial_1 u + a_2 \partial_2 u = 0 -``` -in two space dimensions with constant velocity `a`. -""" -struct LinearScalarAdvectionEquation2D{RealT <: Real} <: - AbstractLinearScalarAdvectionEquation{2, 1} - advection_velocity::SVector{2, RealT} -end - -function LinearScalarAdvectionEquation2D(a::NTuple{2, <:Real}) - LinearScalarAdvectionEquation2D(SVector(a)) -end - -function LinearScalarAdvectionEquation2D(a1::Real, a2::Real) - LinearScalarAdvectionEquation2D(SVector(a1, a2)) -end - -varnames(::typeof(cons2cons), ::LinearScalarAdvectionEquation2D) = ("scalar",) -varnames(::typeof(cons2prim), ::LinearScalarAdvectionEquation2D) = ("scalar",) - -# Calculates translated coordinates `x` for a periodic domain -function x_trans_periodic_2d(x, domain_length = SVector(10, 10), center = SVector(0, 0)) - x_normalized = x .- center - x_shifted = x_normalized .% domain_length - x_offset = ((x_shifted .< -0.5f0 * domain_length) - - (x_shifted .> 0.5f0 * domain_length)) .* domain_length - return center + x_shifted + x_offset -end - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::LinearScalarAdvectionEquation2D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equation::LinearScalarAdvectionEquation2D) - # Store translated coordinate for easy use of exact solution - RealT = eltype(x) - x_trans = x_trans_periodic_2d(x - equation.advection_velocity * t) - - return SVector(RealT(2)) -end - -""" - initial_condition_convergence_test(x, t, equations::LinearScalarAdvectionEquation2D) - -A smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, - equation::LinearScalarAdvectionEquation2D) - # Store translated coordinate for easy use of exact solution - RealT = eltype(x) - x_trans = x - equation.advection_velocity * t - - c = 1 - A = 0.5f0 - L = 2 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - scalar = c + A * sin(omega * sum(x_trans)) - return SVector(scalar) -end - -""" - initial_condition_gauss(x, t, equation::LinearScalarAdvectionEquation2D) - -A Gaussian pulse used together with -[`BoundaryConditionDirichlet(initial_condition_gauss)`](@ref). -""" -function initial_condition_gauss(x, t, equation::LinearScalarAdvectionEquation2D) - # Store translated coordinate for easy use of exact solution - x_trans = x_trans_periodic_2d(x - equation.advection_velocity * t) - - scalar = exp(-(x_trans[1]^2 + x_trans[2]^2)) - return SVector(scalar) -end - -""" - initial_condition_sin_sin(x, t, equations::LinearScalarAdvectionEquation2D) - -A sine wave in the conserved variable. -""" -function initial_condition_sin_sin(x, t, equation::LinearScalarAdvectionEquation2D) - # Store translated coordinate for easy use of exact solution - x_trans = x - equation.advection_velocity * t - - scalar = sinpi(2 * x_trans[1]) * sinpi(2 * x_trans[2]) - return SVector(scalar) -end - -""" - initial_condition_linear_x_y(x, t, equations::LinearScalarAdvectionEquation2D) - -A linear function of `x[1] + x[2]` used together with -[`boundary_condition_linear_x_y`](@ref). -""" -function initial_condition_linear_x_y(x, t, equation::LinearScalarAdvectionEquation2D) - # Store translated coordinate for easy use of exact solution - x_trans = x - equation.advection_velocity * t - - return SVector(sum(x_trans)) -end - -""" - boundary_condition_linear_x_y(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation2D) - -Boundary conditions for -[`initial_condition_linear_x_y`](@ref). -""" -function boundary_condition_linear_x_y(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation2D) - u_boundary = initial_condition_linear_x_y(x, t, equation) - - # Calculate boundary flux - if direction in (2, 4) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equation) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equation) + #! format: noindent + + @doc raw""" + LinearScalarAdvectionEquation2D + + The linear scalar advection equation + ```math + \partial_t u + a_1 \partial_1 u + a_2 \partial_2 u = 0 + ``` + in two space dimensions with constant velocity `a`. + """ + struct LinearScalarAdvectionEquation2D{RealT <: Real} <: + AbstractLinearScalarAdvectionEquation{2, 1} + advection_velocity::SVector{2, RealT} end - return flux -end - -""" - initial_condition_linear_x(x, t, equations::LinearScalarAdvectionEquation2D) - -A linear function of `x[1]` used together with -[`boundary_condition_linear_x`](@ref). -""" -function initial_condition_linear_x(x, t, equation::LinearScalarAdvectionEquation2D) - # Store translated coordinate for easy use of exact solution - x_trans = x - equation.advection_velocity * t - - return SVector(x_trans[1]) -end - -""" - boundary_condition_linear_x(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation2D) - -Boundary conditions for -[`initial_condition_linear_x`](@ref). -""" -function boundary_condition_linear_x(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation2D) - u_boundary = initial_condition_linear_x(x, t, equation) - - # Calculate boundary flux - if direction in (2, 4) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equation) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equation) + function LinearScalarAdvectionEquation2D(a::NTuple{2, <:Real}) + LinearScalarAdvectionEquation2D(SVector(a)) end - return flux -end - -""" - initial_condition_linear_y(x, t, equations::LinearScalarAdvectionEquation2D) - -A linear function of `x[1]` used together with -[`boundary_condition_linear_y`](@ref). -""" -function initial_condition_linear_y(x, t, equation::LinearScalarAdvectionEquation2D) - # Store translated coordinate for easy use of exact solution - x_trans = x - equation.advection_velocity * t - - return SVector(x_trans[2]) -end - -""" - boundary_condition_linear_y(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation2D) - -Boundary conditions for -[`initial_condition_linear_y`](@ref). -""" -function boundary_condition_linear_y(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation2D) - u_boundary = initial_condition_linear_y(x, t, equation) - - # Calculate boundary flux - if direction in (2, 4) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equation) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equation) + function LinearScalarAdvectionEquation2D(a1::Real, a2::Real) + LinearScalarAdvectionEquation2D(SVector(a1, a2)) end - return flux -end - -# Pre-defined source terms should be implemented as -# function source_terms_WHATEVER(u, x, t, equations::LinearScalarAdvectionEquation2D) - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, - equation::LinearScalarAdvectionEquation2D) - a = equation.advection_velocity[orientation] - return a * u -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equation::LinearScalarAdvectionEquation2D) - λ_max = abs(equation.advection_velocity[orientation]) -end - -# Calculate 1D flux for a single point in the normal direction -# Note, this directional vector is not normalized -@inline function flux(u, normal_direction::AbstractVector, - equation::LinearScalarAdvectionEquation2D) - a = dot(equation.advection_velocity, normal_direction) # velocity in normal direction - return a * u -end - -# Calculate maximum wave speed in the normal direction for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equation::LinearScalarAdvectionEquation2D) - a = dot(equation.advection_velocity, normal_direction) # velocity in normal direction - return abs(a) -end - -# Essentially first order upwind, see e.g. -# https://math.stackexchange.com/a/4355076/805029 -function flux_godunov(u_ll, u_rr, orientation::Integer, - equation::LinearScalarAdvectionEquation2D) - u_L = u_ll[1] - u_R = u_rr[1] - - v_normal = equation.advection_velocity[orientation] - if v_normal >= 0 - return SVector(v_normal * u_L) - else - return SVector(v_normal * u_R) + varnames(::typeof(cons2cons), ::LinearScalarAdvectionEquation2D) = ("scalar",) + varnames(::typeof(cons2prim), ::LinearScalarAdvectionEquation2D) = ("scalar",) + + # Calculates translated coordinates `x` for a periodic domain + function x_trans_periodic_2d(x, domain_length = SVector(10, 10), center = SVector(0, 0)) + x_normalized = x .- center + x_shifted = x_normalized .% domain_length + x_offset = ( + (x_shifted .< -0.5f0 * domain_length) - + (x_shifted .> 0.5f0 * domain_length) + ) .* domain_length + return center + x_shifted + x_offset end -end - -# Essentially first order upwind, see e.g. -# https://math.stackexchange.com/a/4355076/805029 -function flux_godunov(u_ll, u_rr, normal_direction::AbstractVector, - equation::LinearScalarAdvectionEquation2D) - u_L = u_ll[1] - u_R = u_rr[1] - - a_normal = dot(equation.advection_velocity, normal_direction) - if a_normal >= 0 - return SVector(a_normal * u_L) - else - return SVector(a_normal * u_R) + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::LinearScalarAdvectionEquation2D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equation::LinearScalarAdvectionEquation2D) + # Store translated coordinate for easy use of exact solution + RealT = eltype(x) + x_trans = x_trans_periodic_2d(x - equation.advection_velocity * t) + + return SVector(RealT(2)) + end + + """ + initial_condition_convergence_test(x, t, equations::LinearScalarAdvectionEquation2D) + + A smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test( + x, t, + equation::LinearScalarAdvectionEquation2D + ) + # Store translated coordinate for easy use of exact solution + RealT = eltype(x) + x_trans = x - equation.advection_velocity * t + + c = 1 + A = 0.5f0 + L = 2 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + scalar = c + A * sin(omega * sum(x_trans)) + return SVector(scalar) + end + + """ + initial_condition_gauss(x, t, equation::LinearScalarAdvectionEquation2D) + + A Gaussian pulse used together with + [`BoundaryConditionDirichlet(initial_condition_gauss)`](@ref). + """ + function initial_condition_gauss(x, t, equation::LinearScalarAdvectionEquation2D) + # Store translated coordinate for easy use of exact solution + x_trans = x_trans_periodic_2d(x - equation.advection_velocity * t) + + scalar = exp(-(x_trans[1]^2 + x_trans[2]^2)) + return SVector(scalar) + end + + """ + initial_condition_sin_sin(x, t, equations::LinearScalarAdvectionEquation2D) + + A sine wave in the conserved variable. + """ + function initial_condition_sin_sin(x, t, equation::LinearScalarAdvectionEquation2D) + # Store translated coordinate for easy use of exact solution + x_trans = x - equation.advection_velocity * t + + scalar = sinpi(2 * x_trans[1]) * sinpi(2 * x_trans[2]) + return SVector(scalar) + end + + """ + initial_condition_linear_x_y(x, t, equations::LinearScalarAdvectionEquation2D) + + A linear function of `x[1] + x[2]` used together with + [`boundary_condition_linear_x_y`](@ref). + """ + function initial_condition_linear_x_y(x, t, equation::LinearScalarAdvectionEquation2D) + # Store translated coordinate for easy use of exact solution + x_trans = x - equation.advection_velocity * t + + return SVector(sum(x_trans)) + end + + """ + boundary_condition_linear_x_y(u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation2D) + + Boundary conditions for + [`initial_condition_linear_x_y`](@ref). + """ + function boundary_condition_linear_x_y( + u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation2D + ) + u_boundary = initial_condition_linear_x_y(x, t, equation) + + # Calculate boundary flux + if direction in (2, 4) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equation) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equation) + end + + return flux + end + + """ + initial_condition_linear_x(x, t, equations::LinearScalarAdvectionEquation2D) + + A linear function of `x[1]` used together with + [`boundary_condition_linear_x`](@ref). + """ + function initial_condition_linear_x(x, t, equation::LinearScalarAdvectionEquation2D) + # Store translated coordinate for easy use of exact solution + x_trans = x - equation.advection_velocity * t + + return SVector(x_trans[1]) end -end -@inline have_constant_speed(::LinearScalarAdvectionEquation2D) = True() + """ + boundary_condition_linear_x(u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation2D) + + Boundary conditions for + [`initial_condition_linear_x`](@ref). + """ + function boundary_condition_linear_x( + u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation2D + ) + u_boundary = initial_condition_linear_x(x, t, equation) + + # Calculate boundary flux + if direction in (2, 4) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equation) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equation) + end + + return flux + end + + """ + initial_condition_linear_y(x, t, equations::LinearScalarAdvectionEquation2D) + + A linear function of `x[1]` used together with + [`boundary_condition_linear_y`](@ref). + """ + function initial_condition_linear_y(x, t, equation::LinearScalarAdvectionEquation2D) + # Store translated coordinate for easy use of exact solution + x_trans = x - equation.advection_velocity * t + + return SVector(x_trans[2]) + end + + """ + boundary_condition_linear_y(u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation2D) + + Boundary conditions for + [`initial_condition_linear_y`](@ref). + """ + function boundary_condition_linear_y( + u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation2D + ) + u_boundary = initial_condition_linear_y(x, t, equation) + + # Calculate boundary flux + if direction in (2, 4) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equation) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equation) + end + + return flux + end + + # Pre-defined source terms should be implemented as + # function source_terms_WHATEVER(u, x, t, equations::LinearScalarAdvectionEquation2D) -@inline function max_abs_speeds(equation::LinearScalarAdvectionEquation2D) - return abs.(equation.advection_velocity) -end + # Calculate 1D flux for a single point + @inline function flux( + u, orientation::Integer, + equation::LinearScalarAdvectionEquation2D + ) + a = equation.advection_velocity[orientation] + return a * u + end -# Convert conservative variables to primitive -@inline cons2prim(u, equation::LinearScalarAdvectionEquation2D) = u + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equation::LinearScalarAdvectionEquation2D + ) + λ_max = abs(equation.advection_velocity[orientation]) + end -# Convert conservative variables to entropy variables -@inline cons2entropy(u, equation::LinearScalarAdvectionEquation2D) = u + # Calculate 1D flux for a single point in the normal direction + # Note, this directional vector is not normalized + @inline function flux( + u, normal_direction::AbstractVector, + equation::LinearScalarAdvectionEquation2D + ) + a = dot(equation.advection_velocity, normal_direction) # velocity in normal direction + return a * u + end -# Calculate entropy for a conservative state `cons` -@inline entropy(u::Real, ::LinearScalarAdvectionEquation2D) = 0.5f0 * u^2 -@inline entropy(u, equation::LinearScalarAdvectionEquation2D) = entropy(u[1], equation) + # Calculate maximum wave speed in the normal direction for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equation::LinearScalarAdvectionEquation2D + ) + a = dot(equation.advection_velocity, normal_direction) # velocity in normal direction + return abs(a) + end -# Calculate total energy for a conservative state `cons` -@inline energy_total(u::Real, ::LinearScalarAdvectionEquation2D) = 0.5f0 * u^2 -@inline function energy_total(u, equation::LinearScalarAdvectionEquation2D) - energy_total(u[1], equation) -end + # Essentially first order upwind, see e.g. + # https://math.stackexchange.com/a/4355076/805029 + function flux_godunov( + u_ll, u_rr, orientation::Integer, + equation::LinearScalarAdvectionEquation2D + ) + u_L = u_ll[1] + u_R = u_rr[1] + + v_normal = equation.advection_velocity[orientation] + if v_normal >= 0 + return SVector(v_normal * u_L) + else + return SVector(v_normal * u_R) + end + end + + # Essentially first order upwind, see e.g. + # https://math.stackexchange.com/a/4355076/805029 + function flux_godunov( + u_ll, u_rr, normal_direction::AbstractVector, + equation::LinearScalarAdvectionEquation2D + ) + u_L = u_ll[1] + u_R = u_rr[1] + + a_normal = dot(equation.advection_velocity, normal_direction) + if a_normal >= 0 + return SVector(a_normal * u_L) + else + return SVector(a_normal * u_R) + end + end + + @inline have_constant_speed(::LinearScalarAdvectionEquation2D) = True() + + @inline function max_abs_speeds(equation::LinearScalarAdvectionEquation2D) + return abs.(equation.advection_velocity) + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equation::LinearScalarAdvectionEquation2D) = u + + # Convert conservative variables to entropy variables + @inline cons2entropy(u, equation::LinearScalarAdvectionEquation2D) = u + + # Calculate entropy for a conservative state `cons` + @inline entropy(u::Real, ::LinearScalarAdvectionEquation2D) = 0.5f0 * u^2 + @inline entropy(u, equation::LinearScalarAdvectionEquation2D) = entropy(u[1], equation) + + # Calculate total energy for a conservative state `cons` + @inline energy_total(u::Real, ::LinearScalarAdvectionEquation2D) = 0.5f0 * u^2 + @inline function energy_total(u, equation::LinearScalarAdvectionEquation2D) + energy_total(u[1], equation) + end end # @muladd diff --git a/src/equations/linear_scalar_advection_3d.jl b/src/equations/linear_scalar_advection_3d.jl index 088f934cc3..dd652f64bd 100644 --- a/src/equations/linear_scalar_advection_3d.jl +++ b/src/equations/linear_scalar_advection_3d.jl @@ -3,210 +3,226 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - LinearScalarAdvectionEquation3D - -The linear scalar advection equation -```math -\partial_t u + a_1 \partial_1 u + a_2 \partial_2 u + a_3 \partial_3 u = 0 -``` -in three space dimensions with constant velocity `a`. -""" -struct LinearScalarAdvectionEquation3D{RealT <: Real} <: - AbstractLinearScalarAdvectionEquation{3, 1} - advection_velocity::SVector{3, RealT} -end - -function LinearScalarAdvectionEquation3D(a::NTuple{3, <:Real}) - LinearScalarAdvectionEquation3D(SVector(a)) -end - -function LinearScalarAdvectionEquation3D(a1::Real, a2::Real, a3::Real) - LinearScalarAdvectionEquation3D(SVector(a1, a2, a3)) -end - -varnames(::typeof(cons2cons), ::LinearScalarAdvectionEquation3D) = ("scalar",) -varnames(::typeof(cons2prim), ::LinearScalarAdvectionEquation3D) = ("scalar",) - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_constant(x, t, equations::LinearScalarAdvectionEquation1D) - -A constant initial condition to test free-stream preservation. -""" -function initial_condition_constant(x, t, equation::LinearScalarAdvectionEquation3D) - # Store translated coordinate for easy use of exact solution - RealT = eltype(x) - x_trans = x - equation.advection_velocity * t - - return SVector(RealT(2)) -end - -""" - initial_condition_convergence_test(x, t, equations::LinearScalarAdvectionEquation1D) - -A smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, - equation::LinearScalarAdvectionEquation3D) - # Store translated coordinate for easy use of exact solution - RealT = eltype(x) - x_trans = x - equation.advection_velocity * t - - c = 1 - A = 0.5f0 - L = 2 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - scalar = c + A * sin(omega * sum(x_trans)) - return SVector(scalar) -end - -""" - initial_condition_gauss(x, t, equations::LinearScalarAdvectionEquation1D) - -A Gaussian pulse. -""" -function initial_condition_gauss(x, t, equation::LinearScalarAdvectionEquation3D) - # Store translated coordinate for easy use of exact solution - x_trans = x - equation.advection_velocity * t - - scalar = exp(-(x_trans[1]^2 + x_trans[2]^2 + x_trans[3]^2)) - return SVector(scalar) -end - -""" - initial_condition_sin(x, t, equations::LinearScalarAdvectionEquation1D) - -A sine wave in the conserved variable. -""" -function initial_condition_sin(x, t, equation::LinearScalarAdvectionEquation3D) - # Store translated coordinate for easy use of exact solution - RealT = eltype(x) - x_trans = x - equation.advection_velocity * t - - scalar = sinpi(2 * x_trans[1]) * sinpi(2 * x_trans[2]) * sinpi(2 * x_trans[3]) - return SVector(scalar) -end - -""" - initial_condition_linear_z(x, t, equations::LinearScalarAdvectionEquation1D) - -A linear function of `x[3]` used together with -[`boundary_condition_linear_z`](@ref). -""" -function initial_condition_linear_z(x, t, equation::LinearScalarAdvectionEquation3D) - # Store translated coordinate for easy use of exact solution - x_trans = x - equation.advection_velocity * t - - return SVector(x_trans[3]) -end - -""" - boundary_condition_linear_z(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation1D) - -Boundary conditions for -[`boundary_condition_linear_z`](@ref). -""" -function boundary_condition_linear_z(u_inner, orientation, direction, x, t, - surface_flux_function, - equation::LinearScalarAdvectionEquation3D) - u_boundary = initial_condition_linear_z(x, t, equation) - - # Calculate boundary flux - if direction in (2, 4, 6) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equation) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equation) - end - - return flux -end - -# Pre-defined source terms should be implemented as -# function source_terms_WHATEVER(u, x, t, equation::LinearScalarAdvectionEquation3D) - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, - equation::LinearScalarAdvectionEquation3D) - a = equation.advection_velocity[orientation] - return a * u -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equation::LinearScalarAdvectionEquation3D) - λ_max = abs(equation.advection_velocity[orientation]) -end - -# Calculate 1D flux for a single point in the normal direction -# Note, this directional vector is not normalized -@inline function flux(u, normal_direction::AbstractVector, - equation::LinearScalarAdvectionEquation3D) - a = dot(equation.advection_velocity, normal_direction) # velocity in normal direction - return a * u -end - -# Calculate maximum wave speed in the normal direction for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equation::LinearScalarAdvectionEquation3D) - a = dot(equation.advection_velocity, normal_direction) # velocity in normal direction - return abs(a) -end - -# Essentially first order upwind, see e.g. -# https://math.stackexchange.com/a/4355076/805029 -function flux_godunov(u_ll, u_rr, orientation::Integer, - equation::LinearScalarAdvectionEquation3D) - u_L = u_ll[1] - u_R = u_rr[1] - - v_normal = equation.advection_velocity[orientation] - if v_normal >= 0 - return SVector(v_normal * u_L) - else - return SVector(v_normal * u_R) - end -end - -# Essentially first order upwind, see e.g. -# https://math.stackexchange.com/a/4355076/805029 -function flux_godunov(u_ll, u_rr, normal_direction::AbstractVector, - equation::LinearScalarAdvectionEquation3D) - u_L = u_ll[1] - u_R = u_rr[1] - - a_normal = dot(equation.advection_velocity, normal_direction) - if a_normal >= 0 - return SVector(a_normal * u_L) - else - return SVector(a_normal * u_R) - end -end - -@inline have_constant_speed(::LinearScalarAdvectionEquation3D) = True() - -@inline function max_abs_speeds(equation::LinearScalarAdvectionEquation3D) - return abs.(equation.advection_velocity) -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equation::LinearScalarAdvectionEquation3D) = u - -# Convert conservative variables to entropy variables -@inline cons2entropy(u, equation::LinearScalarAdvectionEquation3D) = u - -# Calculate entropy for a conservative state `cons` -@inline entropy(u::Real, ::LinearScalarAdvectionEquation3D) = 0.5f0 * u^2 -@inline entropy(u, equation::LinearScalarAdvectionEquation3D) = entropy(u[1], equation) - -# Calculate total energy for a conservative state `cons` -@inline energy_total(u::Real, ::LinearScalarAdvectionEquation3D) = 0.5f0 * u^2 -@inline function energy_total(u, equation::LinearScalarAdvectionEquation3D) - energy_total(u[1], equation) -end + #! format: noindent + + @doc raw""" + LinearScalarAdvectionEquation3D + + The linear scalar advection equation + ```math + \partial_t u + a_1 \partial_1 u + a_2 \partial_2 u + a_3 \partial_3 u = 0 + ``` + in three space dimensions with constant velocity `a`. + """ + struct LinearScalarAdvectionEquation3D{RealT <: Real} <: + AbstractLinearScalarAdvectionEquation{3, 1} + advection_velocity::SVector{3, RealT} + end + + function LinearScalarAdvectionEquation3D(a::NTuple{3, <:Real}) + LinearScalarAdvectionEquation3D(SVector(a)) + end + + function LinearScalarAdvectionEquation3D(a1::Real, a2::Real, a3::Real) + LinearScalarAdvectionEquation3D(SVector(a1, a2, a3)) + end + + varnames(::typeof(cons2cons), ::LinearScalarAdvectionEquation3D) = ("scalar",) + varnames(::typeof(cons2prim), ::LinearScalarAdvectionEquation3D) = ("scalar",) + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_constant(x, t, equations::LinearScalarAdvectionEquation1D) + + A constant initial condition to test free-stream preservation. + """ + function initial_condition_constant(x, t, equation::LinearScalarAdvectionEquation3D) + # Store translated coordinate for easy use of exact solution + RealT = eltype(x) + x_trans = x - equation.advection_velocity * t + + return SVector(RealT(2)) + end + + """ + initial_condition_convergence_test(x, t, equations::LinearScalarAdvectionEquation1D) + + A smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test( + x, t, + equation::LinearScalarAdvectionEquation3D + ) + # Store translated coordinate for easy use of exact solution + RealT = eltype(x) + x_trans = x - equation.advection_velocity * t + + c = 1 + A = 0.5f0 + L = 2 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + scalar = c + A * sin(omega * sum(x_trans)) + return SVector(scalar) + end + + """ + initial_condition_gauss(x, t, equations::LinearScalarAdvectionEquation1D) + + A Gaussian pulse. + """ + function initial_condition_gauss(x, t, equation::LinearScalarAdvectionEquation3D) + # Store translated coordinate for easy use of exact solution + x_trans = x - equation.advection_velocity * t + + scalar = exp(-(x_trans[1]^2 + x_trans[2]^2 + x_trans[3]^2)) + return SVector(scalar) + end + + """ + initial_condition_sin(x, t, equations::LinearScalarAdvectionEquation1D) + + A sine wave in the conserved variable. + """ + function initial_condition_sin(x, t, equation::LinearScalarAdvectionEquation3D) + # Store translated coordinate for easy use of exact solution + RealT = eltype(x) + x_trans = x - equation.advection_velocity * t + + scalar = sinpi(2 * x_trans[1]) * sinpi(2 * x_trans[2]) * sinpi(2 * x_trans[3]) + return SVector(scalar) + end + + """ + initial_condition_linear_z(x, t, equations::LinearScalarAdvectionEquation1D) + + A linear function of `x[3]` used together with + [`boundary_condition_linear_z`](@ref). + """ + function initial_condition_linear_z(x, t, equation::LinearScalarAdvectionEquation3D) + # Store translated coordinate for easy use of exact solution + x_trans = x - equation.advection_velocity * t + + return SVector(x_trans[3]) + end + + """ + boundary_condition_linear_z(u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation1D) + + Boundary conditions for + [`boundary_condition_linear_z`](@ref). + """ + function boundary_condition_linear_z( + u_inner, orientation, direction, x, t, + surface_flux_function, + equation::LinearScalarAdvectionEquation3D + ) + u_boundary = initial_condition_linear_z(x, t, equation) + + # Calculate boundary flux + if direction in (2, 4, 6) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equation) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equation) + end + + return flux + end + + # Pre-defined source terms should be implemented as + # function source_terms_WHATEVER(u, x, t, equation::LinearScalarAdvectionEquation3D) + + # Calculate 1D flux in for a single point + @inline function flux( + u, orientation::Integer, + equation::LinearScalarAdvectionEquation3D + ) + a = equation.advection_velocity[orientation] + return a * u + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equation::LinearScalarAdvectionEquation3D + ) + λ_max = abs(equation.advection_velocity[orientation]) + end + + # Calculate 1D flux for a single point in the normal direction + # Note, this directional vector is not normalized + @inline function flux( + u, normal_direction::AbstractVector, + equation::LinearScalarAdvectionEquation3D + ) + a = dot(equation.advection_velocity, normal_direction) # velocity in normal direction + return a * u + end + + # Calculate maximum wave speed in the normal direction for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equation::LinearScalarAdvectionEquation3D + ) + a = dot(equation.advection_velocity, normal_direction) # velocity in normal direction + return abs(a) + end + + # Essentially first order upwind, see e.g. + # https://math.stackexchange.com/a/4355076/805029 + function flux_godunov( + u_ll, u_rr, orientation::Integer, + equation::LinearScalarAdvectionEquation3D + ) + u_L = u_ll[1] + u_R = u_rr[1] + + v_normal = equation.advection_velocity[orientation] + if v_normal >= 0 + return SVector(v_normal * u_L) + else + return SVector(v_normal * u_R) + end + end + + # Essentially first order upwind, see e.g. + # https://math.stackexchange.com/a/4355076/805029 + function flux_godunov( + u_ll, u_rr, normal_direction::AbstractVector, + equation::LinearScalarAdvectionEquation3D + ) + u_L = u_ll[1] + u_R = u_rr[1] + + a_normal = dot(equation.advection_velocity, normal_direction) + if a_normal >= 0 + return SVector(a_normal * u_L) + else + return SVector(a_normal * u_R) + end + end + + @inline have_constant_speed(::LinearScalarAdvectionEquation3D) = True() + + @inline function max_abs_speeds(equation::LinearScalarAdvectionEquation3D) + return abs.(equation.advection_velocity) + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equation::LinearScalarAdvectionEquation3D) = u + + # Convert conservative variables to entropy variables + @inline cons2entropy(u, equation::LinearScalarAdvectionEquation3D) = u + + # Calculate entropy for a conservative state `cons` + @inline entropy(u::Real, ::LinearScalarAdvectionEquation3D) = 0.5f0 * u^2 + @inline entropy(u, equation::LinearScalarAdvectionEquation3D) = entropy(u[1], equation) + + # Calculate total energy for a conservative state `cons` + @inline energy_total(u::Real, ::LinearScalarAdvectionEquation3D) = 0.5f0 * u^2 + @inline function energy_total(u, equation::LinearScalarAdvectionEquation3D) + energy_total(u[1], equation) + end end # @muladd diff --git a/src/equations/linearized_euler_1d.jl b/src/equations/linearized_euler_1d.jl index 19a3bdcb3b..d8153d746a 100644 --- a/src/equations/linearized_euler_1d.jl +++ b/src/equations/linearized_euler_1d.jl @@ -3,142 +3,158 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - LinearizedEulerEquations1D(v_mean_global, c_mean_global, rho_mean_global) - -Linearized Euler equations in one space dimension. The equations are given by -```math -\partial_t -\begin{pmatrix} - \rho' \\ v_1' \\ p' -\end{pmatrix} -+ -\partial_x -\begin{pmatrix} - \bar{\rho} v_1' + \bar{v_1} \rho ' \\ \bar{v_1} v_1' + \frac{p'}{\bar{\rho}} \\ \bar{v_1} p' + c^2 \bar{\rho} v_1' -\end{pmatrix} -= -\begin{pmatrix} - 0 \\ 0 \\ 0 -\end{pmatrix} -``` -The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and ``c`` is the speed of sound. -The unknowns are the perturbation quantities of the acoustic velocity ``v_1'``, the pressure ``p'`` -and the density ``\rho'``. -""" -struct LinearizedEulerEquations1D{RealT <: Real} <: - AbstractLinearizedEulerEquations{1, 3} - v_mean_global::RealT - c_mean_global::RealT - rho_mean_global::RealT -end - -function LinearizedEulerEquations1D(v_mean_global::Real, - c_mean_global::Real, rho_mean_global::Real) - if rho_mean_global < 0 - throw(ArgumentError("rho_mean_global must be non-negative")) - elseif c_mean_global < 0 - throw(ArgumentError("c_mean_global must be non-negative")) + #! format: noindent + + @doc raw""" + LinearizedEulerEquations1D(v_mean_global, c_mean_global, rho_mean_global) + + Linearized Euler equations in one space dimension. The equations are given by + ```math + \partial_t + \begin{pmatrix} + \rho' \\ v_1' \\ p' + \end{pmatrix} + + + \partial_x + \begin{pmatrix} + \bar{\rho} v_1' + \bar{v_1} \rho ' \\ \bar{v_1} v_1' + \frac{p'}{\bar{\rho}} \\ \bar{v_1} p' + c^2 \bar{\rho} v_1' + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 \\ 0 + \end{pmatrix} + ``` + The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and ``c`` is the speed of sound. + The unknowns are the perturbation quantities of the acoustic velocity ``v_1'``, the pressure ``p'`` + and the density ``\rho'``. + """ + struct LinearizedEulerEquations1D{RealT <: Real} <: + AbstractLinearizedEulerEquations{1, 3} + v_mean_global::RealT + c_mean_global::RealT + rho_mean_global::RealT end - return LinearizedEulerEquations1D(v_mean_global, c_mean_global, - rho_mean_global) -end - -# Constructor with keywords -function LinearizedEulerEquations1D(; v_mean_global::Real, - c_mean_global::Real, rho_mean_global::Real) - return LinearizedEulerEquations1D(v_mean_global, c_mean_global, - rho_mean_global) -end - -function varnames(::typeof(cons2cons), ::LinearizedEulerEquations1D) - ("rho_prime", "v1_prime", "p_prime") -end -function varnames(::typeof(cons2prim), ::LinearizedEulerEquations1D) - ("rho_prime", "v1_prime", "p_prime") -end - -""" - initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations1D) - -A smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations1D) - rho_prime = -cospi(2 * t) * sinpi(2 * x[1]) - v1_prime = sinpi(2 * t) * cospi(2 * x[1]) - p_prime = rho_prime - - return SVector(rho_prime, v1_prime, p_prime) -end - -""" - boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, - equations::LinearizedEulerEquations1D) - -Boundary conditions for a solid wall. -""" -function boundary_condition_wall(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::LinearizedEulerEquations1D) - # Boundary state is equal to the inner state except for the velocity. For boundaries - # in the -x/+x direction, we multiply the velocity (in the x direction by) -1. - u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3]) - - # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + function LinearizedEulerEquations1D( + v_mean_global::Real, + c_mean_global::Real, rho_mean_global::Real + ) + if rho_mean_global < 0 + throw(ArgumentError("rho_mean_global must be non-negative")) + elseif c_mean_global < 0 + throw(ArgumentError("c_mean_global must be non-negative")) + end + + return LinearizedEulerEquations1D( + v_mean_global, c_mean_global, + rho_mean_global + ) end - return flux -end - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, equations::LinearizedEulerEquations1D) - @unpack v_mean_global, c_mean_global, rho_mean_global = equations - rho_prime, v1_prime, p_prime = u - f1 = v_mean_global * rho_prime + rho_mean_global * v1_prime - f2 = v_mean_global * v1_prime + p_prime / rho_mean_global - f3 = v_mean_global * p_prime + c_mean_global^2 * rho_mean_global * v1_prime - - return SVector(f1, f2, f3) -end - -@inline have_constant_speed(::LinearizedEulerEquations1D) = True() - -@inline function max_abs_speeds(equations::LinearizedEulerEquations1D) - @unpack v_mean_global, c_mean_global = equations - return abs(v_mean_global) + c_mean_global -end - -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations1D) - @unpack v_mean_global, c_mean_global = equations - return abs(v_mean_global) + c_mean_global -end - -# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations1D) - min_max_speed_davis(u_ll, u_rr, orientation, equations) -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations1D) - @unpack v_mean_global, c_mean_global = equations - - λ_min = v_mean_global - c_mean_global - λ_max = v_mean_global + c_mean_global - - return λ_min, λ_max -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equations::LinearizedEulerEquations1D) = u -@inline cons2entropy(u, ::LinearizedEulerEquations1D) = u + # Constructor with keywords + function LinearizedEulerEquations1D(; + v_mean_global::Real, + c_mean_global::Real, rho_mean_global::Real + ) + return LinearizedEulerEquations1D( + v_mean_global, c_mean_global, + rho_mean_global + ) + end + + function varnames(::typeof(cons2cons), ::LinearizedEulerEquations1D) + ("rho_prime", "v1_prime", "p_prime") + end + function varnames(::typeof(cons2prim), ::LinearizedEulerEquations1D) + ("rho_prime", "v1_prime", "p_prime") + end + + """ + initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations1D) + + A smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations1D) + rho_prime = -cospi(2 * t) * sinpi(2 * x[1]) + v1_prime = sinpi(2 * t) * cospi(2 * x[1]) + p_prime = rho_prime + + return SVector(rho_prime, v1_prime, p_prime) + end + + """ + boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, + equations::LinearizedEulerEquations1D) + + Boundary conditions for a solid wall. + """ + function boundary_condition_wall( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LinearizedEulerEquations1D + ) + # Boundary state is equal to the inner state except for the velocity. For boundaries + # in the -x/+x direction, we multiply the velocity (in the x direction by) -1. + u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3]) + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end + + # Calculate 1D flux for a single point + @inline function flux(u, orientation::Integer, equations::LinearizedEulerEquations1D) + @unpack v_mean_global, c_mean_global, rho_mean_global = equations + rho_prime, v1_prime, p_prime = u + f1 = v_mean_global * rho_prime + rho_mean_global * v1_prime + f2 = v_mean_global * v1_prime + p_prime / rho_mean_global + f3 = v_mean_global * p_prime + c_mean_global^2 * rho_mean_global * v1_prime + + return SVector(f1, f2, f3) + end + + @inline have_constant_speed(::LinearizedEulerEquations1D) = True() + + @inline function max_abs_speeds(equations::LinearizedEulerEquations1D) + @unpack v_mean_global, c_mean_global = equations + return abs(v_mean_global) + c_mean_global + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations1D + ) + @unpack v_mean_global, c_mean_global = equations + return abs(v_mean_global) + c_mean_global + end + + # Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations1D + ) + min_max_speed_davis(u_ll, u_rr, orientation, equations) + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations1D + ) + @unpack v_mean_global, c_mean_global = equations + + λ_min = v_mean_global - c_mean_global + λ_max = v_mean_global + c_mean_global + + return λ_min, λ_max + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equations::LinearizedEulerEquations1D) = u + @inline cons2entropy(u, ::LinearizedEulerEquations1D) = u end # muladd diff --git a/src/equations/linearized_euler_2d.jl b/src/equations/linearized_euler_2d.jl index 985fd04e60..da4871aaa5 100644 --- a/src/equations/linearized_euler_2d.jl +++ b/src/equations/linearized_euler_2d.jl @@ -3,243 +3,347 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - LinearizedEulerEquations2D(v_mean_global, c_mean_global, rho_mean_global) - -Linearized Euler equations in two space dimensions. The equations are given by -```math -\partial_t -\begin{pmatrix} - \rho' \\ v_1' \\ v_2' \\ p' -\end{pmatrix} -+ -\partial_x -\begin{pmatrix} - \bar{\rho} v_1' + \bar{v_1} \rho ' \\ \bar{v_1} v_1' + \frac{p'}{\bar{\rho}} \\ \bar{v_1} v_2' \\ \bar{v_1} p' + c^2 \bar{\rho} v_1' -\end{pmatrix} -+ -\partial_y -\begin{pmatrix} - \bar{\rho} v_2' + \bar{v_2} \rho ' \\ \bar{v_2} v_1' \\ \bar{v_2} v_2' + \frac{p'}{\bar{\rho}} \\ \bar{v_2} p' + c^2 \bar{\rho} v_2' -\end{pmatrix} -= -\begin{pmatrix} - 0 \\ 0 \\ 0 \\ 0 -\end{pmatrix} -``` -The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and ``c`` is the speed of sound. -The unknowns are the acoustic velocities ``v' = (v_1', v_2')``, the pressure ``p'`` and the density ``\rho'``. -""" -struct LinearizedEulerEquations2D{RealT <: Real} <: - AbstractLinearizedEulerEquations{2, 4} - v_mean_global::SVector{2, RealT} - c_mean_global::RealT - rho_mean_global::RealT -end - -function LinearizedEulerEquations2D(v_mean_global::NTuple{2, <:Real}, - c_mean_global::Real, rho_mean_global::Real) - if rho_mean_global < 0 - throw(ArgumentError("rho_mean_global must be non-negative")) - elseif c_mean_global < 0 - throw(ArgumentError("c_mean_global must be non-negative")) + #! format: noindent + + @doc raw""" + LinearizedEulerEquations2D(v_mean_global, c_mean_global, rho_mean_global) + + Linearized Euler equations in two space dimensions. The equations are given by + ```math + \partial_t + \begin{pmatrix} + \rho' \\ v_1' \\ v_2' \\ p' + \end{pmatrix} + + + \partial_x + \begin{pmatrix} + \bar{\rho} v_1' + \bar{v_1} \rho ' \\ \bar{v_1} v_1' + \frac{p'}{\bar{\rho}} \\ \bar{v_1} v_2' \\ \bar{v_1} p' + c^2 \bar{\rho} v_1' + \end{pmatrix} + + + \partial_y + \begin{pmatrix} + \bar{\rho} v_2' + \bar{v_2} \rho ' \\ \bar{v_2} v_1' \\ \bar{v_2} v_2' + \frac{p'}{\bar{\rho}} \\ \bar{v_2} p' + c^2 \bar{\rho} v_2' + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 \\ 0 \\ 0 + \end{pmatrix} + ``` + The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and ``c`` is the speed of sound. + The unknowns are the acoustic velocities ``v' = (v_1', v_2')``, the pressure ``p'`` and the density ``\rho'``. + """ + struct LinearizedEulerEquations2D{RealT <: Real} <: + AbstractLinearizedEulerEquations{2, 4} + v_mean_global::SVector{2, RealT} + c_mean_global::RealT + rho_mean_global::RealT end - return LinearizedEulerEquations2D(SVector(v_mean_global), c_mean_global, - rho_mean_global) -end - -function LinearizedEulerEquations2D(; v_mean_global::NTuple{2, <:Real}, - c_mean_global::Real, rho_mean_global::Real) - return LinearizedEulerEquations2D(v_mean_global, c_mean_global, - rho_mean_global) -end - -function varnames(::typeof(cons2cons), ::LinearizedEulerEquations2D) - ("rho_prime", "v1_prime", "v2_prime", "p_prime") -end -function varnames(::typeof(cons2prim), ::LinearizedEulerEquations2D) - ("rho_prime", "v1_prime", "v2_prime", "p_prime") -end - -""" - initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations2D) - -A smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations2D) - rho_prime = -cospi(2 * t) * (sinpi(2 * x[1]) + sinpi(2 * x[2])) - v1_prime = sinpi(2 * t) * cospi(2 * x[1]) - v2_prime = sinpi(2 * t) * cospi(2 * x[2]) - p_prime = rho_prime - - return SVector(rho_prime, v1_prime, v2_prime, p_prime) -end - -""" - boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, - equations::LinearizedEulerEquations2D) - -Boundary conditions for a solid wall. -""" -function boundary_condition_wall(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::LinearizedEulerEquations2D) - # Boundary state is equal to the inner state except for the velocity. For boundaries - # in the -x/+x direction, we multiply the velocity in the x direction by -1. - # Similarly, for boundaries in the -y/+y direction, we multiply the velocity in the - # y direction by -1 - if direction in (1, 2) # x direction - u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3], u_inner[4]) - else # y direction - u_boundary = SVector(u_inner[1], u_inner[2], -u_inner[3], u_inner[4]) + function LinearizedEulerEquations2D( + v_mean_global::NTuple{2, <:Real}, + c_mean_global::Real, rho_mean_global::Real + ) + if rho_mean_global < 0 + throw(ArgumentError("rho_mean_global must be non-negative")) + elseif c_mean_global < 0 + throw(ArgumentError("c_mean_global must be non-negative")) + end + + return LinearizedEulerEquations2D( + SVector(v_mean_global), c_mean_global, + rho_mean_global + ) end - # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + function LinearizedEulerEquations2D(; + v_mean_global::NTuple{2, <:Real}, + c_mean_global::Real, rho_mean_global::Real + ) + return LinearizedEulerEquations2D( + v_mean_global, c_mean_global, + rho_mean_global + ) end - return flux -end - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, equations::LinearizedEulerEquations2D) - @unpack v_mean_global, c_mean_global, rho_mean_global = equations - rho_prime, v1_prime, v2_prime, p_prime = u - if orientation == 1 - f1 = v_mean_global[1] * rho_prime + rho_mean_global * v1_prime - f2 = v_mean_global[1] * v1_prime + p_prime / rho_mean_global - f3 = v_mean_global[1] * v2_prime - f4 = v_mean_global[1] * p_prime + c_mean_global^2 * rho_mean_global * v1_prime - else - f1 = v_mean_global[2] * rho_prime + rho_mean_global * v2_prime - f2 = v_mean_global[2] * v1_prime - f3 = v_mean_global[2] * v2_prime + p_prime / rho_mean_global - f4 = v_mean_global[2] * p_prime + c_mean_global^2 * rho_mean_global * v2_prime + function varnames(::typeof(cons2cons), ::LinearizedEulerEquations2D) + ("rho_prime", "v1_prime", "v2_prime", "p_prime") end + function varnames(::typeof(cons2prim), ::LinearizedEulerEquations2D) + ("rho_prime", "v1_prime", "v2_prime", "p_prime") + end + + """ + initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations2D) - return SVector(f1, f2, f3, f4) -end - -# Calculate 1D flux for a single point -@inline function flux(u, normal_direction::AbstractVector, - equations::LinearizedEulerEquations2D) - @unpack v_mean_global, c_mean_global, rho_mean_global = equations - rho_prime, v1_prime, v2_prime, p_prime = u - - v_mean_normal = v_mean_global[1] * normal_direction[1] + - v_mean_global[2] * normal_direction[2] - v_prime_normal = v1_prime * normal_direction[1] + v2_prime * normal_direction[2] - - f1 = v_mean_normal * rho_prime + rho_mean_global * v_prime_normal - f2 = v_mean_normal * v1_prime + normal_direction[1] * p_prime / rho_mean_global - f3 = v_mean_normal * v2_prime + normal_direction[2] * p_prime / rho_mean_global - f4 = v_mean_normal * p_prime + c_mean_global^2 * rho_mean_global * v_prime_normal - - return SVector(f1, f2, f3, f4) -end - -@inline have_constant_speed(::LinearizedEulerEquations2D) = True() - -@inline function max_abs_speeds(equations::LinearizedEulerEquations2D) - @unpack v_mean_global, c_mean_global = equations - return abs(v_mean_global[1]) + c_mean_global, abs(v_mean_global[2]) + c_mean_global -end - -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations2D) - @unpack v_mean_global, c_mean_global = equations - if orientation == 1 - return abs(v_mean_global[1]) + c_mean_global - else # orientation == 2 - return abs(v_mean_global[2]) + c_mean_global + A smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations2D) + rho_prime = -cospi(2 * t) * (sinpi(2 * x[1]) + sinpi(2 * x[2])) + v1_prime = sinpi(2 * t) * cospi(2 * x[1]) + v2_prime = sinpi(2 * t) * cospi(2 * x[2]) + p_prime = rho_prime + + return SVector(rho_prime, v1_prime, v2_prime, p_prime) end -end - -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::LinearizedEulerEquations2D) - @unpack v_mean_global, c_mean_global = equations - v_mean_normal = normal_direction[1] * v_mean_global[1] + - normal_direction[2] * v_mean_global[2] - return abs(v_mean_normal) + c_mean_global * norm(normal_direction) -end - -@doc raw""" - flux_godunov(u_ll, u_rr, orientation_or_normal_direction, - equations::LinearizedEulerEquations2D) - -An upwind flux for the linearized Euler equations based on diagonalization of the physical -flux matrix. Given the physical flux ``Au``, ``A=T \Lambda T^{-1}`` with -``\Lambda`` being a diagonal matrix that holds the eigenvalues of ``A``, decompose -``\Lambda = \Lambda^+ + \Lambda^-`` where ``\Lambda^+`` and ``\Lambda^-`` are diagonal -matrices holding the positive and negative eigenvalues of ``A``, respectively. Then for -left and right states ``u_L, u_R``, the numerical flux calculated by this function is given -by ``A^+ u_L + A^- u_R`` where ``A^{\pm} = T \Lambda^{\pm} T^{-1}``. - -The diagonalization of the flux matrix can be found in -- R. F. Warming, Richard M. Beam and B. J. Hyett (1975) - Diagonalization and simultaneous symmetrization of the gas-dynamic matrices - [DOI: 10.1090/S0025-5718-1975-0388967-5](https://doi.org/10.1090/S0025-5718-1975-0388967-5) -""" -@inline function flux_godunov(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations2D) - @unpack v_mean_global, rho_mean_global, c_mean_global = equations - v1_mean = v_mean_global[1] - v2_mean = v_mean_global[2] - - rho_prime_ll, v1_prime_ll, v2_prime_ll, p_prime_ll = u_ll - rho_prime_rr, v1_prime_rr, v2_prime_rr, p_prime_rr = u_rr - - if orientation == 1 - # Eigenvalues of the flux matrix - lambda1 = v1_mean - lambda2 = v1_mean - c_mean_global - lambda3 = v1_mean + c_mean_global - lambda1_p = positive_part(lambda1) - lambda2_p = positive_part(lambda2) - lambda3_p = positive_part(lambda3) - lambda2p3_half_p = 0.5f0 * (lambda2_p + lambda3_p) - lambda3m2_half_p = 0.5f0 * (lambda3_p - lambda2_p) + """ + boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, + equations::LinearizedEulerEquations2D) + + Boundary conditions for a solid wall. + """ + function boundary_condition_wall( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LinearizedEulerEquations2D + ) + # Boundary state is equal to the inner state except for the velocity. For boundaries + # in the -x/+x direction, we multiply the velocity in the x direction by -1. + # Similarly, for boundaries in the -y/+y direction, we multiply the velocity in the + # y direction by -1 + if direction in (1, 2) # x direction + u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3], u_inner[4]) + else # y direction + u_boundary = SVector(u_inner[1], u_inner[2], -u_inner[3], u_inner[4]) + end + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end - lambda1_m = negative_part(lambda1) - lambda2_m = negative_part(lambda2) - lambda3_m = negative_part(lambda3) - lambda2p3_half_m = 0.5f0 * (lambda2_m + lambda3_m) - lambda3m2_half_m = 0.5f0 * (lambda3_m - lambda2_m) + # Calculate 1D flux for a single point + @inline function flux(u, orientation::Integer, equations::LinearizedEulerEquations2D) + @unpack v_mean_global, c_mean_global, rho_mean_global = equations + rho_prime, v1_prime, v2_prime, p_prime = u + if orientation == 1 + f1 = v_mean_global[1] * rho_prime + rho_mean_global * v1_prime + f2 = v_mean_global[1] * v1_prime + p_prime / rho_mean_global + f3 = v_mean_global[1] * v2_prime + f4 = v_mean_global[1] * p_prime + c_mean_global^2 * rho_mean_global * v1_prime + else + f1 = v_mean_global[2] * rho_prime + rho_mean_global * v2_prime + f2 = v_mean_global[2] * v1_prime + f3 = v_mean_global[2] * v2_prime + p_prime / rho_mean_global + f4 = v_mean_global[2] * p_prime + c_mean_global^2 * rho_mean_global * v2_prime + end + + return SVector(f1, f2, f3, f4) + end - f1p = (lambda1_p * rho_prime_ll + - lambda3m2_half_p / c_mean_global * rho_mean_global * v1_prime_ll + - (lambda2p3_half_p - lambda1_p) / c_mean_global^2 * p_prime_ll) - f2p = (lambda2p3_half_p * v1_prime_ll + - lambda3m2_half_p / c_mean_global * p_prime_ll / rho_mean_global) - f3p = lambda1_p * v2_prime_ll - f4p = (lambda3m2_half_p * c_mean_global * rho_mean_global * v1_prime_ll + - lambda2p3_half_p * p_prime_ll) - - f1m = (lambda1_m * rho_prime_rr + - lambda3m2_half_m / c_mean_global * rho_mean_global * v1_prime_rr + - (lambda2p3_half_m - lambda1_m) / c_mean_global^2 * p_prime_rr) - f2m = (lambda2p3_half_m * v1_prime_rr + - lambda3m2_half_m / c_mean_global * p_prime_rr / rho_mean_global) - f3m = lambda1_m * v2_prime_rr - f4m = (lambda3m2_half_m * c_mean_global * rho_mean_global * v1_prime_rr + - lambda2p3_half_m * p_prime_rr) + # Calculate 1D flux for a single point + @inline function flux( + u, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D + ) + @unpack v_mean_global, c_mean_global, rho_mean_global = equations + rho_prime, v1_prime, v2_prime, p_prime = u + + v_mean_normal = v_mean_global[1] * normal_direction[1] + + v_mean_global[2] * normal_direction[2] + v_prime_normal = v1_prime * normal_direction[1] + v2_prime * normal_direction[2] + + f1 = v_mean_normal * rho_prime + rho_mean_global * v_prime_normal + f2 = v_mean_normal * v1_prime + normal_direction[1] * p_prime / rho_mean_global + f3 = v_mean_normal * v2_prime + normal_direction[2] * p_prime / rho_mean_global + f4 = v_mean_normal * p_prime + c_mean_global^2 * rho_mean_global * v_prime_normal + + return SVector(f1, f2, f3, f4) + end + + @inline have_constant_speed(::LinearizedEulerEquations2D) = True() + + @inline function max_abs_speeds(equations::LinearizedEulerEquations2D) + @unpack v_mean_global, c_mean_global = equations + return abs(v_mean_global[1]) + c_mean_global, abs(v_mean_global[2]) + c_mean_global + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations2D + ) + @unpack v_mean_global, c_mean_global = equations + if orientation == 1 + return abs(v_mean_global[1]) + c_mean_global + else # orientation == 2 + return abs(v_mean_global[2]) + c_mean_global + end + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D + ) + @unpack v_mean_global, c_mean_global = equations + v_mean_normal = normal_direction[1] * v_mean_global[1] + + normal_direction[2] * v_mean_global[2] + return abs(v_mean_normal) + c_mean_global * norm(normal_direction) + end + + @doc raw""" + flux_godunov(u_ll, u_rr, orientation_or_normal_direction, + equations::LinearizedEulerEquations2D) + + An upwind flux for the linearized Euler equations based on diagonalization of the physical + flux matrix. Given the physical flux ``Au``, ``A=T \Lambda T^{-1}`` with + ``\Lambda`` being a diagonal matrix that holds the eigenvalues of ``A``, decompose + ``\Lambda = \Lambda^+ + \Lambda^-`` where ``\Lambda^+`` and ``\Lambda^-`` are diagonal + matrices holding the positive and negative eigenvalues of ``A``, respectively. Then for + left and right states ``u_L, u_R``, the numerical flux calculated by this function is given + by ``A^+ u_L + A^- u_R`` where ``A^{\pm} = T \Lambda^{\pm} T^{-1}``. + + The diagonalization of the flux matrix can be found in + - R. F. Warming, Richard M. Beam and B. J. Hyett (1975) + Diagonalization and simultaneous symmetrization of the gas-dynamic matrices + [DOI: 10.1090/S0025-5718-1975-0388967-5](https://doi.org/10.1090/S0025-5718-1975-0388967-5) + """ + @inline function flux_godunov( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations2D + ) + @unpack v_mean_global, rho_mean_global, c_mean_global = equations + v1_mean = v_mean_global[1] + v2_mean = v_mean_global[2] + + rho_prime_ll, v1_prime_ll, v2_prime_ll, p_prime_ll = u_ll + rho_prime_rr, v1_prime_rr, v2_prime_rr, p_prime_rr = u_rr + + if orientation == 1 + # Eigenvalues of the flux matrix + lambda1 = v1_mean + lambda2 = v1_mean - c_mean_global + lambda3 = v1_mean + c_mean_global + + lambda1_p = positive_part(lambda1) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + lambda2p3_half_p = 0.5f0 * (lambda2_p + lambda3_p) + lambda3m2_half_p = 0.5f0 * (lambda3_p - lambda2_p) + + lambda1_m = negative_part(lambda1) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + lambda2p3_half_m = 0.5f0 * (lambda2_m + lambda3_m) + lambda3m2_half_m = 0.5f0 * (lambda3_m - lambda2_m) + + f1p = ( + lambda1_p * rho_prime_ll + + lambda3m2_half_p / c_mean_global * rho_mean_global * v1_prime_ll + + (lambda2p3_half_p - lambda1_p) / c_mean_global^2 * p_prime_ll + ) + f2p = ( + lambda2p3_half_p * v1_prime_ll + + lambda3m2_half_p / c_mean_global * p_prime_ll / rho_mean_global + ) + f3p = lambda1_p * v2_prime_ll + f4p = ( + lambda3m2_half_p * c_mean_global * rho_mean_global * v1_prime_ll + + lambda2p3_half_p * p_prime_ll + ) + + f1m = ( + lambda1_m * rho_prime_rr + + lambda3m2_half_m / c_mean_global * rho_mean_global * v1_prime_rr + + (lambda2p3_half_m - lambda1_m) / c_mean_global^2 * p_prime_rr + ) + f2m = ( + lambda2p3_half_m * v1_prime_rr + + lambda3m2_half_m / c_mean_global * p_prime_rr / rho_mean_global + ) + f3m = lambda1_m * v2_prime_rr + f4m = ( + lambda3m2_half_m * c_mean_global * rho_mean_global * v1_prime_rr + + lambda2p3_half_m * p_prime_rr + ) + + f1 = f1p + f1m + f2 = f2p + f2m + f3 = f3p + f3m + f4 = f4p + f4m + else # orientation == 2 + # Eigenvalues of the flux matrix + lambda1 = v2_mean + lambda2 = v2_mean - c_mean_global + lambda3 = v2_mean + c_mean_global + + lambda1_p = positive_part(lambda1) + lambda2_p = positive_part(lambda2) + lambda3_p = positive_part(lambda3) + lambda2p3_half_p = 0.5f0 * (lambda2_p + lambda3_p) + lambda3m2_half_p = 0.5f0 * (lambda3_p - lambda2_p) + + lambda1_m = negative_part(lambda1) + lambda2_m = negative_part(lambda2) + lambda3_m = negative_part(lambda3) + lambda2p3_half_m = 0.5f0 * (lambda2_m + lambda3_m) + lambda3m2_half_m = 0.5f0 * (lambda3_m - lambda2_m) + + f1p = ( + lambda1_p * rho_prime_ll + + lambda3m2_half_p / c_mean_global * rho_mean_global * v2_prime_ll + + (lambda2p3_half_p - lambda1_p) / c_mean_global^2 * p_prime_ll + ) + f2p = lambda1_p * v1_prime_ll + f3p = ( + lambda2p3_half_p * v2_prime_ll + + lambda3m2_half_p / c_mean_global * p_prime_ll / rho_mean_global + ) + f4p = ( + lambda3m2_half_p * c_mean_global * rho_mean_global * v2_prime_ll + + lambda2p3_half_p * p_prime_ll + ) + + f1m = ( + lambda1_m * rho_prime_rr + + lambda3m2_half_m / c_mean_global * rho_mean_global * v2_prime_rr + + (lambda2p3_half_m - lambda1_m) / c_mean_global^2 * p_prime_rr + ) + f2m = lambda1_m * v1_prime_rr + f3m = ( + lambda2p3_half_m * v2_prime_rr + + lambda3m2_half_m / c_mean_global * p_prime_rr / rho_mean_global + ) + f4m = ( + lambda3m2_half_m * c_mean_global * rho_mean_global * v2_prime_rr + + lambda2p3_half_m * p_prime_rr + ) + + f1 = f1p + f1m + f2 = f2p + f2m + f3 = f3p + f3m + f4 = f4p + f4m + end + + return SVector(f1, f2, f3, f4) + end + + @inline function flux_godunov( + u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D + ) + @unpack v_mean_global, rho_mean_global, c_mean_global = equations + rho_prime_ll, v1_prime_ll, v2_prime_ll, p_prime_ll = u_ll + rho_prime_rr, v1_prime_rr, v2_prime_rr, p_prime_rr = u_rr + + # Do not use `normalize` since we use `norm_` later to scale the eigenvalues + norm_ = norm(normal_direction) + normal_vector = normal_direction / norm_ + + # Use normalized vector here, scaling is applied via eigenvalues of the flux matrix + v_mean_normal = v_mean_global[1] * normal_vector[1] + + v_mean_global[2] * normal_vector[2] + v_prime_normal_ll = v1_prime_ll * normal_vector[1] + v2_prime_ll * normal_vector[2] + v_prime_normal_rr = v1_prime_rr * normal_vector[1] + v2_prime_rr * normal_vector[2] - f1 = f1p + f1m - f2 = f2p + f2m - f3 = f3p + f3m - f4 = f4p + f4m - else # orientation == 2 # Eigenvalues of the flux matrix - lambda1 = v2_mean - lambda2 = v2_mean - c_mean_global - lambda3 = v2_mean + c_mean_global + lambda1 = v_mean_normal * norm_ + lambda2 = (v_mean_normal - c_mean_global) * norm_ + lambda3 = (v_mean_normal + c_mean_global) * norm_ lambda1_p = positive_part(lambda1) lambda2_p = positive_part(lambda2) @@ -253,145 +357,125 @@ The diagonalization of the flux matrix can be found in lambda2p3_half_m = 0.5f0 * (lambda2_m + lambda3_m) lambda3m2_half_m = 0.5f0 * (lambda3_m - lambda2_m) - f1p = (lambda1_p * rho_prime_ll + - lambda3m2_half_p / c_mean_global * rho_mean_global * v2_prime_ll + - (lambda2p3_half_p - lambda1_p) / c_mean_global^2 * p_prime_ll) - f2p = lambda1_p * v1_prime_ll - f3p = (lambda2p3_half_p * v2_prime_ll + - lambda3m2_half_p / c_mean_global * p_prime_ll / rho_mean_global) - f4p = (lambda3m2_half_p * c_mean_global * rho_mean_global * v2_prime_ll + - lambda2p3_half_p * p_prime_ll) - - f1m = (lambda1_m * rho_prime_rr + - lambda3m2_half_m / c_mean_global * rho_mean_global * v2_prime_rr + - (lambda2p3_half_m - lambda1_m) / c_mean_global^2 * p_prime_rr) - f2m = lambda1_m * v1_prime_rr - f3m = (lambda2p3_half_m * v2_prime_rr + - lambda3m2_half_m / c_mean_global * p_prime_rr / rho_mean_global) - f4m = (lambda3m2_half_m * c_mean_global * rho_mean_global * v2_prime_rr + - lambda2p3_half_m * p_prime_rr) + f1p = ( + lambda1_p * rho_prime_ll + + lambda3m2_half_p / c_mean_global * rho_mean_global * v_prime_normal_ll + + (lambda2p3_half_p - lambda1_p) / c_mean_global^2 * p_prime_ll + ) + f2p = ( + ( + ( + lambda1_p * normal_vector[2]^2 + + lambda2p3_half_p * normal_vector[1]^2 + ) * v1_prime_ll + + (lambda2p3_half_p - lambda1_p) * prod(normal_vector) * v2_prime_ll + ) + + lambda3m2_half_p / c_mean_global * normal_vector[1] * p_prime_ll / + rho_mean_global + ) + f3p = ( + ( + ( + lambda1_p * normal_vector[1]^2 + + lambda2p3_half_p * normal_vector[2]^2 + ) * v2_prime_ll + + (lambda2p3_half_p - lambda1_p) * prod(normal_vector) * v1_prime_ll + ) + + lambda3m2_half_p / c_mean_global * normal_vector[2] * p_prime_ll / + rho_mean_global + ) + f4p = ( + lambda3m2_half_p * c_mean_global * rho_mean_global * v_prime_normal_ll + + lambda2p3_half_p * p_prime_ll + ) + + f1m = ( + lambda1_m * rho_prime_rr + + lambda3m2_half_m / c_mean_global * rho_mean_global * v_prime_normal_rr + + (lambda2p3_half_m - lambda1_m) / c_mean_global^2 * p_prime_rr + ) + f2m = ( + ( + ( + lambda1_m * normal_vector[2]^2 + + lambda2p3_half_m * normal_vector[1]^2 + ) * v1_prime_rr + + (lambda2p3_half_m - lambda1_m) * prod(normal_vector) * v2_prime_rr + ) + + lambda3m2_half_m / c_mean_global * normal_vector[1] * p_prime_rr / + rho_mean_global + ) + f3m = ( + ( + ( + lambda1_m * normal_vector[1]^2 + + lambda2p3_half_m * normal_vector[2]^2 + ) * v2_prime_rr + + (lambda2p3_half_m - lambda1_m) * prod(normal_vector) * v1_prime_rr + ) + + lambda3m2_half_m / c_mean_global * normal_vector[2] * p_prime_rr / + rho_mean_global + ) + f4m = ( + lambda3m2_half_m * c_mean_global * rho_mean_global * v_prime_normal_rr + + lambda2p3_half_m * p_prime_rr + ) f1 = f1p + f1m f2 = f2p + f2m f3 = f3p + f3m f4 = f4p + f4m + + return SVector(f1, f2, f3, f4) + end + + # Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations2D + ) + min_max_speed_davis(u_ll, u_rr, orientation, equations) + end + + @inline function min_max_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D + ) + min_max_speed_davis(u_ll, u_rr, normal_direction, equations) + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations2D + ) + @unpack v_mean_global, c_mean_global = equations + + λ_min = v_mean_global[orientation] - c_mean_global + λ_max = v_mean_global[orientation] + c_mean_global + + return λ_min, λ_max + end + + @inline function min_max_speed_davis( + u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations2D + ) + @unpack v_mean_global, c_mean_global = equations + + norm_ = norm(normal_direction) + + v_normal = v_mean_global[1] * normal_direction[1] + + v_mean_global[2] * normal_direction[2] + + # The v_normals are already scaled by the norm + λ_min = v_normal - c_mean_global * norm_ + λ_max = v_normal + c_mean_global * norm_ + + return λ_min, λ_max end - return SVector(f1, f2, f3, f4) -end - -@inline function flux_godunov(u_ll, u_rr, normal_direction::AbstractVector, - equations::LinearizedEulerEquations2D) - @unpack v_mean_global, rho_mean_global, c_mean_global = equations - rho_prime_ll, v1_prime_ll, v2_prime_ll, p_prime_ll = u_ll - rho_prime_rr, v1_prime_rr, v2_prime_rr, p_prime_rr = u_rr - - # Do not use `normalize` since we use `norm_` later to scale the eigenvalues - norm_ = norm(normal_direction) - normal_vector = normal_direction / norm_ - - # Use normalized vector here, scaling is applied via eigenvalues of the flux matrix - v_mean_normal = v_mean_global[1] * normal_vector[1] + - v_mean_global[2] * normal_vector[2] - v_prime_normal_ll = v1_prime_ll * normal_vector[1] + v2_prime_ll * normal_vector[2] - v_prime_normal_rr = v1_prime_rr * normal_vector[1] + v2_prime_rr * normal_vector[2] - - # Eigenvalues of the flux matrix - lambda1 = v_mean_normal * norm_ - lambda2 = (v_mean_normal - c_mean_global) * norm_ - lambda3 = (v_mean_normal + c_mean_global) * norm_ - - lambda1_p = positive_part(lambda1) - lambda2_p = positive_part(lambda2) - lambda3_p = positive_part(lambda3) - lambda2p3_half_p = 0.5f0 * (lambda2_p + lambda3_p) - lambda3m2_half_p = 0.5f0 * (lambda3_p - lambda2_p) - - lambda1_m = negative_part(lambda1) - lambda2_m = negative_part(lambda2) - lambda3_m = negative_part(lambda3) - lambda2p3_half_m = 0.5f0 * (lambda2_m + lambda3_m) - lambda3m2_half_m = 0.5f0 * (lambda3_m - lambda2_m) - - f1p = (lambda1_p * rho_prime_ll + - lambda3m2_half_p / c_mean_global * rho_mean_global * v_prime_normal_ll + - (lambda2p3_half_p - lambda1_p) / c_mean_global^2 * p_prime_ll) - f2p = (((lambda1_p * normal_vector[2]^2 + - lambda2p3_half_p * normal_vector[1]^2) * v1_prime_ll + - (lambda2p3_half_p - lambda1_p) * prod(normal_vector) * v2_prime_ll) + - lambda3m2_half_p / c_mean_global * normal_vector[1] * p_prime_ll / - rho_mean_global) - f3p = (((lambda1_p * normal_vector[1]^2 + - lambda2p3_half_p * normal_vector[2]^2) * v2_prime_ll + - (lambda2p3_half_p - lambda1_p) * prod(normal_vector) * v1_prime_ll) + - lambda3m2_half_p / c_mean_global * normal_vector[2] * p_prime_ll / - rho_mean_global) - f4p = (lambda3m2_half_p * c_mean_global * rho_mean_global * v_prime_normal_ll + - lambda2p3_half_p * p_prime_ll) - - f1m = (lambda1_m * rho_prime_rr + - lambda3m2_half_m / c_mean_global * rho_mean_global * v_prime_normal_rr + - (lambda2p3_half_m - lambda1_m) / c_mean_global^2 * p_prime_rr) - f2m = (((lambda1_m * normal_vector[2]^2 + - lambda2p3_half_m * normal_vector[1]^2) * v1_prime_rr + - (lambda2p3_half_m - lambda1_m) * prod(normal_vector) * v2_prime_rr) + - lambda3m2_half_m / c_mean_global * normal_vector[1] * p_prime_rr / - rho_mean_global) - f3m = (((lambda1_m * normal_vector[1]^2 + - lambda2p3_half_m * normal_vector[2]^2) * v2_prime_rr + - (lambda2p3_half_m - lambda1_m) * prod(normal_vector) * v1_prime_rr) + - lambda3m2_half_m / c_mean_global * normal_vector[2] * p_prime_rr / - rho_mean_global) - f4m = (lambda3m2_half_m * c_mean_global * rho_mean_global * v_prime_normal_rr + - lambda2p3_half_m * p_prime_rr) - - f1 = f1p + f1m - f2 = f2p + f2m - f3 = f3p + f3m - f4 = f4p + f4m - - return SVector(f1, f2, f3, f4) -end - -# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations2D) - min_max_speed_davis(u_ll, u_rr, orientation, equations) -end - -@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::LinearizedEulerEquations2D) - min_max_speed_davis(u_ll, u_rr, normal_direction, equations) -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations2D) - @unpack v_mean_global, c_mean_global = equations - - λ_min = v_mean_global[orientation] - c_mean_global - λ_max = v_mean_global[orientation] + c_mean_global - - return λ_min, λ_max -end - -@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, - equations::LinearizedEulerEquations2D) - @unpack v_mean_global, c_mean_global = equations - - norm_ = norm(normal_direction) - - v_normal = v_mean_global[1] * normal_direction[1] + - v_mean_global[2] * normal_direction[2] - - # The v_normals are already scaled by the norm - λ_min = v_normal - c_mean_global * norm_ - λ_max = v_normal + c_mean_global * norm_ - - return λ_min, λ_max -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equations::LinearizedEulerEquations2D) = u -@inline cons2entropy(u, ::LinearizedEulerEquations2D) = u + # Convert conservative variables to primitive + @inline cons2prim(u, equations::LinearizedEulerEquations2D) = u + @inline cons2entropy(u, ::LinearizedEulerEquations2D) = u end # muladd diff --git a/src/equations/linearized_euler_3d.jl b/src/equations/linearized_euler_3d.jl index ab5f9863db..996e0cace7 100644 --- a/src/equations/linearized_euler_3d.jl +++ b/src/equations/linearized_euler_3d.jl @@ -3,252 +3,282 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - LinearizedEulerEquations3D(v_mean_global, c_mean_global, rho_mean_global) - -Linearized Euler equations in three space dimensions. The equations are given by -```math -\partial_t -\begin{pmatrix} - \rho' \\ v_1' \\ v_2' \\ v_3' \\ p' -\end{pmatrix} -+ -\partial_x -\begin{pmatrix} - \bar{\rho} v_1' + \bar{v_1} \rho ' \\ - \bar{v_1} v_1' + \frac{p'}{\bar{\rho}} \\ - \bar{v_1} v_2' \\ - \bar{v_1} v_3' \\ - \bar{v_1} p' + c^2 \bar{\rho} v_1' -\end{pmatrix} -+ -\partial_y -\begin{pmatrix} - \bar{\rho} v_2' + \bar{v_2} \rho ' \\ - \bar{v_2} v_1' \\ - \bar{v_2} v_2' + \frac{p'}{\bar{\rho}} \\ - \bar{v_2} v_3' \\ - \bar{v_2} p' + c^2 \bar{\rho} v_2' -\end{pmatrix} -+ -\partial_z -\begin{pmatrix} - \bar{\rho} v_3' + \bar{v_3} \rho ' \\ - \bar{v_3} v_1' \\ - \bar{v_3} v_2' \\ - \bar{v_3} v_3' + \frac{p'}{\bar{\rho}} \\ - \bar{v_3} p' + c^2 \bar{\rho} v_3' -\end{pmatrix} -= -\begin{pmatrix} - 0 \\ 0 \\ 0 \\ 0 \\ 0 -\end{pmatrix} -``` -The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and ``c`` is the speed of sound. -The unknowns are the acoustic velocities ``v' = (v_1', v_2, v_3')``, the pressure ``p'`` and the density ``\rho'``. -""" -struct LinearizedEulerEquations3D{RealT <: Real} <: - AbstractLinearizedEulerEquations{3, 5} - v_mean_global::SVector{3, RealT} - c_mean_global::RealT - rho_mean_global::RealT -end - -function LinearizedEulerEquations3D(v_mean_global::NTuple{3, <:Real}, - c_mean_global::Real, rho_mean_global::Real) - if rho_mean_global < 0 - throw(ArgumentError("rho_mean_global must be non-negative")) - elseif c_mean_global < 0 - throw(ArgumentError("c_mean_global must be non-negative")) - end - - return LinearizedEulerEquations3D(SVector(v_mean_global), c_mean_global, - rho_mean_global) -end - -function LinearizedEulerEquations3D(; v_mean_global::NTuple{3, <:Real}, - c_mean_global::Real, rho_mean_global::Real) - return LinearizedEulerEquations3D(v_mean_global, c_mean_global, - rho_mean_global) -end - -function varnames(::typeof(cons2cons), ::LinearizedEulerEquations3D) - ("rho_prime", "v1_prime", "v2_prime", "v3_prime", "p_prime") -end -function varnames(::typeof(cons2prim), ::LinearizedEulerEquations3D) - ("rho_prime", "v1_prime", "v2_prime", "v3_prime", "p_prime") -end - -""" - initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations3D) - -A smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations3D) - rho_prime = -cospi(2 * t) * (sinpi(2 * x[1]) + sinpi(2 * x[2]) + sinpi(2 * x[3])) - v1_prime = sinpi(2 * t) * cospi(2 * x[1]) - v2_prime = sinpi(2 * t) * cospi(2 * x[2]) - v3_prime = sinpi(2 * t) * cospi(2 * x[3]) - p_prime = rho_prime - - return SVector(rho_prime, v1_prime, v2_prime, v3_prime, p_prime) -end - -""" - boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, - equations::LinearizedEulerEquations3D) - -Boundary conditions for a solid wall. -""" -function boundary_condition_wall(u_inner, orientation, direction, x, t, - surface_flux_function, - equations::LinearizedEulerEquations3D) - # Boundary state is equal to the inner state except for the velocity. For boundaries - # in the -x/+x direction, we multiply the velocity in the x direction by -1. - # Similarly, for boundaries in the -y/+y or -z/+z direction, we multiply the - # velocity in the y or z direction by -1 - if direction in (1, 2) # x direction - u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3], u_inner[4], - u_inner[5]) - elseif direction in (3, 4) # y direction - u_boundary = SVector(u_inner[1], u_inner[2], -u_inner[3], u_inner[4], - u_inner[5]) - else # z direction = (5, 6) - u_boundary = SVector(u_inner[1], u_inner[2], u_inner[3], -u_inner[4], - u_inner[5]) - end - - # Calculate boundary flux depending on the orientation of the boundary - # Odd directions are in negative coordinate direction, - # even directions are in positive coordinate direction. - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) - end - - return flux -end - -# Calculate 3D flux for a single point -@inline function flux(u, orientation::Integer, equations::LinearizedEulerEquations3D) - @unpack v_mean_global, c_mean_global, rho_mean_global = equations - rho_prime, v1_prime, v2_prime, v3_prime, p_prime = u - if orientation == 1 - f1 = v_mean_global[1] * rho_prime + rho_mean_global * v1_prime - f2 = v_mean_global[1] * v1_prime + p_prime / rho_mean_global - f3 = v_mean_global[1] * v2_prime - f4 = v_mean_global[1] * v3_prime - f5 = v_mean_global[1] * p_prime + c_mean_global^2 * rho_mean_global * v1_prime - elseif orientation == 2 - f1 = v_mean_global[2] * rho_prime + rho_mean_global * v2_prime - f2 = v_mean_global[2] * v1_prime - f3 = v_mean_global[2] * v2_prime + p_prime / rho_mean_global - f4 = v_mean_global[2] * v3_prime - f5 = v_mean_global[2] * p_prime + c_mean_global^2 * rho_mean_global * v2_prime - else # orientation == 3 - f1 = v_mean_global[3] * rho_prime + rho_mean_global * v3_prime - f2 = v_mean_global[3] * v1_prime - f3 = v_mean_global[3] * v2_prime - f4 = v_mean_global[3] * v3_prime + p_prime / rho_mean_global - f5 = v_mean_global[3] * p_prime + c_mean_global^2 * rho_mean_global * v3_prime - end - - return SVector(f1, f2, f3, f4, f5) -end - -# Calculate 3D flux for a single point -@inline function flux(u, normal_direction::AbstractVector, - equations::LinearizedEulerEquations3D) - @unpack v_mean_global, c_mean_global, rho_mean_global = equations - rho_prime, v1_prime, v2_prime, v3_prime, p_prime = u - - v_mean_normal = v_mean_global[1] * normal_direction[1] + - v_mean_global[2] * normal_direction[2] + - v_mean_global[3] * normal_direction[3] - v_prime_normal = v1_prime * normal_direction[1] + v2_prime * normal_direction[2] + - v3_prime * normal_direction[3] - - f1 = v_mean_normal * rho_prime + rho_mean_global * v_prime_normal - f2 = v_mean_normal * v1_prime + normal_direction[1] * p_prime / rho_mean_global - f3 = v_mean_normal * v2_prime + normal_direction[2] * p_prime / rho_mean_global - f4 = v_mean_normal * v3_prime + normal_direction[3] * p_prime / rho_mean_global - f5 = v_mean_normal * p_prime + c_mean_global^2 * rho_mean_global * v_prime_normal - - return SVector(f1, f2, f3, f4, f5) -end - -@inline have_constant_speed(::LinearizedEulerEquations3D) = True() - -@inline function max_abs_speeds(equations::LinearizedEulerEquations3D) - @unpack v_mean_global, c_mean_global = equations - return abs(v_mean_global[1]) + c_mean_global, abs(v_mean_global[2]) + c_mean_global, - abs(v_mean_global[3]) + c_mean_global -end - -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations3D) - @unpack v_mean_global, c_mean_global = equations - if orientation == 1 - return abs(v_mean_global[1]) + c_mean_global - elseif orientation == 2 - return abs(v_mean_global[2]) + c_mean_global - else # orientation == 3 - return abs(v_mean_global[3]) + c_mean_global - end -end - -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::LinearizedEulerEquations3D) - @unpack v_mean_global, c_mean_global = equations - v_mean_normal = normal_direction[1] * v_mean_global[1] + - normal_direction[2] * v_mean_global[2] + - normal_direction[3] * v_mean_global[3] - return abs(v_mean_normal) + c_mean_global * norm(normal_direction) -end - -# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations3D) - min_max_speed_davis(u_ll, u_rr, orientation, equations) -end - -@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::LinearizedEulerEquations3D) - min_max_speed_davis(u_ll, u_rr, normal_direction, equations) -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::LinearizedEulerEquations3D) - @unpack v_mean_global, c_mean_global = equations - - λ_min = v_mean_global[orientation] - c_mean_global - λ_max = v_mean_global[orientation] + c_mean_global - - return λ_min, λ_max -end - -@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, - equations::LinearizedEulerEquations3D) - @unpack v_mean_global, c_mean_global = equations - - norm_ = norm(normal_direction) - - v_normal = v_mean_global[1] * normal_direction[1] + - v_mean_global[2] * normal_direction[2] + - v_mean_global[3] * normal_direction[3] - - # The v_normals are already scaled by the norm - λ_min = v_normal - c_mean_global * norm_ - λ_max = v_normal + c_mean_global * norm_ - - return λ_min, λ_max -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equations::LinearizedEulerEquations3D) = u -@inline cons2entropy(u, ::LinearizedEulerEquations3D) = u + #! format: noindent + + @doc raw""" + LinearizedEulerEquations3D(v_mean_global, c_mean_global, rho_mean_global) + + Linearized Euler equations in three space dimensions. The equations are given by + ```math + \partial_t + \begin{pmatrix} + \rho' \\ v_1' \\ v_2' \\ v_3' \\ p' + \end{pmatrix} + + + \partial_x + \begin{pmatrix} + \bar{\rho} v_1' + \bar{v_1} \rho ' \\ + \bar{v_1} v_1' + \frac{p'}{\bar{\rho}} \\ + \bar{v_1} v_2' \\ + \bar{v_1} v_3' \\ + \bar{v_1} p' + c^2 \bar{\rho} v_1' + \end{pmatrix} + + + \partial_y + \begin{pmatrix} + \bar{\rho} v_2' + \bar{v_2} \rho ' \\ + \bar{v_2} v_1' \\ + \bar{v_2} v_2' + \frac{p'}{\bar{\rho}} \\ + \bar{v_2} v_3' \\ + \bar{v_2} p' + c^2 \bar{\rho} v_2' + \end{pmatrix} + + + \partial_z + \begin{pmatrix} + \bar{\rho} v_3' + \bar{v_3} \rho ' \\ + \bar{v_3} v_1' \\ + \bar{v_3} v_2' \\ + \bar{v_3} v_3' + \frac{p'}{\bar{\rho}} \\ + \bar{v_3} p' + c^2 \bar{\rho} v_3' + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 \\ 0 \\ 0 \\ 0 + \end{pmatrix} + ``` + The bar ``\bar{(\cdot)}`` indicates uniform mean flow variables and ``c`` is the speed of sound. + The unknowns are the acoustic velocities ``v' = (v_1', v_2, v_3')``, the pressure ``p'`` and the density ``\rho'``. + """ + struct LinearizedEulerEquations3D{RealT <: Real} <: + AbstractLinearizedEulerEquations{3, 5} + v_mean_global::SVector{3, RealT} + c_mean_global::RealT + rho_mean_global::RealT + end + + function LinearizedEulerEquations3D( + v_mean_global::NTuple{3, <:Real}, + c_mean_global::Real, rho_mean_global::Real + ) + if rho_mean_global < 0 + throw(ArgumentError("rho_mean_global must be non-negative")) + elseif c_mean_global < 0 + throw(ArgumentError("c_mean_global must be non-negative")) + end + + return LinearizedEulerEquations3D( + SVector(v_mean_global), c_mean_global, + rho_mean_global + ) + end + + function LinearizedEulerEquations3D(; + v_mean_global::NTuple{3, <:Real}, + c_mean_global::Real, rho_mean_global::Real + ) + return LinearizedEulerEquations3D( + v_mean_global, c_mean_global, + rho_mean_global + ) + end + + function varnames(::typeof(cons2cons), ::LinearizedEulerEquations3D) + ("rho_prime", "v1_prime", "v2_prime", "v3_prime", "p_prime") + end + function varnames(::typeof(cons2prim), ::LinearizedEulerEquations3D) + ("rho_prime", "v1_prime", "v2_prime", "v3_prime", "p_prime") + end + + """ + initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations3D) + + A smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test(x, t, equations::LinearizedEulerEquations3D) + rho_prime = -cospi(2 * t) * (sinpi(2 * x[1]) + sinpi(2 * x[2]) + sinpi(2 * x[3])) + v1_prime = sinpi(2 * t) * cospi(2 * x[1]) + v2_prime = sinpi(2 * t) * cospi(2 * x[2]) + v3_prime = sinpi(2 * t) * cospi(2 * x[3]) + p_prime = rho_prime + + return SVector(rho_prime, v1_prime, v2_prime, v3_prime, p_prime) + end + + """ + boundary_condition_wall(u_inner, orientation, direction, x, t, surface_flux_function, + equations::LinearizedEulerEquations3D) + + Boundary conditions for a solid wall. + """ + function boundary_condition_wall( + u_inner, orientation, direction, x, t, + surface_flux_function, + equations::LinearizedEulerEquations3D + ) + # Boundary state is equal to the inner state except for the velocity. For boundaries + # in the -x/+x direction, we multiply the velocity in the x direction by -1. + # Similarly, for boundaries in the -y/+y or -z/+z direction, we multiply the + # velocity in the y or z direction by -1 + if direction in (1, 2) # x direction + u_boundary = SVector( + u_inner[1], -u_inner[2], u_inner[3], u_inner[4], + u_inner[5] + ) + elseif direction in (3, 4) # y direction + u_boundary = SVector( + u_inner[1], u_inner[2], -u_inner[3], u_inner[4], + u_inner[5] + ) + else # z direction = (5, 6) + u_boundary = SVector( + u_inner[1], u_inner[2], u_inner[3], -u_inner[4], + u_inner[5] + ) + end + + # Calculate boundary flux depending on the orientation of the boundary + # Odd directions are in negative coordinate direction, + # even directions are in positive coordinate direction. + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end + + # Calculate 3D flux for a single point + @inline function flux(u, orientation::Integer, equations::LinearizedEulerEquations3D) + @unpack v_mean_global, c_mean_global, rho_mean_global = equations + rho_prime, v1_prime, v2_prime, v3_prime, p_prime = u + if orientation == 1 + f1 = v_mean_global[1] * rho_prime + rho_mean_global * v1_prime + f2 = v_mean_global[1] * v1_prime + p_prime / rho_mean_global + f3 = v_mean_global[1] * v2_prime + f4 = v_mean_global[1] * v3_prime + f5 = v_mean_global[1] * p_prime + c_mean_global^2 * rho_mean_global * v1_prime + elseif orientation == 2 + f1 = v_mean_global[2] * rho_prime + rho_mean_global * v2_prime + f2 = v_mean_global[2] * v1_prime + f3 = v_mean_global[2] * v2_prime + p_prime / rho_mean_global + f4 = v_mean_global[2] * v3_prime + f5 = v_mean_global[2] * p_prime + c_mean_global^2 * rho_mean_global * v2_prime + else # orientation == 3 + f1 = v_mean_global[3] * rho_prime + rho_mean_global * v3_prime + f2 = v_mean_global[3] * v1_prime + f3 = v_mean_global[3] * v2_prime + f4 = v_mean_global[3] * v3_prime + p_prime / rho_mean_global + f5 = v_mean_global[3] * p_prime + c_mean_global^2 * rho_mean_global * v3_prime + end + + return SVector(f1, f2, f3, f4, f5) + end + + # Calculate 3D flux for a single point + @inline function flux( + u, normal_direction::AbstractVector, + equations::LinearizedEulerEquations3D + ) + @unpack v_mean_global, c_mean_global, rho_mean_global = equations + rho_prime, v1_prime, v2_prime, v3_prime, p_prime = u + + v_mean_normal = v_mean_global[1] * normal_direction[1] + + v_mean_global[2] * normal_direction[2] + + v_mean_global[3] * normal_direction[3] + v_prime_normal = v1_prime * normal_direction[1] + v2_prime * normal_direction[2] + + v3_prime * normal_direction[3] + + f1 = v_mean_normal * rho_prime + rho_mean_global * v_prime_normal + f2 = v_mean_normal * v1_prime + normal_direction[1] * p_prime / rho_mean_global + f3 = v_mean_normal * v2_prime + normal_direction[2] * p_prime / rho_mean_global + f4 = v_mean_normal * v3_prime + normal_direction[3] * p_prime / rho_mean_global + f5 = v_mean_normal * p_prime + c_mean_global^2 * rho_mean_global * v_prime_normal + + return SVector(f1, f2, f3, f4, f5) + end + + @inline have_constant_speed(::LinearizedEulerEquations3D) = True() + + @inline function max_abs_speeds(equations::LinearizedEulerEquations3D) + @unpack v_mean_global, c_mean_global = equations + return abs(v_mean_global[1]) + c_mean_global, abs(v_mean_global[2]) + c_mean_global, + abs(v_mean_global[3]) + c_mean_global + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations3D + ) + @unpack v_mean_global, c_mean_global = equations + if orientation == 1 + return abs(v_mean_global[1]) + c_mean_global + elseif orientation == 2 + return abs(v_mean_global[2]) + c_mean_global + else # orientation == 3 + return abs(v_mean_global[3]) + c_mean_global + end + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations3D + ) + @unpack v_mean_global, c_mean_global = equations + v_mean_normal = normal_direction[1] * v_mean_global[1] + + normal_direction[2] * v_mean_global[2] + + normal_direction[3] * v_mean_global[3] + return abs(v_mean_normal) + c_mean_global * norm(normal_direction) + end + + # Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations3D + ) + min_max_speed_davis(u_ll, u_rr, orientation, equations) + end + + @inline function min_max_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations3D + ) + min_max_speed_davis(u_ll, u_rr, normal_direction, equations) + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::LinearizedEulerEquations3D + ) + @unpack v_mean_global, c_mean_global = equations + + λ_min = v_mean_global[orientation] - c_mean_global + λ_max = v_mean_global[orientation] + c_mean_global + + return λ_min, λ_max + end + + @inline function min_max_speed_davis( + u_ll, u_rr, normal_direction::AbstractVector, + equations::LinearizedEulerEquations3D + ) + @unpack v_mean_global, c_mean_global = equations + + norm_ = norm(normal_direction) + + v_normal = v_mean_global[1] * normal_direction[1] + + v_mean_global[2] * normal_direction[2] + + v_mean_global[3] * normal_direction[3] + + # The v_normals are already scaled by the norm + λ_min = v_normal - c_mean_global * norm_ + λ_max = v_normal + c_mean_global * norm_ + + return λ_min, λ_max + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equations::LinearizedEulerEquations3D) = u + @inline cons2entropy(u, ::LinearizedEulerEquations3D) = u end # muladd diff --git a/src/equations/maxwell_1d.jl b/src/equations/maxwell_1d.jl index 096fe3946c..97644502c0 100644 --- a/src/equations/maxwell_1d.jl +++ b/src/equations/maxwell_1d.jl @@ -3,102 +3,110 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - MaxwellEquations1D(c = 299_792_458.0) - -The Maxwell equations of electro dynamics -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -E \\ B -\end{pmatrix} -+ -\frac{\partial}{\partial x} -\begin{pmatrix} -c^2 B \\ E -\end{pmatrix} -= -\begin{pmatrix} -0 \\ 0 -\end{pmatrix} -``` -in one dimension with speed of light `c = 299792458 m/s` (in vacuum). -In one dimension the Maxwell equations reduce to a wave equation. -The orthogonal magnetic (e.g.`B_y`) and electric field (`E_z`) propagate as waves -through the domain in `x`-direction. -For reference, see -- e.g. p.15 of Numerical Methods for Conservation Laws: From Analysis to Algorithms - https://doi.org/10.1137/1.9781611975109 - -- or equation (1) in https://inria.hal.science/hal-01720293/document -""" -struct MaxwellEquations1D{RealT <: Real} <: AbstractMaxwellEquations{1, 2} - speed_of_light::RealT # c - - function MaxwellEquations1D(c::Real = 299_792_458.0) - new{typeof(c)}(c) + #! format: noindent + + @doc raw""" + MaxwellEquations1D(c = 299_792_458.0) + + The Maxwell equations of electro dynamics + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + E \\ B + \end{pmatrix} + + + \frac{\partial}{\partial x} + \begin{pmatrix} + c^2 B \\ E + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 + \end{pmatrix} + ``` + in one dimension with speed of light `c = 299792458 m/s` (in vacuum). + In one dimension the Maxwell equations reduce to a wave equation. + The orthogonal magnetic (e.g.`B_y`) and electric field (`E_z`) propagate as waves + through the domain in `x`-direction. + For reference, see + - e.g. p.15 of Numerical Methods for Conservation Laws: From Analysis to Algorithms + https://doi.org/10.1137/1.9781611975109 + + - or equation (1) in https://inria.hal.science/hal-01720293/document + """ + struct MaxwellEquations1D{RealT <: Real} <: AbstractMaxwellEquations{1, 2} + speed_of_light::RealT # c + + function MaxwellEquations1D(c::Real = 299_792_458.0) + new{typeof(c)}(c) + end end -end - -function varnames(::typeof(cons2cons), ::MaxwellEquations1D) - ("E", "B") -end -function varnames(::typeof(cons2prim), ::MaxwellEquations1D) - ("E", "B") -end - -""" - initial_condition_convergence_test(x, t, equations::MaxwellEquations1D) - -A smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, equations::MaxwellEquations1D) - c = equations.speed_of_light - char_pos = c * t + x[1] - - sin_char_pos = sinpi(2 * char_pos) - - E = -c * sin_char_pos - B = sin_char_pos - - return SVector(E, B) -end - -# Calculate 1D flux for a single point -@inline function flux(u, orientation::Integer, - equations::MaxwellEquations1D) - E, B = u - return SVector(equations.speed_of_light^2 * B, E) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Int, - equations::MaxwellEquations1D) - λ_max = equations.speed_of_light -end - -@inline have_constant_speed(::MaxwellEquations1D) = True() - -@inline function max_abs_speeds(equations::MaxwellEquations1D) - return equations.speed_of_light -end - -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::MaxwellEquations1D) - min_max_speed_davis(u_ll, u_rr, orientation, equations) -end - -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::MaxwellEquations1D) - λ_min = -equations.speed_of_light - λ_max = equations.speed_of_light - - return λ_min, λ_max -end - -# Convert conservative variables to primitive -@inline cons2prim(u, ::MaxwellEquations1D) = u -@inline cons2entropy(u, ::MaxwellEquations1D) = u + + function varnames(::typeof(cons2cons), ::MaxwellEquations1D) + ("E", "B") + end + function varnames(::typeof(cons2prim), ::MaxwellEquations1D) + ("E", "B") + end + + """ + initial_condition_convergence_test(x, t, equations::MaxwellEquations1D) + + A smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test(x, t, equations::MaxwellEquations1D) + c = equations.speed_of_light + char_pos = c * t + x[1] + + sin_char_pos = sinpi(2 * char_pos) + + E = -c * sin_char_pos + B = sin_char_pos + + return SVector(E, B) + end + + # Calculate 1D flux for a single point + @inline function flux( + u, orientation::Integer, + equations::MaxwellEquations1D + ) + E, B = u + return SVector(equations.speed_of_light^2 * B, E) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Int, + equations::MaxwellEquations1D + ) + λ_max = equations.speed_of_light + end + + @inline have_constant_speed(::MaxwellEquations1D) = True() + + @inline function max_abs_speeds(equations::MaxwellEquations1D) + return equations.speed_of_light + end + + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::MaxwellEquations1D + ) + min_max_speed_davis(u_ll, u_rr, orientation, equations) + end + + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::MaxwellEquations1D + ) + λ_min = -equations.speed_of_light + λ_max = equations.speed_of_light + + return λ_min, λ_max + end + + # Convert conservative variables to primitive + @inline cons2prim(u, ::MaxwellEquations1D) = u + @inline cons2entropy(u, ::MaxwellEquations1D) = u end # @muladd diff --git a/src/equations/numerical_fluxes.jl b/src/equations/numerical_fluxes.jl index ea75b99b7f..c9e3db8abe 100644 --- a/src/equations/numerical_fluxes.jl +++ b/src/equations/numerical_fluxes.jl @@ -3,443 +3,481 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This file contains general numerical fluxes that are not specific to certain equations - -""" - flux_central(u_ll, u_rr, orientation_or_normal_direction, equations::AbstractEquations) - -The classical central numerical flux `f((u_ll) + f(u_rr)) / 2`. When this flux is -used as volume flux, the discretization is equivalent to the classical weak form -DG method (except floating point errors). -""" -@inline function flux_central(u_ll, u_rr, orientation_or_normal_direction, - equations::AbstractEquations) - # Calculate regular 1D fluxes - f_ll = flux(u_ll, orientation_or_normal_direction, equations) - f_rr = flux(u_rr, orientation_or_normal_direction, equations) - - # Average regular fluxes - return 0.5f0 * (f_ll + f_rr) -end + #! format: noindent + + # This file contains general numerical fluxes that are not specific to certain equations + + """ + flux_central(u_ll, u_rr, orientation_or_normal_direction, equations::AbstractEquations) + + The classical central numerical flux `f((u_ll) + f(u_rr)) / 2`. When this flux is + used as volume flux, the discretization is equivalent to the classical weak form + DG method (except floating point errors). + """ + @inline function flux_central( + u_ll, u_rr, orientation_or_normal_direction, + equations::AbstractEquations + ) + # Calculate regular 1D fluxes + f_ll = flux(u_ll, orientation_or_normal_direction, equations) + f_rr = flux(u_rr, orientation_or_normal_direction, equations) -""" - FluxPlusDissipation(numerical_flux, dissipation) + # Average regular fluxes + return 0.5f0 * (f_ll + f_rr) + end -Combine a `numerical_flux` with a `dissipation` operator to create a new numerical flux. -""" -struct FluxPlusDissipation{NumericalFlux, Dissipation} - numerical_flux::NumericalFlux - dissipation::Dissipation -end + """ + FluxPlusDissipation(numerical_flux, dissipation) -@inline function (numflux::FluxPlusDissipation)(u_ll, u_rr, - orientation_or_normal_direction, - equations) - @unpack numerical_flux, dissipation = numflux - - return (numerical_flux(u_ll, u_rr, orientation_or_normal_direction, equations) - + - dissipation(u_ll, u_rr, orientation_or_normal_direction, equations)) -end - -function Base.show(io::IO, f::FluxPlusDissipation) - print(io, "FluxPlusDissipation(", f.numerical_flux, ", ", f.dissipation, ")") -end - -""" - FluxRotated(numerical_flux) - -Compute a `numerical_flux` flux in direction of a normal vector by rotating the solution, -computing the numerical flux in x-direction, and rotating the calculated flux back. - -Requires a rotationally invariant equation with equation-specific functions -[`rotate_to_x`](@ref) and [`rotate_from_x`](@ref). -""" -struct FluxRotated{NumericalFlux} - numerical_flux::NumericalFlux -end - -# Rotated surface flux computation (2D version) -@inline function (flux_rotated::FluxRotated)(u, - normal_direction::AbstractVector, - equations::AbstractEquations{2}) - @unpack numerical_flux = flux_rotated - - norm_ = norm(normal_direction) - # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later - normal_vector = normal_direction / norm_ - - u_rotated = rotate_to_x(u, normal_vector, equations) - - f = numerical_flux(u_rotated, 1, equations) - - return rotate_from_x(f, normal_vector, equations) * norm_ -end - -# Rotated surface flux computation (2D version) -@inline function (flux_rotated::FluxRotated)(u_ll, u_rr, - normal_direction::AbstractVector, - equations::AbstractEquations{2}) - @unpack numerical_flux = flux_rotated - - norm_ = norm(normal_direction) - # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later - normal_vector = normal_direction / norm_ - - u_ll_rotated = rotate_to_x(u_ll, normal_vector, equations) - u_rr_rotated = rotate_to_x(u_rr, normal_vector, equations) - - f = numerical_flux(u_ll_rotated, u_rr_rotated, 1, equations) - - return rotate_from_x(f, normal_vector, equations) * norm_ -end - -# Rotated surface flux computation (3D version) -@inline function (flux_rotated::FluxRotated)(u_ll, u_rr, - normal_direction::AbstractVector, - equations::AbstractEquations{3}) - @unpack numerical_flux = flux_rotated - - # Storing these vectors could increase the performance by 20 percent - norm_ = norm(normal_direction) - # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later - normal_vector = normal_direction / norm_ - - # Some vector that can't be identical to normal_vector (unless normal_vector == 0) - tangent1 = SVector(normal_direction[2], normal_direction[3], -normal_direction[1]) - # Orthogonal projection - tangent1 -= dot(normal_vector, tangent1) * normal_vector - tangent1 = normalize(tangent1) - - # Third orthogonal vector - tangent2 = normalize(cross(normal_direction, tangent1)) - - u_ll_rotated = rotate_to_x(u_ll, normal_vector, tangent1, tangent2, equations) - u_rr_rotated = rotate_to_x(u_rr, normal_vector, tangent1, tangent2, equations) - - f = numerical_flux(u_ll_rotated, u_rr_rotated, 1, equations) - - return rotate_from_x(f, normal_vector, tangent1, tangent2, equations) * norm_ -end - -Base.show(io::IO, f::FluxRotated) = print(io, "FluxRotated(", f.numerical_flux, ")") - -""" - DissipationGlobalLaxFriedrichs(λ) - -Create a global Lax-Friedrichs dissipation operator with dissipation coefficient `λ`. -""" -struct DissipationGlobalLaxFriedrichs{RealT} - λ::RealT -end - -@inline function (dissipation::DissipationGlobalLaxFriedrichs)(u_ll, u_rr, - orientation::Integer, - equations) - @unpack λ = dissipation - return -λ / 2 * (u_rr - u_ll) -end - -@inline function (dissipation::DissipationGlobalLaxFriedrichs)(u_ll, u_rr, - normal_direction::AbstractVector, - equations) - @unpack λ = dissipation - return -λ / 2 * norm(normal_direction) * (u_rr - u_ll) -end - -function Base.show(io::IO, d::DissipationGlobalLaxFriedrichs) - print(io, "DissipationGlobalLaxFriedrichs(", d.λ, ")") -end - -""" - DissipationLocalLaxFriedrichs(max_abs_speed=max_abs_speed_naive) - -Create a local Lax-Friedrichs dissipation operator where the maximum absolute wave speed -is estimated as -`max_abs_speed(u_ll, u_rr, orientation_or_normal_direction, equations)`, -defaulting to [`max_abs_speed_naive`](@ref). -""" -struct DissipationLocalLaxFriedrichs{MaxAbsSpeed} - max_abs_speed::MaxAbsSpeed -end - -DissipationLocalLaxFriedrichs() = DissipationLocalLaxFriedrichs(max_abs_speed_naive) - -@inline function (dissipation::DissipationLocalLaxFriedrichs)(u_ll, u_rr, - orientation_or_normal_direction, - equations) - λ = dissipation.max_abs_speed(u_ll, u_rr, orientation_or_normal_direction, - equations) - return -0.5f0 * λ * (u_rr - u_ll) -end - -function Base.show(io::IO, d::DissipationLocalLaxFriedrichs) - print(io, "DissipationLocalLaxFriedrichs(", d.max_abs_speed, ")") -end - -""" - max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations) - max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, equations) - -Simple and fast estimate of the maximal wave speed of the Riemann problem with left and right states -`u_ll, u_rr`, based only on the local wave speeds associated to `u_ll` and `u_rr`. - -For non-integer arguments `normal_direction` in one dimension, `max_abs_speed_naive` returns -`abs(normal_direction[1]) * max_abs_speed_naive(u_ll, u_rr, 1, equations)`. -""" -function max_abs_speed_naive end - -# for non-integer `orientation_or_normal` arguments. -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::AbstractEquations{1}) - return abs(normal_direction[1]) * max_abs_speed_naive(u_ll, u_rr, 1, equations) -end - -const FluxLaxFriedrichs{MaxAbsSpeed} = FluxPlusDissipation{typeof(flux_central), - DissipationLocalLaxFriedrichs{MaxAbsSpeed}} -""" - FluxLaxFriedrichs(max_abs_speed=max_abs_speed_naive) - -Local Lax-Friedrichs (Rusanov) flux with maximum wave speed estimate provided by -`max_abs_speed`, cf. [`DissipationLocalLaxFriedrichs`](@ref) and -[`max_abs_speed_naive`](@ref). -""" -function FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive) - FluxPlusDissipation(flux_central, DissipationLocalLaxFriedrichs(max_abs_speed)) -end - -function Base.show(io::IO, f::FluxLaxFriedrichs) - print(io, "FluxLaxFriedrichs(", f.dissipation.max_abs_speed, ")") -end - -""" - flux_lax_friedrichs - -See [`FluxLaxFriedrichs`](@ref). -""" -const flux_lax_friedrichs = FluxLaxFriedrichs() - -""" - FluxHLL(min_max_speed=min_max_speed_davis) - -Create an HLL (Harten, Lax, van Leer) numerical flux where the minimum and maximum -wave speeds are estimated as -`λ_min, λ_max = min_max_speed(u_ll, u_rr, orientation_or_normal_direction, equations)`, -defaulting to [`min_max_speed_davis`](@ref). -Original paper: -- Amiram Harten, Peter D. Lax, Bram van Leer (1983) - On Upstream Differencing and Godunov-Type Schemes for Hyperbolic Conservation Laws - [DOI: 10.1137/1025002](https://doi.org/10.1137/1025002) -""" -struct FluxHLL{MinMaxSpeed} - min_max_speed::MinMaxSpeed -end - -FluxHLL() = FluxHLL(min_max_speed_davis) - -""" - min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations) - min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, equations) - -Simple and fast estimate(!) of the minimal and maximal wave speed of the Riemann problem with -left and right states `u_ll, u_rr`, usually based only on the local wave speeds associated to -`u_ll` and `u_rr`. -Slightly more diffusive than [`min_max_speed_davis`](@ref). -- Amiram Harten, Peter D. Lax, Bram van Leer (1983) - On Upstream Differencing and Godunov-Type Schemes for Hyperbolic Conservation Laws - [DOI: 10.1137/1025002](https://doi.org/10.1137/1025002) - -See eq. (10.37) from -- Eleuterio F. Toro (2009) - Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction - [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) - -See also [`FluxHLL`](@ref), [`min_max_speed_davis`](@ref), [`min_max_speed_einfeldt`](@ref). -""" -function min_max_speed_naive end - -""" - min_max_speed_davis(u_ll, u_rr, orientation::Integer, equations) - min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, equations) - -Simple and fast estimates of the minimal and maximal wave speed of the Riemann problem with -left and right states `u_ll, u_rr`, usually based only on the local wave speeds associated to -`u_ll` and `u_rr`. - -- S.F. Davis (1988) - Simplified Second-Order Godunov-Type Methods - [DOI: 10.1137/0909030](https://doi.org/10.1137/0909030) - -See eq. (10.38) from -- Eleuterio F. Toro (2009) - Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction - [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) -See also [`FluxHLL`](@ref), [`min_max_speed_naive`](@ref), [`min_max_speed_einfeldt`](@ref). -""" -function min_max_speed_davis end - -""" - min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, equations) - min_max_speed_einfeldt(u_ll, u_rr, normal_direction::AbstractVector, equations) - -More advanced mininmal and maximal wave speed computation based on -- Bernd Einfeldt (1988) - On Godunov-type methods for gas dynamics. - [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) -- Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) - On Godunov-type methods near low densities. - [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) - -originally developed for the compressible Euler equations. -A compact representation can be found in [this lecture notes, eq. (9.28)](https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf). - -See also [`FluxHLL`](@ref), [`min_max_speed_naive`](@ref), [`min_max_speed_davis`](@ref). -""" -function min_max_speed_einfeldt end - -@inline function (numflux::FluxHLL)(u_ll, u_rr, orientation_or_normal_direction, - equations) - λ_min, λ_max = numflux.min_max_speed(u_ll, u_rr, orientation_or_normal_direction, - equations) - - if λ_min >= 0 && λ_max >= 0 - return flux(u_ll, orientation_or_normal_direction, equations) - elseif λ_max <= 0 && λ_min <= 0 - return flux(u_rr, orientation_or_normal_direction, equations) - else - f_ll = flux(u_ll, orientation_or_normal_direction, equations) - f_rr = flux(u_rr, orientation_or_normal_direction, equations) - inv_λ_max_minus_λ_min = inv(λ_max - λ_min) - factor_ll = λ_max * inv_λ_max_minus_λ_min - factor_rr = λ_min * inv_λ_max_minus_λ_min - factor_diss = λ_min * λ_max * inv_λ_max_minus_λ_min - return factor_ll * f_ll - factor_rr * f_rr + factor_diss * (u_rr - u_ll) + Combine a `numerical_flux` with a `dissipation` operator to create a new numerical flux. + """ + struct FluxPlusDissipation{NumericalFlux, Dissipation} + numerical_flux::NumericalFlux + dissipation::Dissipation + end + + @inline function (numflux::FluxPlusDissipation)( + u_ll, u_rr, + orientation_or_normal_direction, + equations + ) + @unpack numerical_flux, dissipation = numflux + + return ( + numerical_flux(u_ll, u_rr, orientation_or_normal_direction, equations) + + + dissipation(u_ll, u_rr, orientation_or_normal_direction, equations) + ) + end + + function Base.show(io::IO, f::FluxPlusDissipation) + print(io, "FluxPlusDissipation(", f.numerical_flux, ", ", f.dissipation, ")") + end + + """ + FluxRotated(numerical_flux) + + Compute a `numerical_flux` flux in direction of a normal vector by rotating the solution, + computing the numerical flux in x-direction, and rotating the calculated flux back. + + Requires a rotationally invariant equation with equation-specific functions + [`rotate_to_x`](@ref) and [`rotate_from_x`](@ref). + """ + struct FluxRotated{NumericalFlux} + numerical_flux::NumericalFlux + end + + # Rotated surface flux computation (2D version) + @inline function (flux_rotated::FluxRotated)( + u, + normal_direction::AbstractVector, + equations::AbstractEquations{2} + ) + @unpack numerical_flux = flux_rotated + + norm_ = norm(normal_direction) + # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later + normal_vector = normal_direction / norm_ + + u_rotated = rotate_to_x(u, normal_vector, equations) + + f = numerical_flux(u_rotated, 1, equations) + + return rotate_from_x(f, normal_vector, equations) * norm_ + end + + # Rotated surface flux computation (2D version) + @inline function (flux_rotated::FluxRotated)( + u_ll, u_rr, + normal_direction::AbstractVector, + equations::AbstractEquations{2} + ) + @unpack numerical_flux = flux_rotated + + norm_ = norm(normal_direction) + # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later + normal_vector = normal_direction / norm_ + + u_ll_rotated = rotate_to_x(u_ll, normal_vector, equations) + u_rr_rotated = rotate_to_x(u_rr, normal_vector, equations) + + f = numerical_flux(u_ll_rotated, u_rr_rotated, 1, equations) + + return rotate_from_x(f, normal_vector, equations) * norm_ + end + + # Rotated surface flux computation (3D version) + @inline function (flux_rotated::FluxRotated)( + u_ll, u_rr, + normal_direction::AbstractVector, + equations::AbstractEquations{3} + ) + @unpack numerical_flux = flux_rotated + + # Storing these vectors could increase the performance by 20 percent + norm_ = norm(normal_direction) + # Normalize the vector without using `normalize` since we need to multiply by the `norm_` later + normal_vector = normal_direction / norm_ + + # Some vector that can't be identical to normal_vector (unless normal_vector == 0) + tangent1 = SVector(normal_direction[2], normal_direction[3], -normal_direction[1]) + # Orthogonal projection + tangent1 -= dot(normal_vector, tangent1) * normal_vector + tangent1 = normalize(tangent1) + + # Third orthogonal vector + tangent2 = normalize(cross(normal_direction, tangent1)) + + u_ll_rotated = rotate_to_x(u_ll, normal_vector, tangent1, tangent2, equations) + u_rr_rotated = rotate_to_x(u_rr, normal_vector, tangent1, tangent2, equations) + + f = numerical_flux(u_ll_rotated, u_rr_rotated, 1, equations) + + return rotate_from_x(f, normal_vector, tangent1, tangent2, equations) * norm_ end -end - -Base.show(io::IO, numflux::FluxHLL) = print(io, "FluxHLL(", numflux.min_max_speed, ")") - -""" - flux_hll - -See [`FluxHLL`](@ref). -""" -const flux_hll = FluxHLL() - -""" - flux_hlle - -See [`min_max_speed_einfeldt`](@ref). -This is a [`FluxHLL`](@ref)-type two-wave solver with special estimates of the wave speeds. -""" -const flux_hlle = FluxHLL(min_max_speed_einfeldt) - -""" - flux_shima_etal_turbo(u_ll, u_rr, orientation_or_normal_direction, equations) - -Equivalent to [`flux_shima_etal`](@ref) except that it may use specialized -methods, e.g., when used with [`VolumeIntegralFluxDifferencing`](@ref). -These specialized methods may enable better use of SIMD instructions to -increase runtime efficiency on modern hardware. -""" -@inline function flux_shima_etal_turbo(u_ll, u_rr, orientation_or_normal_direction, - equations) - flux_shima_etal(u_ll, u_rr, orientation_or_normal_direction, equations) -end - -""" - flux_ranocha_turbo(u_ll, u_rr, orientation_or_normal_direction, equations) - -Equivalent to [`flux_ranocha`](@ref) except that it may use specialized -methods, e.g., when used with [`VolumeIntegralFluxDifferencing`](@ref). -These specialized methods may enable better use of SIMD instructions to -increase runtime efficiency on modern hardware. -""" -@inline function flux_ranocha_turbo(u_ll, u_rr, orientation_or_normal_direction, - equations) - flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, equations) -end - -""" - FluxHydrostaticReconstruction(numerical_flux, hydrostatic_reconstruction) - -!!! warning "Experimental code" - This numerical flux is experimental and may change in any future release. - -Allow for some kind of hydrostatic reconstruction of the solution state prior to the -surface flux computation. This is a particular strategy to ensure that the method remains -well-balanced for the shallow water equations, see [`ShallowWaterEquations1D`](@ref) -or [`ShallowWaterEquations2D`](@ref). - -For example, the hydrostatic reconstruction from Audusse et al. is implemented -in one and two spatial dimensions, see [`hydrostatic_reconstruction_audusse_etal`](@ref) or -the original paper -- Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) - A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows - [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) - -Other hydrostatic reconstruction techniques are available, particularly to handle wet / dry -fronts. A good overview of the development and application of hydrostatic reconstruction can be found in -- Guoxian Chen and Sebastian Noelle - A unified surface-gradient and hydrostatic reconstruction scheme for the shallow water equations (2021) - [RWTH Aachen preprint](https://www.igpm.rwth-aachen.de/forschung/preprints/517) -- Andreas Buttinger-Kreuzhuber, Zsolt Horváth, Sebastian Noelle, Günter Blöschl and Jürgen Waser (2019) - A fast second-order shallow water scheme on two-dimensional structured grids over abrupt topography - [DOI: 10.1016/j.advwatres.2019.03.010](https://doi.org/10.1016/j.advwatres.2019.03.010) -""" -struct FluxHydrostaticReconstruction{NumericalFlux, HydrostaticReconstruction} - numerical_flux::NumericalFlux - hydrostatic_reconstruction::HydrostaticReconstruction -end - -@inline function (numflux::FluxHydrostaticReconstruction)(u_ll, u_rr, - orientation_or_normal_direction, - equations::AbstractEquations) - @unpack numerical_flux, hydrostatic_reconstruction = numflux - - # Create the reconstructed left/right solution states in conservative form - u_ll_star, u_rr_star = hydrostatic_reconstruction(u_ll, u_rr, equations) - - # Use the reconstructed states to compute the numerical surface flux - return numerical_flux(u_ll_star, u_rr_star, orientation_or_normal_direction, - equations) -end - -""" - FluxUpwind(splitting) - -A numerical flux `f(u_left, u_right) = f⁺(u_left) + f⁻(u_right)` based on -flux vector splitting. - -The [`SurfaceIntegralUpwind`](@ref) with a given `splitting` is equivalent to -the [`SurfaceIntegralStrongForm`](@ref) with `FluxUpwind(splitting)` -as numerical flux (up to floating point differences). Note, that -[`SurfaceIntegralUpwind`](@ref) is only available on [`TreeMesh`](@ref). - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. -""" -struct FluxUpwind{Splitting} - splitting::Splitting -end - -@inline function (numflux::FluxUpwind)(u_ll, u_rr, orientation::Int, equations) - @unpack splitting = numflux - fm = splitting(u_rr, Val{:minus}(), orientation, equations) - fp = splitting(u_ll, Val{:plus}(), orientation, equations) - return fm + fp -end - -@inline function (numflux::FluxUpwind)(u_ll, u_rr, - normal_direction::AbstractVector, - equations::AbstractEquations{2}) - @unpack splitting = numflux - f_tilde_m = splitting(u_rr, Val{:minus}(), normal_direction, equations) - f_tilde_p = splitting(u_ll, Val{:plus}(), normal_direction, equations) - return f_tilde_m + f_tilde_p -end - -Base.show(io::IO, f::FluxUpwind) = print(io, "FluxUpwind(", f.splitting, ")") + + Base.show(io::IO, f::FluxRotated) = print(io, "FluxRotated(", f.numerical_flux, ")") + + """ + DissipationGlobalLaxFriedrichs(λ) + + Create a global Lax-Friedrichs dissipation operator with dissipation coefficient `λ`. + """ + struct DissipationGlobalLaxFriedrichs{RealT} + λ::RealT + end + + @inline function (dissipation::DissipationGlobalLaxFriedrichs)( + u_ll, u_rr, + orientation::Integer, + equations + ) + @unpack λ = dissipation + return -λ / 2 * (u_rr - u_ll) + end + + @inline function (dissipation::DissipationGlobalLaxFriedrichs)( + u_ll, u_rr, + normal_direction::AbstractVector, + equations + ) + @unpack λ = dissipation + return -λ / 2 * norm(normal_direction) * (u_rr - u_ll) + end + + function Base.show(io::IO, d::DissipationGlobalLaxFriedrichs) + print(io, "DissipationGlobalLaxFriedrichs(", d.λ, ")") + end + + """ + DissipationLocalLaxFriedrichs(max_abs_speed=max_abs_speed_naive) + + Create a local Lax-Friedrichs dissipation operator where the maximum absolute wave speed + is estimated as + `max_abs_speed(u_ll, u_rr, orientation_or_normal_direction, equations)`, + defaulting to [`max_abs_speed_naive`](@ref). + """ + struct DissipationLocalLaxFriedrichs{MaxAbsSpeed} + max_abs_speed::MaxAbsSpeed + end + + DissipationLocalLaxFriedrichs() = DissipationLocalLaxFriedrichs(max_abs_speed_naive) + + @inline function (dissipation::DissipationLocalLaxFriedrichs)( + u_ll, u_rr, + orientation_or_normal_direction, + equations + ) + λ = dissipation.max_abs_speed( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + return -0.5f0 * λ * (u_rr - u_ll) + end + + function Base.show(io::IO, d::DissipationLocalLaxFriedrichs) + print(io, "DissipationLocalLaxFriedrichs(", d.max_abs_speed, ")") + end + + """ + max_abs_speed_naive(u_ll, u_rr, orientation::Integer, equations) + max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, equations) + + Simple and fast estimate of the maximal wave speed of the Riemann problem with left and right states + `u_ll, u_rr`, based only on the local wave speeds associated to `u_ll` and `u_rr`. + + For non-integer arguments `normal_direction` in one dimension, `max_abs_speed_naive` returns + `abs(normal_direction[1]) * max_abs_speed_naive(u_ll, u_rr, 1, equations)`. + """ + function max_abs_speed_naive end + + # for non-integer `orientation_or_normal` arguments. + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::AbstractEquations{1} + ) + return abs(normal_direction[1]) * max_abs_speed_naive(u_ll, u_rr, 1, equations) + end + + const FluxLaxFriedrichs{MaxAbsSpeed} = FluxPlusDissipation{ + typeof(flux_central), + DissipationLocalLaxFriedrichs{MaxAbsSpeed}, + } + """ + FluxLaxFriedrichs(max_abs_speed=max_abs_speed_naive) + + Local Lax-Friedrichs (Rusanov) flux with maximum wave speed estimate provided by + `max_abs_speed`, cf. [`DissipationLocalLaxFriedrichs`](@ref) and + [`max_abs_speed_naive`](@ref). + """ + function FluxLaxFriedrichs(max_abs_speed = max_abs_speed_naive) + FluxPlusDissipation(flux_central, DissipationLocalLaxFriedrichs(max_abs_speed)) + end + + function Base.show(io::IO, f::FluxLaxFriedrichs) + print(io, "FluxLaxFriedrichs(", f.dissipation.max_abs_speed, ")") + end + + """ + flux_lax_friedrichs + + See [`FluxLaxFriedrichs`](@ref). + """ + const flux_lax_friedrichs = FluxLaxFriedrichs() + + """ + FluxHLL(min_max_speed=min_max_speed_davis) + + Create an HLL (Harten, Lax, van Leer) numerical flux where the minimum and maximum + wave speeds are estimated as + `λ_min, λ_max = min_max_speed(u_ll, u_rr, orientation_or_normal_direction, equations)`, + defaulting to [`min_max_speed_davis`](@ref). + Original paper: + - Amiram Harten, Peter D. Lax, Bram van Leer (1983) + On Upstream Differencing and Godunov-Type Schemes for Hyperbolic Conservation Laws + [DOI: 10.1137/1025002](https://doi.org/10.1137/1025002) + """ + struct FluxHLL{MinMaxSpeed} + min_max_speed::MinMaxSpeed + end + + FluxHLL() = FluxHLL(min_max_speed_davis) + + """ + min_max_speed_naive(u_ll, u_rr, orientation::Integer, equations) + min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, equations) + + Simple and fast estimate(!) of the minimal and maximal wave speed of the Riemann problem with + left and right states `u_ll, u_rr`, usually based only on the local wave speeds associated to + `u_ll` and `u_rr`. + Slightly more diffusive than [`min_max_speed_davis`](@ref). + - Amiram Harten, Peter D. Lax, Bram van Leer (1983) + On Upstream Differencing and Godunov-Type Schemes for Hyperbolic Conservation Laws + [DOI: 10.1137/1025002](https://doi.org/10.1137/1025002) + + See eq. (10.37) from + - Eleuterio F. Toro (2009) + Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + + See also [`FluxHLL`](@ref), [`min_max_speed_davis`](@ref), [`min_max_speed_einfeldt`](@ref). + """ + function min_max_speed_naive end + + """ + min_max_speed_davis(u_ll, u_rr, orientation::Integer, equations) + min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, equations) + + Simple and fast estimates of the minimal and maximal wave speed of the Riemann problem with + left and right states `u_ll, u_rr`, usually based only on the local wave speeds associated to + `u_ll` and `u_rr`. + + - S.F. Davis (1988) + Simplified Second-Order Godunov-Type Methods + [DOI: 10.1137/0909030](https://doi.org/10.1137/0909030) + + See eq. (10.38) from + - Eleuterio F. Toro (2009) + Riemann Solvers and Numerical Methods for Fluid Dynamics: A Practical Introduction + [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) + See also [`FluxHLL`](@ref), [`min_max_speed_naive`](@ref), [`min_max_speed_einfeldt`](@ref). + """ + function min_max_speed_davis end + + """ + min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, equations) + min_max_speed_einfeldt(u_ll, u_rr, normal_direction::AbstractVector, equations) + + More advanced mininmal and maximal wave speed computation based on + - Bernd Einfeldt (1988) + On Godunov-type methods for gas dynamics. + [DOI: 10.1137/0725021](https://doi.org/10.1137/0725021) + - Bernd Einfeldt, Claus-Dieter Munz, Philip L. Roe and Björn Sjögreen (1991) + On Godunov-type methods near low densities. + [DOI: 10.1016/0021-9991(91)90211-3](https://doi.org/10.1016/0021-9991(91)90211-3) + + originally developed for the compressible Euler equations. + A compact representation can be found in [this lecture notes, eq. (9.28)](https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf). + + See also [`FluxHLL`](@ref), [`min_max_speed_naive`](@ref), [`min_max_speed_davis`](@ref). + """ + function min_max_speed_einfeldt end + + @inline function (numflux::FluxHLL)( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + λ_min, λ_max = numflux.min_max_speed( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + + if λ_min >= 0 && λ_max >= 0 + return flux(u_ll, orientation_or_normal_direction, equations) + elseif λ_max <= 0 && λ_min <= 0 + return flux(u_rr, orientation_or_normal_direction, equations) + else + f_ll = flux(u_ll, orientation_or_normal_direction, equations) + f_rr = flux(u_rr, orientation_or_normal_direction, equations) + inv_λ_max_minus_λ_min = inv(λ_max - λ_min) + factor_ll = λ_max * inv_λ_max_minus_λ_min + factor_rr = λ_min * inv_λ_max_minus_λ_min + factor_diss = λ_min * λ_max * inv_λ_max_minus_λ_min + return factor_ll * f_ll - factor_rr * f_rr + factor_diss * (u_rr - u_ll) + end + end + + Base.show(io::IO, numflux::FluxHLL) = print(io, "FluxHLL(", numflux.min_max_speed, ")") + + """ + flux_hll + + See [`FluxHLL`](@ref). + """ + const flux_hll = FluxHLL() + + """ + flux_hlle + + See [`min_max_speed_einfeldt`](@ref). + This is a [`FluxHLL`](@ref)-type two-wave solver with special estimates of the wave speeds. + """ + const flux_hlle = FluxHLL(min_max_speed_einfeldt) + + """ + flux_shima_etal_turbo(u_ll, u_rr, orientation_or_normal_direction, equations) + + Equivalent to [`flux_shima_etal`](@ref) except that it may use specialized + methods, e.g., when used with [`VolumeIntegralFluxDifferencing`](@ref). + These specialized methods may enable better use of SIMD instructions to + increase runtime efficiency on modern hardware. + """ + @inline function flux_shima_etal_turbo( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + flux_shima_etal(u_ll, u_rr, orientation_or_normal_direction, equations) + end + + """ + flux_ranocha_turbo(u_ll, u_rr, orientation_or_normal_direction, equations) + + Equivalent to [`flux_ranocha`](@ref) except that it may use specialized + methods, e.g., when used with [`VolumeIntegralFluxDifferencing`](@ref). + These specialized methods may enable better use of SIMD instructions to + increase runtime efficiency on modern hardware. + """ + @inline function flux_ranocha_turbo( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + flux_ranocha(u_ll, u_rr, orientation_or_normal_direction, equations) + end + + """ + FluxHydrostaticReconstruction(numerical_flux, hydrostatic_reconstruction) + + !!! warning "Experimental code" + This numerical flux is experimental and may change in any future release. + + Allow for some kind of hydrostatic reconstruction of the solution state prior to the + surface flux computation. This is a particular strategy to ensure that the method remains + well-balanced for the shallow water equations, see [`ShallowWaterEquations1D`](@ref) + or [`ShallowWaterEquations2D`](@ref). + + For example, the hydrostatic reconstruction from Audusse et al. is implemented + in one and two spatial dimensions, see [`hydrostatic_reconstruction_audusse_etal`](@ref) or + the original paper + - Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) + A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows + [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) + + Other hydrostatic reconstruction techniques are available, particularly to handle wet / dry + fronts. A good overview of the development and application of hydrostatic reconstruction can be found in + - Guoxian Chen and Sebastian Noelle + A unified surface-gradient and hydrostatic reconstruction scheme for the shallow water equations (2021) + [RWTH Aachen preprint](https://www.igpm.rwth-aachen.de/forschung/preprints/517) + - Andreas Buttinger-Kreuzhuber, Zsolt Horváth, Sebastian Noelle, Günter Blöschl and Jürgen Waser (2019) + A fast second-order shallow water scheme on two-dimensional structured grids over abrupt topography + [DOI: 10.1016/j.advwatres.2019.03.010](https://doi.org/10.1016/j.advwatres.2019.03.010) + """ + struct FluxHydrostaticReconstruction{NumericalFlux, HydrostaticReconstruction} + numerical_flux::NumericalFlux + hydrostatic_reconstruction::HydrostaticReconstruction + end + + @inline function (numflux::FluxHydrostaticReconstruction)( + u_ll, u_rr, + orientation_or_normal_direction, + equations::AbstractEquations + ) + @unpack numerical_flux, hydrostatic_reconstruction = numflux + + # Create the reconstructed left/right solution states in conservative form + u_ll_star, u_rr_star = hydrostatic_reconstruction(u_ll, u_rr, equations) + + # Use the reconstructed states to compute the numerical surface flux + return numerical_flux( + u_ll_star, u_rr_star, orientation_or_normal_direction, + equations + ) + end + + """ + FluxUpwind(splitting) + + A numerical flux `f(u_left, u_right) = f⁺(u_left) + f⁻(u_right)` based on + flux vector splitting. + + The [`SurfaceIntegralUpwind`](@ref) with a given `splitting` is equivalent to + the [`SurfaceIntegralStrongForm`](@ref) with `FluxUpwind(splitting)` + as numerical flux (up to floating point differences). Note, that + [`SurfaceIntegralUpwind`](@ref) is only available on [`TreeMesh`](@ref). + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + """ + struct FluxUpwind{Splitting} + splitting::Splitting + end + + @inline function (numflux::FluxUpwind)(u_ll, u_rr, orientation::Int, equations) + @unpack splitting = numflux + fm = splitting(u_rr, Val{:minus}(), orientation, equations) + fp = splitting(u_ll, Val{:plus}(), orientation, equations) + return fm + fp + end + + @inline function (numflux::FluxUpwind)( + u_ll, u_rr, + normal_direction::AbstractVector, + equations::AbstractEquations{2} + ) + @unpack splitting = numflux + f_tilde_m = splitting(u_rr, Val{:minus}(), normal_direction, equations) + f_tilde_p = splitting(u_ll, Val{:plus}(), normal_direction, equations) + return f_tilde_m + f_tilde_p + end + + Base.show(io::IO, f::FluxUpwind) = print(io, "FluxUpwind(", f.splitting, ")") end # @muladd diff --git a/src/equations/polytropic_euler_2d.jl b/src/equations/polytropic_euler_2d.jl index 571d4723f6..4e2aeda095 100644 --- a/src/equations/polytropic_euler_2d.jl +++ b/src/equations/polytropic_euler_2d.jl @@ -3,395 +3,417 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - PolytropicEulerEquations2D(gamma, kappa) - -The polytropic Euler equations -```math -\frac{\partial}{\partial t} -\begin{pmatrix} -\rho \\ \rho v_1 \\ \rho v_2 -\end{pmatrix} -+ -\frac{\partial}{\partial x} -\begin{pmatrix} - \rho v_1 \\ \rho v_1^2 + \kappa\rho^\gamma \\ \rho v_1 v_2 -\end{pmatrix} -+ -\frac{\partial}{\partial y} -\begin{pmatrix} -\rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + \kappa\rho^\gamma -\end{pmatrix} -= -\begin{pmatrix} -0 \\ 0 \\ 0 -\end{pmatrix} -``` -for an ideal gas with ratio of specific heats `gamma` -in two space dimensions. -Here, ``\rho`` is the density and ``v_1`` and`v_2` the velocities and -```math -p = \kappa\rho^\gamma -``` -the pressure, which we replaced using this relation. -""" -struct PolytropicEulerEquations2D{RealT <: Real} <: - AbstractPolytropicEulerEquations{2, 3} - gamma::RealT # ratio of specific heats - kappa::RealT # fluid scaling factor - - function PolytropicEulerEquations2D(gamma, kappa) - gamma_, kappa_ = promote(gamma, kappa) - new{typeof(gamma_)}(gamma_, kappa_) + #! format: noindent + + @doc raw""" + PolytropicEulerEquations2D(gamma, kappa) + + The polytropic Euler equations + ```math + \frac{\partial}{\partial t} + \begin{pmatrix} + \rho \\ \rho v_1 \\ \rho v_2 + \end{pmatrix} + + + \frac{\partial}{\partial x} + \begin{pmatrix} + \rho v_1 \\ \rho v_1^2 + \kappa\rho^\gamma \\ \rho v_1 v_2 + \end{pmatrix} + + + \frac{\partial}{\partial y} + \begin{pmatrix} + \rho v_2 \\ \rho v_1 v_2 \\ \rho v_2^2 + \kappa\rho^\gamma + \end{pmatrix} + = + \begin{pmatrix} + 0 \\ 0 \\ 0 + \end{pmatrix} + ``` + for an ideal gas with ratio of specific heats `gamma` + in two space dimensions. + Here, ``\rho`` is the density and ``v_1`` and`v_2` the velocities and + ```math + p = \kappa\rho^\gamma + ``` + the pressure, which we replaced using this relation. + """ + struct PolytropicEulerEquations2D{RealT <: Real} <: + AbstractPolytropicEulerEquations{2, 3} + gamma::RealT # ratio of specific heats + kappa::RealT # fluid scaling factor + + function PolytropicEulerEquations2D(gamma, kappa) + gamma_, kappa_ = promote(gamma, kappa) + new{typeof(gamma_)}(gamma_, kappa_) + end end -end - -function varnames(::typeof(cons2cons), ::PolytropicEulerEquations2D) - ("rho", "rho_v1", "rho_v2") -end -varnames(::typeof(cons2prim), ::PolytropicEulerEquations2D) = ("rho", "v1", "v2") - -""" - initial_condition_convergence_test(x, t, equations::PolytropicEulerEquations2D) - -Manufactured smooth initial condition used for convergence tests -in combination with [`source_terms_convergence_test`](@ref). -""" -function initial_condition_convergence_test(x, t, equations::PolytropicEulerEquations2D) - # manufactured initial condition from Winters (2019) [0.1007/s10543-019-00789-w] - # domain must be set to [0, 1] x [0, 1] - h = 8 + cospi(2 * x[1]) * sinpi(2 * x[2]) * cospi(2 * t) - - return SVector(h, h / 2, 3 * h / 2) -end - -""" - source_terms_convergence_test(u, x, t, equations::PolytropicEulerEquations2D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::PolytropicEulerEquations2D) - rho, v1, v2 = cons2prim(u, equations) - - # Residual from Winters (2019) [0.1007/s10543-019-00789-w] eq. (5.2). - RealT = eltype(u) - h = 8 + cospi(2 * x[1]) * sinpi(2 * x[2]) * cospi(2 * t) - h_t = -2 * convert(RealT, pi) * cospi(2 * x[1]) * sinpi(2 * x[2]) * sinpi(2 * t) - h_x = -2 * convert(RealT, pi) * sinpi(2 * x[1]) * sinpi(2 * x[2]) * cospi(2 * t) - h_y = 2 * convert(RealT, pi) * cospi(2 * x[1]) * cospi(2 * x[2]) * cospi(2 * t) - - rho_x = h_x - rho_y = h_y - - b = equations.kappa * equations.gamma * h^(equations.gamma - 1) - - r_1 = h_t + h_x / 2 + 3 * h_y / 2 - r_2 = h_t / 2 + h_x / 4 + b * rho_x + 3 * h_y / 4 - r_3 = 3 * h_t / 2 + 3 * h_x / 4 + 9 * h_y / 4 + b * rho_y - - return SVector(r_1, r_2, r_3) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::PolytropicEulerEquations2D) - -A weak blast wave adapted from -- Sebastian Hennemann, Gregor J. Gassner (2020) - A provably entropy stable subcell shock capturing approach for high order split form DG - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -function initial_condition_weak_blast_wave(x, t, equations::PolytropicEulerEquations2D) - # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) - # Set up polar coordinates - inicenter = (0, 0) - x_norm = x[1] - inicenter[1] - y_norm = x[2] - inicenter[2] - r = sqrt(x_norm^2 + y_norm^2) - phi = atan(y_norm, x_norm) - - # Calculate primitive variables - RealT = eltype(x) - rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) - v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin(phi) - - return prim2cons(SVector(rho, v1, v2), equations) -end - -# Calculate 2D flux for a single point in the normal direction -# Note, this directional vector is not normalized -@inline function flux(u, normal_direction::AbstractVector, - equations::PolytropicEulerEquations2D) - rho, v1, v2 = cons2prim(u, equations) - p = pressure(u, equations) - - v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] - rho_v_normal = rho * v_normal - f1 = rho_v_normal - f2 = rho_v_normal * v1 + p * normal_direction[1] - f3 = rho_v_normal * v2 + p * normal_direction[2] - return SVector(f1, f2, f3) -end - -# Calculate 2D flux for a single point -@inline function flux(u, orientation::Integer, equations::PolytropicEulerEquations2D) - _, v1, v2 = cons2prim(u, equations) - p = pressure(u, equations) - - rho_v1 = u[2] - rho_v2 = u[3] - - if orientation == 1 - f1 = rho_v1 - f2 = rho_v1 * v1 + p - f3 = rho_v1 * v2 - else - f1 = rho_v2 - f2 = rho_v2 * v1 - f3 = rho_v2 * v2 + p + + function varnames(::typeof(cons2cons), ::PolytropicEulerEquations2D) + ("rho", "rho_v1", "rho_v2") + end + varnames(::typeof(cons2prim), ::PolytropicEulerEquations2D) = ("rho", "v1", "v2") + + """ + initial_condition_convergence_test(x, t, equations::PolytropicEulerEquations2D) + + Manufactured smooth initial condition used for convergence tests + in combination with [`source_terms_convergence_test`](@ref). + """ + function initial_condition_convergence_test(x, t, equations::PolytropicEulerEquations2D) + # manufactured initial condition from Winters (2019) [0.1007/s10543-019-00789-w] + # domain must be set to [0, 1] x [0, 1] + h = 8 + cospi(2 * x[1]) * sinpi(2 * x[2]) * cospi(2 * t) + + return SVector(h, h / 2, 3 * h / 2) + end + + """ + source_terms_convergence_test(u, x, t, equations::PolytropicEulerEquations2D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::PolytropicEulerEquations2D + ) + rho, v1, v2 = cons2prim(u, equations) + + # Residual from Winters (2019) [0.1007/s10543-019-00789-w] eq. (5.2). + RealT = eltype(u) + h = 8 + cospi(2 * x[1]) * sinpi(2 * x[2]) * cospi(2 * t) + h_t = -2 * convert(RealT, pi) * cospi(2 * x[1]) * sinpi(2 * x[2]) * sinpi(2 * t) + h_x = -2 * convert(RealT, pi) * sinpi(2 * x[1]) * sinpi(2 * x[2]) * cospi(2 * t) + h_y = 2 * convert(RealT, pi) * cospi(2 * x[1]) * cospi(2 * x[2]) * cospi(2 * t) + + rho_x = h_x + rho_y = h_y + + b = equations.kappa * equations.gamma * h^(equations.gamma - 1) + + r_1 = h_t + h_x / 2 + 3 * h_y / 2 + r_2 = h_t / 2 + h_x / 4 + b * rho_x + 3 * h_y / 4 + r_3 = 3 * h_t / 2 + 3 * h_x / 4 + 9 * h_y / 4 + b * rho_y + + return SVector(r_1, r_2, r_3) + end + + """ + initial_condition_weak_blast_wave(x, t, equations::PolytropicEulerEquations2D) + + A weak blast wave adapted from + - Sebastian Hennemann, Gregor J. Gassner (2020) + A provably entropy stable subcell shock capturing approach for high order split form DG + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + function initial_condition_weak_blast_wave(x, t, equations::PolytropicEulerEquations2D) + # Adapted MHD version of the weak blast wave from Hennemann & Gassner JCP paper 2020 (Sec. 6.3) + # Set up polar coordinates + inicenter = (0, 0) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + phi = atan(y_norm, x_norm) + + # Calculate primitive variables + RealT = eltype(x) + rho = r > 0.5f0 ? one(RealT) : convert(RealT, 1.1691) + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos(phi) + v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin(phi) + + return prim2cons(SVector(rho, v1, v2), equations) end - return SVector(f1, f2, f3) -end - -""" - flux_winters_etal(u_ll, u_rr, orientation_or_normal_direction, - equations::PolytropicEulerEquations2D) - -Entropy conserving two-point flux for isothermal or polytropic gases. -Requires a special weighted Stolarsky mean for the evaluation of the density -denoted here as `stolarsky_mean`. Note, for isothermal gases where `gamma = 1` -this `stolarsky_mean` becomes the [`ln_mean`](@ref). - -For details see Section 3.2 of the following reference -- Andrew R. Winters, Christof Czernik, Moritz B. Schily & Gregor J. Gassner (2020) - Entropy stable numerical approximations for the isothermal and polytropic - Euler equations - [DOI: 10.1007/s10543-019-00789-w](https://doi.org/10.1007/s10543-019-00789-w) -""" -@inline function flux_winters_etal(u_ll, u_rr, normal_direction::AbstractVector, - equations::PolytropicEulerEquations2D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) - p_ll = equations.kappa * rho_ll^equations.gamma - p_rr = equations.kappa * rho_rr^equations.gamma - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # Compute the necessary mean values - if equations.gamma == 1 # isothermal gas - rho_mean = ln_mean(rho_ll, rho_rr) - else # equations.gamma > 1 # polytropic gas - rho_mean = stolarsky_mean(rho_ll, rho_rr, equations.gamma) + + # Calculate 2D flux for a single point in the normal direction + # Note, this directional vector is not normalized + @inline function flux( + u, normal_direction::AbstractVector, + equations::PolytropicEulerEquations2D + ) + rho, v1, v2 = cons2prim(u, equations) + p = pressure(u, equations) + + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + rho_v_normal = rho * v_normal + f1 = rho_v_normal + f2 = rho_v_normal * v1 + p * normal_direction[1] + f3 = rho_v_normal * v2 + p * normal_direction[2] + return SVector(f1, f2, f3) end - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - - # Calculate fluxes depending on normal_direction - f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - f2 = f1 * v1_avg + p_avg * normal_direction[1] - f3 = f1 * v2_avg + p_avg * normal_direction[2] - - return SVector(f1, f2, f3) -end - -@inline function flux_winters_etal(u_ll, u_rr, orientation::Integer, - equations::PolytropicEulerEquations2D) - # Unpack left and right state - rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) - p_ll = equations.kappa * rho_ll^equations.gamma - p_rr = equations.kappa * rho_rr^equations.gamma - - # Compute the necessary mean values - if equations.gamma == 1 # isothermal gas - rho_mean = ln_mean(rho_ll, rho_rr) - else # equations.gamma > 1 # polytropic gas - rho_mean = stolarsky_mean(rho_ll, rho_rr, equations.gamma) + + # Calculate 2D flux for a single point + @inline function flux(u, orientation::Integer, equations::PolytropicEulerEquations2D) + _, v1, v2 = cons2prim(u, equations) + p = pressure(u, equations) + + rho_v1 = u[2] + rho_v2 = u[3] + + if orientation == 1 + f1 = rho_v1 + f2 = rho_v1 * v1 + p + f3 = rho_v1 * v2 + else + f1 = rho_v2 + f2 = rho_v2 * v1 + f3 = rho_v2 * v2 + p + end + return SVector(f1, f2, f3) end - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_avg = 0.5f0 * (p_ll + p_rr) - - if orientation == 1 # x-direction - f1 = rho_mean * 0.5f0 * (v1_ll + v1_rr) - f2 = f1 * v1_avg + p_avg - f3 = f1 * v2_avg - else # y-direction - f1 = rho_mean * 0.5f0 * (v2_ll + v2_rr) - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_avg + + """ + flux_winters_etal(u_ll, u_rr, orientation_or_normal_direction, + equations::PolytropicEulerEquations2D) + + Entropy conserving two-point flux for isothermal or polytropic gases. + Requires a special weighted Stolarsky mean for the evaluation of the density + denoted here as `stolarsky_mean`. Note, for isothermal gases where `gamma = 1` + this `stolarsky_mean` becomes the [`ln_mean`](@ref). + + For details see Section 3.2 of the following reference + - Andrew R. Winters, Christof Czernik, Moritz B. Schily & Gregor J. Gassner (2020) + Entropy stable numerical approximations for the isothermal and polytropic + Euler equations + [DOI: 10.1007/s10543-019-00789-w](https://doi.org/10.1007/s10543-019-00789-w) + """ + @inline function flux_winters_etal( + u_ll, u_rr, normal_direction::AbstractVector, + equations::PolytropicEulerEquations2D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) + p_ll = equations.kappa * rho_ll^equations.gamma + p_rr = equations.kappa * rho_rr^equations.gamma + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # Compute the necessary mean values + if equations.gamma == 1 # isothermal gas + rho_mean = ln_mean(rho_ll, rho_rr) + else # equations.gamma > 1 # polytropic gas + rho_mean = stolarsky_mean(rho_ll, rho_rr, equations.gamma) + end + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + + # Calculate fluxes depending on normal_direction + f1 = rho_mean * 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + f2 = f1 * v1_avg + p_avg * normal_direction[1] + f3 = f1 * v2_avg + p_avg * normal_direction[2] + + return SVector(f1, f2, f3) end - return SVector(f1, f2, f3) -end - -@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::PolytropicEulerEquations2D) - rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) - p_ll = equations.kappa * rho_ll^equations.gamma - p_rr = equations.kappa * rho_rr^equations.gamma - - v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - norm_ = norm(normal_direction) - # The v_normals are already scaled by the norm - lambda_min = v_normal_ll - sqrt(equations.gamma * p_ll / rho_ll) * norm_ - lambda_max = v_normal_rr + sqrt(equations.gamma * p_rr / rho_rr) * norm_ - - return lambda_min, lambda_max -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::PolytropicEulerEquations2D) - rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) - # Pressure for polytropic Euler - p_ll = equations.kappa * rho_ll^equations.gamma - p_rr = equations.kappa * rho_rr^equations.gamma - - c_ll = sqrt(equations.gamma * p_ll / rho_ll) - c_rr = sqrt(equations.gamma * p_rr / rho_rr) - - if orientation == 1 # x-direction - λ_min = min(v1_ll - c_ll, v1_rr - c_rr) - λ_max = max(v1_ll + c_ll, v1_rr + c_rr) - else # y-direction - λ_min = min(v2_ll - c_ll, v2_rr - c_rr) - λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + @inline function flux_winters_etal( + u_ll, u_rr, orientation::Integer, + equations::PolytropicEulerEquations2D + ) + # Unpack left and right state + rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) + p_ll = equations.kappa * rho_ll^equations.gamma + p_rr = equations.kappa * rho_rr^equations.gamma + + # Compute the necessary mean values + if equations.gamma == 1 # isothermal gas + rho_mean = ln_mean(rho_ll, rho_rr) + else # equations.gamma > 1 # polytropic gas + rho_mean = stolarsky_mean(rho_ll, rho_rr, equations.gamma) + end + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * (p_ll + p_rr) + + if orientation == 1 # x-direction + f1 = rho_mean * 0.5f0 * (v1_ll + v1_rr) + f2 = f1 * v1_avg + p_avg + f3 = f1 * v2_avg + else # y-direction + f1 = rho_mean * 0.5f0 * (v2_ll + v2_rr) + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_avg + end + + return SVector(f1, f2, f3) end - return λ_min, λ_max -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, - equations::PolytropicEulerEquations2D) - rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) - # Pressure for polytropic Euler - p_ll = equations.kappa * rho_ll^equations.gamma - p_rr = equations.kappa * rho_rr^equations.gamma - - norm_ = norm(normal_direction) - - c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ - c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ - - v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # The v_normals are already scaled by the norm - λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) - λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) - - return λ_min, λ_max -end - -@inline function max_abs_speeds(u, equations::PolytropicEulerEquations2D) - rho, v1, v2 = cons2prim(u, equations) - c = sqrt(equations.gamma * equations.kappa * rho^(equations.gamma - 1)) - - return abs(v1) + c, abs(v2) + c -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the -# maximum velocity magnitude plus the maximum speed of sound -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::PolytropicEulerEquations2D) - rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) - - # Get the velocity value in the appropriate direction - if orientation == 1 - v_ll = v1_ll - v_rr = v1_rr - else # orientation == 2 - v_ll = v2_ll - v_rr = v2_rr + @inline function min_max_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::PolytropicEulerEquations2D + ) + rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) + p_ll = equations.kappa * rho_ll^equations.gamma + p_rr = equations.kappa * rho_rr^equations.gamma + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + norm_ = norm(normal_direction) + # The v_normals are already scaled by the norm + lambda_min = v_normal_ll - sqrt(equations.gamma * p_ll / rho_ll) * norm_ + lambda_max = v_normal_rr + sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + return lambda_min, lambda_max end - # Calculate sound speeds (we have p = kappa * rho^gamma) - c_ll = sqrt(equations.gamma * equations.kappa * rho_ll^(equations.gamma - 1)) - c_rr = sqrt(equations.gamma * equations.kappa * rho_rr^(equations.gamma - 1)) - - λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) -end - -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::PolytropicEulerEquations2D) - rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) - rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) - - # Calculate normal velocities and sound speed (we have p = kappa * rho^gamma) - # left - v_ll = (v1_ll * normal_direction[1] + - v2_ll * normal_direction[2]) - c_ll = sqrt(equations.gamma * equations.kappa * rho_ll^(equations.gamma - 1)) - # right - v_rr = (v1_rr * normal_direction[1] + - v2_rr * normal_direction[2]) - c_rr = sqrt(equations.gamma * equations.kappa * rho_rr^(equations.gamma - 1)) - - return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::PolytropicEulerEquations2D) - rho, rho_v1, rho_v2 = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - - return SVector(rho, v1, v2) -end - -# Convert conservative variables to entropy -@inline function cons2entropy(u, equations::PolytropicEulerEquations2D) - rho, rho_v1, rho_v2 = u - - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v_square = v1^2 + v2^2 - p = pressure(u, equations) - # Form of the internal energy depends on gas type - if equations.gamma == 1 # isothermal gas - internal_energy = equations.kappa * log(rho) - else # equations.gamma > 1 # polytropic gas - internal_energy = equations.kappa * rho^(equations.gamma - 1) / - (equations.gamma - 1) + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::PolytropicEulerEquations2D + ) + rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) + # Pressure for polytropic Euler + p_ll = equations.kappa * rho_ll^equations.gamma + p_rr = equations.kappa * rho_rr^equations.gamma + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) + c_rr = sqrt(equations.gamma * p_rr / rho_rr) + + if orientation == 1 # x-direction + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + else # y-direction + λ_min = min(v2_ll - c_ll, v2_rr - c_rr) + λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + end + + return λ_min, λ_max end - w1 = internal_energy + p / rho - 0.5f0 * v_square - w2 = v1 - w3 = v2 - - return SVector(w1, w2, w3) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::PolytropicEulerEquations2D) - rho, v1, v2 = prim - rho_v1 = rho * v1 - rho_v2 = rho * v2 - return SVector(rho, rho_v1, rho_v2) -end - -@inline function density(u, equations::PolytropicEulerEquations2D) - rho = u[1] - return rho -end - -@inline function pressure(u, equations::PolytropicEulerEquations2D) - rho, rho_v1, rho_v2 = u - p = equations.kappa * rho^equations.gamma - return p -end + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, normal_direction::AbstractVector, + equations::PolytropicEulerEquations2D + ) + rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) + # Pressure for polytropic Euler + p_ll = equations.kappa * rho_ll^equations.gamma + p_rr = equations.kappa * rho_rr^equations.gamma + + norm_ = norm(normal_direction) + + c_ll = sqrt(equations.gamma * p_ll / rho_ll) * norm_ + c_rr = sqrt(equations.gamma * p_rr / rho_rr) * norm_ + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # The v_normals are already scaled by the norm + λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) + λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) + + return λ_min, λ_max + end + + @inline function max_abs_speeds(u, equations::PolytropicEulerEquations2D) + rho, v1, v2 = cons2prim(u, equations) + c = sqrt(equations.gamma * equations.kappa * rho^(equations.gamma - 1)) + + return abs(v1) + c, abs(v2) + c + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the + # maximum velocity magnitude plus the maximum speed of sound + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::PolytropicEulerEquations2D + ) + rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) + + # Get the velocity value in the appropriate direction + if orientation == 1 + v_ll = v1_ll + v_rr = v1_rr + else # orientation == 2 + v_ll = v2_ll + v_rr = v2_rr + end + # Calculate sound speeds (we have p = kappa * rho^gamma) + c_ll = sqrt(equations.gamma * equations.kappa * rho_ll^(equations.gamma - 1)) + c_rr = sqrt(equations.gamma * equations.kappa * rho_rr^(equations.gamma - 1)) + + λ_max = max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) + end + + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::PolytropicEulerEquations2D + ) + rho_ll, v1_ll, v2_ll = cons2prim(u_ll, equations) + rho_rr, v1_rr, v2_rr = cons2prim(u_rr, equations) + + # Calculate normal velocities and sound speed (we have p = kappa * rho^gamma) + # left + v_ll = ( + v1_ll * normal_direction[1] + + v2_ll * normal_direction[2] + ) + c_ll = sqrt(equations.gamma * equations.kappa * rho_ll^(equations.gamma - 1)) + # right + v_rr = ( + v1_rr * normal_direction[1] + + v2_rr * normal_direction[2] + ) + c_rr = sqrt(equations.gamma * equations.kappa * rho_rr^(equations.gamma - 1)) + + return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::PolytropicEulerEquations2D) + rho, rho_v1, rho_v2 = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + + return SVector(rho, v1, v2) + end + + # Convert conservative variables to entropy + @inline function cons2entropy(u, equations::PolytropicEulerEquations2D) + rho, rho_v1, rho_v2 = u + + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v_square = v1^2 + v2^2 + p = pressure(u, equations) + # Form of the internal energy depends on gas type + if equations.gamma == 1 # isothermal gas + internal_energy = equations.kappa * log(rho) + else # equations.gamma > 1 # polytropic gas + internal_energy = equations.kappa * rho^(equations.gamma - 1) / + (equations.gamma - 1) + end + + w1 = internal_energy + p / rho - 0.5f0 * v_square + w2 = v1 + w3 = v2 + + return SVector(w1, w2, w3) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::PolytropicEulerEquations2D) + rho, v1, v2 = prim + rho_v1 = rho * v1 + rho_v2 = rho * v2 + return SVector(rho, rho_v1, rho_v2) + end + + @inline function density(u, equations::PolytropicEulerEquations2D) + rho = u[1] + return rho + end + + @inline function pressure(u, equations::PolytropicEulerEquations2D) + rho, rho_v1, rho_v2 = u + p = equations.kappa * rho^equations.gamma + return p + end end # @muladd diff --git a/src/equations/shallow_water_1d.jl b/src/equations/shallow_water_1d.jl index 3c218eee9d..6501c3b047 100644 --- a/src/equations/shallow_water_1d.jl +++ b/src/equations/shallow_water_1d.jl @@ -3,638 +3,682 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - ShallowWaterEquations1D(; gravity, H0 = 0) - -Shallow water equations (SWE) in one space dimension. The equations are given by -```math -\begin{aligned} - \frac{\partial h}{\partial t} + \frac{\partial}{\partial x}(h v) &= 0 \\ - \frac{\partial}{\partial t}(h v) + \frac{\partial}{\partial x}\left(h v^2 + \frac{g}{2}h^2\right) - + g h \frac{\partial b}{\partial x} &= 0 -\end{aligned} -``` -The unknown quantities of the SWE are the water height ``h`` and the velocity ``v``. -The gravitational constant is denoted by `g` and the (possibly) variable bottom topography function ``b(x)``. -Conservative variable water height ``h`` is measured from the bottom topography ``b``, therefore one -also defines the total water height as ``H = h + b``. - -The additional quantity ``H_0`` is also available to store a reference value for the total water height that -is useful to set initial conditions or test the "lake-at-rest" well-balancedness. - -The bottom topography function ``b(x)`` is set inside the initial condition routine -for a particular problem setup. To test the conservative form of the SWE one can set the bottom topography -variable `b` to zero. - -In addition to the unknowns, Trixi.jl currently stores the bottom topography values at the approximation points -despite being fixed in time. This is done for convenience of computing the bottom topography gradients -on the fly during the approximation as well as computing auxiliary quantities like the total water height ``H`` -or the entropy variables. -This affects the implementation and use of these equations in various ways: -* The flux values corresponding to the bottom topography must be zero. -* The bottom topography values must be included when defining initial conditions, boundary conditions or - source terms. -* [`AnalysisCallback`](@ref) analyzes this variable. -* Trixi.jl's visualization tools will visualize the bottom topography by default. - -References for the SWE are many but a good introduction is available in Chapter 13 of the book: -- Randall J. LeVeque (2002) - Finite Volume Methods for Hyperbolic Problems - [DOI: 10.1017/CBO9780511791253](https://doi.org/10.1017/CBO9780511791253) -""" -struct ShallowWaterEquations1D{RealT <: Real} <: AbstractShallowWaterEquations{1, 3} - gravity::RealT # gravitational constant - H0::RealT # constant "lake-at-rest" total water height -end - -# Allow for flexibility to set the gravitational constant within an elixir depending on the -# application where `gravity_constant=1.0` or `gravity_constant=9.81` are common values. -# The reference total water height H0 defaults to 0.0 but is used for the "lake-at-rest" -# well-balancedness test cases. -function ShallowWaterEquations1D(; gravity_constant, H0 = zero(gravity_constant)) - ShallowWaterEquations1D(gravity_constant, H0) -end - -have_nonconservative_terms(::ShallowWaterEquations1D) = True() -varnames(::typeof(cons2cons), ::ShallowWaterEquations1D) = ("h", "h_v", "b") -# Note, we use the total water height, H = h + b, as the first primitive variable for easier -# visualization and setting initial conditions -varnames(::typeof(cons2prim), ::ShallowWaterEquations1D) = ("H", "v", "b") - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_convergence_test(x, t, equations::ShallowWaterEquations1D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -function initial_condition_convergence_test(x, t, equations::ShallowWaterEquations1D) - # some constants are chosen such that the function is periodic on the domain [0,sqrt(2)] - RealT = eltype(x) - c = 7 - omega_x = 2 * convert(RealT, pi) * sqrt(convert(RealT, 2)) - omega_t = 2 * convert(RealT, pi) - - H = c + cos(omega_x * x[1]) * cos(omega_t * t) - v = 0.5f0 - b = 2 + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x[1]) - return prim2cons(SVector(H, v, b), equations) -end - -""" - source_terms_convergence_test(u, x, t, equations::ShallowWaterEquations1D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). - -This manufactured solution source term is specifically designed for the bottom topography function -`b(x) = 2.0 + 0.5 * sinpi(sqrt(2.0) * x[1])` -as defined in [`initial_condition_convergence_test`](@ref). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::ShallowWaterEquations1D) - # Same settings as in `initial_condition_convergence_test`. Some derivative simplify because - # this manufactured solution velocity is taken to be constant - RealT = eltype(u) - c = 7 - omega_x = 2 * convert(RealT, pi) * sqrt(convert(RealT, 2)) - omega_t = 2 * convert(RealT, pi) - omega_b = sqrt(convert(RealT, 2)) * convert(RealT, pi) - v = 0.5f0 - - sinX, cosX = sincos(omega_x * x[1]) - sinT, cosT = sincos(omega_t * t) - - H = c + cosX * cosT - H_x = -omega_x * sinX * cosT - # this time derivative for the water height exploits that the bottom topography is - # fixed in time such that H_t = (h+b)_t = h_t + 0 - H_t = -omega_t * cosX * sinT - - # bottom topography and its spatial derivative - b = 2 + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x[1]) - b_x = 0.5f0 * omega_b * cos(omega_b * x[1]) - - du1 = H_t + v * (H_x - b_x) - du2 = v * du1 + equations.gravity * (H - b) * H_x - return SVector(du1, du2, 0) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::ShallowWaterEquations1D) - -A weak blast wave discontinuity useful for testing, e.g., total energy conservation. -Note for the shallow water equations to the total energy acts as a mathematical entropy function. -""" -function initial_condition_weak_blast_wave(x, t, equations::ShallowWaterEquations1D) - RealT = eltype(x) - inicenter = convert(RealT, 0.7) - x_norm = x[1] - inicenter - r = abs(x_norm) - - # Calculate primitive variables - H = r > 0.5f0 ? 3.25f0 : 4.0f0 - v = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) - b = sin(x[1]) # arbitrary continuous function - - return prim2cons(SVector(H, v, b), equations) -end - -""" - boundary_condition_slip_wall(u_inner, orientation_or_normal, x, t, surface_flux_function, - equations::ShallowWaterEquations1D) - -Create a boundary state by reflecting the normal velocity component and keep -the tangential velocity component unchanged. The boundary water height is taken from -the internal value. - -For details see Section 9.2.5 of the book: -- Eleuterio F. Toro (2001) - Shock-Capturing Methods for Free-Surface Shallow Flows - 1st edition - ISBN 0471987662 -""" -@inline function boundary_condition_slip_wall(u_inner, orientation_or_normal, direction, - x, t, - surface_flux_function, - equations::ShallowWaterEquations1D) - - # create the "external" boundary solution state - u_boundary = SVector(u_inner[1], - -u_inner[2], - u_inner[3]) + #! format: noindent + + @doc raw""" + ShallowWaterEquations1D(; gravity, H0 = 0) + + Shallow water equations (SWE) in one space dimension. The equations are given by + ```math + \begin{aligned} + \frac{\partial h}{\partial t} + \frac{\partial}{\partial x}(h v) &= 0 \\ + \frac{\partial}{\partial t}(h v) + \frac{\partial}{\partial x}\left(h v^2 + \frac{g}{2}h^2\right) + + g h \frac{\partial b}{\partial x} &= 0 + \end{aligned} + ``` + The unknown quantities of the SWE are the water height ``h`` and the velocity ``v``. + The gravitational constant is denoted by `g` and the (possibly) variable bottom topography function ``b(x)``. + Conservative variable water height ``h`` is measured from the bottom topography ``b``, therefore one + also defines the total water height as ``H = h + b``. + + The additional quantity ``H_0`` is also available to store a reference value for the total water height that + is useful to set initial conditions or test the "lake-at-rest" well-balancedness. + + The bottom topography function ``b(x)`` is set inside the initial condition routine + for a particular problem setup. To test the conservative form of the SWE one can set the bottom topography + variable `b` to zero. + + In addition to the unknowns, Trixi.jl currently stores the bottom topography values at the approximation points + despite being fixed in time. This is done for convenience of computing the bottom topography gradients + on the fly during the approximation as well as computing auxiliary quantities like the total water height ``H`` + or the entropy variables. + This affects the implementation and use of these equations in various ways: + * The flux values corresponding to the bottom topography must be zero. + * The bottom topography values must be included when defining initial conditions, boundary conditions or + source terms. + * [`AnalysisCallback`](@ref) analyzes this variable. + * Trixi.jl's visualization tools will visualize the bottom topography by default. + + References for the SWE are many but a good introduction is available in Chapter 13 of the book: + - Randall J. LeVeque (2002) + Finite Volume Methods for Hyperbolic Problems + [DOI: 10.1017/CBO9780511791253](https://doi.org/10.1017/CBO9780511791253) + """ + struct ShallowWaterEquations1D{RealT <: Real} <: AbstractShallowWaterEquations{1, 3} + gravity::RealT # gravitational constant + H0::RealT # constant "lake-at-rest" total water height + end - # calculate the boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation_or_normal, - equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation_or_normal, - equations) + # Allow for flexibility to set the gravitational constant within an elixir depending on the + # application where `gravity_constant=1.0` or `gravity_constant=9.81` are common values. + # The reference total water height H0 defaults to 0.0 but is used for the "lake-at-rest" + # well-balancedness test cases. + function ShallowWaterEquations1D(; gravity_constant, H0 = zero(gravity_constant)) + ShallowWaterEquations1D(gravity_constant, H0) end - return flux -end + have_nonconservative_terms(::ShallowWaterEquations1D) = True() + varnames(::typeof(cons2cons), ::ShallowWaterEquations1D) = ("h", "h_v", "b") + # Note, we use the total water height, H = h + b, as the first primitive variable for easier + # visualization and setting initial conditions + varnames(::typeof(cons2prim), ::ShallowWaterEquations1D) = ("H", "v", "b") + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_convergence_test(x, t, equations::ShallowWaterEquations1D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + function initial_condition_convergence_test(x, t, equations::ShallowWaterEquations1D) + # some constants are chosen such that the function is periodic on the domain [0,sqrt(2)] + RealT = eltype(x) + c = 7 + omega_x = 2 * convert(RealT, pi) * sqrt(convert(RealT, 2)) + omega_t = 2 * convert(RealT, pi) + + H = c + cos(omega_x * x[1]) * cos(omega_t * t) + v = 0.5f0 + b = 2 + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x[1]) + return prim2cons(SVector(H, v, b), equations) + end -# Calculate 1D flux for a single point -# Note, the bottom topography has no flux -@inline function flux(u, orientation::Integer, equations::ShallowWaterEquations1D) - h, h_v, _ = u - v = velocity(u, equations) + """ + source_terms_convergence_test(u, x, t, equations::ShallowWaterEquations1D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + + This manufactured solution source term is specifically designed for the bottom topography function + `b(x) = 2.0 + 0.5 * sinpi(sqrt(2.0) * x[1])` + as defined in [`initial_condition_convergence_test`](@ref). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::ShallowWaterEquations1D + ) + # Same settings as in `initial_condition_convergence_test`. Some derivative simplify because + # this manufactured solution velocity is taken to be constant + RealT = eltype(u) + c = 7 + omega_x = 2 * convert(RealT, pi) * sqrt(convert(RealT, 2)) + omega_t = 2 * convert(RealT, pi) + omega_b = sqrt(convert(RealT, 2)) * convert(RealT, pi) + v = 0.5f0 + + sinX, cosX = sincos(omega_x * x[1]) + sinT, cosT = sincos(omega_t * t) + + H = c + cosX * cosT + H_x = -omega_x * sinX * cosT + # this time derivative for the water height exploits that the bottom topography is + # fixed in time such that H_t = (h+b)_t = h_t + 0 + H_t = -omega_t * cosX * sinT + + # bottom topography and its spatial derivative + b = 2 + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x[1]) + b_x = 0.5f0 * omega_b * cos(omega_b * x[1]) + + du1 = H_t + v * (H_x - b_x) + du2 = v * du1 + equations.gravity * (H - b) * H_x + return SVector(du1, du2, 0) + end - p = 0.5f0 * equations.gravity * h^2 + """ + initial_condition_weak_blast_wave(x, t, equations::ShallowWaterEquations1D) - f1 = h_v - f2 = h_v * v + p + A weak blast wave discontinuity useful for testing, e.g., total energy conservation. + Note for the shallow water equations to the total energy acts as a mathematical entropy function. + """ + function initial_condition_weak_blast_wave(x, t, equations::ShallowWaterEquations1D) + RealT = eltype(x) + inicenter = convert(RealT, 0.7) + x_norm = x[1] - inicenter + r = abs(x_norm) - return SVector(f1, f2, 0) -end + # Calculate primitive variables + H = r > 0.5f0 ? 3.25f0 : 4.0f0 + v = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) + b = sin(x[1]) # arbitrary continuous function -""" - flux_nonconservative_wintermeyer_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) + return prim2cons(SVector(H, v, b), equations) + end -Non-symmetric two-point volume flux discretizing the nonconservative (source) term -that contains the gradient of the bottom topography [`ShallowWaterEquations1D`](@ref). - -Gives entropy conservation and well-balancedness on both the volume and surface when combined with -[`flux_wintermeyer_etal`](@ref). - -Further details are available in the papers: -- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) - An entropy stable nodal discontinuous Galerkin method for the two dimensional - shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry - [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) -- Patrick Ersing, Andrew R. Winters (2023) - An entropy stable discontinuous Galerkin method for the two-layer shallow water equations on - curvilinear meshes - [DOI: 10.48550/arXiv.2306.12699](https://doi.org/10.48550/arXiv.2306.12699) -""" -@inline function flux_nonconservative_wintermeyer_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) - # Pull the necessary left and right state information - h_ll = waterheight(u_ll, equations) - b_jump = u_rr[3] - u_ll[3] - - # Bottom gradient nonconservative term: (0, g h b_x, 0) - f = SVector(0, equations.gravity * h_ll * b_jump, 0) - - return f -end - -""" - flux_nonconservative_fjordholm_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) - -Non-symmetric two-point surface flux discretizing the nonconservative (source) term of -that contains the gradient of the bottom topography [`ShallowWaterEquations1D`](@ref). - -This flux can be used together with [`flux_fjordholm_etal`](@ref) at interfaces to ensure entropy -conservation and well-balancedness. - -Further details for the original finite volume formulation are available in -- Ulrik S. Fjordholm, Siddhartha Mishr and Eitan Tadmor (2011) - Well-balanced and energy stable schemes for the shallow water equations with discontinuous topography - [DOI: 10.1016/j.jcp.2011.03.042](https://doi.org/10.1016/j.jcp.2011.03.042) -and for curvilinear 2D case in the paper: -- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) - An entropy stable nodal discontinuous Galerkin method for the two dimensional - shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry - [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) -""" -@inline function flux_nonconservative_fjordholm_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) - # Pull the necessary left and right state information - h_ll, _, b_ll = u_ll - h_rr, _, b_rr = u_rr - - h_average = 0.5f0 * (h_ll + h_rr) - b_jump = b_rr - b_ll - - # Includes two parts: - # (i) Diagonal (consistent) term from the volume flux that uses `b_ll` to avoid - # cross-averaging across a discontinuous bottom topography - # (ii) True surface part that uses `h_average` and `b_jump` to handle discontinuous bathymetry - f = SVector(0, - equations.gravity * h_average * b_jump, - 0) - - return f -end - -""" - flux_nonconservative_audusse_etal(u_ll, u_rr, orientation::Integer, + """ + boundary_condition_slip_wall(u_inner, orientation_or_normal, x, t, surface_flux_function, equations::ShallowWaterEquations1D) -Non-symmetric two-point surface flux that discretizes the nonconservative (source) term. -The discretization uses the `hydrostatic_reconstruction_audusse_etal` on the conservative -variables. - -This hydrostatic reconstruction ensures that the finite volume numerical fluxes remain -well-balanced for discontinuous bottom topographies [`ShallowWaterEquations1D`](@ref). -Should be used together with [`FluxHydrostaticReconstruction`](@ref) and -[`hydrostatic_reconstruction_audusse_etal`](@ref) in the surface flux to ensure consistency. - -Further details on the hydrostatic reconstruction and its motivation can be found in -- Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) - A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows - [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) -""" -@inline function flux_nonconservative_audusse_etal(u_ll, u_rr, - orientation::Integer, - equations::ShallowWaterEquations1D) - # Pull the water height and bottom topography on the left - h_ll, _, _ = u_ll - - # Create the hydrostatic reconstruction for the left solution state - u_ll_star, _ = hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, equations) - - # Copy the reconstructed water height for easier to read code - h_ll_star = u_ll_star[1] - - return SVector(0, - equations.gravity * (h_ll^2 - h_ll_star^2), - 0) -end - -""" - flux_fjordholm_etal(u_ll, u_rr, orientation, - equations::ShallowWaterEquations1D) - -Total energy conservative (mathematical entropy for shallow water equations). When the bottom topography -is nonzero this should only be used as a surface flux otherwise the scheme will not be well-balanced. -For well-balancedness in the volume flux use [`flux_wintermeyer_etal`](@ref). - -Details are available in Eq. (4.1) in the paper: -- Ulrik S. Fjordholm, Siddhartha Mishr and Eitan Tadmor (2011) - Well-balanced and energy stable schemes for the shallow water equations with discontinuous topography - [DOI: 10.1016/j.jcp.2011.03.042](https://doi.org/10.1016/j.jcp.2011.03.042) -""" -@inline function flux_fjordholm_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) - # Unpack left and right state - h_ll = waterheight(u_ll, equations) - v_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v_rr = velocity(u_rr, equations) - - # Average each factor of products in flux - h_avg = 0.5f0 * (h_ll + h_rr) - v_avg = 0.5f0 * (v_ll + v_rr) - p_avg = 0.25f0 * equations.gravity * (h_ll^2 + h_rr^2) - - # Calculate fluxes depending on orientation - f1 = h_avg * v_avg - f2 = f1 * v_avg + p_avg - - return SVector(f1, f2, 0) -end - -""" - flux_wintermeyer_etal(u_ll, u_rr, orientation, - equations::ShallowWaterEquations1D) - -Total energy conservative (mathematical entropy for shallow water equations) split form. -When the bottom topography is nonzero this scheme will be well-balanced when used as a `volume_flux`. -For the `surface_flux` either [`flux_wintermeyer_etal`](@ref) or [`flux_fjordholm_etal`](@ref) can -be used to ensure well-balancedness and entropy conservation. - -Further details are available in Theorem 1 of the paper: -- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) - An entropy stable nodal discontinuous Galerkin method for the two dimensional - shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry - [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) -""" -@inline function flux_wintermeyer_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) - # Unpack left and right state - h_ll, h_v_ll, _ = u_ll - h_rr, h_v_rr, _ = u_rr - - # Get the velocities on either side - v_ll = velocity(u_ll, equations) - v_rr = velocity(u_rr, equations) - - # Average each factor of products in flux - v_avg = 0.5f0 * (v_ll + v_rr) - p_avg = 0.5f0 * equations.gravity * h_ll * h_rr - - # Calculate fluxes depending on orientation - f1 = 0.5f0 * (h_v_ll + h_v_rr) - f2 = f1 * v_avg + p_avg - - return SVector(f1, f2, 0) -end - -""" - hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, orientation::Integer, + Create a boundary state by reflecting the normal velocity component and keep + the tangential velocity component unchanged. The boundary water height is taken from + the internal value. + + For details see Section 9.2.5 of the book: + - Eleuterio F. Toro (2001) + Shock-Capturing Methods for Free-Surface Shallow Flows + 1st edition + ISBN 0471987662 + """ + @inline function boundary_condition_slip_wall( + u_inner, orientation_or_normal, direction, + x, t, + surface_flux_function, + equations::ShallowWaterEquations1D + ) + + # create the "external" boundary solution state + u_boundary = SVector( + u_inner[1], + -u_inner[2], + u_inner[3] + ) + + # calculate the boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function( + u_inner, u_boundary, orientation_or_normal, + equations + ) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function( + u_boundary, u_inner, orientation_or_normal, + equations + ) + end + + return flux + end + + # Calculate 1D flux for a single point + # Note, the bottom topography has no flux + @inline function flux(u, orientation::Integer, equations::ShallowWaterEquations1D) + h, h_v, _ = u + v = velocity(u, equations) + + p = 0.5f0 * equations.gravity * h^2 + + f1 = h_v + f2 = h_v * v + p + + return SVector(f1, f2, 0) + end + + """ + flux_nonconservative_wintermeyer_etal(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D) + + Non-symmetric two-point volume flux discretizing the nonconservative (source) term + that contains the gradient of the bottom topography [`ShallowWaterEquations1D`](@ref). + + Gives entropy conservation and well-balancedness on both the volume and surface when combined with + [`flux_wintermeyer_etal`](@ref). + + Further details are available in the papers: + - Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) + An entropy stable nodal discontinuous Galerkin method for the two dimensional + shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry + [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) + - Patrick Ersing, Andrew R. Winters (2023) + An entropy stable discontinuous Galerkin method for the two-layer shallow water equations on + curvilinear meshes + [DOI: 10.48550/arXiv.2306.12699](https://doi.org/10.48550/arXiv.2306.12699) + """ + @inline function flux_nonconservative_wintermeyer_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D + ) + # Pull the necessary left and right state information + h_ll = waterheight(u_ll, equations) + b_jump = u_rr[3] - u_ll[3] + + # Bottom gradient nonconservative term: (0, g h b_x, 0) + f = SVector(0, equations.gravity * h_ll * b_jump, 0) + + return f + end + + """ + flux_nonconservative_fjordholm_etal(u_ll, u_rr, orientation::Integer, equations::ShallowWaterEquations1D) -A particular type of hydrostatic reconstruction on the water height to guarantee well-balancedness -for a general bottom topography [`ShallowWaterEquations1D`](@ref). The reconstructed solution states -`u_ll_star` and `u_rr_star` variables are then used to evaluate the surface numerical flux at the interface. -Use in combination with the generic numerical flux routine [`FluxHydrostaticReconstruction`](@ref). - -Further details on this hydrostatic reconstruction and its motivation can be found in -- Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) - A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows - [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) -""" -@inline function hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, - equations::ShallowWaterEquations1D) - # Unpack left and right water heights and bottom topographies - h_ll, _, b_ll = u_ll - h_rr, _, b_rr = u_rr - - # Get the velocities on either side - v1_ll = velocity(u_ll, equations) - v1_rr = velocity(u_rr, equations) - - # Compute the reconstructed water heights - h_ll_star = max(0, h_ll + b_ll - max(b_ll, b_rr)) - h_rr_star = max(0, h_rr + b_rr - max(b_ll, b_rr)) - - # Create the conservative variables using the reconstruted water heights - u_ll_star = SVector(h_ll_star, h_ll_star * v1_ll, b_ll) - u_rr_star = SVector(h_rr_star, h_rr_star * v1_rr, b_rr) - - return u_ll_star, u_rr_star -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the -# maximum velocity magnitude plus the maximum speed of sound -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) - # Get the velocity quantities - v_ll = velocity(u_ll, equations) - v_rr = velocity(u_rr, equations) - - # Calculate the wave celerity on the left and right - h_ll = waterheight(u_ll, equations) - h_rr = waterheight(u_rr, equations) - c_ll = sqrt(equations.gravity * h_ll) - c_rr = sqrt(equations.gravity * h_rr) - - return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) -end - -# Specialized `DissipationLocalLaxFriedrichs` to avoid spurious dissipation in the bottom topography -@inline function (dissipation::DissipationLocalLaxFriedrichs)(u_ll, u_rr, - orientation_or_normal_direction, - equations::ShallowWaterEquations1D) - λ = dissipation.max_abs_speed(u_ll, u_rr, orientation_or_normal_direction, - equations) - diss = -0.5f0 * λ * (u_rr - u_ll) - return SVector(diss[1], diss[2], 0) -end - -# Specialized `FluxHLL` to avoid spurious dissipation in the bottom topography -@inline function (numflux::FluxHLL)(u_ll, u_rr, orientation_or_normal_direction, - equations::ShallowWaterEquations1D) - λ_min, λ_max = numflux.min_max_speed(u_ll, u_rr, orientation_or_normal_direction, - equations) - - if λ_min >= 0 && λ_max >= 0 - return flux(u_ll, orientation_or_normal_direction, equations) - elseif λ_max <= 0 && λ_min <= 0 - return flux(u_rr, orientation_or_normal_direction, equations) - else - f_ll = flux(u_ll, orientation_or_normal_direction, equations) - f_rr = flux(u_rr, orientation_or_normal_direction, equations) - inv_λ_max_minus_λ_min = inv(λ_max - λ_min) - factor_ll = λ_max * inv_λ_max_minus_λ_min - factor_rr = λ_min * inv_λ_max_minus_λ_min - factor_diss = λ_min * λ_max * inv_λ_max_minus_λ_min - diss = u_rr - u_ll - return factor_ll * f_ll - factor_rr * f_rr + - factor_diss * SVector(diss[1], diss[2], 0) - end -end - -# Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) - h_ll = waterheight(u_ll, equations) - v_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v_rr = velocity(u_rr, equations) - - λ_min = v_ll - sqrt(equations.gravity * h_ll) - λ_max = v_rr + sqrt(equations.gravity * h_rr) - - return λ_min, λ_max -end - -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) - h_ll = waterheight(u_ll, equations) - v_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v_rr = velocity(u_rr, equations) - - c_ll = sqrt(equations.gravity * h_ll) - c_rr = sqrt(equations.gravity * h_rr) - - λ_min = min(v_ll - c_ll, v_rr - c_rr) - λ_max = max(v_ll + c_ll, v_rr + c_rr) - - return λ_min, λ_max -end - -@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations1D) - h_ll = waterheight(u_ll, equations) - v_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v_rr = velocity(u_rr, equations) - - c_ll = sqrt(equations.gravity * h_ll) - c_rr = sqrt(equations.gravity * h_rr) - - v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, orientation, equations) - - λ_min = min(v_ll - c_ll, v_roe - c_roe) - λ_max = max(v_rr + c_rr, v_roe + c_roe) - - return λ_min, λ_max -end - -@inline function max_abs_speeds(u, equations::ShallowWaterEquations1D) - h = waterheight(u, equations) - v = velocity(u, equations) - - c = sqrt(equations.gravity * h) - return (abs(v) + c,) -end - -# Helper function to extract the velocity vector from the conservative variables -@inline function velocity(u, equations::ShallowWaterEquations1D) - h, h_v, _ = u - - v = h_v / h - - return v -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::ShallowWaterEquations1D) - h, _, b = u - - H = h + b - v = velocity(u, equations) - return SVector(H, v, b) -end - -# Convert conservative variables to entropy -# Note, only the first two are the entropy variables, the third entry still -# just carries the bottom topography values for convenience -@inline function cons2entropy(u, equations::ShallowWaterEquations1D) - h, _, b = u - - v = velocity(u, equations) - - w1 = equations.gravity * (h + b) - 0.5f0 * v^2 - w2 = v - - return SVector(w1, w2, b) -end - -# Convert entropy variables to conservative -@inline function entropy2cons(w, equations::ShallowWaterEquations1D) - w1, w2, b = w - - h = (w1 + 0.5f0 * w2^2) / equations.gravity - b - h_v = h * w2 - return SVector(h, h_v, b) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::ShallowWaterEquations1D) - H, v, b = prim - - h = H - b - h_v = h * v - - return SVector(h, h_v, b) -end + Non-symmetric two-point surface flux discretizing the nonconservative (source) term of + that contains the gradient of the bottom topography [`ShallowWaterEquations1D`](@ref). + + This flux can be used together with [`flux_fjordholm_etal`](@ref) at interfaces to ensure entropy + conservation and well-balancedness. + + Further details for the original finite volume formulation are available in + - Ulrik S. Fjordholm, Siddhartha Mishr and Eitan Tadmor (2011) + Well-balanced and energy stable schemes for the shallow water equations with discontinuous topography + [DOI: 10.1016/j.jcp.2011.03.042](https://doi.org/10.1016/j.jcp.2011.03.042) + and for curvilinear 2D case in the paper: + - Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) + An entropy stable nodal discontinuous Galerkin method for the two dimensional + shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry + [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) + """ + @inline function flux_nonconservative_fjordholm_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D + ) + # Pull the necessary left and right state information + h_ll, _, b_ll = u_ll + h_rr, _, b_rr = u_rr + + h_average = 0.5f0 * (h_ll + h_rr) + b_jump = b_rr - b_ll + + # Includes two parts: + # (i) Diagonal (consistent) term from the volume flux that uses `b_ll` to avoid + # cross-averaging across a discontinuous bottom topography + # (ii) True surface part that uses `h_average` and `b_jump` to handle discontinuous bathymetry + f = SVector( + 0, + equations.gravity * h_average * b_jump, + 0 + ) + + return f + end + + """ + flux_nonconservative_audusse_etal(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D) + + Non-symmetric two-point surface flux that discretizes the nonconservative (source) term. + The discretization uses the `hydrostatic_reconstruction_audusse_etal` on the conservative + variables. + + This hydrostatic reconstruction ensures that the finite volume numerical fluxes remain + well-balanced for discontinuous bottom topographies [`ShallowWaterEquations1D`](@ref). + Should be used together with [`FluxHydrostaticReconstruction`](@ref) and + [`hydrostatic_reconstruction_audusse_etal`](@ref) in the surface flux to ensure consistency. + + Further details on the hydrostatic reconstruction and its motivation can be found in + - Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) + A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows + [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) + """ + @inline function flux_nonconservative_audusse_etal( + u_ll, u_rr, + orientation::Integer, + equations::ShallowWaterEquations1D + ) + # Pull the water height and bottom topography on the left + h_ll, _, _ = u_ll + + # Create the hydrostatic reconstruction for the left solution state + u_ll_star, _ = hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, equations) + + # Copy the reconstructed water height for easier to read code + h_ll_star = u_ll_star[1] + + return SVector( + 0, + equations.gravity * (h_ll^2 - h_ll_star^2), + 0 + ) + end + + """ + flux_fjordholm_etal(u_ll, u_rr, orientation, + equations::ShallowWaterEquations1D) + + Total energy conservative (mathematical entropy for shallow water equations). When the bottom topography + is nonzero this should only be used as a surface flux otherwise the scheme will not be well-balanced. + For well-balancedness in the volume flux use [`flux_wintermeyer_etal`](@ref). + + Details are available in Eq. (4.1) in the paper: + - Ulrik S. Fjordholm, Siddhartha Mishr and Eitan Tadmor (2011) + Well-balanced and energy stable schemes for the shallow water equations with discontinuous topography + [DOI: 10.1016/j.jcp.2011.03.042](https://doi.org/10.1016/j.jcp.2011.03.042) + """ + @inline function flux_fjordholm_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D + ) + # Unpack left and right state + h_ll = waterheight(u_ll, equations) + v_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v_rr = velocity(u_rr, equations) + + # Average each factor of products in flux + h_avg = 0.5f0 * (h_ll + h_rr) + v_avg = 0.5f0 * (v_ll + v_rr) + p_avg = 0.25f0 * equations.gravity * (h_ll^2 + h_rr^2) + + # Calculate fluxes depending on orientation + f1 = h_avg * v_avg + f2 = f1 * v_avg + p_avg + + return SVector(f1, f2, 0) + end + + """ + flux_wintermeyer_etal(u_ll, u_rr, orientation, + equations::ShallowWaterEquations1D) + + Total energy conservative (mathematical entropy for shallow water equations) split form. + When the bottom topography is nonzero this scheme will be well-balanced when used as a `volume_flux`. + For the `surface_flux` either [`flux_wintermeyer_etal`](@ref) or [`flux_fjordholm_etal`](@ref) can + be used to ensure well-balancedness and entropy conservation. + + Further details are available in Theorem 1 of the paper: + - Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) + An entropy stable nodal discontinuous Galerkin method for the two dimensional + shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry + [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) + """ + @inline function flux_wintermeyer_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D + ) + # Unpack left and right state + h_ll, h_v_ll, _ = u_ll + h_rr, h_v_rr, _ = u_rr + + # Get the velocities on either side + v_ll = velocity(u_ll, equations) + v_rr = velocity(u_rr, equations) + + # Average each factor of products in flux + v_avg = 0.5f0 * (v_ll + v_rr) + p_avg = 0.5f0 * equations.gravity * h_ll * h_rr + + # Calculate fluxes depending on orientation + f1 = 0.5f0 * (h_v_ll + h_v_rr) + f2 = f1 * v_avg + p_avg + + return SVector(f1, f2, 0) + end + + """ + hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D) + + A particular type of hydrostatic reconstruction on the water height to guarantee well-balancedness + for a general bottom topography [`ShallowWaterEquations1D`](@ref). The reconstructed solution states + `u_ll_star` and `u_rr_star` variables are then used to evaluate the surface numerical flux at the interface. + Use in combination with the generic numerical flux routine [`FluxHydrostaticReconstruction`](@ref). + + Further details on this hydrostatic reconstruction and its motivation can be found in + - Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) + A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows + [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) + """ + @inline function hydrostatic_reconstruction_audusse_etal( + u_ll, u_rr, + equations::ShallowWaterEquations1D + ) + # Unpack left and right water heights and bottom topographies + h_ll, _, b_ll = u_ll + h_rr, _, b_rr = u_rr + + # Get the velocities on either side + v1_ll = velocity(u_ll, equations) + v1_rr = velocity(u_rr, equations) + + # Compute the reconstructed water heights + h_ll_star = max(0, h_ll + b_ll - max(b_ll, b_rr)) + h_rr_star = max(0, h_rr + b_rr - max(b_ll, b_rr)) + + # Create the conservative variables using the reconstruted water heights + u_ll_star = SVector(h_ll_star, h_ll_star * v1_ll, b_ll) + u_rr_star = SVector(h_rr_star, h_rr_star * v1_rr, b_rr) + + return u_ll_star, u_rr_star + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the + # maximum velocity magnitude plus the maximum speed of sound + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D + ) + # Get the velocity quantities + v_ll = velocity(u_ll, equations) + v_rr = velocity(u_rr, equations) + + # Calculate the wave celerity on the left and right + h_ll = waterheight(u_ll, equations) + h_rr = waterheight(u_rr, equations) + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) + end + + # Specialized `DissipationLocalLaxFriedrichs` to avoid spurious dissipation in the bottom topography + @inline function (dissipation::DissipationLocalLaxFriedrichs)( + u_ll, u_rr, + orientation_or_normal_direction, + equations::ShallowWaterEquations1D + ) + λ = dissipation.max_abs_speed( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + diss = -0.5f0 * λ * (u_rr - u_ll) + return SVector(diss[1], diss[2], 0) + end + + # Specialized `FluxHLL` to avoid spurious dissipation in the bottom topography + @inline function (numflux::FluxHLL)( + u_ll, u_rr, orientation_or_normal_direction, + equations::ShallowWaterEquations1D + ) + λ_min, λ_max = numflux.min_max_speed( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + + if λ_min >= 0 && λ_max >= 0 + return flux(u_ll, orientation_or_normal_direction, equations) + elseif λ_max <= 0 && λ_min <= 0 + return flux(u_rr, orientation_or_normal_direction, equations) + else + f_ll = flux(u_ll, orientation_or_normal_direction, equations) + f_rr = flux(u_rr, orientation_or_normal_direction, equations) + inv_λ_max_minus_λ_min = inv(λ_max - λ_min) + factor_ll = λ_max * inv_λ_max_minus_λ_min + factor_rr = λ_min * inv_λ_max_minus_λ_min + factor_diss = λ_min * λ_max * inv_λ_max_minus_λ_min + diss = u_rr - u_ll + return factor_ll * f_ll - factor_rr * f_rr + + factor_diss * SVector(diss[1], diss[2], 0) + end + end + + # Calculate estimate for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D + ) + h_ll = waterheight(u_ll, equations) + v_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v_rr = velocity(u_rr, equations) + + λ_min = v_ll - sqrt(equations.gravity * h_ll) + λ_max = v_rr + sqrt(equations.gravity * h_rr) + + return λ_min, λ_max + end + + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D + ) + h_ll = waterheight(u_ll, equations) + v_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v_rr = velocity(u_rr, equations) + + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + λ_min = min(v_ll - c_ll, v_rr - c_rr) + λ_max = max(v_ll + c_ll, v_rr + c_rr) + + return λ_min, λ_max + end + + @inline function min_max_speed_einfeldt( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations1D + ) + h_ll = waterheight(u_ll, equations) + v_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v_rr = velocity(u_rr, equations) -@inline function waterheight(u, equations::ShallowWaterEquations1D) - return u[1] -end - -@inline function pressure(u, equations::ShallowWaterEquations1D) - h = waterheight(u, equations) - p = 0.5f0 * equations.gravity * h^2 - return p -end - -@inline function waterheight_pressure(u, equations::ShallowWaterEquations1D) - return waterheight(u, equations) * pressure(u, equations) -end + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) -""" - calc_wavespeed_roe(u_ll, u_rr, direction::Integer, - equations::ShallowWaterEquations1D) - -Calculate Roe-averaged velocity `v_roe` and wavespeed `c_roe = sqrt{g * h_roe}` -See for instance equation (62) in -- Paul A. Ullrich, Christiane Jablonowski, and Bram van Leer (2010) - High-order finite-volume methods for the shallow-water equations on the sphere - [DOI: 10.1016/j.jcp.2010.04.044](https://doi.org/10.1016/j.jcp.2010.04.044) -Or equation (9.17) in [this lecture notes](https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf). -""" -@inline function calc_wavespeed_roe(u_ll, u_rr, direction::Integer, - equations::ShallowWaterEquations1D) - h_ll = waterheight(u_ll, equations) - v_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v_rr = velocity(u_rr, equations) + v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, orientation, equations) - h_roe = 0.5f0 * (h_ll + h_rr) - c_roe = sqrt(equations.gravity * h_roe) - - h_ll_sqrt = sqrt(h_ll) - h_rr_sqrt = sqrt(h_rr) - - v_roe = (h_ll_sqrt * v_ll + h_rr_sqrt * v_rr) / (h_ll_sqrt + h_rr_sqrt) - - return v_roe, c_roe -end - -# Entropy function for the shallow water equations is the total energy -@inline function entropy(cons, equations::ShallowWaterEquations1D) - energy_total(cons, equations) -end - -# Calculate total energy for a conservative state `cons` -@inline function energy_total(cons, equations::ShallowWaterEquations1D) - h, h_v, b = cons - - e = (h_v^2) / (2 * h) + 0.5f0 * equations.gravity * h^2 + equations.gravity * h * b - return e -end - -# Calculate kinetic energy for a conservative state `cons` -@inline function energy_kinetic(u, equations::ShallowWaterEquations1D) - h, h_v, _ = u - return (h_v^2) / (2 * h) -end - -# Calculate potential energy for a conservative state `cons` -@inline function energy_internal(cons, equations::ShallowWaterEquations1D) - return energy_total(cons, equations) - energy_kinetic(cons, equations) -end - -# Calculate the error for the "lake-at-rest" test case where H = h+b should -# be a constant value over time. -@inline function lake_at_rest_error(u, equations::ShallowWaterEquations1D) - h, _, b = u - - return abs(equations.H0 - (h + b)) -end + λ_min = min(v_ll - c_ll, v_roe - c_roe) + λ_max = max(v_rr + c_rr, v_roe + c_roe) + + return λ_min, λ_max + end + + @inline function max_abs_speeds(u, equations::ShallowWaterEquations1D) + h = waterheight(u, equations) + v = velocity(u, equations) + + c = sqrt(equations.gravity * h) + return (abs(v) + c,) + end + + # Helper function to extract the velocity vector from the conservative variables + @inline function velocity(u, equations::ShallowWaterEquations1D) + h, h_v, _ = u + + v = h_v / h + + return v + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::ShallowWaterEquations1D) + h, _, b = u + + H = h + b + v = velocity(u, equations) + return SVector(H, v, b) + end + + # Convert conservative variables to entropy + # Note, only the first two are the entropy variables, the third entry still + # just carries the bottom topography values for convenience + @inline function cons2entropy(u, equations::ShallowWaterEquations1D) + h, _, b = u + + v = velocity(u, equations) + + w1 = equations.gravity * (h + b) - 0.5f0 * v^2 + w2 = v + + return SVector(w1, w2, b) + end + + # Convert entropy variables to conservative + @inline function entropy2cons(w, equations::ShallowWaterEquations1D) + w1, w2, b = w + + h = (w1 + 0.5f0 * w2^2) / equations.gravity - b + h_v = h * w2 + return SVector(h, h_v, b) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::ShallowWaterEquations1D) + H, v, b = prim + + h = H - b + h_v = h * v + + return SVector(h, h_v, b) + end + + @inline function waterheight(u, equations::ShallowWaterEquations1D) + return u[1] + end + + @inline function pressure(u, equations::ShallowWaterEquations1D) + h = waterheight(u, equations) + p = 0.5f0 * equations.gravity * h^2 + return p + end + + @inline function waterheight_pressure(u, equations::ShallowWaterEquations1D) + return waterheight(u, equations) * pressure(u, equations) + end + + """ + calc_wavespeed_roe(u_ll, u_rr, direction::Integer, + equations::ShallowWaterEquations1D) + + Calculate Roe-averaged velocity `v_roe` and wavespeed `c_roe = sqrt{g * h_roe}` + See for instance equation (62) in + - Paul A. Ullrich, Christiane Jablonowski, and Bram van Leer (2010) + High-order finite-volume methods for the shallow-water equations on the sphere + [DOI: 10.1016/j.jcp.2010.04.044](https://doi.org/10.1016/j.jcp.2010.04.044) + Or equation (9.17) in [this lecture notes](https://metaphor.ethz.ch/x/2019/hs/401-4671-00L/literature/mishra_hyperbolic_pdes.pdf). + """ + @inline function calc_wavespeed_roe( + u_ll, u_rr, direction::Integer, + equations::ShallowWaterEquations1D + ) + h_ll = waterheight(u_ll, equations) + v_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v_rr = velocity(u_rr, equations) + + h_roe = 0.5f0 * (h_ll + h_rr) + c_roe = sqrt(equations.gravity * h_roe) + + h_ll_sqrt = sqrt(h_ll) + h_rr_sqrt = sqrt(h_rr) + + v_roe = (h_ll_sqrt * v_ll + h_rr_sqrt * v_rr) / (h_ll_sqrt + h_rr_sqrt) + + return v_roe, c_roe + end + + # Entropy function for the shallow water equations is the total energy + @inline function entropy(cons, equations::ShallowWaterEquations1D) + energy_total(cons, equations) + end + + # Calculate total energy for a conservative state `cons` + @inline function energy_total(cons, equations::ShallowWaterEquations1D) + h, h_v, b = cons + + e = (h_v^2) / (2 * h) + 0.5f0 * equations.gravity * h^2 + equations.gravity * h * b + return e + end + + # Calculate kinetic energy for a conservative state `cons` + @inline function energy_kinetic(u, equations::ShallowWaterEquations1D) + h, h_v, _ = u + return (h_v^2) / (2 * h) + end + + # Calculate potential energy for a conservative state `cons` + @inline function energy_internal(cons, equations::ShallowWaterEquations1D) + return energy_total(cons, equations) - energy_kinetic(cons, equations) + end + + # Calculate the error for the "lake-at-rest" test case where H = h+b should + # be a constant value over time. + @inline function lake_at_rest_error(u, equations::ShallowWaterEquations1D) + h, _, b = u + + return abs(equations.H0 - (h + b)) + end end # @muladd diff --git a/src/equations/shallow_water_2d.jl b/src/equations/shallow_water_2d.jl index 4ecaf3b6e1..321e2439e0 100644 --- a/src/equations/shallow_water_2d.jl +++ b/src/equations/shallow_water_2d.jl @@ -3,988 +3,1058 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - ShallowWaterEquations2D(; gravity, H0 = 0) - -Shallow water equations (SWE) in two space dimensions. The equations are given by -```math -\begin{aligned} - \frac{\partial h}{\partial t} + \frac{\partial}{\partial x}(h v_1) - + \frac{\partial}{\partial y}(h v_2) &= 0 \\ - \frac{\partial}{\partial t}(h v_1) + \frac{\partial}{\partial x}\left(h v_1^2 + \frac{g}{2}h^2\right) - + \frac{\partial}{\partial y}(h v_1 v_2) + g h \frac{\partial b}{\partial x} &= 0 \\ - \frac{\partial}{\partial t}(h v_2) + \frac{\partial}{\partial x}(h v_1 v_2) - + \frac{\partial}{\partial y}\left(h v_2^2 + \frac{g}{2}h^2\right) + g h \frac{\partial b}{\partial y} &= 0. -\end{aligned} -``` -The unknown quantities of the SWE are the water height ``h`` and the velocities ``\mathbf{v} = (v_1, v_2)^T``. -The gravitational constant is denoted by `g` and the (possibly) variable bottom topography function ``b(x,y)``. -Conservative variable water height ``h`` is measured from the bottom topography ``b``, therefore one -also defines the total water height as ``H = h + b``. - -The additional quantity ``H_0`` is also available to store a reference value for the total water height that -is useful to set initial conditions or test the "lake-at-rest" well-balancedness. - -The bottom topography function ``b(x,y)`` is set inside the initial condition routine -for a particular problem setup. To test the conservative form of the SWE one can set the bottom topography -variable `b` to zero. - -In addition to the unknowns, Trixi.jl currently stores the bottom topography values at the approximation points -despite being fixed in time. This is done for convenience of computing the bottom topography gradients -on the fly during the approximation as well as computing auxiliary quantities like the total water height ``H`` -or the entropy variables. -This affects the implementation and use of these equations in various ways: -* The flux values corresponding to the bottom topography must be zero. -* The bottom topography values must be included when defining initial conditions, boundary conditions or - source terms. -* [`AnalysisCallback`](@ref) analyzes this variable. -* Trixi.jl's visualization tools will visualize the bottom topography by default. - -References for the SWE are many but a good introduction is available in Chapter 13 of the book: -- Randall J. LeVeque (2002) - Finite Volume Methods for Hyperbolic Problems - [DOI: 10.1017/CBO9780511791253](https://doi.org/10.1017/CBO9780511791253) -""" -struct ShallowWaterEquations2D{RealT <: Real} <: AbstractShallowWaterEquations{2, 4} - gravity::RealT # gravitational constant - H0::RealT # constant "lake-at-rest" total water height -end - -# Allow for flexibility to set the gravitational constant within an elixir depending on the -# application where `gravity_constant=1.0` or `gravity_constant=9.81` are common values. -# The reference total water height H0 defaults to 0.0 but is used for the "lake-at-rest" -# well-balancedness test cases. -# Strict default values for thresholds that performed well in many numerical experiments -function ShallowWaterEquations2D(; gravity_constant, H0 = zero(gravity_constant)) - ShallowWaterEquations2D(gravity_constant, H0) -end - -have_nonconservative_terms(::ShallowWaterEquations2D) = True() -varnames(::typeof(cons2cons), ::ShallowWaterEquations2D) = ("h", "h_v1", "h_v2", "b") -# Note, we use the total water height, H = h + b, as the first primitive variable for easier -# visualization and setting initial conditions -varnames(::typeof(cons2prim), ::ShallowWaterEquations2D) = ("H", "v1", "v2", "b") - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_convergence_test(x, t, equations::ShallowWaterEquations2D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -function initial_condition_convergence_test(x, t, equations::ShallowWaterEquations2D) - # some constants are chosen such that the function is periodic on the domain [0,sqrt(2)]^2 - RealT = eltype(x) - c = 7 - omega_x = 2 * convert(RealT, pi) * sqrt(convert(RealT, 2)) - omega_t = 2 * convert(RealT, pi) - - x1, x2 = x - - H = c + cos(omega_x * x1) * sin(omega_x * x2) * cos(omega_t * t) - v1 = 0.5f0 - v2 = 1.5f0 - b = 2 + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x1) + - 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x2) - return prim2cons(SVector(H, v1, v2, b), equations) -end - -""" - source_terms_convergence_test(u, x, t, equations::ShallowWaterEquations2D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). - -This manufactured solution source term is specifically designed for the bottom topography function -`b(x,y) = 2 + 0.5 * sinpi(sqrt(2) * x) + 0.5 * sinpi(sqrt(2) * y)` -as defined in [`initial_condition_convergence_test`](@ref). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::ShallowWaterEquations2D) - # Same settings as in `initial_condition_convergence_test`. Some derivative simplify because - # this manufactured solution velocities are taken to be constants - RealT = eltype(u) - c = 7 - omega_x = 2 * convert(RealT, pi) * sqrt(convert(RealT, 2)) - omega_t = 2 * convert(RealT, pi) - omega_b = sqrt(convert(RealT, 2)) * convert(RealT, pi) - v1 = 0.5f0 - v2 = 1.5f0 - - x1, x2 = x - - sinX, cosX = sincos(omega_x * x1) - sinY, cosY = sincos(omega_x * x2) - sinT, cosT = sincos(omega_t * t) - - H = c + cosX * sinY * cosT - H_x = -omega_x * sinX * sinY * cosT - H_y = omega_x * cosX * cosY * cosT - # this time derivative for the water height exploits that the bottom topography is - # fixed in time such that H_t = (h+b)_t = h_t + 0 - H_t = -omega_t * cosX * sinY * sinT - - # bottom topography and its gradient - b = 2 + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x1) + - 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x2) - tmp1 = 0.5f0 * omega_b - b_x = tmp1 * cos(omega_b * x1) - b_y = tmp1 * cos(omega_b * x2) - - du1 = H_t + v1 * (H_x - b_x) + v2 * (H_y - b_y) - du2 = v1 * du1 + equations.gravity * (H - b) * H_x - du3 = v2 * du1 + equations.gravity * (H - b) * H_y - return SVector(du1, du2, du3, 0) -end - -""" - initial_condition_weak_blast_wave(x, t, equations::ShallowWaterEquations2D) - -A weak blast wave discontinuity useful for testing, e.g., total energy conservation. -Note for the shallow water equations to the total energy acts as a mathematical entropy function. -""" -function initial_condition_weak_blast_wave(x, t, equations::ShallowWaterEquations2D) - # Set up polar coordinates - RealT = eltype(x) - inicenter = SVector(convert(RealT, 0.7), convert(RealT, 0.7)) - x_norm = x[1] - inicenter[1] - y_norm = x[2] - inicenter[2] - r = sqrt(x_norm^2 + y_norm^2) - phi = atan(y_norm, x_norm) - sin_phi, cos_phi = sincos(phi) - - # Calculate primitive variables - H = r > 0.5f0 ? 3.25f0 : 4.0f0 - v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi - v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin_phi - b = 0 # by default assume there is no bottom topography - - return prim2cons(SVector(H, v1, v2, b), equations) -end - -""" - boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, - equations::ShallowWaterEquations2D) -Create a boundary state by reflecting the normal velocity component and keep -the tangential velocity component unchanged. The boundary water height is taken from -the internal value. -For details see Section 9.2.5 of the book: -- Eleuterio F. Toro (2001) - Shock-Capturing Methods for Free-Surface Shallow Flows - 1st edition - ISBN 0471987662 -""" -@inline function boundary_condition_slip_wall(u_inner, normal_direction::AbstractVector, - x, t, - surface_flux_function, - equations::ShallowWaterEquations2D) - # normalize the outward pointing direction - normal = normal_direction / norm(normal_direction) + #! format: noindent + + @doc raw""" + ShallowWaterEquations2D(; gravity, H0 = 0) + + Shallow water equations (SWE) in two space dimensions. The equations are given by + ```math + \begin{aligned} + \frac{\partial h}{\partial t} + \frac{\partial}{\partial x}(h v_1) + + \frac{\partial}{\partial y}(h v_2) &= 0 \\ + \frac{\partial}{\partial t}(h v_1) + \frac{\partial}{\partial x}\left(h v_1^2 + \frac{g}{2}h^2\right) + + \frac{\partial}{\partial y}(h v_1 v_2) + g h \frac{\partial b}{\partial x} &= 0 \\ + \frac{\partial}{\partial t}(h v_2) + \frac{\partial}{\partial x}(h v_1 v_2) + + \frac{\partial}{\partial y}\left(h v_2^2 + \frac{g}{2}h^2\right) + g h \frac{\partial b}{\partial y} &= 0. + \end{aligned} + ``` + The unknown quantities of the SWE are the water height ``h`` and the velocities ``\mathbf{v} = (v_1, v_2)^T``. + The gravitational constant is denoted by `g` and the (possibly) variable bottom topography function ``b(x,y)``. + Conservative variable water height ``h`` is measured from the bottom topography ``b``, therefore one + also defines the total water height as ``H = h + b``. + + The additional quantity ``H_0`` is also available to store a reference value for the total water height that + is useful to set initial conditions or test the "lake-at-rest" well-balancedness. + + The bottom topography function ``b(x,y)`` is set inside the initial condition routine + for a particular problem setup. To test the conservative form of the SWE one can set the bottom topography + variable `b` to zero. + + In addition to the unknowns, Trixi.jl currently stores the bottom topography values at the approximation points + despite being fixed in time. This is done for convenience of computing the bottom topography gradients + on the fly during the approximation as well as computing auxiliary quantities like the total water height ``H`` + or the entropy variables. + This affects the implementation and use of these equations in various ways: + * The flux values corresponding to the bottom topography must be zero. + * The bottom topography values must be included when defining initial conditions, boundary conditions or + source terms. + * [`AnalysisCallback`](@ref) analyzes this variable. + * Trixi.jl's visualization tools will visualize the bottom topography by default. + + References for the SWE are many but a good introduction is available in Chapter 13 of the book: + - Randall J. LeVeque (2002) + Finite Volume Methods for Hyperbolic Problems + [DOI: 10.1017/CBO9780511791253](https://doi.org/10.1017/CBO9780511791253) + """ + struct ShallowWaterEquations2D{RealT <: Real} <: AbstractShallowWaterEquations{2, 4} + gravity::RealT # gravitational constant + H0::RealT # constant "lake-at-rest" total water height + end - # compute the normal velocity - u_normal = normal[1] * u_inner[2] + normal[2] * u_inner[3] + # Allow for flexibility to set the gravitational constant within an elixir depending on the + # application where `gravity_constant=1.0` or `gravity_constant=9.81` are common values. + # The reference total water height H0 defaults to 0.0 but is used for the "lake-at-rest" + # well-balancedness test cases. + # Strict default values for thresholds that performed well in many numerical experiments + function ShallowWaterEquations2D(; gravity_constant, H0 = zero(gravity_constant)) + ShallowWaterEquations2D(gravity_constant, H0) + end - # create the "external" boundary solution state - u_boundary = SVector(u_inner[1], - u_inner[2] - 2 * u_normal * normal[1], - u_inner[3] - 2 * u_normal * normal[2], - u_inner[4]) + have_nonconservative_terms(::ShallowWaterEquations2D) = True() + varnames(::typeof(cons2cons), ::ShallowWaterEquations2D) = ("h", "h_v1", "h_v2", "b") + # Note, we use the total water height, H = h + b, as the first primitive variable for easier + # visualization and setting initial conditions + varnames(::typeof(cons2prim), ::ShallowWaterEquations2D) = ("H", "v1", "v2", "b") + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_convergence_test(x, t, equations::ShallowWaterEquations2D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + function initial_condition_convergence_test(x, t, equations::ShallowWaterEquations2D) + # some constants are chosen such that the function is periodic on the domain [0,sqrt(2)]^2 + RealT = eltype(x) + c = 7 + omega_x = 2 * convert(RealT, pi) * sqrt(convert(RealT, 2)) + omega_t = 2 * convert(RealT, pi) + + x1, x2 = x + + H = c + cos(omega_x * x1) * sin(omega_x * x2) * cos(omega_t * t) + v1 = 0.5f0 + v2 = 1.5f0 + b = 2 + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x1) + + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x2) + return prim2cons(SVector(H, v1, v2, b), equations) + end - # calculate the boundary flux - flux = surface_flux_function(u_inner, u_boundary, normal_direction, equations) + """ + source_terms_convergence_test(u, x, t, equations::ShallowWaterEquations2D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + + This manufactured solution source term is specifically designed for the bottom topography function + `b(x,y) = 2 + 0.5 * sinpi(sqrt(2) * x) + 0.5 * sinpi(sqrt(2) * y)` + as defined in [`initial_condition_convergence_test`](@ref). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::ShallowWaterEquations2D + ) + # Same settings as in `initial_condition_convergence_test`. Some derivative simplify because + # this manufactured solution velocities are taken to be constants + RealT = eltype(u) + c = 7 + omega_x = 2 * convert(RealT, pi) * sqrt(convert(RealT, 2)) + omega_t = 2 * convert(RealT, pi) + omega_b = sqrt(convert(RealT, 2)) * convert(RealT, pi) + v1 = 0.5f0 + v2 = 1.5f0 + + x1, x2 = x + + sinX, cosX = sincos(omega_x * x1) + sinY, cosY = sincos(omega_x * x2) + sinT, cosT = sincos(omega_t * t) + + H = c + cosX * sinY * cosT + H_x = -omega_x * sinX * sinY * cosT + H_y = omega_x * cosX * cosY * cosT + # this time derivative for the water height exploits that the bottom topography is + # fixed in time such that H_t = (h+b)_t = h_t + 0 + H_t = -omega_t * cosX * sinY * sinT + + # bottom topography and its gradient + b = 2 + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x1) + + 0.5f0 * sinpi(sqrt(convert(RealT, 2)) * x2) + tmp1 = 0.5f0 * omega_b + b_x = tmp1 * cos(omega_b * x1) + b_y = tmp1 * cos(omega_b * x2) + + du1 = H_t + v1 * (H_x - b_x) + v2 * (H_y - b_y) + du2 = v1 * du1 + equations.gravity * (H - b) * H_x + du3 = v2 * du1 + equations.gravity * (H - b) * H_y + return SVector(du1, du2, du3, 0) + end - return flux -end + """ + initial_condition_weak_blast_wave(x, t, equations::ShallowWaterEquations2D) + + A weak blast wave discontinuity useful for testing, e.g., total energy conservation. + Note for the shallow water equations to the total energy acts as a mathematical entropy function. + """ + function initial_condition_weak_blast_wave(x, t, equations::ShallowWaterEquations2D) + # Set up polar coordinates + RealT = eltype(x) + inicenter = SVector(convert(RealT, 0.7), convert(RealT, 0.7)) + x_norm = x[1] - inicenter[1] + y_norm = x[2] - inicenter[2] + r = sqrt(x_norm^2 + y_norm^2) + phi = atan(y_norm, x_norm) + sin_phi, cos_phi = sincos(phi) + + # Calculate primitive variables + H = r > 0.5f0 ? 3.25f0 : 4.0f0 + v1 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * cos_phi + v2 = r > 0.5f0 ? zero(RealT) : convert(RealT, 0.1882) * sin_phi + b = 0 # by default assume there is no bottom topography + + return prim2cons(SVector(H, v1, v2, b), equations) + end -""" - boundary_condition_slip_wall(u_inner, orientation, direction, x, t, - surface_flux_function, equations::ShallowWaterEquations2D) + """ + boundary_condition_slip_wall(u_inner, normal_direction, x, t, surface_flux_function, + equations::ShallowWaterEquations2D) + Create a boundary state by reflecting the normal velocity component and keep + the tangential velocity component unchanged. The boundary water height is taken from + the internal value. + For details see Section 9.2.5 of the book: + - Eleuterio F. Toro (2001) + Shock-Capturing Methods for Free-Surface Shallow Flows + 1st edition + ISBN 0471987662 + """ + @inline function boundary_condition_slip_wall( + u_inner, normal_direction::AbstractVector, + x, t, + surface_flux_function, + equations::ShallowWaterEquations2D + ) + # normalize the outward pointing direction + normal = normal_direction / norm(normal_direction) + + # compute the normal velocity + u_normal = normal[1] * u_inner[2] + normal[2] * u_inner[3] + + # create the "external" boundary solution state + u_boundary = SVector( + u_inner[1], + u_inner[2] - 2 * u_normal * normal[1], + u_inner[3] - 2 * u_normal * normal[2], + u_inner[4] + ) + + # calculate the boundary flux + flux = surface_flux_function(u_inner, u_boundary, normal_direction, equations) + + return flux + end -Should be used together with [`TreeMesh`](@ref). -""" -@inline function boundary_condition_slip_wall(u_inner, orientation, - direction, x, t, - surface_flux_function, + """ + boundary_condition_slip_wall(u_inner, orientation, direction, x, t, + surface_flux_function, equations::ShallowWaterEquations2D) + + Should be used together with [`TreeMesh`](@ref). + """ + @inline function boundary_condition_slip_wall( + u_inner, orientation, + direction, x, t, + surface_flux_function, + equations::ShallowWaterEquations2D + ) + ## get the appropriate normal vector from the orientation + if orientation == 1 + u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3], u_inner[4]) + else # orientation == 2 + u_boundary = SVector(u_inner[1], u_inner[2], -u_inner[3], u_inner[4]) + end + + # Calculate boundary flux + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + + return flux + end + + # Calculate 1D flux for a single point + # Note, the bottom topography has no flux + @inline function flux(u, orientation::Integer, equations::ShallowWaterEquations2D) + h, h_v1, h_v2, _ = u + v1, v2 = velocity(u, equations) + + p = 0.5f0 * equations.gravity * h^2 + if orientation == 1 + f1 = h_v1 + f2 = h_v1 * v1 + p + f3 = h_v1 * v2 + else + f1 = h_v2 + f2 = h_v2 * v1 + f3 = h_v2 * v2 + p + end + return SVector(f1, f2, f3, 0) + end + + # Calculate 1D flux for a single point in the normal direction + # Note, this directional vector is not normalized and the bottom topography has no flux + @inline function flux( + u, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D + ) + h = waterheight(u, equations) + v1, v2 = velocity(u, equations) + + v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] + h_v_normal = h * v_normal + p = 0.5f0 * equations.gravity * h^2 + + f1 = h_v_normal + f2 = h_v_normal * v1 + p * normal_direction[1] + f3 = h_v_normal * v2 + p * normal_direction[2] + return SVector(f1, f2, f3, 0) + end + + """ + flux_nonconservative_wintermeyer_etal(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D) + flux_nonconservative_wintermeyer_etal(u_ll, u_rr, + normal_direction_ll ::AbstractVector, + normal_direction_average::AbstractVector, equations::ShallowWaterEquations2D) - ## get the appropriate normal vector from the orientation - if orientation == 1 - u_boundary = SVector(u_inner[1], -u_inner[2], u_inner[3], u_inner[4]) - else # orientation == 2 - u_boundary = SVector(u_inner[1], u_inner[2], -u_inner[3], u_inner[4]) - end - - # Calculate boundary flux - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) - end - - return flux -end - -# Calculate 1D flux for a single point -# Note, the bottom topography has no flux -@inline function flux(u, orientation::Integer, equations::ShallowWaterEquations2D) - h, h_v1, h_v2, _ = u - v1, v2 = velocity(u, equations) - - p = 0.5f0 * equations.gravity * h^2 - if orientation == 1 - f1 = h_v1 - f2 = h_v1 * v1 + p - f3 = h_v1 * v2 - else - f1 = h_v2 - f2 = h_v2 * v1 - f3 = h_v2 * v2 + p - end - return SVector(f1, f2, f3, 0) -end - -# Calculate 1D flux for a single point in the normal direction -# Note, this directional vector is not normalized and the bottom topography has no flux -@inline function flux(u, normal_direction::AbstractVector, - equations::ShallowWaterEquations2D) - h = waterheight(u, equations) - v1, v2 = velocity(u, equations) - - v_normal = v1 * normal_direction[1] + v2 * normal_direction[2] - h_v_normal = h * v_normal - p = 0.5f0 * equations.gravity * h^2 - - f1 = h_v_normal - f2 = h_v_normal * v1 + p * normal_direction[1] - f3 = h_v_normal * v2 + p * normal_direction[2] - return SVector(f1, f2, f3, 0) -end - -""" - flux_nonconservative_wintermeyer_etal(u_ll, u_rr, orientation::Integer, + + Non-symmetric two-point volume flux discretizing the nonconservative (source) term + that contains the gradient of the bottom topography [`ShallowWaterEquations2D`](@ref). + + For the `surface_flux` either [`flux_wintermeyer_etal`](@ref) or [`flux_fjordholm_etal`](@ref) can + be used to ensure well-balancedness and entropy conservation. + + Further details are available in the papers: + - Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) + An entropy stable nodal discontinuous Galerkin method for the two dimensional + shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry + [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) + - Patrick Ersing, Andrew R. Winters (2023) + An entropy stable discontinuous Galerkin method for the two-layer shallow water equations on + curvilinear meshes + [DOI: 10.48550/arXiv.2306.12699](https://doi.org/10.48550/arXiv.2306.12699) + """ + @inline function flux_nonconservative_wintermeyer_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + # Pull the necessary left and right state information + h_ll = waterheight(u_ll, equations) + b_jump = u_rr[4] - u_ll[4] + + # Bottom gradient nonconservative term: (0, g h b_x, g h b_y, 0) + if orientation == 1 + f = SVector(0, equations.gravity * h_ll * b_jump, 0, 0) + else # orientation == 2 + f = SVector(0, 0, equations.gravity * h_ll * b_jump, 0) + end + return f + end + + @inline function flux_nonconservative_wintermeyer_etal( + u_ll, u_rr, + normal_direction_ll::AbstractVector, + normal_direction_average::AbstractVector, + equations::ShallowWaterEquations2D + ) + # Pull the necessary left and right state information + h_ll = waterheight(u_ll, equations) + b_jump = u_rr[4] - u_ll[4] + + # Bottom gradient nonconservative term: (0, g h b_x, g h b_y, 0) + return SVector( + 0, + normal_direction_average[1] * equations.gravity * h_ll * b_jump, + normal_direction_average[2] * equations.gravity * h_ll * b_jump, + 0 + ) + end + + """ + flux_nonconservative_fjordholm_etal(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D) + flux_nonconservative_fjordholm_etal(u_ll, u_rr, + normal_direction_ll ::AbstractVector, + normal_direction_average::AbstractVector, + equations::ShallowWaterEquations2D) + + Non-symmetric two-point surface flux discretizing the nonconservative (source) term of + that contains the gradient of the bottom topography [`ShallowWaterEquations2D`](@ref). + + This flux can be used together with [`flux_fjordholm_etal`](@ref) at interfaces to ensure entropy + conservation and well-balancedness. + + Further details for the original finite volume formulation are available in + - Ulrik S. Fjordholm, Siddhartha Mishr and Eitan Tadmor (2011) + Well-balanced and energy stable schemes for the shallow water equations with discontinuous topography + [DOI: 10.1016/j.jcp.2011.03.042](https://doi.org/10.1016/j.jcp.2011.03.042) + and for curvilinear 2D case in the paper: + - Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) + An entropy stable nodal discontinuous Galerkin method for the two dimensional + shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry + [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) + """ + @inline function flux_nonconservative_fjordholm_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + # Pull the necessary left and right state information + h_ll, _, _, b_ll = u_ll + h_rr, _, _, b_rr = u_rr + + h_average = 0.5f0 * (h_ll + h_rr) + b_jump = b_rr - b_ll + + # Bottom gradient nonconservative term: (0, g h b_x, g h b_y, 0) + if orientation == 1 + f = SVector( + 0, + equations.gravity * h_average * b_jump, + 0, 0 + ) + else # orientation == 2 + f = SVector( + 0, 0, + equations.gravity * h_average * b_jump, + 0 + ) + end + + return f + end + + @inline function flux_nonconservative_fjordholm_etal( + u_ll, u_rr, + normal_direction_ll::AbstractVector, + normal_direction_average::AbstractVector, + equations::ShallowWaterEquations2D + ) + # Pull the necessary left and right state information + h_ll, _, _, b_ll = u_ll + h_rr, _, _, b_rr = u_rr + + h_average = 0.5f0 * (h_ll + h_rr) + b_jump = b_rr - b_ll + + # Bottom gradient nonconservative term: (0, g h b_x, g h b_y, 0) + f2 = normal_direction_average[1] * equations.gravity * h_average * b_jump + f3 = normal_direction_average[2] * equations.gravity * h_average * b_jump + + # First and last equations do not have a nonconservative flux + f1 = f4 = 0 + + return SVector(f1, f2, f3, f4) + end + + """ + hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, orientation_or_normal_direction, + equations::ShallowWaterEquations2D) + + A particular type of hydrostatic reconstruction on the water height to guarantee well-balancedness + for a general bottom topography [`ShallowWaterEquations2D`](@ref). The reconstructed solution states + `u_ll_star` and `u_rr_star` variables are used to evaluate the surface numerical flux at the interface. + Use in combination with the generic numerical flux routine [`FluxHydrostaticReconstruction`](@ref). + + Further details for the hydrostatic reconstruction and its motivation can be found in + - Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) + A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows + [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) + """ + @inline function hydrostatic_reconstruction_audusse_etal( + u_ll, u_rr, + equations::ShallowWaterEquations2D + ) + # Unpack left and right water heights and bottom topographies + h_ll, _, _, b_ll = u_ll + h_rr, _, _, b_rr = u_rr + + # Get the velocities on either side + v1_ll, v2_ll = velocity(u_ll, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + # Compute the reconstructed water heights + h_ll_star = max(0, h_ll + b_ll - max(b_ll, b_rr)) + h_rr_star = max(0, h_rr + b_rr - max(b_ll, b_rr)) + + # Create the conservative variables using the reconstruted water heights + u_ll_star = SVector(h_ll_star, h_ll_star * v1_ll, h_ll_star * v2_ll, b_ll) + u_rr_star = SVector(h_rr_star, h_rr_star * v1_rr, h_rr_star * v2_rr, b_rr) + + return u_ll_star, u_rr_star + end + + """ + flux_nonconservative_audusse_etal(u_ll, u_rr, orientation::Integer, equations::ShallowWaterEquations2D) - flux_nonconservative_wintermeyer_etal(u_ll, u_rr, + flux_nonconservative_audusse_etal(u_ll, u_rr, normal_direction_ll ::AbstractVector, normal_direction_average::AbstractVector, equations::ShallowWaterEquations2D) -Non-symmetric two-point volume flux discretizing the nonconservative (source) term -that contains the gradient of the bottom topography [`ShallowWaterEquations2D`](@ref). - -For the `surface_flux` either [`flux_wintermeyer_etal`](@ref) or [`flux_fjordholm_etal`](@ref) can -be used to ensure well-balancedness and entropy conservation. - -Further details are available in the papers: -- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) - An entropy stable nodal discontinuous Galerkin method for the two dimensional - shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry - [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) -- Patrick Ersing, Andrew R. Winters (2023) - An entropy stable discontinuous Galerkin method for the two-layer shallow water equations on - curvilinear meshes - [DOI: 10.48550/arXiv.2306.12699](https://doi.org/10.48550/arXiv.2306.12699) -""" -@inline function flux_nonconservative_wintermeyer_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - # Pull the necessary left and right state information - h_ll = waterheight(u_ll, equations) - b_jump = u_rr[4] - u_ll[4] - - # Bottom gradient nonconservative term: (0, g h b_x, g h b_y, 0) - if orientation == 1 - f = SVector(0, equations.gravity * h_ll * b_jump, 0, 0) - else # orientation == 2 - f = SVector(0, 0, equations.gravity * h_ll * b_jump, 0) - end - return f -end - -@inline function flux_nonconservative_wintermeyer_etal(u_ll, u_rr, - normal_direction_ll::AbstractVector, - normal_direction_average::AbstractVector, - equations::ShallowWaterEquations2D) - # Pull the necessary left and right state information - h_ll = waterheight(u_ll, equations) - b_jump = u_rr[4] - u_ll[4] - - # Bottom gradient nonconservative term: (0, g h b_x, g h b_y, 0) - return SVector(0, - normal_direction_average[1] * equations.gravity * h_ll * b_jump, - normal_direction_average[2] * equations.gravity * h_ll * b_jump, - 0) -end - -""" - flux_nonconservative_fjordholm_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - flux_nonconservative_fjordholm_etal(u_ll, u_rr, - normal_direction_ll ::AbstractVector, - normal_direction_average::AbstractVector, - equations::ShallowWaterEquations2D) - -Non-symmetric two-point surface flux discretizing the nonconservative (source) term of -that contains the gradient of the bottom topography [`ShallowWaterEquations2D`](@ref). - -This flux can be used together with [`flux_fjordholm_etal`](@ref) at interfaces to ensure entropy -conservation and well-balancedness. - -Further details for the original finite volume formulation are available in -- Ulrik S. Fjordholm, Siddhartha Mishr and Eitan Tadmor (2011) - Well-balanced and energy stable schemes for the shallow water equations with discontinuous topography - [DOI: 10.1016/j.jcp.2011.03.042](https://doi.org/10.1016/j.jcp.2011.03.042) -and for curvilinear 2D case in the paper: -- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) - An entropy stable nodal discontinuous Galerkin method for the two dimensional - shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry - [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) -""" -@inline function flux_nonconservative_fjordholm_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - # Pull the necessary left and right state information - h_ll, _, _, b_ll = u_ll - h_rr, _, _, b_rr = u_rr - - h_average = 0.5f0 * (h_ll + h_rr) - b_jump = b_rr - b_ll - - # Bottom gradient nonconservative term: (0, g h b_x, g h b_y, 0) - if orientation == 1 - f = SVector(0, - equations.gravity * h_average * b_jump, - 0, 0) - else # orientation == 2 - f = SVector(0, 0, - equations.gravity * h_average * b_jump, - 0) - end - - return f -end - -@inline function flux_nonconservative_fjordholm_etal(u_ll, u_rr, - normal_direction_ll::AbstractVector, - normal_direction_average::AbstractVector, - equations::ShallowWaterEquations2D) - # Pull the necessary left and right state information - h_ll, _, _, b_ll = u_ll - h_rr, _, _, b_rr = u_rr - - h_average = 0.5f0 * (h_ll + h_rr) - b_jump = b_rr - b_ll - - # Bottom gradient nonconservative term: (0, g h b_x, g h b_y, 0) - f2 = normal_direction_average[1] * equations.gravity * h_average * b_jump - f3 = normal_direction_average[2] * equations.gravity * h_average * b_jump - - # First and last equations do not have a nonconservative flux - f1 = f4 = 0 - - return SVector(f1, f2, f3, f4) -end - -""" - hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, orientation_or_normal_direction, - equations::ShallowWaterEquations2D) + Non-symmetric two-point surface flux that discretizes the nonconservative (source) term. + The discretization uses the `hydrostatic_reconstruction_audusse_etal` on the conservative + variables. + + This hydrostatic reconstruction ensures that the finite volume numerical fluxes remain + well-balanced for discontinuous bottom topographies [`ShallowWaterEquations2D`](@ref). + Should be used together with [`FluxHydrostaticReconstruction`](@ref) and + [`hydrostatic_reconstruction_audusse_etal`](@ref) in the surface flux to ensure consistency. + + Further details for the hydrostatic reconstruction and its motivation can be found in + - Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) + A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows + [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) + """ + @inline function flux_nonconservative_audusse_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + # Pull the water height and bottom topography on the left + h_ll, _, _, b_ll = u_ll + + # Create the hydrostatic reconstruction for the left solution state + u_ll_star, _ = hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, equations) + + # Copy the reconstructed water height for easier to read code + h_ll_star = u_ll_star[1] + + if orientation == 1 + f = SVector( + 0, + equations.gravity * (h_ll^2 - h_ll_star^2), + 0, 0 + ) + else # orientation == 2 + f = SVector( + 0, 0, + equations.gravity * (h_ll^2 - h_ll_star^2), + 0 + ) + end + + return f + end -A particular type of hydrostatic reconstruction on the water height to guarantee well-balancedness -for a general bottom topography [`ShallowWaterEquations2D`](@ref). The reconstructed solution states -`u_ll_star` and `u_rr_star` variables are used to evaluate the surface numerical flux at the interface. -Use in combination with the generic numerical flux routine [`FluxHydrostaticReconstruction`](@ref). - -Further details for the hydrostatic reconstruction and its motivation can be found in -- Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) - A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows - [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) -""" -@inline function hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, - equations::ShallowWaterEquations2D) - # Unpack left and right water heights and bottom topographies - h_ll, _, _, b_ll = u_ll - h_rr, _, _, b_rr = u_rr - - # Get the velocities on either side - v1_ll, v2_ll = velocity(u_ll, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - # Compute the reconstructed water heights - h_ll_star = max(0, h_ll + b_ll - max(b_ll, b_rr)) - h_rr_star = max(0, h_rr + b_rr - max(b_ll, b_rr)) - - # Create the conservative variables using the reconstruted water heights - u_ll_star = SVector(h_ll_star, h_ll_star * v1_ll, h_ll_star * v2_ll, b_ll) - u_rr_star = SVector(h_rr_star, h_rr_star * v1_rr, h_rr_star * v2_rr, b_rr) - - return u_ll_star, u_rr_star -end - -""" - flux_nonconservative_audusse_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - flux_nonconservative_audusse_etal(u_ll, u_rr, - normal_direction_ll ::AbstractVector, - normal_direction_average::AbstractVector, - equations::ShallowWaterEquations2D) - -Non-symmetric two-point surface flux that discretizes the nonconservative (source) term. -The discretization uses the `hydrostatic_reconstruction_audusse_etal` on the conservative -variables. - -This hydrostatic reconstruction ensures that the finite volume numerical fluxes remain -well-balanced for discontinuous bottom topographies [`ShallowWaterEquations2D`](@ref). -Should be used together with [`FluxHydrostaticReconstruction`](@ref) and -[`hydrostatic_reconstruction_audusse_etal`](@ref) in the surface flux to ensure consistency. - -Further details for the hydrostatic reconstruction and its motivation can be found in -- Emmanuel Audusse, François Bouchut, Marie-Odile Bristeau, Rupert Klein, and Benoit Perthame (2004) - A fast and stable well-balanced scheme with hydrostatic reconstruction for shallow water flows - [DOI: 10.1137/S1064827503431090](https://doi.org/10.1137/S1064827503431090) -""" -@inline function flux_nonconservative_audusse_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - # Pull the water height and bottom topography on the left - h_ll, _, _, b_ll = u_ll - - # Create the hydrostatic reconstruction for the left solution state - u_ll_star, _ = hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, equations) - - # Copy the reconstructed water height for easier to read code - h_ll_star = u_ll_star[1] - - if orientation == 1 - f = SVector(0, - equations.gravity * (h_ll^2 - h_ll_star^2), - 0, 0) - else # orientation == 2 - f = SVector(0, 0, - equations.gravity * (h_ll^2 - h_ll_star^2), - 0) - end - - return f -end - -@inline function flux_nonconservative_audusse_etal(u_ll, u_rr, - normal_direction_ll::AbstractVector, - normal_direction_average::AbstractVector, - equations::ShallowWaterEquations2D) - # Pull the water height and bottom topography on the left - h_ll, _, _, b_ll = u_ll - - # Create the hydrostatic reconstruction for the left solution state - u_ll_star, _ = hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, equations) - - # Copy the reconstructed water height for easier to read code - h_ll_star = u_ll_star[1] - - f2 = normal_direction_average[1] * equations.gravity * (h_ll^2 - h_ll_star^2) - f3 = normal_direction_average[2] * equations.gravity * (h_ll^2 - h_ll_star^2) - - # First and last equations do not have a nonconservative flux - f1 = f4 = 0 - - return SVector(f1, f2, f3, f4) -end - -""" - flux_fjordholm_etal(u_ll, u_rr, orientation_or_normal_direction, - equations::ShallowWaterEquations2D) - -Total energy conservative (mathematical entropy for shallow water equations). When the bottom topography -is nonzero this should only be used as a surface flux otherwise the scheme will not be well-balanced. -For well-balancedness in the volume flux use [`flux_wintermeyer_etal`](@ref). - -Details are available in Eq. (4.1) in the paper: -- Ulrik S. Fjordholm, Siddhartha Mishr and Eitan Tadmor (2011) - Well-balanced and energy stable schemes for the shallow water equations with discontinuous topography - [DOI: 10.1016/j.jcp.2011.03.042](https://doi.org/10.1016/j.jcp.2011.03.042) -""" -@inline function flux_fjordholm_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - # Unpack left and right state - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - # Average each factor of products in flux - h_avg = 0.5f0 * (h_ll + h_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_avg = 0.25f0 * equations.gravity * (h_ll^2 + h_rr^2) - - # Calculate fluxes depending on orientation - if orientation == 1 - f1 = h_avg * v1_avg - f2 = f1 * v1_avg + p_avg - f3 = f1 * v2_avg - else - f1 = h_avg * v2_avg - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_avg - end - - return SVector(f1, f2, f3, 0) -end - -@inline function flux_fjordholm_etal(u_ll, u_rr, normal_direction::AbstractVector, - equations::ShallowWaterEquations2D) - # Unpack left and right state - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # Average each factor of products in flux - h_avg = 0.5f0 * (h_ll + h_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - h2_avg = 0.5f0 * (h_ll^2 + h_rr^2) - p_avg = 0.5f0 * equations.gravity * h2_avg - v_dot_n_avg = 0.5f0 * (v_dot_n_ll + v_dot_n_rr) - - # Calculate fluxes depending on normal_direction - f1 = h_avg * v_dot_n_avg - f2 = f1 * v1_avg + p_avg * normal_direction[1] - f3 = f1 * v2_avg + p_avg * normal_direction[2] - - return SVector(f1, f2, f3, 0) -end - -""" - flux_wintermeyer_etal(u_ll, u_rr, orientation_or_normal_direction, - equations::ShallowWaterEquations2D) - -Total energy conservative (mathematical entropy for shallow water equations) split form. -When the bottom topography is nonzero this scheme will be well-balanced when used as a `volume_flux`. -For the `surface_flux` either [`flux_wintermeyer_etal`](@ref) or [`flux_fjordholm_etal`](@ref) can -be used to ensure well-balancedness and entropy conservation. - -Further details are available in Theorem 1 of the paper: -- Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) - An entropy stable nodal discontinuous Galerkin method for the two dimensional - shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry - [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) -""" -@inline function flux_wintermeyer_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - # Unpack left and right state - h_ll, h_v1_ll, h_v2_ll, _ = u_ll - h_rr, h_v1_rr, h_v2_rr, _ = u_rr - - # Get the velocities on either side - v1_ll, v2_ll = velocity(u_ll, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - # Average each factor of products in flux - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_avg = 0.5f0 * equations.gravity * h_ll * h_rr - - # Calculate fluxes depending on orientation - if orientation == 1 - f1 = 0.5f0 * (h_v1_ll + h_v1_rr) - f2 = f1 * v1_avg + p_avg - f3 = f1 * v2_avg - else - f1 = 0.5f0 * (h_v2_ll + h_v2_rr) - f2 = f1 * v1_avg - f3 = f1 * v2_avg + p_avg - end - - return SVector(f1, f2, f3, 0) -end - -@inline function flux_wintermeyer_etal(u_ll, u_rr, normal_direction::AbstractVector, - equations::ShallowWaterEquations2D) - # Unpack left and right state - h_ll, h_v1_ll, h_v2_ll, _ = u_ll - h_rr, h_v1_rr, h_v2_rr, _ = u_rr - - # Get the velocities on either side - v1_ll, v2_ll = velocity(u_ll, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - # Average each factor of products in flux - h_v1_avg = 0.5f0 * (h_v1_ll + h_v1_rr) - h_v2_avg = 0.5f0 * (h_v2_ll + h_v2_rr) - v1_avg = 0.5f0 * (v1_ll + v1_rr) - v2_avg = 0.5f0 * (v2_ll + v2_rr) - p_avg = 0.5f0 * equations.gravity * h_ll * h_rr - - # Calculate fluxes depending on normal_direction - f1 = h_v1_avg * normal_direction[1] + h_v2_avg * normal_direction[2] - f2 = f1 * v1_avg + p_avg * normal_direction[1] - f3 = f1 * v2_avg + p_avg * normal_direction[2] - - return SVector(f1, f2, f3, 0) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the -# maximum velocity magnitude plus the maximum speed of sound -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - # Get the velocity quantities in the appropriate direction - if orientation == 1 - v_ll, _ = velocity(u_ll, equations) - v_rr, _ = velocity(u_rr, equations) - else - _, v_ll = velocity(u_ll, equations) - _, v_rr = velocity(u_rr, equations) + @inline function flux_nonconservative_audusse_etal( + u_ll, u_rr, + normal_direction_ll::AbstractVector, + normal_direction_average::AbstractVector, + equations::ShallowWaterEquations2D + ) + # Pull the water height and bottom topography on the left + h_ll, _, _, b_ll = u_ll + + # Create the hydrostatic reconstruction for the left solution state + u_ll_star, _ = hydrostatic_reconstruction_audusse_etal(u_ll, u_rr, equations) + + # Copy the reconstructed water height for easier to read code + h_ll_star = u_ll_star[1] + + f2 = normal_direction_average[1] * equations.gravity * (h_ll^2 - h_ll_star^2) + f3 = normal_direction_average[2] * equations.gravity * (h_ll^2 - h_ll_star^2) + + # First and last equations do not have a nonconservative flux + f1 = f4 = 0 + + return SVector(f1, f2, f3, f4) + end + + """ + flux_fjordholm_etal(u_ll, u_rr, orientation_or_normal_direction, + equations::ShallowWaterEquations2D) + + Total energy conservative (mathematical entropy for shallow water equations). When the bottom topography + is nonzero this should only be used as a surface flux otherwise the scheme will not be well-balanced. + For well-balancedness in the volume flux use [`flux_wintermeyer_etal`](@ref). + + Details are available in Eq. (4.1) in the paper: + - Ulrik S. Fjordholm, Siddhartha Mishr and Eitan Tadmor (2011) + Well-balanced and energy stable schemes for the shallow water equations with discontinuous topography + [DOI: 10.1016/j.jcp.2011.03.042](https://doi.org/10.1016/j.jcp.2011.03.042) + """ + @inline function flux_fjordholm_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + # Unpack left and right state + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + # Average each factor of products in flux + h_avg = 0.5f0 * (h_ll + h_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.25f0 * equations.gravity * (h_ll^2 + h_rr^2) + + # Calculate fluxes depending on orientation + if orientation == 1 + f1 = h_avg * v1_avg + f2 = f1 * v1_avg + p_avg + f3 = f1 * v2_avg + else + f1 = h_avg * v2_avg + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_avg + end + + return SVector(f1, f2, f3, 0) end - # Calculate the wave celerity on the left and right - h_ll = waterheight(u_ll, equations) - h_rr = waterheight(u_rr, equations) - c_ll = sqrt(equations.gravity * h_ll) - c_rr = sqrt(equations.gravity * h_rr) + @inline function flux_fjordholm_etal( + u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D + ) + # Unpack left and right state + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + v_dot_n_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_dot_n_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # Average each factor of products in flux + h_avg = 0.5f0 * (h_ll + h_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + h2_avg = 0.5f0 * (h_ll^2 + h_rr^2) + p_avg = 0.5f0 * equations.gravity * h2_avg + v_dot_n_avg = 0.5f0 * (v_dot_n_ll + v_dot_n_rr) + + # Calculate fluxes depending on normal_direction + f1 = h_avg * v_dot_n_avg + f2 = f1 * v1_avg + p_avg * normal_direction[1] + f3 = f1 * v2_avg + p_avg * normal_direction[2] + + return SVector(f1, f2, f3, 0) + end - return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) -end + """ + flux_wintermeyer_etal(u_ll, u_rr, orientation_or_normal_direction, + equations::ShallowWaterEquations2D) + + Total energy conservative (mathematical entropy for shallow water equations) split form. + When the bottom topography is nonzero this scheme will be well-balanced when used as a `volume_flux`. + For the `surface_flux` either [`flux_wintermeyer_etal`](@ref) or [`flux_fjordholm_etal`](@ref) can + be used to ensure well-balancedness and entropy conservation. + + Further details are available in Theorem 1 of the paper: + - Niklas Wintermeyer, Andrew R. Winters, Gregor J. Gassner and David A. Kopriva (2017) + An entropy stable nodal discontinuous Galerkin method for the two dimensional + shallow water equations on unstructured curvilinear meshes with discontinuous bathymetry + [DOI: 10.1016/j.jcp.2017.03.036](https://doi.org/10.1016/j.jcp.2017.03.036) + """ + @inline function flux_wintermeyer_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + # Unpack left and right state + h_ll, h_v1_ll, h_v2_ll, _ = u_ll + h_rr, h_v1_rr, h_v2_rr, _ = u_rr + + # Get the velocities on either side + v1_ll, v2_ll = velocity(u_ll, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + # Average each factor of products in flux + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * equations.gravity * h_ll * h_rr + + # Calculate fluxes depending on orientation + if orientation == 1 + f1 = 0.5f0 * (h_v1_ll + h_v1_rr) + f2 = f1 * v1_avg + p_avg + f3 = f1 * v2_avg + else + f1 = 0.5f0 * (h_v2_ll + h_v2_rr) + f2 = f1 * v1_avg + f3 = f1 * v2_avg + p_avg + end + + return SVector(f1, f2, f3, 0) + end -@inline function max_abs_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::ShallowWaterEquations2D) - # Extract and compute the velocities in the normal direction - v1_ll, v2_ll = velocity(u_ll, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - v_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # Compute the wave celerity on the left and right - h_ll = waterheight(u_ll, equations) - h_rr = waterheight(u_rr, equations) - c_ll = sqrt(equations.gravity * h_ll) - c_rr = sqrt(equations.gravity * h_rr) - - # The normal velocities are already scaled by the norm - return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) -end - -# Specialized `DissipationLocalLaxFriedrichs` to avoid spurious dissipation in the bottom topography -@inline function (dissipation::DissipationLocalLaxFriedrichs)(u_ll, u_rr, - orientation_or_normal_direction, - equations::ShallowWaterEquations2D) - λ = dissipation.max_abs_speed(u_ll, u_rr, orientation_or_normal_direction, - equations) - diss = -0.5f0 * λ * (u_rr - u_ll) - return SVector(diss[1], diss[2], diss[3], 0) -end - -# Specialized `FluxHLL` to avoid spurious dissipation in the bottom topography -@inline function (numflux::FluxHLL)(u_ll, u_rr, orientation_or_normal_direction, - equations::ShallowWaterEquations2D) - λ_min, λ_max = numflux.min_max_speed(u_ll, u_rr, orientation_or_normal_direction, - equations) - - if λ_min >= 0 && λ_max >= 0 - return flux(u_ll, orientation_or_normal_direction, equations) - elseif λ_max <= 0 && λ_min <= 0 - return flux(u_rr, orientation_or_normal_direction, equations) - else - f_ll = flux(u_ll, orientation_or_normal_direction, equations) - f_rr = flux(u_rr, orientation_or_normal_direction, equations) - inv_λ_max_minus_λ_min = inv(λ_max - λ_min) - factor_ll = λ_max * inv_λ_max_minus_λ_min - factor_rr = λ_min * inv_λ_max_minus_λ_min - factor_diss = λ_min * λ_max * inv_λ_max_minus_λ_min - diss = u_rr - u_ll - return factor_ll * f_ll - factor_rr * f_rr + - factor_diss * SVector(diss[1], diss[2], diss[3], 0) - end -end - -# Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) + @inline function flux_wintermeyer_etal( + u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D + ) + # Unpack left and right state + h_ll, h_v1_ll, h_v2_ll, _ = u_ll + h_rr, h_v1_rr, h_v2_rr, _ = u_rr + + # Get the velocities on either side + v1_ll, v2_ll = velocity(u_ll, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + # Average each factor of products in flux + h_v1_avg = 0.5f0 * (h_v1_ll + h_v1_rr) + h_v2_avg = 0.5f0 * (h_v2_ll + h_v2_rr) + v1_avg = 0.5f0 * (v1_ll + v1_rr) + v2_avg = 0.5f0 * (v2_ll + v2_rr) + p_avg = 0.5f0 * equations.gravity * h_ll * h_rr + + # Calculate fluxes depending on normal_direction + f1 = h_v1_avg * normal_direction[1] + h_v2_avg * normal_direction[2] + f2 = f1 * v1_avg + p_avg * normal_direction[1] + f3 = f1 * v2_avg + p_avg * normal_direction[2] + + return SVector(f1, f2, f3, 0) + end - if orientation == 1 # x-direction - λ_min = v1_ll - sqrt(equations.gravity * h_ll) - λ_max = v1_rr + sqrt(equations.gravity * h_rr) - else # y-direction - λ_min = v2_ll - sqrt(equations.gravity * h_ll) - λ_max = v2_rr + sqrt(equations.gravity * h_rr) + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the + # maximum velocity magnitude plus the maximum speed of sound + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + # Get the velocity quantities in the appropriate direction + if orientation == 1 + v_ll, _ = velocity(u_ll, equations) + v_rr, _ = velocity(u_rr, equations) + else + _, v_ll = velocity(u_ll, equations) + _, v_rr = velocity(u_rr, equations) + end + + # Calculate the wave celerity on the left and right + h_ll = waterheight(u_ll, equations) + h_rr = waterheight(u_rr, equations) + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) end - return λ_min, λ_max -end + @inline function max_abs_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D + ) + # Extract and compute the velocities in the normal direction + v1_ll, v2_ll = velocity(u_ll, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + v_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # Compute the wave celerity on the left and right + h_ll = waterheight(u_ll, equations) + h_rr = waterheight(u_rr, equations) + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + # The normal velocities are already scaled by the norm + return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) * norm(normal_direction) + end -@inline function min_max_speed_naive(u_ll, u_rr, normal_direction::AbstractVector, - equations::ShallowWaterEquations2D) - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) + # Specialized `DissipationLocalLaxFriedrichs` to avoid spurious dissipation in the bottom topography + @inline function (dissipation::DissipationLocalLaxFriedrichs)( + u_ll, u_rr, + orientation_or_normal_direction, + equations::ShallowWaterEquations2D + ) + λ = dissipation.max_abs_speed( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + diss = -0.5f0 * λ * (u_rr - u_ll) + return SVector(diss[1], diss[2], diss[3], 0) + end - v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + # Specialized `FluxHLL` to avoid spurious dissipation in the bottom topography + @inline function (numflux::FluxHLL)( + u_ll, u_rr, orientation_or_normal_direction, + equations::ShallowWaterEquations2D + ) + λ_min, λ_max = numflux.min_max_speed( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + + if λ_min >= 0 && λ_max >= 0 + return flux(u_ll, orientation_or_normal_direction, equations) + elseif λ_max <= 0 && λ_min <= 0 + return flux(u_rr, orientation_or_normal_direction, equations) + else + f_ll = flux(u_ll, orientation_or_normal_direction, equations) + f_rr = flux(u_rr, orientation_or_normal_direction, equations) + inv_λ_max_minus_λ_min = inv(λ_max - λ_min) + factor_ll = λ_max * inv_λ_max_minus_λ_min + factor_rr = λ_min * inv_λ_max_minus_λ_min + factor_diss = λ_min * λ_max * inv_λ_max_minus_λ_min + diss = u_rr - u_ll + return factor_ll * f_ll - factor_rr * f_rr + + factor_diss * SVector(diss[1], diss[2], diss[3], 0) + end + end - norm_ = norm(normal_direction) - # The v_normals are already scaled by the norm - λ_min = v_normal_ll - sqrt(equations.gravity * h_ll) * norm_ - λ_max = v_normal_rr + sqrt(equations.gravity * h_rr) * norm_ + # Calculate estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + if orientation == 1 # x-direction + λ_min = v1_ll - sqrt(equations.gravity * h_ll) + λ_max = v1_rr + sqrt(equations.gravity * h_rr) + else # y-direction + λ_min = v2_ll - sqrt(equations.gravity * h_ll) + λ_max = v2_rr + sqrt(equations.gravity * h_rr) + end + + return λ_min, λ_max + end - return λ_min, λ_max -end + @inline function min_max_speed_naive( + u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D + ) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) -# More refined estimates for minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - c_ll = sqrt(equations.gravity * h_ll) - c_rr = sqrt(equations.gravity * h_rr) + norm_ = norm(normal_direction) + # The v_normals are already scaled by the norm + λ_min = v_normal_ll - sqrt(equations.gravity * h_ll) * norm_ + λ_max = v_normal_rr + sqrt(equations.gravity * h_rr) * norm_ - if orientation == 1 # x-direction - λ_min = min(v1_ll - c_ll, v1_rr - c_rr) - λ_max = max(v1_ll + c_ll, v1_rr + c_rr) - else # y-direction - λ_min = min(v2_ll - c_ll, v2_rr - c_rr) - λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + return λ_min, λ_max end - return λ_min, λ_max -end + # More refined estimates for minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + if orientation == 1 # x-direction + λ_min = min(v1_ll - c_ll, v1_rr - c_rr) + λ_max = max(v1_ll + c_ll, v1_rr + c_rr) + else # y-direction + λ_min = min(v2_ll - c_ll, v2_rr - c_rr) + λ_max = max(v2_ll + c_ll, v2_rr + c_rr) + end + + return λ_min, λ_max + end -@inline function min_max_speed_davis(u_ll, u_rr, normal_direction::AbstractVector, - equations::ShallowWaterEquations2D) - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - norm_ = norm(normal_direction) - c_ll = sqrt(equations.gravity * h_ll) * norm_ - c_rr = sqrt(equations.gravity * h_rr) * norm_ - - v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] - v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] - - # The v_normals are already scaled by the norm - λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) - λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) - - return λ_min, λ_max -end - -@inline function min_max_speed_einfeldt(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - c_ll = sqrt(equations.gravity * h_ll) - c_rr = sqrt(equations.gravity * h_rr) - - if orientation == 1 # x-direction - v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, orientation, equations) - λ_min = min(v1_ll - c_ll, v_roe - c_roe) - λ_max = max(v1_rr + c_rr, v_roe + c_roe) - else # y-direction - v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, orientation, equations) - λ_min = min(v2_ll - c_ll, v_roe - c_roe) - λ_max = max(v2_rr + c_rr, v_roe + c_roe) - end - - return λ_min, λ_max -end - -@inline function min_max_speed_einfeldt(u_ll, u_rr, normal_direction::AbstractVector, - equations::ShallowWaterEquations2D) - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - norm_ = norm(normal_direction) - - c_ll = sqrt(equations.gravity * h_ll) * norm_ - c_rr = sqrt(equations.gravity * h_rr) * norm_ - - v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) - v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) - - v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, normal_direction, equations) - λ_min = min(v_normal_ll - c_ll, v_roe - c_roe) - λ_max = max(v_normal_rr + c_rr, v_roe + c_roe) - - return λ_min, λ_max -end - -@inline function max_abs_speeds(u, equations::ShallowWaterEquations2D) - h = waterheight(u, equations) - v1, v2 = velocity(u, equations) - - c = sqrt(equations.gravity * h) - return abs(v1) + c, abs(v2) + c -end - -# Helper function to extract the velocity vector from the conservative variables -@inline function velocity(u, equations::ShallowWaterEquations2D) - h, h_v1, h_v2, _ = u - - v1 = h_v1 / h - v2 = h_v2 / h - return SVector(v1, v2) -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::ShallowWaterEquations2D) - h, _, _, b = u - - H = h + b - v1, v2 = velocity(u, equations) - return SVector(H, v1, v2, b) -end - -# Convert conservative variables to entropy -# Note, only the first three are the entropy variables, the fourth entry still -# just carries the bottom topography values for convenience -@inline function cons2entropy(u, equations::ShallowWaterEquations2D) - h, h_v1, h_v2, b = u - - v1, v2 = velocity(u, equations) - v_square = v1^2 + v2^2 - - w1 = equations.gravity * (h + b) - 0.5f0 * v_square - w2 = v1 - w3 = v2 - return SVector(w1, w2, w3, b) -end - -# Convert entropy variables to conservative -@inline function entropy2cons(w, equations::ShallowWaterEquations2D) - w1, w2, w3, b = w - - h = (w1 + 0.5f0 * (w2^2 + w3^2)) / equations.gravity - b - h_v1 = h * w2 - h_v2 = h * w3 - return SVector(h, h_v1, h_v2, b) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::ShallowWaterEquations2D) - H, v1, v2, b = prim - - h = H - b - h_v1 = h * v1 - h_v2 = h * v2 - return SVector(h, h_v1, h_v2, b) -end - -@inline function waterheight(u, equations::ShallowWaterEquations2D) - return u[1] -end - -@inline function pressure(u, equations::ShallowWaterEquations2D) - h = waterheight(u, equations) - p = 0.5f0 * equations.gravity * h^2 - return p -end - -@inline function waterheight_pressure(u, equations::ShallowWaterEquations2D) - return waterheight(u, equations) * pressure(u, equations) -end - -""" - calc_wavespeed_roe(u_ll, u_rr, direction::Integer, - equations::ShallowWaterEquations2D) - -Calculate Roe-averaged velocity `v_roe` and wavespeed `c_roe = sqrt{g * h_roe}` depending on direction. -See for instance equation (62) in -- Paul A. Ullrich, Christiane Jablonowski, and Bram van Leer (2010) - High-order finite-volume methods for the shallow-water equations on the sphere - [DOI: 10.1016/j.jcp.2010.04.044](https://doi.org/10.1016/j.jcp.2010.04.044) -Or [this slides](https://faculty.washington.edu/rjl/classes/am574w2011/slides/am574lecture20nup3.pdf), -slides 8 and 9. -""" -@inline function calc_wavespeed_roe(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquations2D) - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - h_roe = 0.5f0 * (h_ll + h_rr) - c_roe = sqrt(equations.gravity * h_roe) - - h_ll_sqrt = sqrt(h_ll) - h_rr_sqrt = sqrt(h_rr) - - if orientation == 1 # x-direction - v_roe = (h_ll_sqrt * v1_ll + h_rr_sqrt * v1_rr) / (h_ll_sqrt + h_rr_sqrt) - else # y-direction - v_roe = (h_ll_sqrt * v2_ll + h_rr_sqrt * v2_rr) / (h_ll_sqrt + h_rr_sqrt) - end - - return v_roe, c_roe -end - -@inline function calc_wavespeed_roe(u_ll, u_rr, normal_direction::AbstractVector, - equations::ShallowWaterEquations2D) - h_ll = waterheight(u_ll, equations) - v1_ll, v2_ll = velocity(u_ll, equations) - h_rr = waterheight(u_rr, equations) - v1_rr, v2_rr = velocity(u_rr, equations) - - norm_ = norm(normal_direction) - - h_roe = 0.5f0 * (h_ll + h_rr) - c_roe = sqrt(equations.gravity * h_roe) * norm_ - - h_ll_sqrt = sqrt(h_ll) - h_rr_sqrt = sqrt(h_rr) - - v1_roe = (h_ll_sqrt * v1_ll + h_rr_sqrt * v1_rr) / (h_ll_sqrt + h_rr_sqrt) - v2_roe = (h_ll_sqrt * v2_ll + h_rr_sqrt * v2_rr) / (h_ll_sqrt + h_rr_sqrt) - - v_roe = (v1_roe * normal_direction[1] + v2_roe * normal_direction[2]) - - return v_roe, c_roe -end - -# Entropy function for the shallow water equations is the total energy -@inline function entropy(cons, equations::ShallowWaterEquations2D) - energy_total(cons, equations) -end - -# Calculate total energy for a conservative state `cons` -@inline function energy_total(cons, equations::ShallowWaterEquations2D) - h, h_v1, h_v2, b = cons - - e = (h_v1^2 + h_v2^2) / (2 * h) + 0.5f0 * equations.gravity * h^2 + - equations.gravity * h * b - return e -end - -# Calculate kinetic energy for a conservative state `cons` -@inline function energy_kinetic(u, equations::ShallowWaterEquations2D) - h, h_v1, h_v2, _ = u - return (h_v1^2 + h_v2^2) / (2 * h) -end - -# Calculate potential energy for a conservative state `cons` -@inline function energy_internal(cons, equations::ShallowWaterEquations2D) - return energy_total(cons, equations) - energy_kinetic(cons, equations) -end - -# Calculate the error for the "lake-at-rest" test case where H = h+b should -# be a constant value over time. -@inline function lake_at_rest_error(u, equations::ShallowWaterEquations2D) - h, _, _, b = u - - return abs(equations.H0 - (h + b)) -end + @inline function min_max_speed_davis( + u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D + ) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + norm_ = norm(normal_direction) + c_ll = sqrt(equations.gravity * h_ll) * norm_ + c_rr = sqrt(equations.gravity * h_rr) * norm_ + + v_normal_ll = v1_ll * normal_direction[1] + v2_ll * normal_direction[2] + v_normal_rr = v1_rr * normal_direction[1] + v2_rr * normal_direction[2] + + # The v_normals are already scaled by the norm + λ_min = min(v_normal_ll - c_ll, v_normal_rr - c_rr) + λ_max = max(v_normal_ll + c_ll, v_normal_rr + c_rr) + + return λ_min, λ_max + end + + @inline function min_max_speed_einfeldt( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + if orientation == 1 # x-direction + v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v1_ll - c_ll, v_roe - c_roe) + λ_max = max(v1_rr + c_rr, v_roe + c_roe) + else # y-direction + v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, orientation, equations) + λ_min = min(v2_ll - c_ll, v_roe - c_roe) + λ_max = max(v2_rr + c_rr, v_roe + c_roe) + end + + return λ_min, λ_max + end + + @inline function min_max_speed_einfeldt( + u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D + ) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + norm_ = norm(normal_direction) + + c_ll = sqrt(equations.gravity * h_ll) * norm_ + c_rr = sqrt(equations.gravity * h_rr) * norm_ + + v_normal_ll = (v1_ll * normal_direction[1] + v2_ll * normal_direction[2]) + v_normal_rr = (v1_rr * normal_direction[1] + v2_rr * normal_direction[2]) + + v_roe, c_roe = calc_wavespeed_roe(u_ll, u_rr, normal_direction, equations) + λ_min = min(v_normal_ll - c_ll, v_roe - c_roe) + λ_max = max(v_normal_rr + c_rr, v_roe + c_roe) + + return λ_min, λ_max + end + + @inline function max_abs_speeds(u, equations::ShallowWaterEquations2D) + h = waterheight(u, equations) + v1, v2 = velocity(u, equations) + + c = sqrt(equations.gravity * h) + return abs(v1) + c, abs(v2) + c + end + + # Helper function to extract the velocity vector from the conservative variables + @inline function velocity(u, equations::ShallowWaterEquations2D) + h, h_v1, h_v2, _ = u + + v1 = h_v1 / h + v2 = h_v2 / h + return SVector(v1, v2) + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::ShallowWaterEquations2D) + h, _, _, b = u + + H = h + b + v1, v2 = velocity(u, equations) + return SVector(H, v1, v2, b) + end + + # Convert conservative variables to entropy + # Note, only the first three are the entropy variables, the fourth entry still + # just carries the bottom topography values for convenience + @inline function cons2entropy(u, equations::ShallowWaterEquations2D) + h, h_v1, h_v2, b = u + + v1, v2 = velocity(u, equations) + v_square = v1^2 + v2^2 + + w1 = equations.gravity * (h + b) - 0.5f0 * v_square + w2 = v1 + w3 = v2 + return SVector(w1, w2, w3, b) + end + + # Convert entropy variables to conservative + @inline function entropy2cons(w, equations::ShallowWaterEquations2D) + w1, w2, w3, b = w + + h = (w1 + 0.5f0 * (w2^2 + w3^2)) / equations.gravity - b + h_v1 = h * w2 + h_v2 = h * w3 + return SVector(h, h_v1, h_v2, b) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::ShallowWaterEquations2D) + H, v1, v2, b = prim + + h = H - b + h_v1 = h * v1 + h_v2 = h * v2 + return SVector(h, h_v1, h_v2, b) + end + + @inline function waterheight(u, equations::ShallowWaterEquations2D) + return u[1] + end + + @inline function pressure(u, equations::ShallowWaterEquations2D) + h = waterheight(u, equations) + p = 0.5f0 * equations.gravity * h^2 + return p + end + + @inline function waterheight_pressure(u, equations::ShallowWaterEquations2D) + return waterheight(u, equations) * pressure(u, equations) + end + + """ + calc_wavespeed_roe(u_ll, u_rr, direction::Integer, + equations::ShallowWaterEquations2D) + + Calculate Roe-averaged velocity `v_roe` and wavespeed `c_roe = sqrt{g * h_roe}` depending on direction. + See for instance equation (62) in + - Paul A. Ullrich, Christiane Jablonowski, and Bram van Leer (2010) + High-order finite-volume methods for the shallow-water equations on the sphere + [DOI: 10.1016/j.jcp.2010.04.044](https://doi.org/10.1016/j.jcp.2010.04.044) + Or [this slides](https://faculty.washington.edu/rjl/classes/am574w2011/slides/am574lecture20nup3.pdf), + slides 8 and 9. + """ + @inline function calc_wavespeed_roe( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquations2D + ) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + h_roe = 0.5f0 * (h_ll + h_rr) + c_roe = sqrt(equations.gravity * h_roe) + + h_ll_sqrt = sqrt(h_ll) + h_rr_sqrt = sqrt(h_rr) + + if orientation == 1 # x-direction + v_roe = (h_ll_sqrt * v1_ll + h_rr_sqrt * v1_rr) / (h_ll_sqrt + h_rr_sqrt) + else # y-direction + v_roe = (h_ll_sqrt * v2_ll + h_rr_sqrt * v2_rr) / (h_ll_sqrt + h_rr_sqrt) + end + + return v_roe, c_roe + end + + @inline function calc_wavespeed_roe( + u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquations2D + ) + h_ll = waterheight(u_ll, equations) + v1_ll, v2_ll = velocity(u_ll, equations) + h_rr = waterheight(u_rr, equations) + v1_rr, v2_rr = velocity(u_rr, equations) + + norm_ = norm(normal_direction) + + h_roe = 0.5f0 * (h_ll + h_rr) + c_roe = sqrt(equations.gravity * h_roe) * norm_ + + h_ll_sqrt = sqrt(h_ll) + h_rr_sqrt = sqrt(h_rr) + + v1_roe = (h_ll_sqrt * v1_ll + h_rr_sqrt * v1_rr) / (h_ll_sqrt + h_rr_sqrt) + v2_roe = (h_ll_sqrt * v2_ll + h_rr_sqrt * v2_rr) / (h_ll_sqrt + h_rr_sqrt) + + v_roe = (v1_roe * normal_direction[1] + v2_roe * normal_direction[2]) + + return v_roe, c_roe + end + + # Entropy function for the shallow water equations is the total energy + @inline function entropy(cons, equations::ShallowWaterEquations2D) + energy_total(cons, equations) + end + + # Calculate total energy for a conservative state `cons` + @inline function energy_total(cons, equations::ShallowWaterEquations2D) + h, h_v1, h_v2, b = cons + + e = (h_v1^2 + h_v2^2) / (2 * h) + 0.5f0 * equations.gravity * h^2 + + equations.gravity * h * b + return e + end + + # Calculate kinetic energy for a conservative state `cons` + @inline function energy_kinetic(u, equations::ShallowWaterEquations2D) + h, h_v1, h_v2, _ = u + return (h_v1^2 + h_v2^2) / (2 * h) + end + + # Calculate potential energy for a conservative state `cons` + @inline function energy_internal(cons, equations::ShallowWaterEquations2D) + return energy_total(cons, equations) - energy_kinetic(cons, equations) + end + + # Calculate the error for the "lake-at-rest" test case where H = h+b should + # be a constant value over time. + @inline function lake_at_rest_error(u, equations::ShallowWaterEquations2D) + h, _, _, b = u + + return abs(equations.H0 - (h + b)) + end end # @muladd diff --git a/src/equations/shallow_water_quasi_1d.jl b/src/equations/shallow_water_quasi_1d.jl index eb90fb53f3..a747a60b3e 100644 --- a/src/equations/shallow_water_quasi_1d.jl +++ b/src/equations/shallow_water_quasi_1d.jl @@ -3,322 +3,342 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - ShallowWaterEquationsQuasi1D(; gravity, H0 = 0, threshold_limiter = nothing threshold_wet = nothing) - -The quasi-1D shallow water equations (SWE). The equations are given by -```math -\begin{aligned} - \frac{\partial}{\partial t}(a h) + \frac{\partial}{\partial x}(a h v) &= 0 \\ - \frac{\partial}{\partial t}(a h v) + \frac{\partial}{\partial x}(a h v^2) - + g a h \frac{\partial}{\partial x}(h + b) &= 0 -\end{aligned} -``` -The unknown quantities of the Quasi-1D SWE are the water height ``h`` and the scaled velocity ``v``. -The gravitational constant is denoted by `g`, the (possibly) variable bottom topography function ``b(x)``, and (possibly) variable channel width ``a(x)``. The water height ``h`` is measured from the bottom topography ``b``, therefore one also defines the total water height as ``H = h + b``. - -The additional quantity ``H_0`` is also available to store a reference value for the total water height that -is useful to set initial conditions or test the "lake-at-rest" well-balancedness. - -The bottom topography function ``b(x)`` and channel width ``a(x)`` are set inside the initial condition routine -for a particular problem setup. To test the conservative form of the SWE one can set the bottom topography -variable `b` to zero and ``a`` to one. - -In addition to the unknowns, Trixi.jl currently stores the bottom topography and channel width values at the approximation points -despite being fixed in time. This is done for convenience of computing the bottom topography gradients -on the fly during the approximation as well as computing auxiliary quantities like the total water height ``H`` -or the entropy variables. -This affects the implementation and use of these equations in various ways: -* The flux values corresponding to the bottom topography and channel width must be zero. -* The bottom topography and channel width values must be included when defining initial conditions, boundary conditions or - source terms. -* [`AnalysisCallback`](@ref) analyzes this variable. -* Trixi.jl's visualization tools will visualize the bottom topography and channel width by default. -""" -struct ShallowWaterEquationsQuasi1D{RealT <: Real} <: - AbstractShallowWaterEquations{1, 4} - gravity::RealT # gravitational constant - H0::RealT # constant "lake-at-rest" total water height -end - -# Allow for flexibility to set the gravitational constant within an elixir depending on the -# application where `gravity_constant=1.0` or `gravity_constant=9.81` are common values. -# The reference total water height H0 defaults to 0.0 but is used for the "lake-at-rest" -# well-balancedness test cases. -# Strict default values for thresholds that performed well in many numerical experiments -function ShallowWaterEquationsQuasi1D(; gravity_constant, H0 = zero(gravity_constant)) - ShallowWaterEquationsQuasi1D(gravity_constant, H0) -end - -have_nonconservative_terms(::ShallowWaterEquationsQuasi1D) = True() -function varnames(::typeof(cons2cons), ::ShallowWaterEquationsQuasi1D) - ("a_h", "a_h_v", "b", "a") -end -# Note, we use the total water height, H = h + b, as the first primitive variable for easier -# visualization and setting initial conditions -varnames(::typeof(cons2prim), ::ShallowWaterEquationsQuasi1D) = ("H", "v", "b", "a") - -# Set initial conditions at physical location `x` for time `t` -""" - initial_condition_convergence_test(x, t, equations::ShallowWaterEquationsQuasi1D) - -A smooth initial condition used for convergence tests in combination with -[`source_terms_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). -""" -function initial_condition_convergence_test(x, t, - equations::ShallowWaterEquationsQuasi1D) - # generates a manufactured solution. - # some constants are chosen such that the function is periodic on the domain [0,sqrt(2)] - RealT = eltype(x) - Omega = sqrt(convert(RealT, 2)) * convert(RealT, pi) - H = 2 + 0.5f0 * sin(Omega * x[1] - t) - v = 0.25f0 - b = convert(RealT, 0.2) - convert(RealT, 0.05) * sin(Omega * x[1]) - a = 1 + convert(RealT, 0.1) * cos(Omega * x[1]) - return prim2cons(SVector(H, v, b, a), equations) -end - -""" - source_terms_convergence_test(u, x, t, equations::ShallowWaterEquationsQuasi1D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref) -(and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). - -This manufactured solution source term is specifically designed for the bottom topography function -`b(x) = 0.2 - 0.05 * sinpi(sqrt(2) * x[1])` and channel width 'a(x)= 1 + 0.1 * cospi(sqrt(2) * x[1])' -as defined in [`initial_condition_convergence_test`](@ref). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::ShallowWaterEquationsQuasi1D) - # Same settings as in `initial_condition_convergence_test`. Some derivative simplify because - # this manufactured solution velocity is taken to be constant - RealT = eltype(u) - Omega = sqrt(convert(RealT, 2)) * convert(RealT, pi) - H = 2 + 0.5f0 * sin(Omega * x[1] - t) - H_x = 0.5f0 * cos(Omega * x[1] - t) * Omega - H_t = -0.5f0 * cos(Omega * x[1] - t) - - v = 0.25f0 - - b = convert(RealT, 0.2) - convert(RealT, 0.05) * sin(Omega * x[1]) - b_x = -convert(RealT, 0.05) * cos(Omega * x[1]) * Omega - - a = 1 + convert(RealT, 0.1) * cos(Omega * x[1]) - a_x = -convert(RealT, 0.1) * sin(Omega * x[1]) * Omega - - du1 = a * H_t + v * (a_x * (H - b) + a * (H_x - b_x)) - du2 = v * du1 + a * (equations.gravity * (H - b) * H_x) - - return SVector(du1, du2, 0, 0) -end - -# Calculate 1D conservative flux for a single point -# Note, the bottom topography and channel width have no flux -@inline function flux(u, orientation::Integer, equations::ShallowWaterEquationsQuasi1D) - _, a_h_v, _, _ = u - v = velocity(u, equations) - - f1 = a_h_v - f2 = a_h_v * v - - return SVector(f1, f2, 0, 0) -end - -""" - flux_nonconservative_chan_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquationsQuasi1D) - flux_nonconservative_chan_etal(u_ll, u_rr, normal_direction::AbstractVector, - equations::ShallowWaterEquationsQuasi1D) - flux_nonconservative_chan_etal(u_ll, u_rr, - normal_ll::AbstractVector, normal_rr::AbstractVector, - equations::ShallowWaterEquationsQuasi1D) - -Non-symmetric two-point volume flux discretizing the nonconservative (source) term -that contains the gradient of the bottom topography [`ShallowWaterEquationsQuasi1D`](@ref) -and the channel width. - -Further details are available in the paper: -- Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023) - High order entropy stable schemes for the quasi-one-dimensional - shallow water and compressible Euler equations - [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) -""" -@inline function flux_nonconservative_chan_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquationsQuasi1D) - a_h_ll, _, b_ll, a_ll = u_ll - a_h_rr, _, b_rr, a_rr = u_rr - - h_ll = waterheight(u_ll, equations) - h_rr = waterheight(u_rr, equations) - - return SVector(0, equations.gravity * a_ll * h_ll * (h_rr + b_rr), 0, 0) -end - -# While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that -# the normal component is incorporated into the numerical flux. -# -# See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a -# similar implementation. -@inline function flux_nonconservative_chan_etal(u_ll, u_rr, - normal_direction::AbstractVector, - equations::ShallowWaterEquationsQuasi1D) - return normal_direction[1] * - flux_nonconservative_chan_etal(u_ll, u_rr, 1, equations) -end - -@inline function flux_nonconservative_chan_etal(u_ll, u_rr, - normal_ll::AbstractVector, - normal_rr::AbstractVector, - equations::ShallowWaterEquationsQuasi1D) - # normal_ll should be equal to normal_rr - return flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, equations) -end - -""" - flux_chan_etal(u_ll, u_rr, orientation, - equations::ShallowWaterEquationsQuasi1D) - -Total energy conservative (mathematical entropy for quasi 1D shallow water equations) split form. -When the bottom topography is nonzero this scheme will be well-balanced when used as a `volume_flux`. -The `surface_flux` should still use, e.g., [`FluxPlusDissipation(flux_chan_etal, DissipationLocalLaxFriedrichs())`](@ref). - -Further details are available in the paper: -- Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023) - High order entropy stable schemes for the quasi-one-dimensional - shallow water and compressible Euler equations - [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) -""" -@inline function flux_chan_etal(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquationsQuasi1D) - a_h_ll, a_h_v_ll, _, _ = u_ll - a_h_rr, a_h_v_rr, _, _ = u_rr - - v_ll = velocity(u_ll, equations) - v_rr = velocity(u_rr, equations) - - f1 = 0.5f0 * (a_h_v_ll + a_h_v_rr) - f2 = f1 * 0.5f0 * (v_ll + v_rr) - - return SVector(f1, f2, 0, 0) -end - -# While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that -# the normal component is incorporated into the numerical flux. -# -# See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a -# similar implementation. -@inline function flux_chan_etal(u_ll, u_rr, normal_direction::AbstractVector, - equations::ShallowWaterEquationsQuasi1D) - return normal_direction[1] * flux_chan_etal(u_ll, u_rr, 1, equations) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the -# maximum velocity magnitude plus the maximum speed of sound -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::ShallowWaterEquationsQuasi1D) - # Get the velocity quantities - v_ll = velocity(u_ll, equations) - v_rr = velocity(u_rr, equations) - - # Calculate the wave celerity on the left and right - h_ll = waterheight(u_ll, equations) - h_rr = waterheight(u_rr, equations) - c_ll = sqrt(equations.gravity * h_ll) - c_rr = sqrt(equations.gravity * h_rr) - - return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) -end - -# Specialized `DissipationLocalLaxFriedrichs` to avoid spurious dissipation in the bottom topography -# and channel width -@inline function (dissipation::DissipationLocalLaxFriedrichs)(u_ll, u_rr, - orientation_or_normal_direction, - equations::ShallowWaterEquationsQuasi1D) - λ = dissipation.max_abs_speed(u_ll, u_rr, orientation_or_normal_direction, - equations) - diss = -0.5f0 * λ * (u_rr - u_ll) - return SVector(diss[1], diss[2], 0, 0) -end - -@inline function max_abs_speeds(u, equations::ShallowWaterEquationsQuasi1D) - h = waterheight(u, equations) - v = velocity(u, equations) - - c = sqrt(equations.gravity * h) - return (abs(v) + c,) -end - -# Helper function to extract the velocity vector from the conservative variables -@inline function velocity(u, equations::ShallowWaterEquationsQuasi1D) - a_h, a_h_v, _, _ = u - - v = a_h_v / a_h - - return v -end - -# Convert conservative variables to primitive -@inline function cons2prim(u, equations::ShallowWaterEquationsQuasi1D) - a_h, _, b, a = u - h = a_h / a - H = h + b - v = velocity(u, equations) - return SVector(H, v, b, a) -end - -# Convert conservative variables to entropy variables -# Note, only the first two are the entropy variables, the third and fourth entries still -# just carry the bottom topography and channel width values for convenience -@inline function cons2entropy(u, equations::ShallowWaterEquationsQuasi1D) - a_h, a_h_v, b, a = u - h = waterheight(u, equations) - v = velocity(u, equations) - #entropy variables are the same as ones in standard shallow water equations - w1 = equations.gravity * (h + b) - 0.5f0 * v^2 - w2 = v - - return SVector(w1, w2, b, a) -end - -# Convert primitive to conservative variables -@inline function prim2cons(prim, equations::ShallowWaterEquationsQuasi1D) - H, v, b, a = prim - - a_h = a * (H - b) - a_h_v = a_h * v - return SVector(a_h, a_h_v, b, a) -end - -@inline function waterheight(u, equations::ShallowWaterEquationsQuasi1D) - return u[1] / u[4] -end - -# Entropy function for the shallow water equations is the total energy -@inline function entropy(cons, equations::ShallowWaterEquationsQuasi1D) - a = cons[4] - return a * energy_total(cons, equations) -end - -# Calculate total energy for a conservative state `cons` -@inline function energy_total(cons, equations::ShallowWaterEquationsQuasi1D) - a_h, a_h_v, b, a = cons - e = (a_h_v^2) / (2 * a * a_h) + 0.5f0 * equations.gravity * (a_h^2 / a) + - equations.gravity * a_h * b - return e -end - -# Calculate the error for the "lake-at-rest" test case where H = h+b should -# be a constant value over time. Note, assumes there is a single reference -# water height `H0` with which to compare. -# -@inline function lake_at_rest_error(u, equations::ShallowWaterEquationsQuasi1D) - _, _, b, _ = u - h = waterheight(u, equations) - - return abs(equations.H0 - (h + b)) -end + #! format: noindent + + @doc raw""" + ShallowWaterEquationsQuasi1D(; gravity, H0 = 0, threshold_limiter = nothing threshold_wet = nothing) + + The quasi-1D shallow water equations (SWE). The equations are given by + ```math + \begin{aligned} + \frac{\partial}{\partial t}(a h) + \frac{\partial}{\partial x}(a h v) &= 0 \\ + \frac{\partial}{\partial t}(a h v) + \frac{\partial}{\partial x}(a h v^2) + + g a h \frac{\partial}{\partial x}(h + b) &= 0 + \end{aligned} + ``` + The unknown quantities of the Quasi-1D SWE are the water height ``h`` and the scaled velocity ``v``. + The gravitational constant is denoted by `g`, the (possibly) variable bottom topography function ``b(x)``, and (possibly) variable channel width ``a(x)``. The water height ``h`` is measured from the bottom topography ``b``, therefore one also defines the total water height as ``H = h + b``. + + The additional quantity ``H_0`` is also available to store a reference value for the total water height that + is useful to set initial conditions or test the "lake-at-rest" well-balancedness. + + The bottom topography function ``b(x)`` and channel width ``a(x)`` are set inside the initial condition routine + for a particular problem setup. To test the conservative form of the SWE one can set the bottom topography + variable `b` to zero and ``a`` to one. + + In addition to the unknowns, Trixi.jl currently stores the bottom topography and channel width values at the approximation points + despite being fixed in time. This is done for convenience of computing the bottom topography gradients + on the fly during the approximation as well as computing auxiliary quantities like the total water height ``H`` + or the entropy variables. + This affects the implementation and use of these equations in various ways: + * The flux values corresponding to the bottom topography and channel width must be zero. + * The bottom topography and channel width values must be included when defining initial conditions, boundary conditions or + source terms. + * [`AnalysisCallback`](@ref) analyzes this variable. + * Trixi.jl's visualization tools will visualize the bottom topography and channel width by default. + """ + struct ShallowWaterEquationsQuasi1D{RealT <: Real} <: + AbstractShallowWaterEquations{1, 4} + gravity::RealT # gravitational constant + H0::RealT # constant "lake-at-rest" total water height + end + + # Allow for flexibility to set the gravitational constant within an elixir depending on the + # application where `gravity_constant=1.0` or `gravity_constant=9.81` are common values. + # The reference total water height H0 defaults to 0.0 but is used for the "lake-at-rest" + # well-balancedness test cases. + # Strict default values for thresholds that performed well in many numerical experiments + function ShallowWaterEquationsQuasi1D(; gravity_constant, H0 = zero(gravity_constant)) + ShallowWaterEquationsQuasi1D(gravity_constant, H0) + end + + have_nonconservative_terms(::ShallowWaterEquationsQuasi1D) = True() + function varnames(::typeof(cons2cons), ::ShallowWaterEquationsQuasi1D) + ("a_h", "a_h_v", "b", "a") + end + # Note, we use the total water height, H = h + b, as the first primitive variable for easier + # visualization and setting initial conditions + varnames(::typeof(cons2prim), ::ShallowWaterEquationsQuasi1D) = ("H", "v", "b", "a") + + # Set initial conditions at physical location `x` for time `t` + """ + initial_condition_convergence_test(x, t, equations::ShallowWaterEquationsQuasi1D) + + A smooth initial condition used for convergence tests in combination with + [`source_terms_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + """ + function initial_condition_convergence_test( + x, t, + equations::ShallowWaterEquationsQuasi1D + ) + # generates a manufactured solution. + # some constants are chosen such that the function is periodic on the domain [0,sqrt(2)] + RealT = eltype(x) + Omega = sqrt(convert(RealT, 2)) * convert(RealT, pi) + H = 2 + 0.5f0 * sin(Omega * x[1] - t) + v = 0.25f0 + b = convert(RealT, 0.2) - convert(RealT, 0.05) * sin(Omega * x[1]) + a = 1 + convert(RealT, 0.1) * cos(Omega * x[1]) + return prim2cons(SVector(H, v, b, a), equations) + end + + """ + source_terms_convergence_test(u, x, t, equations::ShallowWaterEquationsQuasi1D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref) + (and [`BoundaryConditionDirichlet(initial_condition_convergence_test)`](@ref) in non-periodic domains). + + This manufactured solution source term is specifically designed for the bottom topography function + `b(x) = 0.2 - 0.05 * sinpi(sqrt(2) * x[1])` and channel width 'a(x)= 1 + 0.1 * cospi(sqrt(2) * x[1])' + as defined in [`initial_condition_convergence_test`](@ref). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::ShallowWaterEquationsQuasi1D + ) + # Same settings as in `initial_condition_convergence_test`. Some derivative simplify because + # this manufactured solution velocity is taken to be constant + RealT = eltype(u) + Omega = sqrt(convert(RealT, 2)) * convert(RealT, pi) + H = 2 + 0.5f0 * sin(Omega * x[1] - t) + H_x = 0.5f0 * cos(Omega * x[1] - t) * Omega + H_t = -0.5f0 * cos(Omega * x[1] - t) + + v = 0.25f0 + + b = convert(RealT, 0.2) - convert(RealT, 0.05) * sin(Omega * x[1]) + b_x = -convert(RealT, 0.05) * cos(Omega * x[1]) * Omega + + a = 1 + convert(RealT, 0.1) * cos(Omega * x[1]) + a_x = -convert(RealT, 0.1) * sin(Omega * x[1]) * Omega + + du1 = a * H_t + v * (a_x * (H - b) + a * (H_x - b_x)) + du2 = v * du1 + a * (equations.gravity * (H - b) * H_x) + + return SVector(du1, du2, 0, 0) + end + + # Calculate 1D conservative flux for a single point + # Note, the bottom topography and channel width have no flux + @inline function flux(u, orientation::Integer, equations::ShallowWaterEquationsQuasi1D) + _, a_h_v, _, _ = u + v = velocity(u, equations) + + f1 = a_h_v + f2 = a_h_v * v + + return SVector(f1, f2, 0, 0) + end + + """ + flux_nonconservative_chan_etal(u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquationsQuasi1D) + flux_nonconservative_chan_etal(u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquationsQuasi1D) + flux_nonconservative_chan_etal(u_ll, u_rr, + normal_ll::AbstractVector, normal_rr::AbstractVector, + equations::ShallowWaterEquationsQuasi1D) + + Non-symmetric two-point volume flux discretizing the nonconservative (source) term + that contains the gradient of the bottom topography [`ShallowWaterEquationsQuasi1D`](@ref) + and the channel width. + + Further details are available in the paper: + - Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023) + High order entropy stable schemes for the quasi-one-dimensional + shallow water and compressible Euler equations + [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) + """ + @inline function flux_nonconservative_chan_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquationsQuasi1D + ) + a_h_ll, _, b_ll, a_ll = u_ll + a_h_rr, _, b_rr, a_rr = u_rr + + h_ll = waterheight(u_ll, equations) + h_rr = waterheight(u_rr, equations) + + return SVector(0, equations.gravity * a_ll * h_ll * (h_rr + b_rr), 0, 0) + end + + # While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that + # the normal component is incorporated into the numerical flux. + # + # See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a + # similar implementation. + @inline function flux_nonconservative_chan_etal( + u_ll, u_rr, + normal_direction::AbstractVector, + equations::ShallowWaterEquationsQuasi1D + ) + return normal_direction[1] * + flux_nonconservative_chan_etal(u_ll, u_rr, 1, equations) + end + + @inline function flux_nonconservative_chan_etal( + u_ll, u_rr, + normal_ll::AbstractVector, + normal_rr::AbstractVector, + equations::ShallowWaterEquationsQuasi1D + ) + # normal_ll should be equal to normal_rr + return flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, equations) + end + + """ + flux_chan_etal(u_ll, u_rr, orientation, + equations::ShallowWaterEquationsQuasi1D) + + Total energy conservative (mathematical entropy for quasi 1D shallow water equations) split form. + When the bottom topography is nonzero this scheme will be well-balanced when used as a `volume_flux`. + The `surface_flux` should still use, e.g., [`FluxPlusDissipation(flux_chan_etal, DissipationLocalLaxFriedrichs())`](@ref). + + Further details are available in the paper: + - Jesse Chan, Khemraj Shukla, Xinhui Wu, Ruofeng Liu, Prani Nalluri (2023) + High order entropy stable schemes for the quasi-one-dimensional + shallow water and compressible Euler equations + [DOI: 10.48550/arXiv.2307.12089](https://doi.org/10.48550/arXiv.2307.12089) + """ + @inline function flux_chan_etal( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquationsQuasi1D + ) + a_h_ll, a_h_v_ll, _, _ = u_ll + a_h_rr, a_h_v_rr, _, _ = u_rr + + v_ll = velocity(u_ll, equations) + v_rr = velocity(u_rr, equations) + + f1 = 0.5f0 * (a_h_v_ll + a_h_v_rr) + f2 = f1 * 0.5f0 * (v_ll + v_rr) + + return SVector(f1, f2, 0, 0) + end + + # While `normal_direction` isn't strictly necessary in 1D, certain solvers assume that + # the normal component is incorporated into the numerical flux. + # + # See `flux(u, normal_direction::AbstractVector, equations::AbstractEquations{1})` for a + # similar implementation. + @inline function flux_chan_etal( + u_ll, u_rr, normal_direction::AbstractVector, + equations::ShallowWaterEquationsQuasi1D + ) + return normal_direction[1] * flux_chan_etal(u_ll, u_rr, 1, equations) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation as the + # maximum velocity magnitude plus the maximum speed of sound + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::ShallowWaterEquationsQuasi1D + ) + # Get the velocity quantities + v_ll = velocity(u_ll, equations) + v_rr = velocity(u_rr, equations) + + # Calculate the wave celerity on the left and right + h_ll = waterheight(u_ll, equations) + h_rr = waterheight(u_rr, equations) + c_ll = sqrt(equations.gravity * h_ll) + c_rr = sqrt(equations.gravity * h_rr) + + return max(abs(v_ll), abs(v_rr)) + max(c_ll, c_rr) + end + + # Specialized `DissipationLocalLaxFriedrichs` to avoid spurious dissipation in the bottom topography + # and channel width + @inline function (dissipation::DissipationLocalLaxFriedrichs)( + u_ll, u_rr, + orientation_or_normal_direction, + equations::ShallowWaterEquationsQuasi1D + ) + λ = dissipation.max_abs_speed( + u_ll, u_rr, orientation_or_normal_direction, + equations + ) + diss = -0.5f0 * λ * (u_rr - u_ll) + return SVector(diss[1], diss[2], 0, 0) + end + + @inline function max_abs_speeds(u, equations::ShallowWaterEquationsQuasi1D) + h = waterheight(u, equations) + v = velocity(u, equations) + + c = sqrt(equations.gravity * h) + return (abs(v) + c,) + end + + # Helper function to extract the velocity vector from the conservative variables + @inline function velocity(u, equations::ShallowWaterEquationsQuasi1D) + a_h, a_h_v, _, _ = u + + v = a_h_v / a_h + + return v + end + + # Convert conservative variables to primitive + @inline function cons2prim(u, equations::ShallowWaterEquationsQuasi1D) + a_h, _, b, a = u + h = a_h / a + H = h + b + v = velocity(u, equations) + return SVector(H, v, b, a) + end + + # Convert conservative variables to entropy variables + # Note, only the first two are the entropy variables, the third and fourth entries still + # just carry the bottom topography and channel width values for convenience + @inline function cons2entropy(u, equations::ShallowWaterEquationsQuasi1D) + a_h, a_h_v, b, a = u + h = waterheight(u, equations) + v = velocity(u, equations) + #entropy variables are the same as ones in standard shallow water equations + w1 = equations.gravity * (h + b) - 0.5f0 * v^2 + w2 = v + + return SVector(w1, w2, b, a) + end + + # Convert primitive to conservative variables + @inline function prim2cons(prim, equations::ShallowWaterEquationsQuasi1D) + H, v, b, a = prim + + a_h = a * (H - b) + a_h_v = a_h * v + return SVector(a_h, a_h_v, b, a) + end + + @inline function waterheight(u, equations::ShallowWaterEquationsQuasi1D) + return u[1] / u[4] + end + + # Entropy function for the shallow water equations is the total energy + @inline function entropy(cons, equations::ShallowWaterEquationsQuasi1D) + a = cons[4] + return a * energy_total(cons, equations) + end + + # Calculate total energy for a conservative state `cons` + @inline function energy_total(cons, equations::ShallowWaterEquationsQuasi1D) + a_h, a_h_v, b, a = cons + e = (a_h_v^2) / (2 * a * a_h) + 0.5f0 * equations.gravity * (a_h^2 / a) + + equations.gravity * a_h * b + return e + end + + # Calculate the error for the "lake-at-rest" test case where H = h+b should + # be a constant value over time. Note, assumes there is a single reference + # water height `H0` with which to compare. + # + @inline function lake_at_rest_error(u, equations::ShallowWaterEquationsQuasi1D) + _, _, b, _ = u + h = waterheight(u, equations) + + return abs(equations.H0 - (h + b)) + end end # @muladd diff --git a/src/equations/traffic_flow_lwr_1d.jl b/src/equations/traffic_flow_lwr_1d.jl index c41fbb2809..551b875aa4 100644 --- a/src/equations/traffic_flow_lwr_1d.jl +++ b/src/equations/traffic_flow_lwr_1d.jl @@ -3,116 +3,128 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@doc raw""" - TrafficFlowLWREquations1D - -The classic Lighthill-Witham Richards (LWR) model for 1D traffic flow. -The car density is denoted by $u \in [0, 1]$ and -the maximum possible speed (e.g. due to speed limits) is $v_{\text{max}}$. -```math -\partial_t u + v_{\text{max}} \partial_1 [u (1 - u)] = 0 -``` -For more details see e.g. Section 11.1 of -- Randall LeVeque (2002) -Finite Volume Methods for Hyperbolic Problems -[DOI: 10.1017/CBO9780511791253]https://doi.org/10.1017/CBO9780511791253 -""" -struct TrafficFlowLWREquations1D{RealT <: Real} <: AbstractTrafficFlowLWREquations{1, 1} - v_max::RealT - - function TrafficFlowLWREquations1D(v_max = 1.0) - new{typeof(v_max)}(v_max) + #! format: noindent + + @doc raw""" + TrafficFlowLWREquations1D + + The classic Lighthill-Witham Richards (LWR) model for 1D traffic flow. + The car density is denoted by $u \in [0, 1]$ and + the maximum possible speed (e.g. due to speed limits) is $v_{\text{max}}$. + ```math + \partial_t u + v_{\text{max}} \partial_1 [u (1 - u)] = 0 + ``` + For more details see e.g. Section 11.1 of + - Randall LeVeque (2002) + Finite Volume Methods for Hyperbolic Problems + [DOI: 10.1017/CBO9780511791253]https://doi.org/10.1017/CBO9780511791253 + """ + struct TrafficFlowLWREquations1D{RealT <: Real} <: AbstractTrafficFlowLWREquations{1, 1} + v_max::RealT + + function TrafficFlowLWREquations1D(v_max = 1.0) + new{typeof(v_max)}(v_max) + end end -end - -varnames(::typeof(cons2cons), ::TrafficFlowLWREquations1D) = ("car-density",) -varnames(::typeof(cons2prim), ::TrafficFlowLWREquations1D) = ("car-density",) - -""" - initial_condition_convergence_test(x, t, equations::TrafficFlowLWREquations1D) - -A smooth initial condition used for convergence tests. -""" -function initial_condition_convergence_test(x, t, equations::TrafficFlowLWREquations1D) - RealT = eltype(x) - c = 2 - A = 1 - L = 1 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - scalar = c + A * sin(omega * (x[1] - t)) - - return SVector(scalar) -end - -""" - source_terms_convergence_test(u, x, t, equations::TrafficFlowLWREquations1D) - -Source terms used for convergence tests in combination with -[`initial_condition_convergence_test`](@ref). -""" -@inline function source_terms_convergence_test(u, x, t, - equations::TrafficFlowLWREquations1D) - # Same settings as in `initial_condition` - RealT = eltype(x) - c = 2 - A = 1 - L = 1 - f = 1.0f0 / L - omega = 2 * convert(RealT, pi) * f - du = omega * cos(omega * (x[1] - t)) * - (-1 - equations.v_max * (2 * sin(omega * (x[1] - t)) + 3)) - - return SVector(du) -end - -# Calculate 1D flux in for a single point -@inline function flux(u, orientation::Integer, equations::TrafficFlowLWREquations1D) - return SVector(equations.v_max * u[1] * (1 - u[1])) -end - -# Calculate maximum wave speed for local Lax-Friedrichs-type dissipation -@inline function max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - equations::TrafficFlowLWREquations1D) - λ_max = max(abs(equations.v_max * (1 - 2 * u_ll[1])), - abs(equations.v_max * (1 - 2 * u_rr[1]))) -end - -# Calculate minimum and maximum wave speeds for HLL-type fluxes -@inline function min_max_speed_naive(u_ll, u_rr, orientation::Integer, - equations::TrafficFlowLWREquations1D) - jac_L = equations.v_max * (1 - 2 * u_ll[1]) - jac_R = equations.v_max * (1 - 2 * u_rr[1]) - - λ_min = min(jac_L, jac_R) - λ_max = max(jac_L, jac_R) - - return λ_min, λ_max -end - -@inline function min_max_speed_davis(u_ll, u_rr, orientation::Integer, - equations::TrafficFlowLWREquations1D) - min_max_speed_naive(u_ll, u_rr, orientation, equations) -end - -@inline function max_abs_speeds(u, equations::TrafficFlowLWREquations1D) - return (abs(equations.v_max * (1 - 2 * u[1])),) -end - -# Convert conservative variables to primitive -@inline cons2prim(u, equations::TrafficFlowLWREquations1D) = u - -# Convert conservative variables to entropy variables -@inline cons2entropy(u, equations::TrafficFlowLWREquations1D) = u - -# Calculate entropy for a conservative state `cons` -@inline entropy(u::Real, ::TrafficFlowLWREquations1D) = 0.5f0 * u^2 -@inline entropy(u, equations::TrafficFlowLWREquations1D) = entropy(u[1], equations) - -# Calculate total energy for a conservative state `cons` -@inline energy_total(u::Real, ::TrafficFlowLWREquations1D) = 0.5f0 * u^2 -@inline energy_total(u, equations::TrafficFlowLWREquations1D) = energy_total(u[1], - equations) + + varnames(::typeof(cons2cons), ::TrafficFlowLWREquations1D) = ("car-density",) + varnames(::typeof(cons2prim), ::TrafficFlowLWREquations1D) = ("car-density",) + + """ + initial_condition_convergence_test(x, t, equations::TrafficFlowLWREquations1D) + + A smooth initial condition used for convergence tests. + """ + function initial_condition_convergence_test(x, t, equations::TrafficFlowLWREquations1D) + RealT = eltype(x) + c = 2 + A = 1 + L = 1 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + scalar = c + A * sin(omega * (x[1] - t)) + + return SVector(scalar) + end + + """ + source_terms_convergence_test(u, x, t, equations::TrafficFlowLWREquations1D) + + Source terms used for convergence tests in combination with + [`initial_condition_convergence_test`](@ref). + """ + @inline function source_terms_convergence_test( + u, x, t, + equations::TrafficFlowLWREquations1D + ) + # Same settings as in `initial_condition` + RealT = eltype(x) + c = 2 + A = 1 + L = 1 + f = 1.0f0 / L + omega = 2 * convert(RealT, pi) * f + du = omega * cos(omega * (x[1] - t)) * + (-1 - equations.v_max * (2 * sin(omega * (x[1] - t)) + 3)) + + return SVector(du) + end + + # Calculate 1D flux in for a single point + @inline function flux(u, orientation::Integer, equations::TrafficFlowLWREquations1D) + return SVector(equations.v_max * u[1] * (1 - u[1])) + end + + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + @inline function max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::TrafficFlowLWREquations1D + ) + λ_max = max( + abs(equations.v_max * (1 - 2 * u_ll[1])), + abs(equations.v_max * (1 - 2 * u_rr[1])) + ) + end + + # Calculate minimum and maximum wave speeds for HLL-type fluxes + @inline function min_max_speed_naive( + u_ll, u_rr, orientation::Integer, + equations::TrafficFlowLWREquations1D + ) + jac_L = equations.v_max * (1 - 2 * u_ll[1]) + jac_R = equations.v_max * (1 - 2 * u_rr[1]) + + λ_min = min(jac_L, jac_R) + λ_max = max(jac_L, jac_R) + + return λ_min, λ_max + end + + @inline function min_max_speed_davis( + u_ll, u_rr, orientation::Integer, + equations::TrafficFlowLWREquations1D + ) + min_max_speed_naive(u_ll, u_rr, orientation, equations) + end + + @inline function max_abs_speeds(u, equations::TrafficFlowLWREquations1D) + return (abs(equations.v_max * (1 - 2 * u[1])),) + end + + # Convert conservative variables to primitive + @inline cons2prim(u, equations::TrafficFlowLWREquations1D) = u + + # Convert conservative variables to entropy variables + @inline cons2entropy(u, equations::TrafficFlowLWREquations1D) = u + + # Calculate entropy for a conservative state `cons` + @inline entropy(u::Real, ::TrafficFlowLWREquations1D) = 0.5f0 * u^2 + @inline entropy(u, equations::TrafficFlowLWREquations1D) = entropy(u[1], equations) + + # Calculate total energy for a conservative state `cons` + @inline energy_total(u::Real, ::TrafficFlowLWREquations1D) = 0.5f0 * u^2 + @inline energy_total(u, equations::TrafficFlowLWREquations1D) = energy_total( + u[1], + equations + ) end # @muladd diff --git a/src/meshes/abstract_tree.jl b/src/meshes/abstract_tree.jl index 469189ff50..1e0b42100d 100644 --- a/src/meshes/abstract_tree.jl +++ b/src/meshes/abstract_tree.jl @@ -3,777 +3,813 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -abstract type AbstractTree{NDIMS} <: AbstractContainer end - -# Type traits to obtain dimension -@inline Base.ndims(::AbstractTree{NDIMS}) where {NDIMS} = NDIMS - -# Auxiliary methods to allow semantic queries on the tree -# Check whether cell has parent cell -has_parent(t::AbstractTree, cell_id::Int) = t.parent_ids[cell_id] > 0 - -# Count number of children for a given cell -function n_children(t::AbstractTree, cell_id::Int) - count(x -> (x > 0), @view t.child_ids[:, cell_id]) -end - -# Check whether cell has any child cell -has_children(t::AbstractTree, cell_id::Int) = n_children(t, cell_id) > 0 - -# Check whether cell is leaf cell -is_leaf(t::AbstractTree, cell_id::Int) = !has_children(t, cell_id) - -# Check whether cell has specific child cell -has_child(t::AbstractTree, cell_id::Int, child::Int) = t.child_ids[child, cell_id] > 0 - -# Check if cell has a neighbor at the same refinement level in the given direction -function has_neighbor(t::AbstractTree, cell_id::Int, direction::Int) - t.neighbor_ids[direction, cell_id] > 0 -end - -# Check if cell has a coarse neighbor, i.e., with one refinement level lower -function has_coarse_neighbor(t::AbstractTree, cell_id::Int, direction::Int) - return has_parent(t, cell_id) && has_neighbor(t, t.parent_ids[cell_id], direction) -end - -# Check if cell has any neighbor (same-level or lower-level) -function has_any_neighbor(t::AbstractTree, cell_id::Int, direction::Int) - return has_neighbor(t, cell_id, direction) || - has_coarse_neighbor(t, cell_id, direction) -end - -# Check if cell is own cell, i.e., belongs to this MPI rank -is_own_cell(t::AbstractTree, cell_id) = true - -# Return cell length for a given level -length_at_level(t::AbstractTree, level::Int) = t.length_level_0 / 2^level - -# Return cell length for a given cell -length_at_cell(t::AbstractTree, cell_id::Int) = length_at_level(t, t.levels[cell_id]) - -# Return minimum level of any leaf cell -minimum_level(t::AbstractTree) = minimum(t.levels[leaf_cells(t)]) - -# Return maximum level of any leaf cell -maximum_level(t::AbstractTree) = maximum(t.levels[leaf_cells(t)]) - -# Check if tree is periodic -isperiodic(t::AbstractTree) = all(t.periodicity) -isperiodic(t::AbstractTree, dimension) = t.periodicity[dimension] - -# Auxiliary methods for often-required calculations -# Number of potential child cells -n_children_per_cell(::AbstractTree{NDIMS}) where {NDIMS} = 2^NDIMS - -# Number of directions -# -# Directions are indicated by numbers from 1 to 2*ndims: -# 1 -> -x -# 2 -> +x -# 3 -> -y -# 4 -> +y -# 5 -> -z -# 6 -> +z -@inline n_directions(::AbstractTree{NDIMS}) where {NDIMS} = 2 * NDIMS -# TODO: Taal performance, 1:n_directions(tree) vs. Base.OneTo(n_directions(tree)) vs. SOneTo(n_directions(tree)) -""" - eachdirection(tree::AbstractTree) - -Return an iterator over the indices that specify the location in relevant data structures -for the directions in `AbstractTree`. -In particular, not the directions themselves are returned. -""" -@inline eachdirection(tree::AbstractTree) = Base.OneTo(n_directions(tree)) - -# For a given direction, return its opposite direction -# -# dir -> opp -# 1 -> 2 -# 2 -> 1 -# 3 -> 4 -# 4 -> 3 -# 5 -> 6 -# 6 -> 5 -opposite_direction(direction::Int) = direction + 1 - 2 * ((direction + 1) % 2) - -# For a given child position (from 1 to 8) and dimension (from 1 to 3), -# calculate a child cell's position relative to its parent cell. -# -# Essentially calculates the following -# dim=1 dim=2 dim=3 -# child x y z -# 1 - - - -# 2 + - - -# 3 - + - -# 4 + + - -# 5 - - + -# 6 + - + -# 7 - + + -# 8 + + + -# child_sign(child::Int, dim::Int) = 1 - 2 * (div(child + 2^(dim - 1) - 1, 2^(dim-1)) % 2) -# Since we use only a fixed number of dimensions, we use a lookup table for improved performance. -const _child_signs = [-1 -1 -1; - +1 -1 -1; - -1 +1 -1; - +1 +1 -1; - -1 -1 +1; - +1 -1 +1; - -1 +1 +1; - +1 +1 +1] -child_sign(child::Int, dim::Int) = _child_signs[child, dim] - -# For each child position (1 to 8) and a given direction (from 1 to 6), return -# neighboring child position. -const _adjacent_child_ids = [2 2 3 3 5 5; - 1 1 4 4 6 6; - 4 4 1 1 7 7; - 3 3 2 2 8 8; - 6 6 7 7 1 1; - 5 5 8 8 2 2; - 8 8 5 5 3 3; - 7 7 6 6 4 4] -adjacent_child(child::Int, direction::Int) = _adjacent_child_ids[child, direction] - -# For each child position (1 to 8) and a given direction (from 1 to 6), return -# if neighbor is a sibling -function has_sibling(child::Int, direction::Int) - return (child_sign(child, div(direction + 1, 2)) * (-1)^(direction - 1)) > 0 -end - -# Obtain leaf cells that fulfill a given criterion. -# -# The function `f` is passed the cell id of each leaf cell -# as an argument. -function filter_leaf_cells(f, t::AbstractTree) - filtered = Vector{Int}(undef, length(t)) - count = 0 - for cell_id in 1:length(t) - if is_leaf(t, cell_id) && f(cell_id) - count += 1 - filtered[count] = cell_id - end - end + #! format: noindent - return filtered[1:count] -end + abstract type AbstractTree{NDIMS} <: AbstractContainer end -# Return an array with the ids of all leaf cells -leaf_cells(t::AbstractTree) = filter_leaf_cells((cell_id) -> true, t) + # Type traits to obtain dimension + @inline Base.ndims(::AbstractTree{NDIMS}) where {NDIMS} = NDIMS -# Return an array with the ids of all leaf cells for a given rank -leaf_cells_by_rank(t::AbstractTree, rank) = leaf_cells(t) + # Auxiliary methods to allow semantic queries on the tree + # Check whether cell has parent cell + has_parent(t::AbstractTree, cell_id::Int) = t.parent_ids[cell_id] > 0 -# Return an array with the ids of all local leaf cells -local_leaf_cells(t::AbstractTree) = leaf_cells(t) + # Count number of children for a given cell + function n_children(t::AbstractTree, cell_id::Int) + count(x -> (x > 0), @view t.child_ids[:, cell_id]) + end -# Count the number of leaf cells. -count_leaf_cells(t::AbstractTree) = length(leaf_cells(t)) + # Check whether cell has any child cell + has_children(t::AbstractTree, cell_id::Int) = n_children(t, cell_id) > 0 -@inline function cell_coordinates(t::AbstractTree{NDIMS}, cell) where {NDIMS} - SVector(ntuple(d -> t.coordinates[d, cell], Val(NDIMS))) -end + # Check whether cell is leaf cell + is_leaf(t::AbstractTree, cell_id::Int) = !has_children(t, cell_id) -@inline function set_cell_coordinates!(t::AbstractTree{NDIMS}, coords, - cell) where {NDIMS} - for d in 1:NDIMS - t.coordinates[d, cell] = coords[d] + # Check whether cell has specific child cell + has_child(t::AbstractTree, cell_id::Int, child::Int) = t.child_ids[child, cell_id] > 0 + + # Check if cell has a neighbor at the same refinement level in the given direction + function has_neighbor(t::AbstractTree, cell_id::Int, direction::Int) + t.neighbor_ids[direction, cell_id] > 0 end -end - -# Determine if point is located inside cell -function is_point_in_cell(t::AbstractTree, point_coordinates, cell_id) - cell_length = length_at_cell(t, cell_id) - cell_coordinates_ = cell_coordinates(t, cell_id) - min_coordinates = cell_coordinates_ .- cell_length / 2 - max_coordinates = cell_coordinates_ .+ cell_length / 2 - - return all(min_coordinates .<= point_coordinates .<= max_coordinates) -end - -# Store cell id in each cell to use for post-AMR analysis -function reset_original_cell_ids!(t::AbstractTree) - t.original_cell_ids[1:length(t)] .= 1:length(t) -end - -# Efficiently perform uniform refinement up to a given level (works only on mesh with a single cell) -function refine_uniformly!(t::AbstractTree, max_level) - @assert length(t)==1 "efficient uniform refinement only works for a newly created tree" - @assert max_level>=0 "the uniform refinement level must be non-zero" - - # Calculate size of final tree and resize tree - total_length = 1 - for level in 1:max_level - total_length += n_children_per_cell(t)^level + + # Check if cell has a coarse neighbor, i.e., with one refinement level lower + function has_coarse_neighbor(t::AbstractTree, cell_id::Int, direction::Int) + return has_parent(t, cell_id) && has_neighbor(t, t.parent_ids[cell_id], direction) end - resize!(t, total_length) - - # Traverse tree to set parent-child relationships - init_children!(t, 1, max_level) - - # Set all neighbor relationships - init_neighbors!(t, max_level) -end - -# Recursively initialize children up to level `max_level` in depth-first ordering, starting with -# cell `cell_id` and set all information except neighbor relations (see `init_neighbors!`). -# -# Return the number of offspring of the initialized cell plus one -function init_children!(t::AbstractTree, cell_id, max_level) - # Stop recursion if max_level has been reached - if t.levels[cell_id] >= max_level - return 1 - else - # Initialize each child cell, counting the total number of offspring - n_offspring = 0 - for child in 1:n_children_per_cell(t) - # Get cell id of child - child_id = cell_id + 1 + n_offspring - - # Initialize child cell (except neighbors) - init_child!(t, cell_id, child, child_id) - - # Recursively initialize child cell - n_offspring += init_children!(t, child_id, max_level) - end - return n_offspring + 1 + # Check if cell has any neighbor (same-level or lower-level) + function has_any_neighbor(t::AbstractTree, cell_id::Int, direction::Int) + return has_neighbor(t, cell_id, direction) || + has_coarse_neighbor(t, cell_id, direction) end -end -# Iteratively set all neighbor relations, starting at an initialized level 0 cell. Assume that -# parent-child relations have already been initialized (see `init_children!`). -function init_neighbors!(t::AbstractTree, max_level = maximum_level(t)) - @assert all(n >= 0 for n in t.neighbor_ids[:, 1]) "level 0 cell neighbors must be initialized" + # Check if cell is own cell, i.e., belongs to this MPI rank + is_own_cell(t::AbstractTree, cell_id) = true + + # Return cell length for a given level + length_at_level(t::AbstractTree, level::Int) = t.length_level_0 / 2^level + + # Return cell length for a given cell + length_at_cell(t::AbstractTree, cell_id::Int) = length_at_level(t, t.levels[cell_id]) + + # Return minimum level of any leaf cell + minimum_level(t::AbstractTree) = minimum(t.levels[leaf_cells(t)]) + + # Return maximum level of any leaf cell + maximum_level(t::AbstractTree) = maximum(t.levels[leaf_cells(t)]) + + # Check if tree is periodic + isperiodic(t::AbstractTree) = all(t.periodicity) + isperiodic(t::AbstractTree, dimension) = t.periodicity[dimension] + + # Auxiliary methods for often-required calculations + # Number of potential child cells + n_children_per_cell(::AbstractTree{NDIMS}) where {NDIMS} = 2^NDIMS + + # Number of directions + # + # Directions are indicated by numbers from 1 to 2*ndims: + # 1 -> -x + # 2 -> +x + # 3 -> -y + # 4 -> +y + # 5 -> -z + # 6 -> +z + @inline n_directions(::AbstractTree{NDIMS}) where {NDIMS} = 2 * NDIMS + # TODO: Taal performance, 1:n_directions(tree) vs. Base.OneTo(n_directions(tree)) vs. SOneTo(n_directions(tree)) + """ + eachdirection(tree::AbstractTree) + + Return an iterator over the indices that specify the location in relevant data structures + for the directions in `AbstractTree`. + In particular, not the directions themselves are returned. + """ + @inline eachdirection(tree::AbstractTree) = Base.OneTo(n_directions(tree)) + + # For a given direction, return its opposite direction + # + # dir -> opp + # 1 -> 2 + # 2 -> 1 + # 3 -> 4 + # 4 -> 3 + # 5 -> 6 + # 6 -> 5 + opposite_direction(direction::Int) = direction + 1 - 2 * ((direction + 1) % 2) + + # For a given child position (from 1 to 8) and dimension (from 1 to 3), + # calculate a child cell's position relative to its parent cell. + # + # Essentially calculates the following + # dim=1 dim=2 dim=3 + # child x y z + # 1 - - - + # 2 + - - + # 3 - + - + # 4 + + - + # 5 - - + + # 6 + - + + # 7 - + + + # 8 + + + + # child_sign(child::Int, dim::Int) = 1 - 2 * (div(child + 2^(dim - 1) - 1, 2^(dim-1)) % 2) + # Since we use only a fixed number of dimensions, we use a lookup table for improved performance. + const _child_signs = [ + -1 -1 -1; + +1 -1 -1; + -1 +1 -1; + +1 +1 -1; + -1 -1 +1; + +1 -1 +1; + -1 +1 +1; + +1 +1 +1 + ] + child_sign(child::Int, dim::Int) = _child_signs[child, dim] + + # For each child position (1 to 8) and a given direction (from 1 to 6), return + # neighboring child position. + const _adjacent_child_ids = [ + 2 2 3 3 5 5; + 1 1 4 4 6 6; + 4 4 1 1 7 7; + 3 3 2 2 8 8; + 6 6 7 7 1 1; + 5 5 8 8 2 2; + 8 8 5 5 3 3; + 7 7 6 6 4 4 + ] + adjacent_child(child::Int, direction::Int) = _adjacent_child_ids[child, direction] + + # For each child position (1 to 8) and a given direction (from 1 to 6), return + # if neighbor is a sibling + function has_sibling(child::Int, direction::Int) + return (child_sign(child, div(direction + 1, 2)) * (-1)^(direction - 1)) > 0 + end - # Initialize neighbors level by level - for level in 1:max_level - # Walk entire tree, starting from level 0 cell + # Obtain leaf cells that fulfill a given criterion. + # + # The function `f` is passed the cell id of each leaf cell + # as an argument. + function filter_leaf_cells(f, t::AbstractTree) + filtered = Vector{Int}(undef, length(t)) + count = 0 for cell_id in 1:length(t) - # Skip cells whose immediate children are already initialized *or* whose level is too high for this round - if t.levels[cell_id] != level - 1 - continue - end - - # Iterate over children and set neighbor information - for child in 1:n_children_per_cell(t) - child_id = t.child_ids[child, cell_id] - init_child_neighbors!(t, cell_id, child, child_id) + if is_leaf(t, cell_id) && f(cell_id) + count += 1 + filtered[count] = cell_id end end + + return filtered[1:count] end - return nothing -end + # Return an array with the ids of all leaf cells + leaf_cells(t::AbstractTree) = filter_leaf_cells((cell_id) -> true, t) -# Initialize the neighbors of child cell `child_id` based on parent cell `cell_id` -function init_child_neighbors!(t::AbstractTree, cell_id, child, child_id) - t.neighbor_ids[:, child_id] .= zero(eltype(t.neighbor_ids)) - for direction in eachdirection(t) - # If neighbor is a sibling, establish one-sided connectivity - # Note: two-sided is not necessary, as each sibling will do this - if has_sibling(child, direction) - adjacent = adjacent_child(child, direction) - neighbor_id = t.child_ids[adjacent, cell_id] + # Return an array with the ids of all leaf cells for a given rank + leaf_cells_by_rank(t::AbstractTree, rank) = leaf_cells(t) - t.neighbor_ids[direction, child_id] = neighbor_id - continue - end - - # Skip if original cell does have no neighbor in direction - if !has_neighbor(t, cell_id, direction) - continue - end + # Return an array with the ids of all local leaf cells + local_leaf_cells(t::AbstractTree) = leaf_cells(t) - # Otherwise, check if neighbor has children - if not, skip again - neighbor_id = t.neighbor_ids[direction, cell_id] - if !has_children(t, neighbor_id) - continue - end + # Count the number of leaf cells. + count_leaf_cells(t::AbstractTree) = length(leaf_cells(t)) - # Check if neighbor has corresponding child and if yes, establish connectivity - adjacent = adjacent_child(child, direction) - if has_child(t, neighbor_id, adjacent) - neighbor_child_id = t.child_ids[adjacent, neighbor_id] - opposite = opposite_direction(direction) + @inline function cell_coordinates(t::AbstractTree{NDIMS}, cell) where {NDIMS} + SVector(ntuple(d -> t.coordinates[d, cell], Val(NDIMS))) + end - t.neighbor_ids[direction, child_id] = neighbor_child_id - t.neighbor_ids[opposite, neighbor_child_id] = child_id + @inline function set_cell_coordinates!( + t::AbstractTree{NDIMS}, coords, + cell + ) where {NDIMS} + for d in 1:NDIMS + t.coordinates[d, cell] = coords[d] end end - return nothing -end - -# Refine given cells without rebalancing tree. -# -# Note: After a call to this method the tree may be unbalanced! -function refine_unbalanced!(t::AbstractTree, cell_ids, - sorted_unique_cell_ids = sort(unique(cell_ids))) - # Store actual ids refined cells (shifted due to previous insertions) - refined = zeros(Int, length(cell_ids)) - - # Loop over all cells that are to be refined - for (count, original_cell_id) in enumerate(sorted_unique_cell_ids) - # Determine actual cell id, taking into account previously inserted cells - n_children = n_children_per_cell(t) - cell_id = original_cell_id + (count - 1) * n_children - refined[count] = cell_id + # Determine if point is located inside cell + function is_point_in_cell(t::AbstractTree, point_coordinates, cell_id) + cell_length = length_at_cell(t, cell_id) + cell_coordinates_ = cell_coordinates(t, cell_id) + min_coordinates = cell_coordinates_ .- cell_length / 2 + max_coordinates = cell_coordinates_ .+ cell_length / 2 - @assert !has_children(t, cell_id) "Non-leaf cell $cell_id cannot be refined" + return all(min_coordinates .<= point_coordinates .<= max_coordinates) + end - # Insert new cells directly behind parent (depth-first) - insert!(t, cell_id + 1, n_children) + # Store cell id in each cell to use for post-AMR analysis + function reset_original_cell_ids!(t::AbstractTree) + t.original_cell_ids[1:length(t)] .= 1:length(t) + end - # Flip sign of refined cell such that we can easily find it later - t.original_cell_ids[cell_id] = -t.original_cell_ids[cell_id] + # Efficiently perform uniform refinement up to a given level (works only on mesh with a single cell) + function refine_uniformly!(t::AbstractTree, max_level) + @assert length(t) == 1 "efficient uniform refinement only works for a newly created tree" + @assert max_level >= 0 "the uniform refinement level must be non-zero" - # Initialize child cells (except neighbors) - for child in 1:n_children - child_id = cell_id + child - init_child!(t, cell_id, child, child_id) + # Calculate size of final tree and resize tree + total_length = 1 + for level in 1:max_level + total_length += n_children_per_cell(t)^level end + resize!(t, total_length) - # Initialize child cells (only neighbors) - # This separate loop is required since init_child_neighbors requires initialized parent-child - # relationships - for child in 1:n_children - child_id = cell_id + child - init_child_neighbors!(t, cell_id, child, child_id) - end - end + # Traverse tree to set parent-child relationships + init_children!(t, 1, max_level) - return refined -end - -# Refine entire tree by one level -function refine!(t::AbstractTree) - cells = @trixi_timeit timer() "collect all leaf cells" leaf_cells(t) - @trixi_timeit timer() "refine!" refine!(t, cells, cells) -end - -# Refine given cells and rebalance tree. -# -# Note 1: Rebalancing is iterative, i.e., neighboring cells are refined if -# otherwise the 2:1 rule would be violated, which can cause more -# refinements. -# Note 2: Rebalancing currently only considers *Cartesian* neighbors, not diagonal neighbors! -function refine!(t::AbstractTree, cell_ids, - sorted_unique_cell_ids = sort(unique(cell_ids))) - # Reset original cell ids such that each cell knows its current id - reset_original_cell_ids!(t) - - # Refine all requested cells - refined = @trixi_timeit timer() "refine_unbalanced!" refine_unbalanced!(t, cell_ids, - sorted_unique_cell_ids) - refinement_count = length(refined) - - # Iteratively rebalance the tree until it does not change anymore - while length(refined) > 0 - refined = @trixi_timeit timer() "rebalance!" rebalance!(t, refined) - refinement_count += length(refined) + # Set all neighbor relationships + init_neighbors!(t, max_level) end - # Determine list of *original* cell ids that were refined - # Note: original_cell_ids contains the cell_id *before* refinement. At - # refinement, the refined cell's original_cell_ids value has its sign flipped - # to easily find it now. - refined_original_cells = @views(-t.original_cell_ids[1:length(t)][t.original_cell_ids[1:length(t)] .< 0]) - - # Check if count of refinement cells matches information in original_cell_ids - @assert refinement_count==length(refined_original_cells) ("Mismatch in number of refined cells") + # Recursively initialize children up to level `max_level` in depth-first ordering, starting with + # cell `cell_id` and set all information except neighbor relations (see `init_neighbors!`). + # + # Return the number of offspring of the initialized cell plus one + function init_children!(t::AbstractTree, cell_id, max_level) + # Stop recursion if max_level has been reached + if t.levels[cell_id] >= max_level + return 1 + else + # Initialize each child cell, counting the total number of offspring + n_offspring = 0 + for child in 1:n_children_per_cell(t) + # Get cell id of child + child_id = cell_id + 1 + n_offspring - return refined_original_cells -end + # Initialize child cell (except neighbors) + init_child!(t, cell_id, child, child_id) -# Refine all leaf cells with coordinates in a given rectangular box -function refine_box!(t::AbstractTree{NDIMS}, coordinates_min, - coordinates_max) where {NDIMS} - for dim in 1:NDIMS - @assert coordinates_min[dim] cell_coordinates(t, cell_id))) + return n_offspring + 1 + end end - # Refine cells - refine!(t, cells) -end - -# Convenience method for 1D -function refine_box!(t::AbstractTree{1}, coordinates_min::Real, coordinates_max::Real) - return refine_box!(t, [convert(Float64, coordinates_min)], - [convert(Float64, coordinates_max)]) -end + # Iteratively set all neighbor relations, starting at an initialized level 0 cell. Assume that + # parent-child relations have already been initialized (see `init_children!`). + function init_neighbors!(t::AbstractTree, max_level = maximum_level(t)) + @assert all(n >= 0 for n in t.neighbor_ids[:, 1]) "level 0 cell neighbors must be initialized" + + # Initialize neighbors level by level + for level in 1:max_level + # Walk entire tree, starting from level 0 cell + for cell_id in 1:length(t) + # Skip cells whose immediate children are already initialized *or* whose level is too high for this round + if t.levels[cell_id] != level - 1 + continue + end -# Refine all leaf cells with coordinates in a given sphere -function refine_sphere!(t::AbstractTree{NDIMS}, center::SVector{NDIMS}, - radius) where {NDIMS} - @assert radius>=0 "Radius must be positive." + # Iterate over children and set neighbor information + for child in 1:n_children_per_cell(t) + child_id = t.child_ids[child, cell_id] + init_child_neighbors!(t, cell_id, child, child_id) + end + end + end - # Find all leaf cells within sphere - cells = filter_leaf_cells(t) do cell_id - return sum(abs2, cell_coordinates(t, cell_id) - center) < radius^2 + return nothing end - # Refine cells - refine!(t, cells) -end - -# Convenience function to allow passing center as a tuple -function refine_sphere!(t::AbstractTree{NDIMS}, center::NTuple{NDIMS}, - radius) where {NDIMS} - refine_sphere!(t, SVector(center), radius) -end - -# For the given cell ids, check if neighbors need to be refined to restore a rebalanced tree. -# -# Note 1: Rebalancing currently only considers *Cartesian* neighbors, not diagonal neighbors! -# Note 2: The current algorithm assumes that a previous refinement step has -# created level differences of at most 2. That is, before the previous -# refinement step, the tree was balanced. -function rebalance!(t::AbstractTree, refined_cell_ids) - # Create buffer for newly refined cells - to_refine = zeros(Int, n_directions(t) * length(refined_cell_ids)) - count = 0 - - # Iterate over cell ids that have previously been refined - for cell_id in refined_cell_ids - # Go over all potential neighbors of child cell + # Initialize the neighbors of child cell `child_id` based on parent cell `cell_id` + function init_child_neighbors!(t::AbstractTree, cell_id, child, child_id) + t.neighbor_ids[:, child_id] .= zero(eltype(t.neighbor_ids)) for direction in eachdirection(t) - # Continue if refined cell has a neighbor in that direction - if has_neighbor(t, cell_id, direction) + # If neighbor is a sibling, establish one-sided connectivity + # Note: two-sided is not necessary, as each sibling will do this + if has_sibling(child, direction) + adjacent = adjacent_child(child, direction) + neighbor_id = t.child_ids[adjacent, cell_id] + + t.neighbor_ids[direction, child_id] = neighbor_id continue end - # Continue if refined cell has no coarse neighbor, since that would - # mean it there is no neighbor in that direction at all (domain - # boundary) - if !has_coarse_neighbor(t, cell_id, direction) + # Skip if original cell does have no neighbor in direction + if !has_neighbor(t, cell_id, direction) continue end - # Otherwise, the coarse neighbor exists and is not refined, thus it must - # be marked for refinement - coarse_neighbor_id = t.neighbor_ids[direction, t.parent_ids[cell_id]] - count += 1 - to_refine[count] = coarse_neighbor_id + # Otherwise, check if neighbor has children - if not, skip again + neighbor_id = t.neighbor_ids[direction, cell_id] + if !has_children(t, neighbor_id) + continue + end + + # Check if neighbor has corresponding child and if yes, establish connectivity + adjacent = adjacent_child(child, direction) + if has_child(t, neighbor_id, adjacent) + neighbor_child_id = t.child_ids[adjacent, neighbor_id] + opposite = opposite_direction(direction) + + t.neighbor_ids[direction, child_id] = neighbor_child_id + t.neighbor_ids[opposite, neighbor_child_id] = child_id + end end + + return nothing end - # Finally, refine all marked cells... - refined = refine_unbalanced!(t, unique(to_refine[1:count])) + # Refine given cells without rebalancing tree. + # + # Note: After a call to this method the tree may be unbalanced! + function refine_unbalanced!( + t::AbstractTree, cell_ids, + sorted_unique_cell_ids = sort(unique(cell_ids)) + ) + # Store actual ids refined cells (shifted due to previous insertions) + refined = zeros(Int, length(cell_ids)) + + # Loop over all cells that are to be refined + for (count, original_cell_id) in enumerate(sorted_unique_cell_ids) + # Determine actual cell id, taking into account previously inserted cells + n_children = n_children_per_cell(t) + cell_id = original_cell_id + (count - 1) * n_children + refined[count] = cell_id + + @assert !has_children(t, cell_id) "Non-leaf cell $cell_id cannot be refined" + + # Insert new cells directly behind parent (depth-first) + insert!(t, cell_id + 1, n_children) + + # Flip sign of refined cell such that we can easily find it later + t.original_cell_ids[cell_id] = -t.original_cell_ids[cell_id] + + # Initialize child cells (except neighbors) + for child in 1:n_children + child_id = cell_id + child + init_child!(t, cell_id, child, child_id) + end - # ...and return list of refined cells - return refined -end + # Initialize child cells (only neighbors) + # This separate loop is required since init_child_neighbors requires initialized parent-child + # relationships + for child in 1:n_children + child_id = cell_id + child + init_child_neighbors!(t, cell_id, child, child_id) + end + end -# Refine given cells without rebalancing tree. -# -# Note: After a call to this method the tree may be unbalanced! -# function refine_unbalanced!(t::AbstractTree, cell_ids) end + return refined + end -# Wrap single-cell refinements such that `sort(...)` does not complain -refine_unbalanced!(t::AbstractTree, cell_id::Int) = refine_unbalanced!(t, [cell_id]) + # Refine entire tree by one level + function refine!(t::AbstractTree) + cells = @trixi_timeit timer() "collect all leaf cells" leaf_cells(t) + @trixi_timeit timer() "refine!" refine!(t, cells, cells) + end -# Coarsen entire tree by one level -function coarsen!(t::AbstractTree) - # Special case: if there is only one cell (root), there is nothing to do - if length(t) == 1 - return Int[] + # Refine given cells and rebalance tree. + # + # Note 1: Rebalancing is iterative, i.e., neighboring cells are refined if + # otherwise the 2:1 rule would be violated, which can cause more + # refinements. + # Note 2: Rebalancing currently only considers *Cartesian* neighbors, not diagonal neighbors! + function refine!( + t::AbstractTree, cell_ids, + sorted_unique_cell_ids = sort(unique(cell_ids)) + ) + # Reset original cell ids such that each cell knows its current id + reset_original_cell_ids!(t) + + # Refine all requested cells + refined = @trixi_timeit timer() "refine_unbalanced!" refine_unbalanced!( + t, cell_ids, + sorted_unique_cell_ids + ) + refinement_count = length(refined) + + # Iteratively rebalance the tree until it does not change anymore + while length(refined) > 0 + refined = @trixi_timeit timer() "rebalance!" rebalance!(t, refined) + refinement_count += length(refined) + end + + # Determine list of *original* cell ids that were refined + # Note: original_cell_ids contains the cell_id *before* refinement. At + # refinement, the refined cell's original_cell_ids value has its sign flipped + # to easily find it now. + refined_original_cells = @views(-t.original_cell_ids[1:length(t)][t.original_cell_ids[1:length(t)] .< 0]) + + # Check if count of refinement cells matches information in original_cell_ids + @assert refinement_count == length(refined_original_cells) ("Mismatch in number of refined cells") + + return refined_original_cells + end + + # Refine all leaf cells with coordinates in a given rectangular box + function refine_box!( + t::AbstractTree{NDIMS}, coordinates_min, + coordinates_max + ) where {NDIMS} + for dim in 1:NDIMS + @assert coordinates_min[dim] < coordinates_max[dim] "Minimum coordinates are not minimum." + end + + # Find all leaf cells within box + cells = filter_leaf_cells(t) do cell_id + return ( + all(coordinates_min .< cell_coordinates(t, cell_id)) && + all(coordinates_max .> cell_coordinates(t, cell_id)) + ) + end + + # Refine cells + refine!(t, cells) end - # Get list of unique parent ids for all leaf cells - parent_ids = unique(t.parent_ids[leaf_cells(t)]) - coarsen!(t, parent_ids) -end - -# Coarsen given *parent* cells (= these cells must have children who are all -# leaf cells) while retaining a balanced tree. -# -# A cell to be coarsened might cause an unbalanced tree if the neighboring cell -# was already refined. Since it is generally not desired that cells are -# coarsened without specifically asking for it, these cells will then *not* be -# coarsened. -function coarsen!(t::AbstractTree, cell_ids::AbstractArray{Int}) - # Return early if array is empty - if length(cell_ids) == 0 - return Int[] + # Convenience method for 1D + function refine_box!(t::AbstractTree{1}, coordinates_min::Real, coordinates_max::Real) + return refine_box!( + t, [convert(Float64, coordinates_min)], + [convert(Float64, coordinates_max)] + ) end - # Reset original cell ids such that each cell knows its current id - reset_original_cell_ids!(t) + # Refine all leaf cells with coordinates in a given sphere + function refine_sphere!( + t::AbstractTree{NDIMS}, center::SVector{NDIMS}, + radius + ) where {NDIMS} + @assert radius >= 0 "Radius must be positive." - # To maximize the number of cells that may be coarsened, start with the cells at the highest level - sorted_by_level = sort(cell_ids, by = i -> t.levels[i]) + # Find all leaf cells within sphere + cells = filter_leaf_cells(t) do cell_id + return sum(abs2, cell_coordinates(t, cell_id) - center) < radius^2 + end + + # Refine cells + refine!(t, cells) + end + + # Convenience function to allow passing center as a tuple + function refine_sphere!( + t::AbstractTree{NDIMS}, center::NTuple{NDIMS}, + radius + ) where {NDIMS} + refine_sphere!(t, SVector(center), radius) + end + + # For the given cell ids, check if neighbors need to be refined to restore a rebalanced tree. + # + # Note 1: Rebalancing currently only considers *Cartesian* neighbors, not diagonal neighbors! + # Note 2: The current algorithm assumes that a previous refinement step has + # created level differences of at most 2. That is, before the previous + # refinement step, the tree was balanced. + function rebalance!(t::AbstractTree, refined_cell_ids) + # Create buffer for newly refined cells + to_refine = zeros(Int, n_directions(t) * length(refined_cell_ids)) + count = 0 + + # Iterate over cell ids that have previously been refined + for cell_id in refined_cell_ids + # Go over all potential neighbors of child cell + for direction in eachdirection(t) + # Continue if refined cell has a neighbor in that direction + if has_neighbor(t, cell_id, direction) + continue + end - # Keep track of number of cells that were actually coarsened - n_coarsened = 0 + # Continue if refined cell has no coarse neighbor, since that would + # mean it there is no neighbor in that direction at all (domain + # boundary) + if !has_coarse_neighbor(t, cell_id, direction) + continue + end - # Local function to adjust cell ids after some cells have been removed - function adjust_cell_ids!(cell_ids, coarsened_cell_id, count) - for (id, cell_id) in enumerate(cell_ids) - if cell_id > coarsened_cell_id - cell_ids[id] = cell_id - count + # Otherwise, the coarse neighbor exists and is not refined, thus it must + # be marked for refinement + coarse_neighbor_id = t.neighbor_ids[direction, t.parent_ids[cell_id]] + count += 1 + to_refine[count] = coarse_neighbor_id end end + + # Finally, refine all marked cells... + refined = refine_unbalanced!(t, unique(to_refine[1:count])) + + # ...and return list of refined cells + return refined end - # Iterate backwards over cells to coarsen - while true - # Retrieve next cell or quit - if length(sorted_by_level) > 0 - coarse_cell_id = pop!(sorted_by_level) - else - break + # Refine given cells without rebalancing tree. + # + # Note: After a call to this method the tree may be unbalanced! + # function refine_unbalanced!(t::AbstractTree, cell_ids) end + + # Wrap single-cell refinements such that `sort(...)` does not complain + refine_unbalanced!(t::AbstractTree, cell_id::Int) = refine_unbalanced!(t, [cell_id]) + + # Coarsen entire tree by one level + function coarsen!(t::AbstractTree) + # Special case: if there is only one cell (root), there is nothing to do + if length(t) == 1 + return Int[] end - # Ensure that cell has children (violation is an error) - if !has_children(t, coarse_cell_id) - error("cell is leaf and cannot be coarsened to: $coarse_cell_id") + # Get list of unique parent ids for all leaf cells + parent_ids = unique(t.parent_ids[leaf_cells(t)]) + coarsen!(t, parent_ids) + end + + # Coarsen given *parent* cells (= these cells must have children who are all + # leaf cells) while retaining a balanced tree. + # + # A cell to be coarsened might cause an unbalanced tree if the neighboring cell + # was already refined. Since it is generally not desired that cells are + # coarsened without specifically asking for it, these cells will then *not* be + # coarsened. + function coarsen!(t::AbstractTree, cell_ids::AbstractArray{Int}) + # Return early if array is empty + if length(cell_ids) == 0 + return Int[] end - # Ensure that all child cells are leaf cells (violation is an error) - for child in 1:n_children_per_cell(t) - if has_child(t, coarse_cell_id, child) - if !is_leaf(t, t.child_ids[child, coarse_cell_id]) - error("cell $coarse_cell_id has child cell at position $child that is not a leaf cell") + # Reset original cell ids such that each cell knows its current id + reset_original_cell_ids!(t) + + # To maximize the number of cells that may be coarsened, start with the cells at the highest level + sorted_by_level = sort(cell_ids, by = i -> t.levels[i]) + + # Keep track of number of cells that were actually coarsened + n_coarsened = 0 + + # Local function to adjust cell ids after some cells have been removed + function adjust_cell_ids!(cell_ids, coarsened_cell_id, count) + for (id, cell_id) in enumerate(cell_ids) + if cell_id > coarsened_cell_id + cell_ids[id] = cell_id - count end end end - # Check if coarse cell has refined neighbors that would prevent coarsening - skip = false - # Iterate over all children (which are to be removed) - for child in 1:n_children_per_cell(t) - # Continue if child does not exist - if !has_child(t, coarse_cell_id, child) - continue + # Iterate backwards over cells to coarsen + while true + # Retrieve next cell or quit + if length(sorted_by_level) > 0 + coarse_cell_id = pop!(sorted_by_level) + else + break end - child_id = t.child_ids[child, coarse_cell_id] - # Go over all neighbors of child cell. If it has a neighbor that is *not* - # a sibling and that is not a leaf cell, we cannot coarsen its parent - # without creating an unbalanced tree. - for direction in eachdirection(t) - # Continue if neighbor would be a sibling - if has_sibling(child, direction) - continue - end + # Ensure that cell has children (violation is an error) + if !has_children(t, coarse_cell_id) + error("cell is leaf and cannot be coarsened to: $coarse_cell_id") + end - # Continue if child cell has no neighbor in that direction - if !has_neighbor(t, child_id, direction) - continue + # Ensure that all child cells are leaf cells (violation is an error) + for child in 1:n_children_per_cell(t) + if has_child(t, coarse_cell_id, child) + if !is_leaf(t, t.child_ids[child, coarse_cell_id]) + error("cell $coarse_cell_id has child cell at position $child that is not a leaf cell") + end end - neighbor_id = t.neighbor_ids[direction, child_id] + end - if !has_children(t, neighbor_id) + # Check if coarse cell has refined neighbors that would prevent coarsening + skip = false + # Iterate over all children (which are to be removed) + for child in 1:n_children_per_cell(t) + # Continue if child does not exist + if !has_child(t, coarse_cell_id, child) continue end + child_id = t.child_ids[child, coarse_cell_id] + + # Go over all neighbors of child cell. If it has a neighbor that is *not* + # a sibling and that is not a leaf cell, we cannot coarsen its parent + # without creating an unbalanced tree. + for direction in eachdirection(t) + # Continue if neighbor would be a sibling + if has_sibling(child, direction) + continue + end - # If neighbor is not a sibling, is existing, and has children, do not coarsen - skip = true - break + # Continue if child cell has no neighbor in that direction + if !has_neighbor(t, child_id, direction) + continue + end + neighbor_id = t.neighbor_ids[direction, child_id] + + if !has_children(t, neighbor_id) + continue + end + + # If neighbor is not a sibling, is existing, and has children, do not coarsen + skip = true + break + end end - end - # Skip if a neighboring cell prevents coarsening - if skip - continue - end + # Skip if a neighboring cell prevents coarsening + if skip + continue + end + + # Flip sign of cell to be coarsened to such that we can easily find it + t.original_cell_ids[coarse_cell_id] = -t.original_cell_ids[coarse_cell_id] - # Flip sign of cell to be coarsened to such that we can easily find it - t.original_cell_ids[coarse_cell_id] = -t.original_cell_ids[coarse_cell_id] + # If a coarse cell has children that are all leaf cells, they must follow + # immediately due to depth-first ordering of the tree + count = n_children(t, coarse_cell_id) + @assert count == n_children_per_cell(t) "cell $coarse_cell_id does not have all child cells" + remove_shift!(t, coarse_cell_id + 1, coarse_cell_id + count) - # If a coarse cell has children that are all leaf cells, they must follow - # immediately due to depth-first ordering of the tree - count = n_children(t, coarse_cell_id) - @assert count==n_children_per_cell(t) "cell $coarse_cell_id does not have all child cells" - remove_shift!(t, coarse_cell_id + 1, coarse_cell_id + count) + # Take into account shifts in tree that alters cell ids + adjust_cell_ids!(sorted_by_level, coarse_cell_id, count) + + # Keep track of number of coarsened cells + n_coarsened += 1 + end - # Take into account shifts in tree that alters cell ids - adjust_cell_ids!(sorted_by_level, coarse_cell_id, count) + # Determine list of *original* cell ids that were coarsened to + # Note: original_cell_ids contains the cell_id *before* coarsening. At + # coarsening, the coarsened parent cell's original_cell_ids value has its sign flipped + # to easily find it now. + @views coarsened_original_cells = (-t.original_cell_ids[1:length(t)][t.original_cell_ids[1:length(t)] .< 0]) - # Keep track of number of coarsened cells - n_coarsened += 1 + # Check if count of coarsened cells matches information in original_cell_ids + @assert n_coarsened == length(coarsened_original_cells) ("Mismatch in number of coarsened cells") + + return coarsened_original_cells end - # Determine list of *original* cell ids that were coarsened to - # Note: original_cell_ids contains the cell_id *before* coarsening. At - # coarsening, the coarsened parent cell's original_cell_ids value has its sign flipped - # to easily find it now. - @views coarsened_original_cells = (-t.original_cell_ids[1:length(t)][t.original_cell_ids[1:length(t)] .< 0]) + # Wrap single-cell coarsening such that `sort(...)` does not complain + coarsen!(t::AbstractTree, cell_id::Int) = coarsen!(t::AbstractTree, [cell_id]) - # Check if count of coarsened cells matches information in original_cell_ids - @assert n_coarsened==length(coarsened_original_cells) ("Mismatch in number of coarsened cells") + # Coarsen all viable parent cells with coordinates in a given rectangular box + function coarsen_box!( + t::AbstractTree{NDIMS}, coordinates_min::AbstractArray{Float64}, + coordinates_max::AbstractArray{Float64} + ) where {NDIMS} + for dim in 1:NDIMS + @assert coordinates_min[dim] < coordinates_max[dim] "Minimum coordinates are not minimum." + end - return coarsened_original_cells -end + # Find all leaf cells within box + leaves = filter_leaf_cells(t) do cell_id + return ( + all(coordinates_min .< cell_coordinates(t, cell_id)) && + all(coordinates_max .> cell_coordinates(t, cell_id)) + ) + end -# Wrap single-cell coarsening such that `sort(...)` does not complain -coarsen!(t::AbstractTree, cell_id::Int) = coarsen!(t::AbstractTree, [cell_id]) + # Get list of unique parent ids for all leaf cells + parent_ids = unique(t.parent_ids[leaves]) -# Coarsen all viable parent cells with coordinates in a given rectangular box -function coarsen_box!(t::AbstractTree{NDIMS}, coordinates_min::AbstractArray{Float64}, - coordinates_max::AbstractArray{Float64}) where {NDIMS} - for dim in 1:NDIMS - @assert coordinates_min[dim] cell_coordinates(t, cell_id)) + ) + end - # Find all leaf cells within box - leaves = filter_leaf_cells(t) do cell_id - return (all(coordinates_min .< cell_coordinates(t, cell_id)) && - all(coordinates_max .> cell_coordinates(t, cell_id))) + # Coarsen cells + coarsen!(t, parents) end - # Get list of unique parent ids for all leaf cells - parent_ids = unique(t.parent_ids[leaves]) + # Convenience method for 1D + function coarsen_box!(t::AbstractTree{1}, coordinates_min::Real, coordinates_max::Real) + return coarsen_box!( + t, [convert(Float64, coordinates_min)], + [convert(Float64, coordinates_max)] + ) + end - # Filter parent ids to be within box - parents = filter(parent_ids) do cell_id - return (all(coordinates_min .< cell_coordinates(t, cell_id)) && - all(coordinates_max .> cell_coordinates(t, cell_id))) + # Return coordinates of a child cell based on its relative position to the parent. + function child_coordinates( + ::AbstractTree{NDIMS}, parent_coordinates, + parent_length::Number, child::Int + ) where {NDIMS} + # Calculate length of child cells + child_length = parent_length / 2 + return SVector( + ntuple( + d -> parent_coordinates[d] + + child_sign(child, d) * child_length / 2, Val(NDIMS) + ) + ) end - # Coarsen cells - coarsen!(t, parents) -end - -# Convenience method for 1D -function coarsen_box!(t::AbstractTree{1}, coordinates_min::Real, coordinates_max::Real) - return coarsen_box!(t, [convert(Float64, coordinates_min)], - [convert(Float64, coordinates_max)]) -end - -# Return coordinates of a child cell based on its relative position to the parent. -function child_coordinates(::AbstractTree{NDIMS}, parent_coordinates, - parent_length::Number, child::Int) where {NDIMS} - # Calculate length of child cells - child_length = parent_length / 2 - return SVector(ntuple(d -> parent_coordinates[d] + - child_sign(child, d) * child_length / 2, Val(NDIMS))) -end - -# Reset range of cells to values that are prone to cause errors as soon as they are used. -# -# Rationale: If an invalid cell is accidentally used, we want to know it as soon as possible. -# function invalidate!(t::AbstractTree, first::Int, last::Int) end -invalidate!(t::AbstractTree, id::Int) = invalidate!(t, id, id) -invalidate!(t::AbstractTree) = invalidate!(t, 1, length(t)) - -# Delete connectivity with parents/children/neighbors before cells are erased -function delete_connectivity!(t::AbstractTree, first::Int, last::Int) - @assert first > 0 - @assert first <= last - @assert last <= t.capacity + 1 - - # Iterate over all cells - for cell_id in first:last - # Delete connectivity from parent cell - if has_parent(t, cell_id) - parent_id = t.parent_ids[cell_id] - for child in 1:n_children_per_cell(t) - if t.child_ids[child, parent_id] == cell_id - t.child_ids[child, parent_id] = 0 - break + # Reset range of cells to values that are prone to cause errors as soon as they are used. + # + # Rationale: If an invalid cell is accidentally used, we want to know it as soon as possible. + # function invalidate!(t::AbstractTree, first::Int, last::Int) end + invalidate!(t::AbstractTree, id::Int) = invalidate!(t, id, id) + invalidate!(t::AbstractTree) = invalidate!(t, 1, length(t)) + + # Delete connectivity with parents/children/neighbors before cells are erased + function delete_connectivity!(t::AbstractTree, first::Int, last::Int) + @assert first > 0 + @assert first <= last + @assert last <= t.capacity + 1 + + # Iterate over all cells + for cell_id in first:last + # Delete connectivity from parent cell + if has_parent(t, cell_id) + parent_id = t.parent_ids[cell_id] + for child in 1:n_children_per_cell(t) + if t.child_ids[child, parent_id] == cell_id + t.child_ids[child, parent_id] = 0 + break + end end end - end - # Delete connectivity from child cells - for child in 1:n_children_per_cell(t) - if has_child(t, cell_id, child) - t.parent_ids[t._child_ids[child, cell_id]] = 0 + # Delete connectivity from child cells + for child in 1:n_children_per_cell(t) + if has_child(t, cell_id, child) + t.parent_ids[t._child_ids[child, cell_id]] = 0 + end end - end - # Delete connectivity from neighboring cells - for direction in eachdirection(t) - if has_neighbor(t, cell_id, direction) - t.neighbor_ids[opposite_direction(direction), t.neighbor_ids[direction, cell_id]] = 0 + # Delete connectivity from neighboring cells + for direction in eachdirection(t) + if has_neighbor(t, cell_id, direction) + t.neighbor_ids[opposite_direction(direction), t.neighbor_ids[direction, cell_id]] = 0 + end end end end -end - -# Move connectivity with parents/children/neighbors after cells have been moved -function move_connectivity!(t::AbstractTree, first::Int, last::Int, destination::Int) - @assert first > 0 - @assert first <= last - @assert last <= t.capacity + 1 - @assert destination > 0 - @assert destination <= t.capacity + 1 - - # Strategy - # 1) Loop over moved cells (at target location) - # 2) Check if parent/children/neighbors connections are to a cell that was moved - # a) if cell was moved: apply offset to current cell - # b) if cell was not moved: go to connected cell and update connectivity there - - offset = destination - first - has_moved(n) = (first <= n <= last) - - for source in first:last - target = source + offset - - # Update parent - if has_parent(t, target) - # Get parent cell - parent_id = t.parent_ids[target] - if has_moved(parent_id) - # If parent itself was moved, just update parent id accordingly - t.parent_ids[target] += offset - else - # If parent was not moved, update its corresponding child id - for child in 1:n_children_per_cell(t) - if t.child_ids[child, parent_id] == source - t.child_ids[child, parent_id] = target + + # Move connectivity with parents/children/neighbors after cells have been moved + function move_connectivity!(t::AbstractTree, first::Int, last::Int, destination::Int) + @assert first > 0 + @assert first <= last + @assert last <= t.capacity + 1 + @assert destination > 0 + @assert destination <= t.capacity + 1 + + # Strategy + # 1) Loop over moved cells (at target location) + # 2) Check if parent/children/neighbors connections are to a cell that was moved + # a) if cell was moved: apply offset to current cell + # b) if cell was not moved: go to connected cell and update connectivity there + + offset = destination - first + has_moved(n) = (first <= n <= last) + + for source in first:last + target = source + offset + + # Update parent + if has_parent(t, target) + # Get parent cell + parent_id = t.parent_ids[target] + if has_moved(parent_id) + # If parent itself was moved, just update parent id accordingly + t.parent_ids[target] += offset + else + # If parent was not moved, update its corresponding child id + for child in 1:n_children_per_cell(t) + if t.child_ids[child, parent_id] == source + t.child_ids[child, parent_id] = target + end end end end - end - # Update children - for child in 1:n_children_per_cell(t) - if has_child(t, target, child) - # Get child cell - child_id = t.child_ids[child, target] - if has_moved(child_id) - # If child itself was moved, just update child id accordingly - t.child_ids[child, target] += offset - else - # If child was not moved, update its parent id - t.parent_ids[child_id] = target + # Update children + for child in 1:n_children_per_cell(t) + if has_child(t, target, child) + # Get child cell + child_id = t.child_ids[child, target] + if has_moved(child_id) + # If child itself was moved, just update child id accordingly + t.child_ids[child, target] += offset + else + # If child was not moved, update its parent id + t.parent_ids[child_id] = target + end end end - end - # Update neighbors - for direction in eachdirection(t) - if has_neighbor(t, target, direction) - # Get neighbor cell - neighbor_id = t.neighbor_ids[direction, target] - if has_moved(neighbor_id) - # If neighbor itself was moved, just update neighbor id accordingly - t.neighbor_ids[direction, target] += offset - else - # If neighbor was not moved, update its opposing neighbor id - t.neighbor_ids[opposite_direction(direction), neighbor_id] = target + # Update neighbors + for direction in eachdirection(t) + if has_neighbor(t, target, direction) + # Get neighbor cell + neighbor_id = t.neighbor_ids[direction, target] + if has_moved(neighbor_id) + # If neighbor itself was moved, just update neighbor id accordingly + t.neighbor_ids[direction, target] += offset + else + # If neighbor was not moved, update its opposing neighbor id + t.neighbor_ids[opposite_direction(direction), neighbor_id] = target + end end end end end -end -# Raw copy operation for ranges of cells. -# -# This method is used by the higher-level copy operations for AbstractContainer -# function raw_copy!(target::AbstractTree, source::AbstractTree, first::Int, last::Int, destination::Int) end + # Raw copy operation for ranges of cells. + # + # This method is used by the higher-level copy operations for AbstractContainer + # function raw_copy!(target::AbstractTree, source::AbstractTree, first::Int, last::Int, destination::Int) end -# Reset data structures by recreating all internal storage containers and invalidating all elements -# function reset_data_structures!(t::AbstractTree{NDIMS}) where NDIMS end + # Reset data structures by recreating all internal storage containers and invalidating all elements + # function reset_data_structures!(t::AbstractTree{NDIMS}) where NDIMS end end # @muladd diff --git a/src/meshes/dgmulti_meshes.jl b/src/meshes/dgmulti_meshes.jl index 7ae7c0f904..023010f145 100644 --- a/src/meshes/dgmulti_meshes.jl +++ b/src/meshes/dgmulti_meshes.jl @@ -3,49 +3,55 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - DGMultiMesh{NDIMS, ...} + """ + DGMultiMesh{NDIMS, ...} -`DGMultiMesh` describes a mesh type which wraps `StartUpDG.MeshData` and `boundary_faces` in a -dispatchable type. This is intended to store geometric data and connectivities for any type of -mesh (Cartesian, affine, curved, structured/unstructured). -""" -struct DGMultiMesh{NDIMS, MeshType, MeshDataT <: MeshData{NDIMS}, BoundaryFaceT} - md::MeshDataT - boundary_faces::BoundaryFaceT -end + `DGMultiMesh` describes a mesh type which wraps `StartUpDG.MeshData` and `boundary_faces` in a + dispatchable type. This is intended to store geometric data and connectivities for any type of + mesh (Cartesian, affine, curved, structured/unstructured). + """ + struct DGMultiMesh{NDIMS, MeshType, MeshDataT <: MeshData{NDIMS}, BoundaryFaceT} + md::MeshDataT + boundary_faces::BoundaryFaceT + end -# enable use of @set and setproperties(...) for DGMultiMesh -function ConstructionBase.constructorof(::Type{DGMultiMesh{T1, T2, T3, T4}}) where {T1, - T2, - T3, - T4} - DGMultiMesh{T1, T2, T3, T4} -end + # enable use of @set and setproperties(...) for DGMultiMesh + function ConstructionBase.constructorof(::Type{DGMultiMesh{T1, T2, T3, T4}}) where { + T1, + T2, + T3, + T4, + } + DGMultiMesh{T1, T2, T3, T4} + end -Base.ndims(::DGMultiMesh{NDIMS}) where {NDIMS} = NDIMS + Base.ndims(::DGMultiMesh{NDIMS}) where {NDIMS} = NDIMS -function Base.show(io::IO, mesh::DGMultiMesh{NDIMS, MeshType}) where {NDIMS, MeshType} - @nospecialize mesh # reduce precompilation time - print(io, "$MeshType DGMultiMesh with NDIMS = $NDIMS.") -end + function Base.show(io::IO, mesh::DGMultiMesh{NDIMS, MeshType}) where {NDIMS, MeshType} + @nospecialize mesh # reduce precompilation time + print(io, "$MeshType DGMultiMesh with NDIMS = $NDIMS.") + end -function Base.show(io::IO, ::MIME"text/plain", - mesh::DGMultiMesh{NDIMS, MeshType}) where {NDIMS, MeshType} - @nospecialize mesh # reduce precompilation time - if get(io, :compact, false) - show(io, mesh) - else - summary_header(io, "DGMultiMesh{$NDIMS, $MeshType}, ") - summary_line(io, "number of elements", mesh.md.num_elements) - summary_line(io, "number of boundaries", length(mesh.boundary_faces)) - for (boundary_name, faces) in mesh.boundary_faces - summary_line(increment_indent(io), "nfaces on $boundary_name", - length(faces)) + function Base.show( + io::IO, ::MIME"text/plain", + mesh::DGMultiMesh{NDIMS, MeshType} + ) where {NDIMS, MeshType} + @nospecialize mesh # reduce precompilation time + if get(io, :compact, false) + show(io, mesh) + else + summary_header(io, "DGMultiMesh{$NDIMS, $MeshType}, ") + summary_line(io, "number of elements", mesh.md.num_elements) + summary_line(io, "number of boundaries", length(mesh.boundary_faces)) + for (boundary_name, faces) in mesh.boundary_faces + summary_line( + increment_indent(io), "nfaces on $boundary_name", + length(faces) + ) + end + summary_footer(io) end - summary_footer(io) end -end end # @muladd diff --git a/src/meshes/face_interpolant.jl b/src/meshes/face_interpolant.jl index 201cef9a06..df8e18a9cf 100644 --- a/src/meshes/face_interpolant.jl +++ b/src/meshes/face_interpolant.jl @@ -3,52 +3,68 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# CurvedFace{RealT<:Real} -# -# Contains the data needed to represent a curved face with data points (x,y,z) as a Lagrange polynomial -# interpolant written in barycentric form at a given set of nodes. -struct CurvedFace{RealT <: Real} - nodes::Vector{RealT} - barycentric_weights::Vector{RealT} - coordinates::Array{RealT, 3} #[ndims, nnodes, nnodes] -end - -# evaluate the Gamma face interpolant at a particular point s = (s_1, s_2) and return the (x,y,z) coordinate -function evaluate_at(s, boundary_face::CurvedFace) - @unpack nodes, barycentric_weights, coordinates = boundary_face + # CurvedFace{RealT<:Real} + # + # Contains the data needed to represent a curved face with data points (x,y,z) as a Lagrange polynomial + # interpolant written in barycentric form at a given set of nodes. + struct CurvedFace{RealT <: Real} + nodes::Vector{RealT} + barycentric_weights::Vector{RealT} + coordinates::Array{RealT, 3} #[ndims, nnodes, nnodes] + end - x_coordinate_at_s_on_boundary_face = lagrange_interpolation_2d(s, nodes, - view(coordinates, 1, - :, :), - barycentric_weights) - y_coordinate_at_s_on_boundary_face = lagrange_interpolation_2d(s, nodes, - view(coordinates, 2, - :, :), - barycentric_weights) - z_coordinate_at_s_on_boundary_face = lagrange_interpolation_2d(s, nodes, - view(coordinates, 3, - :, :), - barycentric_weights) + # evaluate the Gamma face interpolant at a particular point s = (s_1, s_2) and return the (x,y,z) coordinate + function evaluate_at(s, boundary_face::CurvedFace) + @unpack nodes, barycentric_weights, coordinates = boundary_face - return x_coordinate_at_s_on_boundary_face, - y_coordinate_at_s_on_boundary_face, - z_coordinate_at_s_on_boundary_face -end + x_coordinate_at_s_on_boundary_face = lagrange_interpolation_2d( + s, nodes, + view( + coordinates, 1, + :, : + ), + barycentric_weights + ) + y_coordinate_at_s_on_boundary_face = lagrange_interpolation_2d( + s, nodes, + view( + coordinates, 2, + :, : + ), + barycentric_weights + ) + z_coordinate_at_s_on_boundary_face = lagrange_interpolation_2d( + s, nodes, + view( + coordinates, 3, + :, : + ), + barycentric_weights + ) -# Calculate a 2D Lagrange interpolating polynomial in barycentric 2 form -# of a function f(x,y) at a given coordinate (x,y) for a given node distribution. -function lagrange_interpolation_2d(x, nodes, function_values, barycentric_weights) - f_intermediate = zeros(eltype(function_values), length(nodes)) - for j in eachindex(nodes) - f_intermediate[j] = lagrange_interpolation(x[2], nodes, - view(function_values, j, :), - barycentric_weights) + return x_coordinate_at_s_on_boundary_face, + y_coordinate_at_s_on_boundary_face, + z_coordinate_at_s_on_boundary_face end - point_value = lagrange_interpolation(x[1], nodes, f_intermediate, - barycentric_weights) - return point_value -end + # Calculate a 2D Lagrange interpolating polynomial in barycentric 2 form + # of a function f(x,y) at a given coordinate (x,y) for a given node distribution. + function lagrange_interpolation_2d(x, nodes, function_values, barycentric_weights) + f_intermediate = zeros(eltype(function_values), length(nodes)) + for j in eachindex(nodes) + f_intermediate[j] = lagrange_interpolation( + x[2], nodes, + view(function_values, j, :), + barycentric_weights + ) + end + point_value = lagrange_interpolation( + x[1], nodes, f_intermediate, + barycentric_weights + ) + + return point_value + end end # @muladd diff --git a/src/meshes/mesh_io.jl b/src/meshes/mesh_io.jl index 127bc420f6..df43f949bf 100644 --- a/src/meshes/mesh_io.jl +++ b/src/meshes/mesh_io.jl @@ -3,438 +3,373 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Save current mesh with some context information as an HDF5 file. -function save_mesh_file(mesh::Union{TreeMesh, P4estMesh, T8codeMesh}, output_directory, - timestep = 0) - save_mesh_file(mesh, output_directory, timestep, mpi_parallel(mesh)) -end - -function save_mesh_file(mesh::TreeMesh, output_directory, timestep, - mpi_parallel::False) - # Create output directory (if it does not exist) - mkpath(output_directory) - - # Determine file name based on existence of meaningful time step - if timestep > 0 - filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep)) - else - filename = joinpath(output_directory, "mesh.h5") - end - - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - n_cells = length(mesh.tree) - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["n_cells"] = n_cells - attributes(file)["capacity"] = mesh.tree.capacity - attributes(file)["n_leaf_cells"] = count_leaf_cells(mesh.tree) - attributes(file)["minimum_level"] = minimum_level(mesh.tree) - attributes(file)["maximum_level"] = maximum_level(mesh.tree) - attributes(file)["center_level_0"] = mesh.tree.center_level_0 - attributes(file)["length_level_0"] = mesh.tree.length_level_0 - attributes(file)["periodicity"] = collect(mesh.tree.periodicity) - - # Add tree data - file["parent_ids"] = @view mesh.tree.parent_ids[1:n_cells] - file["child_ids"] = @view mesh.tree.child_ids[:, 1:n_cells] - file["neighbor_ids"] = @view mesh.tree.neighbor_ids[:, 1:n_cells] - file["levels"] = @view mesh.tree.levels[1:n_cells] - file["coordinates"] = @view mesh.tree.coordinates[:, 1:n_cells] + #! format: noindent + + # Save current mesh with some context information as an HDF5 file. + function save_mesh_file( + mesh::Union{TreeMesh, P4estMesh, T8codeMesh}, output_directory, + timestep = 0 + ) + save_mesh_file(mesh, output_directory, timestep, mpi_parallel(mesh)) end - return filename -end + function save_mesh_file( + mesh::TreeMesh, output_directory, timestep, + mpi_parallel::False + ) + # Create output directory (if it does not exist) + mkpath(output_directory) -# Save current mesh with some context information as an HDF5 file. -function save_mesh_file(mesh::TreeMesh, output_directory, timestep, - mpi_parallel::True) - # Create output directory (if it does not exist) - mpi_isroot() && mkpath(output_directory) + # Determine file name based on existence of meaningful time step + if timestep > 0 + filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep)) + else + filename = joinpath(output_directory, "mesh.h5") + end - # Determine file name based on existence of meaningful time step - if timestep >= 0 - filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep)) - else - filename = joinpath(output_directory, "mesh.h5") - end + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + n_cells = length(mesh.tree) + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["n_cells"] = n_cells + attributes(file)["capacity"] = mesh.tree.capacity + attributes(file)["n_leaf_cells"] = count_leaf_cells(mesh.tree) + attributes(file)["minimum_level"] = minimum_level(mesh.tree) + attributes(file)["maximum_level"] = maximum_level(mesh.tree) + attributes(file)["center_level_0"] = mesh.tree.center_level_0 + attributes(file)["length_level_0"] = mesh.tree.length_level_0 + attributes(file)["periodicity"] = collect(mesh.tree.periodicity) + + # Add tree data + file["parent_ids"] = @view mesh.tree.parent_ids[1:n_cells] + file["child_ids"] = @view mesh.tree.child_ids[:, 1:n_cells] + file["neighbor_ids"] = @view mesh.tree.neighbor_ids[:, 1:n_cells] + file["levels"] = @view mesh.tree.levels[1:n_cells] + file["coordinates"] = @view mesh.tree.coordinates[:, 1:n_cells] + end - # Since the mesh is replicated on all ranks, only save from MPI root - if !mpi_isroot() return filename end - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - n_cells = length(mesh.tree) - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["n_cells"] = n_cells - attributes(file)["capacity"] = mesh.tree.capacity - attributes(file)["n_leaf_cells"] = count_leaf_cells(mesh.tree) - attributes(file)["minimum_level"] = minimum_level(mesh.tree) - attributes(file)["maximum_level"] = maximum_level(mesh.tree) - attributes(file)["center_level_0"] = mesh.tree.center_level_0 - attributes(file)["length_level_0"] = mesh.tree.length_level_0 - attributes(file)["periodicity"] = collect(mesh.tree.periodicity) - - # Add tree data - file["parent_ids"] = @view mesh.tree.parent_ids[1:n_cells] - file["child_ids"] = @view mesh.tree.child_ids[:, 1:n_cells] - file["neighbor_ids"] = @view mesh.tree.neighbor_ids[:, 1:n_cells] - file["levels"] = @view mesh.tree.levels[1:n_cells] - file["coordinates"] = @view mesh.tree.coordinates[:, 1:n_cells] - end - - return filename -end - -# Does not save the mesh itself to an HDF5 file. Instead saves important attributes -# of the mesh, like its size and the type of boundary mapping function. -# Then, within Trixi2Vtk, the StructuredMesh and its node coordinates are reconstructured from -# these attributes for plotting purposes -# Note: the `timestep` argument is needed for compatibility with the method for -# `StructuredMeshView` -function save_mesh_file(mesh::StructuredMesh, output_directory; system = "", - timestep = 0) - # Create output directory (if it does not exist) - mkpath(output_directory) - - if isempty(system) - filename = joinpath(output_directory, "mesh.h5") - else - filename = joinpath(output_directory, @sprintf("mesh_%s.h5", system)) - end + # Save current mesh with some context information as an HDF5 file. + function save_mesh_file( + mesh::TreeMesh, output_directory, timestep, + mpi_parallel::True + ) + # Create output directory (if it does not exist) + mpi_isroot() && mkpath(output_directory) + + # Determine file name based on existence of meaningful time step + if timestep >= 0 + filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep)) + else + filename = joinpath(output_directory, "mesh.h5") + end - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["size"] = collect(size(mesh)) - attributes(file)["mapping"] = mesh.mapping_as_string - end + # Since the mesh is replicated on all ranks, only save from MPI root + if !mpi_isroot() + return filename + end - return filename -end - -# Does not save the mesh itself to an HDF5 file. Instead saves important attributes -# of the mesh, like its size and the corresponding `.mesh` file used to construct the mesh. -# Then, within Trixi2Vtk, the UnstructuredMesh2D and its node coordinates are reconstructured -# from these attributes for plotting purposes -function save_mesh_file(mesh::UnstructuredMesh2D, output_directory) - # Create output directory (if it does not exist) - mkpath(output_directory) - - filename = joinpath(output_directory, "mesh.h5") - - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["size"] = length(mesh) - attributes(file)["mesh_filename"] = mesh.filename - attributes(file)["periodicity"] = collect(mesh.periodicity) - end + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + n_cells = length(mesh.tree) + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["n_cells"] = n_cells + attributes(file)["capacity"] = mesh.tree.capacity + attributes(file)["n_leaf_cells"] = count_leaf_cells(mesh.tree) + attributes(file)["minimum_level"] = minimum_level(mesh.tree) + attributes(file)["maximum_level"] = maximum_level(mesh.tree) + attributes(file)["center_level_0"] = mesh.tree.center_level_0 + attributes(file)["length_level_0"] = mesh.tree.length_level_0 + attributes(file)["periodicity"] = collect(mesh.tree.periodicity) + + # Add tree data + file["parent_ids"] = @view mesh.tree.parent_ids[1:n_cells] + file["child_ids"] = @view mesh.tree.child_ids[:, 1:n_cells] + file["neighbor_ids"] = @view mesh.tree.neighbor_ids[:, 1:n_cells] + file["levels"] = @view mesh.tree.levels[1:n_cells] + file["coordinates"] = @view mesh.tree.coordinates[:, 1:n_cells] + end - return filename -end - -# Does not save the mesh itself to an HDF5 file. Instead saves important attributes -# of the mesh, like its size and the type of boundary mapping function. -# Then, within Trixi2Vtk, the P4estMesh and its node coordinates are reconstructured from -# these attributes for plotting purposes -function save_mesh_file(mesh::P4estMesh, output_directory, timestep, - mpi_parallel::False) - # Create output directory (if it does not exist) - mkpath(output_directory) - - # Determine file name based on existence of meaningful time step - if timestep > 0 - filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep)) - p4est_filename = @sprintf("p4est_data_%09d", timestep) - else - filename = joinpath(output_directory, "mesh.h5") - p4est_filename = "p4est_data" + return filename end - p4est_file = joinpath(output_directory, p4est_filename) - - # Save the complete connectivity and `p4est` data to disk. - save_p4est!(p4est_file, mesh.p4est) + # Does not save the mesh itself to an HDF5 file. Instead saves important attributes + # of the mesh, like its size and the type of boundary mapping function. + # Then, within Trixi2Vtk, the StructuredMesh and its node coordinates are reconstructured from + # these attributes for plotting purposes + # Note: the `timestep` argument is needed for compatibility with the method for + # `StructuredMeshView` + function save_mesh_file( + mesh::StructuredMesh, output_directory; system = "", + timestep = 0 + ) + # Create output directory (if it does not exist) + mkpath(output_directory) + + if isempty(system) + filename = joinpath(output_directory, "mesh.h5") + else + filename = joinpath(output_directory, @sprintf("mesh_%s.h5", system)) + end - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["p4est_file"] = p4est_filename + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["size"] = collect(size(mesh)) + attributes(file)["mapping"] = mesh.mapping_as_string + end - file["tree_node_coordinates"] = mesh.tree_node_coordinates - file["nodes"] = Vector(mesh.nodes) # the mesh uses `SVector`s for the nodes - # to increase the runtime performance - # but HDF5 can only handle plain arrays - file["boundary_names"] = mesh.boundary_names .|> String + return filename end - return filename -end + # Does not save the mesh itself to an HDF5 file. Instead saves important attributes + # of the mesh, like its size and the corresponding `.mesh` file used to construct the mesh. + # Then, within Trixi2Vtk, the UnstructuredMesh2D and its node coordinates are reconstructured + # from these attributes for plotting purposes + function save_mesh_file(mesh::UnstructuredMesh2D, output_directory) + # Create output directory (if it does not exist) + mkpath(output_directory) -function save_mesh_file(mesh::P4estMesh, output_directory, timestep, mpi_parallel::True) - # Create output directory (if it does not exist) - mpi_isroot() && mkpath(output_directory) - - # Determine file name based on existence of meaningful time step - if timestep > 0 - filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep)) - p4est_filename = @sprintf("p4est_data_%09d", timestep) - else filename = joinpath(output_directory, "mesh.h5") - p4est_filename = "p4est_data" - end - p4est_file = joinpath(output_directory, p4est_filename) - - # Save the complete connectivity/p4est data to disk. - save_p4est!(p4est_file, mesh.p4est) + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["size"] = length(mesh) + attributes(file)["mesh_filename"] = mesh.filename + attributes(file)["periodicity"] = collect(mesh.periodicity) + end - # Since the mesh attributes are replicated on all ranks, only save from MPI root - if !mpi_isroot() return filename end - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["p4est_file"] = p4est_filename - - file["tree_node_coordinates"] = mesh.tree_node_coordinates - file["nodes"] = Vector(mesh.nodes) # the mesh uses `SVector`s for the nodes - # to increase the runtime performance - # but HDF5 can only handle plain arrays - file["boundary_names"] = mesh.boundary_names .|> String - end + # Does not save the mesh itself to an HDF5 file. Instead saves important attributes + # of the mesh, like its size and the type of boundary mapping function. + # Then, within Trixi2Vtk, the P4estMesh and its node coordinates are reconstructured from + # these attributes for plotting purposes + function save_mesh_file( + mesh::P4estMesh, output_directory, timestep, + mpi_parallel::False + ) + # Create output directory (if it does not exist) + mkpath(output_directory) + + # Determine file name based on existence of meaningful time step + if timestep > 0 + filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep)) + p4est_filename = @sprintf("p4est_data_%09d", timestep) + else + filename = joinpath(output_directory, "mesh.h5") + p4est_filename = "p4est_data" + end - return filename -end + p4est_file = joinpath(output_directory, p4est_filename) -# TODO: Implement this function as soon as there is support for this in `t8code`. -function save_mesh_file(mesh::T8codeMesh, output_directory, timestep, mpi_parallel) - error("Mesh file output not supported yet for `T8codeMesh`.") + # Save the complete connectivity and `p4est` data to disk. + save_p4est!(p4est_file, mesh.p4est) - return joinpath(output_directory, "dummy_mesh.h5") -end + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["p4est_file"] = p4est_filename -""" - load_mesh(restart_file::AbstractString; n_cells_max) + file["tree_node_coordinates"] = mesh.tree_node_coordinates + file["nodes"] = Vector(mesh.nodes) # the mesh uses `SVector`s for the nodes + # to increase the runtime performance + # but HDF5 can only handle plain arrays + file["boundary_names"] = mesh.boundary_names .|> String + end -Load the mesh from the `restart_file`. -""" -function load_mesh(restart_file::AbstractString; n_cells_max = 0, RealT = Float64) - if mpi_isparallel() - mesh_file = get_restart_mesh_filename(restart_file, True()) - return load_mesh_parallel(mesh_file; n_cells_max = n_cells_max, RealT = RealT) - else - mesh_file = get_restart_mesh_filename(restart_file, False()) - load_mesh_serial(mesh_file; n_cells_max = n_cells_max, RealT = RealT) + return filename end -end -function load_mesh_serial(mesh_file::AbstractString; n_cells_max, RealT) - ndims, mesh_type = h5open(mesh_file, "r") do file - return read(attributes(file)["ndims"]), - read(attributes(file)["mesh_type"]) - end + function save_mesh_file(mesh::P4estMesh, output_directory, timestep, mpi_parallel::True) + # Create output directory (if it does not exist) + mpi_isroot() && mkpath(output_directory) - if mesh_type == "TreeMesh" - capacity = h5open(mesh_file, "r") do file - return read(attributes(file)["capacity"]) - end - mesh = TreeMesh(SerialTree{ndims}, max(n_cells_max, capacity)) - load_mesh!(mesh, mesh_file) - elseif mesh_type in ("StructuredMesh", "StructuredMeshView") - size_, mapping_as_string = h5open(mesh_file, "r") do file - return read(attributes(file)["size"]), - read(attributes(file)["mapping"]) + # Determine file name based on existence of meaningful time step + if timestep > 0 + filename = joinpath(output_directory, @sprintf("mesh_%09d.h5", timestep)) + p4est_filename = @sprintf("p4est_data_%09d", timestep) + else + filename = joinpath(output_directory, "mesh.h5") + p4est_filename = "p4est_data" end - size = Tuple(size_) + p4est_file = joinpath(output_directory, p4est_filename) - # TODO: `@eval` is evil - # - # This should be replaced with something more robust and secure, - # see https://github.com/trixi-framework/Trixi.jl/issues/541). - if ndims == 1 - mapping = eval(Meta.parse("""function (xi) - $mapping_as_string - mapping(xi) - end - """)) - elseif ndims == 2 - mapping = eval(Meta.parse("""function (xi, eta) - $mapping_as_string - mapping(xi, eta) - end - """)) - else # ndims == 3 - mapping = eval(Meta.parse("""function (xi, eta, zeta) - $mapping_as_string - mapping(xi, eta, zeta) - end - """)) - end + # Save the complete connectivity/p4est data to disk. + save_p4est!(p4est_file, mesh.p4est) - mesh = StructuredMesh(size, mapping; RealT = RealT, unsaved_changes = false, - mapping_as_string = mapping_as_string) - mesh.current_filename = mesh_file - elseif mesh_type == "UnstructuredMesh2D" - mesh_filename, periodicity_ = h5open(mesh_file, "r") do file - return read(attributes(file)["mesh_filename"]), - read(attributes(file)["periodicity"]) - end - mesh = UnstructuredMesh2D(mesh_filename; RealT = RealT, - periodicity = periodicity_, - unsaved_changes = false) - mesh.current_filename = mesh_file - elseif mesh_type == "P4estMesh" - p4est_filename, tree_node_coordinates, - nodes, boundary_names_ = h5open(mesh_file, "r") do file - return read(attributes(file)["p4est_file"]), - read(file["tree_node_coordinates"]), - read(file["nodes"]), - read(file["boundary_names"]) + # Since the mesh attributes are replicated on all ranks, only save from MPI root + if !mpi_isroot() + return filename end - boundary_names = boundary_names_ .|> Symbol + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["p4est_file"] = p4est_filename + + file["tree_node_coordinates"] = mesh.tree_node_coordinates + file["nodes"] = Vector(mesh.nodes) # the mesh uses `SVector`s for the nodes + # to increase the runtime performance + # but HDF5 can only handle plain arrays + file["boundary_names"] = mesh.boundary_names .|> String + end - p4est_file = joinpath(dirname(mesh_file), p4est_filename) - # Prevent Julia crashes when `p4est` can't find the file - @assert isfile(p4est_file) + return filename + end - p4est = load_p4est(p4est_file, Val(ndims)) + # TODO: Implement this function as soon as there is support for this in `t8code`. + function save_mesh_file(mesh::T8codeMesh, output_directory, timestep, mpi_parallel) + error("Mesh file output not supported yet for `T8codeMesh`.") - mesh = P4estMesh{ndims}(p4est, tree_node_coordinates, - nodes, boundary_names, mesh_file, false, true) - else - error("Unknown mesh type!") + return joinpath(output_directory, "dummy_mesh.h5") end - return mesh -end - -function load_mesh!(mesh::SerialTreeMesh, mesh_file::AbstractString) - mesh.current_filename = mesh_file - mesh.unsaved_changes = false - - # Read mesh file - h5open(mesh_file, "r") do file - # Set domain information - mesh.tree.center_level_0 = read(attributes(file)["center_level_0"]) - mesh.tree.length_level_0 = read(attributes(file)["length_level_0"]) - mesh.tree.periodicity = Tuple(read(attributes(file)["periodicity"])) - - # Set length - n_cells = read(attributes(file)["n_cells"]) - resize!(mesh.tree, n_cells) - - # Read in data - mesh.tree.parent_ids[1:n_cells] = read(file["parent_ids"]) - mesh.tree.child_ids[:, 1:n_cells] = read(file["child_ids"]) - mesh.tree.neighbor_ids[:, 1:n_cells] = read(file["neighbor_ids"]) - mesh.tree.levels[1:n_cells] = read(file["levels"]) - mesh.tree.coordinates[:, 1:n_cells] = read(file["coordinates"]) - end + """ + load_mesh(restart_file::AbstractString; n_cells_max) - return mesh -end + Load the mesh from the `restart_file`. + """ + function load_mesh(restart_file::AbstractString; n_cells_max = 0, RealT = Float64) + if mpi_isparallel() + mesh_file = get_restart_mesh_filename(restart_file, True()) + return load_mesh_parallel(mesh_file; n_cells_max = n_cells_max, RealT = RealT) + else + mesh_file = get_restart_mesh_filename(restart_file, False()) + load_mesh_serial(mesh_file; n_cells_max = n_cells_max, RealT = RealT) + end + end -function load_mesh_parallel(mesh_file::AbstractString; n_cells_max, RealT) - if mpi_isroot() - ndims_, mesh_type = h5open(mesh_file, "r") do file + function load_mesh_serial(mesh_file::AbstractString; n_cells_max, RealT) + ndims, mesh_type = h5open(mesh_file, "r") do file return read(attributes(file)["ndims"]), - read(attributes(file)["mesh_type"]) + read(attributes(file)["mesh_type"]) end - MPI.Bcast!(Ref(ndims_), mpi_root(), mpi_comm()) - MPI.bcast(mesh_type, mpi_root(), mpi_comm()) - else - ndims_ = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[] - mesh_type = MPI.bcast(nothing, mpi_root(), mpi_comm()) - end - if mesh_type == "TreeMesh" - if mpi_isroot() - n_cells, capacity = h5open(mesh_file, "r") do file - return read(attributes(file)["n_cells"]), - read(attributes(file)["capacity"]) + if mesh_type == "TreeMesh" + capacity = h5open(mesh_file, "r") do file + return read(attributes(file)["capacity"]) + end + mesh = TreeMesh(SerialTree{ndims}, max(n_cells_max, capacity)) + load_mesh!(mesh, mesh_file) + elseif mesh_type in ("StructuredMesh", "StructuredMeshView") + size_, mapping_as_string = h5open(mesh_file, "r") do file + return read(attributes(file)["size"]), + read(attributes(file)["mapping"]) end - MPI.Bcast!(Ref(n_cells), mpi_root(), mpi_comm()) - MPI.Bcast!(Ref(capacity), mpi_root(), mpi_comm()) - else - n_cells = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[] - capacity = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[] - end - mesh = TreeMesh(ParallelTree{ndims_}, max(n_cells, n_cells_max, capacity)) - load_mesh!(mesh, mesh_file) - elseif mesh_type == "P4estMesh" - if mpi_isroot() + size = Tuple(size_) + + # TODO: `@eval` is evil + # + # This should be replaced with something more robust and secure, + # see https://github.com/trixi-framework/Trixi.jl/issues/541). + if ndims == 1 + mapping = eval( + Meta.parse( + """function (xi) + $mapping_as_string + mapping(xi) + end + """ + ) + ) + elseif ndims == 2 + mapping = eval( + Meta.parse( + """function (xi, eta) + $mapping_as_string + mapping(xi, eta) + end + """ + ) + ) + else # ndims == 3 + mapping = eval( + Meta.parse( + """function (xi, eta, zeta) + $mapping_as_string + mapping(xi, eta, zeta) + end + """ + ) + ) + end + + mesh = StructuredMesh( + size, mapping; RealT = RealT, unsaved_changes = false, + mapping_as_string = mapping_as_string + ) + mesh.current_filename = mesh_file + elseif mesh_type == "UnstructuredMesh2D" + mesh_filename, periodicity_ = h5open(mesh_file, "r") do file + return read(attributes(file)["mesh_filename"]), + read(attributes(file)["periodicity"]) + end + mesh = UnstructuredMesh2D( + mesh_filename; RealT = RealT, + periodicity = periodicity_, + unsaved_changes = false + ) + mesh.current_filename = mesh_file + elseif mesh_type == "P4estMesh" p4est_filename, tree_node_coordinates, - nodes, boundary_names_ = h5open(mesh_file, "r") do file + nodes, boundary_names_ = h5open(mesh_file, "r") do file return read(attributes(file)["p4est_file"]), - read(file["tree_node_coordinates"]), - read(file["nodes"]), - read(file["boundary_names"]) + read(file["tree_node_coordinates"]), + read(file["nodes"]), + read(file["boundary_names"]) end boundary_names = boundary_names_ .|> Symbol p4est_file = joinpath(dirname(mesh_file), p4est_filename) + # Prevent Julia crashes when `p4est` can't find the file + @assert isfile(p4est_file) + + p4est = load_p4est(p4est_file, Val(ndims)) - data = (p4est_file, tree_node_coordinates, nodes, boundary_names) - MPI.bcast(data, mpi_root(), mpi_comm()) + mesh = P4estMesh{ndims}( + p4est, tree_node_coordinates, + nodes, boundary_names, mesh_file, false, true + ) else - data = MPI.bcast(nothing, mpi_root(), mpi_comm()) - p4est_file, tree_node_coordinates, nodes, boundary_names = data + error("Unknown mesh type!") end - # Prevent Julia crashes when `p4est` can't find the file - @assert isfile(p4est_file) - - p4est = load_p4est(p4est_file, Val(ndims_)) - - mesh = P4estMesh{ndims_}(p4est, tree_node_coordinates, - nodes, boundary_names, mesh_file, false, true) - else - error("Unknown mesh type!") + return mesh end - return mesh -end - -function load_mesh!(mesh::ParallelTreeMesh, mesh_file::AbstractString) - mesh.current_filename = mesh_file - mesh.unsaved_changes = false + function load_mesh!(mesh::SerialTreeMesh, mesh_file::AbstractString) + mesh.current_filename = mesh_file + mesh.unsaved_changes = false - if mpi_isroot() + # Read mesh file h5open(mesh_file, "r") do file # Set domain information mesh.tree.center_level_0 = read(attributes(file)["center_level_0"]) mesh.tree.length_level_0 = read(attributes(file)["length_level_0"]) mesh.tree.periodicity = Tuple(read(attributes(file)["periodicity"])) - MPI.Bcast!(collect(mesh.tree.center_level_0), mpi_root(), mpi_comm()) - MPI.Bcast!(collect(mesh.tree.length_level_0), mpi_root(), mpi_comm()) - MPI.Bcast!(collect(mesh.tree.periodicity), mpi_root(), mpi_comm()) # Set length n_cells = read(attributes(file)["n_cells"]) - MPI.Bcast!(Ref(n_cells), mpi_root(), mpi_comm()) resize!(mesh.tree, n_cells) # Read in data @@ -443,38 +378,145 @@ function load_mesh!(mesh::ParallelTreeMesh, mesh_file::AbstractString) mesh.tree.neighbor_ids[:, 1:n_cells] = read(file["neighbor_ids"]) mesh.tree.levels[1:n_cells] = read(file["levels"]) mesh.tree.coordinates[:, 1:n_cells] = read(file["coordinates"]) + end + + return mesh + end + + function load_mesh_parallel(mesh_file::AbstractString; n_cells_max, RealT) + if mpi_isroot() + ndims_, mesh_type = h5open(mesh_file, "r") do file + return read(attributes(file)["ndims"]), + read(attributes(file)["mesh_type"]) + end + MPI.Bcast!(Ref(ndims_), mpi_root(), mpi_comm()) + MPI.bcast(mesh_type, mpi_root(), mpi_comm()) + else + ndims_ = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[] + mesh_type = MPI.bcast(nothing, mpi_root(), mpi_comm()) + end + + if mesh_type == "TreeMesh" + if mpi_isroot() + n_cells, capacity = h5open(mesh_file, "r") do file + return read(attributes(file)["n_cells"]), + read(attributes(file)["capacity"]) + end + MPI.Bcast!(Ref(n_cells), mpi_root(), mpi_comm()) + MPI.Bcast!(Ref(capacity), mpi_root(), mpi_comm()) + else + n_cells = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[] + capacity = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[] + end + + mesh = TreeMesh(ParallelTree{ndims_}, max(n_cells, n_cells_max, capacity)) + load_mesh!(mesh, mesh_file) + elseif mesh_type == "P4estMesh" + if mpi_isroot() + p4est_filename, tree_node_coordinates, + nodes, boundary_names_ = h5open(mesh_file, "r") do file + return read(attributes(file)["p4est_file"]), + read(file["tree_node_coordinates"]), + read(file["nodes"]), + read(file["boundary_names"]) + end + + boundary_names = boundary_names_ .|> Symbol + + p4est_file = joinpath(dirname(mesh_file), p4est_filename) + + data = (p4est_file, tree_node_coordinates, nodes, boundary_names) + MPI.bcast(data, mpi_root(), mpi_comm()) + else + data = MPI.bcast(nothing, mpi_root(), mpi_comm()) + p4est_file, tree_node_coordinates, nodes, boundary_names = data + end + + # Prevent Julia crashes when `p4est` can't find the file + @assert isfile(p4est_file) + + p4est = load_p4est(p4est_file, Val(ndims_)) + + mesh = P4estMesh{ndims_}( + p4est, tree_node_coordinates, + nodes, boundary_names, mesh_file, false, true + ) + else + error("Unknown mesh type!") + end + + return mesh + end + + function load_mesh!(mesh::ParallelTreeMesh, mesh_file::AbstractString) + mesh.current_filename = mesh_file + mesh.unsaved_changes = false + + if mpi_isroot() + h5open(mesh_file, "r") do file + # Set domain information + mesh.tree.center_level_0 = read(attributes(file)["center_level_0"]) + mesh.tree.length_level_0 = read(attributes(file)["length_level_0"]) + mesh.tree.periodicity = Tuple(read(attributes(file)["periodicity"])) + MPI.Bcast!(collect(mesh.tree.center_level_0), mpi_root(), mpi_comm()) + MPI.Bcast!(collect(mesh.tree.length_level_0), mpi_root(), mpi_comm()) + MPI.Bcast!(collect(mesh.tree.periodicity), mpi_root(), mpi_comm()) + + # Set length + n_cells = read(attributes(file)["n_cells"]) + MPI.Bcast!(Ref(n_cells), mpi_root(), mpi_comm()) + resize!(mesh.tree, n_cells) + + # Read in data + mesh.tree.parent_ids[1:n_cells] = read(file["parent_ids"]) + mesh.tree.child_ids[:, 1:n_cells] = read(file["child_ids"]) + mesh.tree.neighbor_ids[:, 1:n_cells] = read(file["neighbor_ids"]) + mesh.tree.levels[1:n_cells] = read(file["levels"]) + mesh.tree.coordinates[:, 1:n_cells] = read(file["coordinates"]) + @views MPI.Bcast!(mesh.tree.parent_ids[1:n_cells], mpi_root(), mpi_comm()) + @views MPI.Bcast!(mesh.tree.child_ids[:, 1:n_cells], mpi_root(), mpi_comm()) + @views MPI.Bcast!( + mesh.tree.neighbor_ids[:, 1:n_cells], mpi_root(), + mpi_comm() + ) + @views MPI.Bcast!(mesh.tree.levels[1:n_cells], mpi_root(), mpi_comm()) + @views MPI.Bcast!( + mesh.tree.coordinates[:, 1:n_cells], mpi_root(), + mpi_comm() + ) + end + else # non-root ranks + # Set domain information + mesh.tree.center_level_0 = MPI.Bcast!( + collect(mesh.tree.center_level_0), + mpi_root(), mpi_comm() + ) + mesh.tree.length_level_0 = MPI.Bcast!( + collect(mesh.tree.length_level_0), + mpi_root(), mpi_comm() + )[1] + mesh.tree.periodicity = Tuple( + MPI.Bcast!( + collect(mesh.tree.periodicity), + mpi_root(), mpi_comm() + ) + ) + + # Set length + n_cells = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[] + resize!(mesh.tree, n_cells) + + # Read in data @views MPI.Bcast!(mesh.tree.parent_ids[1:n_cells], mpi_root(), mpi_comm()) @views MPI.Bcast!(mesh.tree.child_ids[:, 1:n_cells], mpi_root(), mpi_comm()) - @views MPI.Bcast!(mesh.tree.neighbor_ids[:, 1:n_cells], mpi_root(), - mpi_comm()) + @views MPI.Bcast!(mesh.tree.neighbor_ids[:, 1:n_cells], mpi_root(), mpi_comm()) @views MPI.Bcast!(mesh.tree.levels[1:n_cells], mpi_root(), mpi_comm()) - @views MPI.Bcast!(mesh.tree.coordinates[:, 1:n_cells], mpi_root(), - mpi_comm()) + @views MPI.Bcast!(mesh.tree.coordinates[:, 1:n_cells], mpi_root(), mpi_comm()) end - else # non-root ranks - # Set domain information - mesh.tree.center_level_0 = MPI.Bcast!(collect(mesh.tree.center_level_0), - mpi_root(), mpi_comm()) - mesh.tree.length_level_0 = MPI.Bcast!(collect(mesh.tree.length_level_0), - mpi_root(), mpi_comm())[1] - mesh.tree.periodicity = Tuple(MPI.Bcast!(collect(mesh.tree.periodicity), - mpi_root(), mpi_comm())) - - # Set length - n_cells = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm())[] - resize!(mesh.tree, n_cells) - - # Read in data - @views MPI.Bcast!(mesh.tree.parent_ids[1:n_cells], mpi_root(), mpi_comm()) - @views MPI.Bcast!(mesh.tree.child_ids[:, 1:n_cells], mpi_root(), mpi_comm()) - @views MPI.Bcast!(mesh.tree.neighbor_ids[:, 1:n_cells], mpi_root(), mpi_comm()) - @views MPI.Bcast!(mesh.tree.levels[1:n_cells], mpi_root(), mpi_comm()) - @views MPI.Bcast!(mesh.tree.coordinates[:, 1:n_cells], mpi_root(), mpi_comm()) - end - # Partition mesh - partition!(mesh) + # Partition mesh + partition!(mesh) - return mesh -end + return mesh + end end # @muladd diff --git a/src/meshes/meshes.jl b/src/meshes/meshes.jl index 4d6016e556..2218c1e28d 100644 --- a/src/meshes/meshes.jl +++ b/src/meshes/meshes.jl @@ -3,17 +3,17 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -include("tree_mesh.jl") -include("structured_mesh.jl") -include("structured_mesh_view.jl") -include("surface_interpolant.jl") -include("unstructured_mesh.jl") -include("face_interpolant.jl") -include("transfinite_mappings_3d.jl") -include("p4est_mesh.jl") -include("t8code_mesh.jl") -include("mesh_io.jl") -include("dgmulti_meshes.jl") + include("tree_mesh.jl") + include("structured_mesh.jl") + include("structured_mesh_view.jl") + include("surface_interpolant.jl") + include("unstructured_mesh.jl") + include("face_interpolant.jl") + include("transfinite_mappings_3d.jl") + include("p4est_mesh.jl") + include("t8code_mesh.jl") + include("mesh_io.jl") + include("dgmulti_meshes.jl") end # @muladd diff --git a/src/meshes/p4est_mesh.jl b/src/meshes/p4est_mesh.jl index 526f5d9f23..4e5116e3b7 100644 --- a/src/meshes/p4est_mesh.jl +++ b/src/meshes/p4est_mesh.jl @@ -3,1196 +3,1021 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - P4estMesh{NDIMS} <: AbstractMesh{NDIMS} - -An unstructured curved mesh based on trees that uses the C library `p4est` -to manage trees and mesh refinement. -""" -mutable struct P4estMesh{NDIMS, RealT <: Real, IsParallel, P, Ghost, NDIMSP2, NNODES} <: - AbstractMesh{NDIMS} - p4est :: P # Either PointerWrapper{p4est_t} or PointerWrapper{p8est_t} - is_parallel :: IsParallel - ghost :: Ghost # Either PointerWrapper{p4est_ghost_t} or PointerWrapper{p8est_ghost_t} - # Coordinates at the nodes specified by the tensor product of `nodes` (NDIMS times). - # This specifies the geometry interpolation for each tree. - tree_node_coordinates::Array{RealT, NDIMSP2} # [dimension, i, j, k, tree] - nodes::SVector{NNODES, RealT} - boundary_names::Array{Symbol, 2} # [face direction, tree] - current_filename::String - unsaved_changes::Bool - p4est_partition_allow_for_coarsening::Bool - - function P4estMesh{NDIMS}(p4est, tree_node_coordinates, nodes, boundary_names, - current_filename, unsaved_changes, - p4est_partition_allow_for_coarsening) where {NDIMS} - if NDIMS == 2 - @assert p4est isa Ptr{p4est_t} - elseif NDIMS == 3 - @assert p4est isa Ptr{p8est_t} - end + #! format: noindent + + """ + P4estMesh{NDIMS} <: AbstractMesh{NDIMS} + + An unstructured curved mesh based on trees that uses the C library `p4est` + to manage trees and mesh refinement. + """ + mutable struct P4estMesh{NDIMS, RealT <: Real, IsParallel, P, Ghost, NDIMSP2, NNODES} <: + AbstractMesh{NDIMS} + p4est::P # Either PointerWrapper{p4est_t} or PointerWrapper{p8est_t} + is_parallel::IsParallel + ghost::Ghost # Either PointerWrapper{p4est_ghost_t} or PointerWrapper{p8est_ghost_t} + # Coordinates at the nodes specified by the tensor product of `nodes` (NDIMS times). + # This specifies the geometry interpolation for each tree. + tree_node_coordinates::Array{RealT, NDIMSP2} # [dimension, i, j, k, tree] + nodes::SVector{NNODES, RealT} + boundary_names::Array{Symbol, 2} # [face direction, tree] + current_filename::String + unsaved_changes::Bool + p4est_partition_allow_for_coarsening::Bool + + function P4estMesh{NDIMS}( + p4est, tree_node_coordinates, nodes, boundary_names, + current_filename, unsaved_changes, + p4est_partition_allow_for_coarsening + ) where {NDIMS} + if NDIMS == 2 + @assert p4est isa Ptr{p4est_t} + elseif NDIMS == 3 + @assert p4est isa Ptr{p8est_t} + end - if mpi_isparallel() - if !P4est.uses_mpi() - error("p4est library does not support MPI") + if mpi_isparallel() + if !P4est.uses_mpi() + error("p4est library does not support MPI") + end + is_parallel = True() + else + is_parallel = False() end - is_parallel = True() - else - is_parallel = False() - end - p4est_pw = PointerWrapper(p4est) - - ghost = ghost_new_p4est(p4est) - ghost_pw = PointerWrapper(ghost) - - mesh = new{NDIMS, eltype(tree_node_coordinates), typeof(is_parallel), - typeof(p4est_pw), typeof(ghost_pw), NDIMS + 2, length(nodes)}(p4est_pw, - is_parallel, - ghost_pw, - tree_node_coordinates, - nodes, - boundary_names, - current_filename, - unsaved_changes, - p4est_partition_allow_for_coarsening) - - # Destroy `p4est` structs when the mesh is garbage collected - finalizer(destroy_mesh, mesh) - - return mesh - end -end - -const SerialP4estMesh{NDIMS} = P4estMesh{NDIMS, <:Real, <:False} -const ParallelP4estMesh{NDIMS} = P4estMesh{NDIMS, <:Real, <:True} - -@inline mpi_parallel(mesh::SerialP4estMesh) = False() -@inline mpi_parallel(mesh::ParallelP4estMesh) = True() - -function destroy_mesh(mesh::P4estMesh{2}) - connectivity = mesh.p4est.connectivity - p4est_ghost_destroy(mesh.ghost) - p4est_destroy(mesh.p4est) - p4est_connectivity_destroy(connectivity) -end - -function destroy_mesh(mesh::P4estMesh{3}) - connectivity = mesh.p4est.connectivity - p8est_ghost_destroy(mesh.ghost) - p8est_destroy(mesh.p4est) - p8est_connectivity_destroy(connectivity) -end - -@inline Base.ndims(::P4estMesh{NDIMS}) where {NDIMS} = NDIMS -@inline Base.real(::P4estMesh{NDIMS, RealT}) where {NDIMS, RealT} = RealT - -@inline function ntrees(mesh::P4estMesh) - return mesh.p4est.trees.elem_count[] -end -# returns Int32 by default which causes a weird method error when creating the cache -@inline ncells(mesh::P4estMesh) = Int(mesh.p4est.local_num_quadrants[]) -@inline ncellsglobal(mesh::P4estMesh) = Int(mesh.p4est.global_num_quadrants[]) - -function Base.show(io::IO, mesh::P4estMesh) - print(io, "P4estMesh{", ndims(mesh), ", ", real(mesh), "}") -end - -function Base.show(io::IO, ::MIME"text/plain", mesh::P4estMesh) - if get(io, :compact, false) - show(io, mesh) - else - setup = [ - "#trees" => ntrees(mesh), - "current #cells" => ncellsglobal(mesh), - "polydeg" => length(mesh.nodes) - 1, - ] - summary_box(io, - "P4estMesh{" * string(ndims(mesh)) * ", " * string(real(mesh)) * - "}", setup) - end -end - -""" - P4estMesh(trees_per_dimension; polydeg, - mapping=nothing, faces=nothing, coordinates_min=nothing, coordinates_max=nothing, - RealT=Float64, initial_refinement_level=0, periodicity=true, unsaved_changes=true, - p4est_partition_allow_for_coarsening=true) - -Create a structured curved `P4estMesh` of the specified size. - -There are three ways to map the mesh to the physical domain. -1. Define a `mapping` that maps the hypercube `[-1, 1]^n`. -2. Specify a `Tuple` `faces` of functions that parametrize each face. -3. Create a rectangular mesh by specifying `coordinates_min` and `coordinates_max`. - -Non-periodic boundaries will be called `:x_neg`, `:x_pos`, `:y_neg`, `:y_pos`, `:z_neg`, `:z_pos`. - -# Arguments -- `trees_per_dimension::NTupleE{NDIMS, Int}`: the number of trees in each dimension. -- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. - The mapping will be approximated by an interpolation polynomial - of the specified degree for each tree. -- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms - the reference mesh (`[-1, 1]^n`) to the physical domain. - Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`. -- `faces::NTuple{2*NDIMS}`: a tuple of `2 * NDIMS` functions that describe the faces of the domain. - Each function must take `NDIMS-1` arguments. - `faces[1]` describes the face onto which the face in negative x-direction - of the unit hypercube is mapped. The face in positive x-direction of - the unit hypercube will be mapped onto the face described by `faces[2]`. - `faces[3:4]` describe the faces in positive and negative y-direction respectively - (in 2D and 3D). - `faces[5:6]` describe the faces in positive and negative z-direction respectively (in 3D). - Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`. -- `coordinates_min`: vector or tuple of the coordinates of the corner in the negative direction of each dimension - to create a rectangular mesh. - Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`. -- `coordinates_max`: vector or tuple of the coordinates of the corner in the positive direction of each dimension - to create a rectangular mesh. - Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`. -- `RealT::Type`: the type that should be used for coordinates. -- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. -- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` - deciding for each dimension if the boundaries in this dimension are periodic. -- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file. -- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity - independent of domain partitioning. Should be `false` for static meshes - to permit more fine-grained partitioning. -""" -function P4estMesh(trees_per_dimension; polydeg, - mapping = nothing, faces = nothing, coordinates_min = nothing, - coordinates_max = nothing, - RealT = Float64, initial_refinement_level = 0, periodicity = true, - unsaved_changes = true, - p4est_partition_allow_for_coarsening = true) - @assert ((coordinates_min === nothing)===(coordinates_max === nothing)) "Either both or none of coordinates_min and coordinates_max must be specified" - - @assert count(i -> i !== nothing, - (mapping, faces, coordinates_min))==1 "Exactly one of mapping, faces and coordinates_min/max must be specified" - - # Extract mapping - if faces !== nothing - validate_faces(faces) - mapping = transfinite_mapping(faces) - elseif coordinates_min !== nothing - mapping = coordinates2mapping(coordinates_min, coordinates_max) - end - - NDIMS = length(trees_per_dimension) - - # Convert periodicity to a Tuple of a Bool for every dimension - if all(periodicity) - # Also catches case where periodicity = true - periodicity = ntuple(_ -> true, NDIMS) - elseif !any(periodicity) - # Also catches case where periodicity = false - periodicity = ntuple(_ -> false, NDIMS) - else - # Default case if periodicity is an iterable - periodicity = Tuple(periodicity) - end - - basis = LobattoLegendreBasis(RealT, polydeg) - nodes = basis.nodes - tree_node_coordinates = Array{RealT, NDIMS + 2}(undef, NDIMS, - ntuple(_ -> length(nodes), - NDIMS)..., - prod(trees_per_dimension)) - calc_tree_node_coordinates!(tree_node_coordinates, nodes, mapping, - trees_per_dimension) - - # p4est_connectivity_new_brick has trees in Z-order, so use our own function for this - connectivity = connectivity_structured(trees_per_dimension..., periodicity) - - p4est = new_p4est(connectivity, initial_refinement_level) - - # Non-periodic boundaries - boundary_names = fill(Symbol("---"), 2 * NDIMS, prod(trees_per_dimension)) - - structured_boundary_names!(boundary_names, trees_per_dimension, periodicity) - - return P4estMesh{NDIMS}(p4est, tree_node_coordinates, nodes, - boundary_names, "", unsaved_changes, - p4est_partition_allow_for_coarsening) -end - -# 2D version -function structured_boundary_names!(boundary_names, trees_per_dimension::NTuple{2}, - periodicity) - linear_indices = LinearIndices(trees_per_dimension) - - # Boundaries in x-direction - if !periodicity[1] - for cell_y in 1:trees_per_dimension[2] - tree = linear_indices[1, cell_y] - boundary_names[1, tree] = :x_neg - - tree = linear_indices[end, cell_y] - boundary_names[2, tree] = :x_pos + p4est_pw = PointerWrapper(p4est) + + ghost = ghost_new_p4est(p4est) + ghost_pw = PointerWrapper(ghost) + + mesh = new{ + NDIMS, eltype(tree_node_coordinates), typeof(is_parallel), + typeof(p4est_pw), typeof(ghost_pw), NDIMS + 2, length(nodes), + }( + p4est_pw, + is_parallel, + ghost_pw, + tree_node_coordinates, + nodes, + boundary_names, + current_filename, + unsaved_changes, + p4est_partition_allow_for_coarsening + ) + + # Destroy `p4est` structs when the mesh is garbage collected + finalizer(destroy_mesh, mesh) + + return mesh end end - # Boundaries in y-direction - if !periodicity[2] - for cell_x in 1:trees_per_dimension[1] - tree = linear_indices[cell_x, 1] - boundary_names[3, tree] = :y_neg + const SerialP4estMesh{NDIMS} = P4estMesh{NDIMS, <:Real, <:False} + const ParallelP4estMesh{NDIMS} = P4estMesh{NDIMS, <:Real, <:True} - tree = linear_indices[cell_x, end] - boundary_names[4, tree] = :y_pos - end + @inline mpi_parallel(mesh::SerialP4estMesh) = False() + @inline mpi_parallel(mesh::ParallelP4estMesh) = True() + + function destroy_mesh(mesh::P4estMesh{2}) + connectivity = mesh.p4est.connectivity + p4est_ghost_destroy(mesh.ghost) + p4est_destroy(mesh.p4est) + p4est_connectivity_destroy(connectivity) end -end -# 3D version -function structured_boundary_names!(boundary_names, trees_per_dimension::NTuple{3}, - periodicity) - linear_indices = LinearIndices(trees_per_dimension) + function destroy_mesh(mesh::P4estMesh{3}) + connectivity = mesh.p4est.connectivity + p8est_ghost_destroy(mesh.ghost) + p8est_destroy(mesh.p4est) + p8est_connectivity_destroy(connectivity) + end - # Boundaries in x-direction - if !periodicity[1] - for cell_z in 1:trees_per_dimension[3], cell_y in 1:trees_per_dimension[2] - tree = linear_indices[1, cell_y, cell_z] - boundary_names[1, tree] = :x_neg + @inline Base.ndims(::P4estMesh{NDIMS}) where {NDIMS} = NDIMS + @inline Base.real(::P4estMesh{NDIMS, RealT}) where {NDIMS, RealT} = RealT - tree = linear_indices[end, cell_y, cell_z] - boundary_names[2, tree] = :x_pos - end + @inline function ntrees(mesh::P4estMesh) + return mesh.p4est.trees.elem_count[] end + # returns Int32 by default which causes a weird method error when creating the cache + @inline ncells(mesh::P4estMesh) = Int(mesh.p4est.local_num_quadrants[]) + @inline ncellsglobal(mesh::P4estMesh) = Int(mesh.p4est.global_num_quadrants[]) - # Boundaries in y-direction - if !periodicity[2] - for cell_z in 1:trees_per_dimension[3], cell_x in 1:trees_per_dimension[1] - tree = linear_indices[cell_x, 1, cell_z] - boundary_names[3, tree] = :y_neg + function Base.show(io::IO, mesh::P4estMesh) + print(io, "P4estMesh{", ndims(mesh), ", ", real(mesh), "}") + end - tree = linear_indices[cell_x, end, cell_z] - boundary_names[4, tree] = :y_pos + function Base.show(io::IO, ::MIME"text/plain", mesh::P4estMesh) + if get(io, :compact, false) + show(io, mesh) + else + setup = [ + "#trees" => ntrees(mesh), + "current #cells" => ncellsglobal(mesh), + "polydeg" => length(mesh.nodes) - 1, + ] + summary_box( + io, + "P4estMesh{" * string(ndims(mesh)) * ", " * string(real(mesh)) * + "}", setup + ) end end - # Boundaries in z-direction - if !periodicity[3] - for cell_y in 1:trees_per_dimension[2], cell_x in 1:trees_per_dimension[1] - tree = linear_indices[cell_x, cell_y, 1] - boundary_names[5, tree] = :z_neg + """ + P4estMesh(trees_per_dimension; polydeg, + mapping=nothing, faces=nothing, coordinates_min=nothing, coordinates_max=nothing, + RealT=Float64, initial_refinement_level=0, periodicity=true, unsaved_changes=true, + p4est_partition_allow_for_coarsening=true) + + Create a structured curved `P4estMesh` of the specified size. + + There are three ways to map the mesh to the physical domain. + 1. Define a `mapping` that maps the hypercube `[-1, 1]^n`. + 2. Specify a `Tuple` `faces` of functions that parametrize each face. + 3. Create a rectangular mesh by specifying `coordinates_min` and `coordinates_max`. + + Non-periodic boundaries will be called `:x_neg`, `:x_pos`, `:y_neg`, `:y_pos`, `:z_neg`, `:z_pos`. + + # Arguments + - `trees_per_dimension::NTupleE{NDIMS, Int}`: the number of trees in each dimension. + - `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. + The mapping will be approximated by an interpolation polynomial + of the specified degree for each tree. + - `mapping`: a function of `NDIMS` variables to describe the mapping that transforms + the reference mesh (`[-1, 1]^n`) to the physical domain. + Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`. + - `faces::NTuple{2*NDIMS}`: a tuple of `2 * NDIMS` functions that describe the faces of the domain. + Each function must take `NDIMS-1` arguments. + `faces[1]` describes the face onto which the face in negative x-direction + of the unit hypercube is mapped. The face in positive x-direction of + the unit hypercube will be mapped onto the face described by `faces[2]`. + `faces[3:4]` describe the faces in positive and negative y-direction respectively + (in 2D and 3D). + `faces[5:6]` describe the faces in positive and negative z-direction respectively (in 3D). + Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`. + - `coordinates_min`: vector or tuple of the coordinates of the corner in the negative direction of each dimension + to create a rectangular mesh. + Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`. + - `coordinates_max`: vector or tuple of the coordinates of the corner in the positive direction of each dimension + to create a rectangular mesh. + Use only one of `mapping`, `faces` and `coordinates_min`/`coordinates_max`. + - `RealT::Type`: the type that should be used for coordinates. + - `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. + - `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` + deciding for each dimension if the boundaries in this dimension are periodic. + - `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file. + - `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity + independent of domain partitioning. Should be `false` for static meshes + to permit more fine-grained partitioning. + """ + function P4estMesh( + trees_per_dimension; polydeg, + mapping = nothing, faces = nothing, coordinates_min = nothing, + coordinates_max = nothing, + RealT = Float64, initial_refinement_level = 0, periodicity = true, + unsaved_changes = true, + p4est_partition_allow_for_coarsening = true + ) + @assert ((coordinates_min === nothing) === (coordinates_max === nothing)) "Either both or none of coordinates_min and coordinates_max must be specified" + + @assert count( + i -> i !== nothing, + (mapping, faces, coordinates_min) + ) == 1 "Exactly one of mapping, faces and coordinates_min/max must be specified" + + # Extract mapping + if faces !== nothing + validate_faces(faces) + mapping = transfinite_mapping(faces) + elseif coordinates_min !== nothing + mapping = coordinates2mapping(coordinates_min, coordinates_max) + end - tree = linear_indices[cell_x, cell_y, end] - boundary_names[6, tree] = :z_pos + NDIMS = length(trees_per_dimension) + + # Convert periodicity to a Tuple of a Bool for every dimension + if all(periodicity) + # Also catches case where periodicity = true + periodicity = ntuple(_ -> true, NDIMS) + elseif !any(periodicity) + # Also catches case where periodicity = false + periodicity = ntuple(_ -> false, NDIMS) + else + # Default case if periodicity is an iterable + periodicity = Tuple(periodicity) end + + basis = LobattoLegendreBasis(RealT, polydeg) + nodes = basis.nodes + tree_node_coordinates = Array{RealT, NDIMS + 2}( + undef, NDIMS, + ntuple( + _ -> length(nodes), + NDIMS + )..., + prod(trees_per_dimension) + ) + calc_tree_node_coordinates!( + tree_node_coordinates, nodes, mapping, + trees_per_dimension + ) + + # p4est_connectivity_new_brick has trees in Z-order, so use our own function for this + connectivity = connectivity_structured(trees_per_dimension..., periodicity) + + p4est = new_p4est(connectivity, initial_refinement_level) + + # Non-periodic boundaries + boundary_names = fill(Symbol("---"), 2 * NDIMS, prod(trees_per_dimension)) + + structured_boundary_names!(boundary_names, trees_per_dimension, periodicity) + + return P4estMesh{NDIMS}( + p4est, tree_node_coordinates, nodes, + boundary_names, "", unsaved_changes, + p4est_partition_allow_for_coarsening + ) end -end - -""" - P4estMesh{NDIMS}(meshfile::String; - mapping=nothing, polydeg=1, RealT=Float64, - initial_refinement_level=0, unsaved_changes=true, - p4est_partition_allow_for_coarsening=true, - boundary_symbols = nothing) - -Main mesh constructor for the `P4estMesh` that imports an unstructured, conforming -mesh from an Abaqus mesh file (`.inp`). Each element of the conforming mesh parsed -from the `meshfile` is created as a [`p4est`](https://github.com/cburstedde/p4est) -tree datatype. - -To create a curved unstructured mesh `P4estMesh` two strategies are available: - -- `p4est_mesh_from_hohqmesh_abaqus`: High-order, curved boundary information created by - [`HOHQMesh.jl`](https://github.com/trixi-framework/HOHQMesh.jl) is - available in the `meshfile`. The mesh polynomial degree `polydeg` - of the boundaries is provided from the `meshfile`. The computation of - the mapped tree coordinates is done with transfinite interpolation - with linear blending similar to [`UnstructuredMesh2D`](@ref). Boundary name - information is also parsed from the `meshfile` such that different boundary - conditions can be set at each named boundary on a given tree. -- `p4est_mesh_from_standard_abaqus`: By default, with `mapping=nothing` and `polydeg=1`, this creates a - straight-sided from the information parsed from the `meshfile`. If a mapping - function is specified then it computes the mapped tree coordinates via polynomial - interpolants with degree `polydeg`. The mesh created by this function will only - have one boundary `:all` if `boundary_symbols` is not specified. - If `boundary_symbols` is specified the mesh file will be parsed for nodesets defining - the boundary nodes from which boundary edges (2D) and faces (3D) will be assigned. - -Note that the `mapping` and `polydeg` keyword arguments are only used by the `p4est_mesh_from_standard_abaqus` -function. The `p4est_mesh_from_hohqmesh_abaqus` function obtains the mesh `polydeg` directly from the `meshfile` -and constructs the transfinite mapping internally. - -The particular strategy is selected according to the header present in the `meshfile` where -the constructor checks whether or not the `meshfile` was created with -[HOHQMesh.jl](https://github.com/trixi-framework/HOHQMesh.jl). -If the Abaqus file header is not present in the `meshfile` then the `P4estMesh` is created -with the function `p4est_mesh_from_standard_abaqus`. - -The default keyword argument `initial_refinement_level=0` corresponds to a forest -where the number of trees is the same as the number of elements in the original `meshfile`. -Increasing the `initial_refinement_level` allows one to uniformly refine the base mesh given -in the `meshfile` to create a forest with more trees before the simulation begins. -For example, if a two-dimensional base mesh contains 25 elements then setting -`initial_refinement_level=1` creates an initial forest of `2^2 * 25 = 100` trees. - -# Arguments -- `meshfile::String`: an uncurved Abaqus mesh file that can be imported by `p4est`. -- `mapping`: a function of `NDIMS` variables to describe the mapping that transforms - the imported mesh to the physical domain. Use `nothing` for the identity map. -- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. - The mapping will be approximated by an interpolation polynomial - of the specified degree for each tree. - The default of `1` creates an uncurved geometry. Use a higher value if the mapping - will curve the imported uncurved mesh. -- `RealT::Type`: the type that should be used for coordinates. -- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. -- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file. -- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity - independent of domain partitioning. Should be `false` for static meshes - to permit more fine-grained partitioning. -- `boundary_symbols::Vector{Symbol}`: A vector of symbols that correspond to the boundary names in the `meshfile`. - If `nothing` is passed then all boundaries are named `:all`. -""" -function P4estMesh{NDIMS}(meshfile::String; - mapping = nothing, polydeg = 1, RealT = Float64, - initial_refinement_level = 0, unsaved_changes = true, - p4est_partition_allow_for_coarsening = true, - boundary_symbols = nothing) where {NDIMS} - # Prevent `p4est` from crashing Julia if the file doesn't exist - @assert isfile(meshfile) - - # Read in the Header of the meshfile to determine which constructor is appropriate - header = open(meshfile, "r") do io - readline(io) # *Header of the Abaqus file; discarded - readline(io) # Readin the actual header information - end - - # Check if the meshfile was generated using HOHQMesh - if header == " File created by HOHQMesh" - # Mesh curvature and boundary naming is handled with additional information available in meshfile - p4est, tree_node_coordinates, nodes, boundary_names = p4est_mesh_from_hohqmesh_abaqus(meshfile, - initial_refinement_level, - NDIMS, - RealT) - else - # Mesh curvature is handled directly by applying the mapping keyword argument - p4est, tree_node_coordinates, nodes, boundary_names = p4est_mesh_from_standard_abaqus(meshfile, - mapping, - polydeg, - initial_refinement_level, - NDIMS, - RealT, - boundary_symbols) - end - - return P4estMesh{NDIMS}(p4est, tree_node_coordinates, nodes, - boundary_names, "", unsaved_changes, - p4est_partition_allow_for_coarsening) -end - -# Wrapper for `p4est_connectivity_from_hohqmesh_abaqus`. The latter is used -# by `T8codeMesh`, too. -function p4est_mesh_from_hohqmesh_abaqus(meshfile, initial_refinement_level, - n_dimensions, RealT) - connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_hohqmesh_abaqus(meshfile, - initial_refinement_level, - n_dimensions, - RealT) - - p4est = new_p4est(connectivity, initial_refinement_level) - - return p4est, tree_node_coordinates, nodes, boundary_names -end - -# Wrapper for `p4est_connectivity_from_standard_abaqus`. The latter is used -# by `T8codeMesh`, too. -function p4est_mesh_from_standard_abaqus(meshfile, mapping, polydeg, - initial_refinement_level, n_dimensions, RealT, - boundary_symbols) - connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_standard_abaqus(meshfile, - mapping, - polydeg, - initial_refinement_level, - n_dimensions, - RealT, - boundary_symbols) - - p4est = new_p4est(connectivity, initial_refinement_level) - - return p4est, tree_node_coordinates, nodes, boundary_names -end - -# Create the mesh connectivity, mapped node coordinates within each tree, reference nodes in [-1,1] -# and a list of boundary names for the `P4estMesh`. High-order boundary curve information as well as -# the boundary names on each tree are provided by the `meshfile` created by -# [`HOHQMesh.jl`](https://github.com/trixi-framework/HOHQMesh.jl). -function p4est_connectivity_from_hohqmesh_abaqus(meshfile, initial_refinement_level, - n_dimensions, RealT) - # Create the mesh connectivity using `p4est` - connectivity = read_inp_p4est(meshfile, Val(n_dimensions)) - connectivity_pw = PointerWrapper(connectivity) - - # These need to be of the type Int for unsafe_wrap below to work - n_trees::Int = connectivity_pw.num_trees[] - n_vertices::Int = connectivity_pw.num_vertices[] - - # Extract a copy of the element vertices to compute the tree node coordinates - vertices = unsafe_wrap(Array, connectivity_pw.vertices, (3, n_vertices)) - - # Readin all the information from the mesh file into a string array - file_lines = readlines(open(meshfile)) - - # Get the file index where the mesh polynomial degree is given in the meshfile - file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) - - # Get the polynomial order of the mesh boundary information - current_line = split(file_lines[file_idx]) - mesh_polydeg = parse(Int, current_line[6]) - mesh_nnodes = mesh_polydeg + 1 - - # Create the Chebyshev-Gauss-Lobatto nodes used by HOHQMesh to represent the boundaries - cheby_nodes, _ = chebyshev_gauss_lobatto_nodes_weights(mesh_nnodes) - nodes = SVector{mesh_nnodes}(cheby_nodes) - - # Allocate the memory for the tree node coordinates - tree_node_coordinates = Array{RealT, n_dimensions + 2}(undef, n_dimensions, - ntuple(_ -> length(nodes), - n_dimensions)..., - n_trees) - - # Compute the tree node coordinates and return the updated file index - file_idx = calc_tree_node_coordinates!(tree_node_coordinates, file_lines, nodes, - vertices, RealT) - - # Allocate the memory for the boundary labels - boundary_names = Array{Symbol}(undef, (2 * n_dimensions, n_trees)) - - # Read in the boundary names from the last portion of the meshfile - # Note here the boundary names where "---" means an internal connection - for tree in 1:n_trees - current_line = split(file_lines[file_idx]) - boundary_names[:, tree] = map(Symbol, current_line[2:end]) - file_idx += 1 - end - - return connectivity, tree_node_coordinates, nodes, boundary_names -end - -# Create the mesh connectivity, mapped node coordinates within each tree, reference nodes in [-1,1] -# and a list of boundary names for the `P4estMesh`. The tree node coordinates are computed according to -# the `mapping` passed to this function using polynomial interpolants of degree `polydeg`. All boundary -# names are given the name `:all`. -function p4est_connectivity_from_standard_abaqus(meshfile, mapping, polydeg, - initial_refinement_level, n_dimensions, - RealT, - boundary_symbols) - # Create the mesh connectivity using `p4est` - connectivity = read_inp_p4est(meshfile, Val(n_dimensions)) - connectivity_pw = PointerWrapper(connectivity) - - # These need to be of the type Int for unsafe_wrap below to work - n_trees::Int = connectivity_pw.num_trees[] - n_vertices::Int = connectivity_pw.num_vertices[] - - vertices = unsafe_wrap(Array, connectivity_pw.vertices, (3, n_vertices)) - tree_to_vertex = unsafe_wrap(Array, connectivity_pw.tree_to_vertex, - (2^n_dimensions, n_trees)) - - basis = LobattoLegendreBasis(RealT, polydeg) - nodes = basis.nodes - - tree_node_coordinates = Array{RealT, n_dimensions + 2}(undef, n_dimensions, - ntuple(_ -> length(nodes), - n_dimensions)..., - n_trees) - calc_tree_node_coordinates!(tree_node_coordinates, nodes, mapping, vertices, - tree_to_vertex) - - if boundary_symbols === nothing - # There's no simple and generic way to distinguish boundaries without any information given. - # Name all of them :all. - boundary_names = fill(:all, 2 * n_dimensions, n_trees) - else # Boundary information given - # Read in nodes belonging to boundaries - node_set_dict = parse_node_sets(meshfile, boundary_symbols) - # Read in all elements with associated nodes to specify the boundaries - element_node_matrix = parse_elements(meshfile, n_trees, n_dimensions) - - # Initialize boundary information matrix with symbol for no boundary / internal connection - boundary_names = fill(Symbol("---"), 2 * n_dimensions, n_trees) - - # Fill `boundary_names` such that it can be processed by p4est - assign_boundaries_standard_abaqus!(boundary_names, n_trees, - element_node_matrix, node_set_dict, - Val(n_dimensions)) - end - - return connectivity, tree_node_coordinates, nodes, boundary_names -end - -function parse_elements(meshfile, n_trees, n_dims) - @assert n_dims in (2, 3) "Only 2D and 3D meshes are supported" - # Valid element types (that can be processed by p4est) based on dimension - element_types = n_dims == 2 ? - ["*ELEMENT, type=CPS4", "*ELEMENT, type=C2D4", - "*ELEMENT, type=S4"] : ["*ELEMENT, type=C3D8"] - # 2D quads: 4 nodes + element index, 3D hexes: 8 nodes + element index - expected_content_length = n_dims == 2 ? 5 : 9 - - element_node_matrix = Matrix{Int64}(undef, n_trees, expected_content_length - 1) - el_list_follows = false - tree_id = 1 - - open(meshfile, "r") do file - for line in eachline(file) - if any(startswith(line, el_type) for el_type in element_types) - el_list_follows = true - elseif el_list_follows - content = split(line, ",") - if length(content) == expected_content_length # Check that we still read in connectivity data - content_int = parse.(Int64, content) - # Add constituent nodes to the element_node_matrix. - # Important: Do not use index from the Abaqus file, but the one from p4est. - element_node_matrix[tree_id, :] = content_int[2:end] # First entry is element id - tree_id += 1 - else # Processed all elements for this ELSET - el_list_follows = false - end + + # 2D version + function structured_boundary_names!( + boundary_names, trees_per_dimension::NTuple{2}, + periodicity + ) + linear_indices = LinearIndices(trees_per_dimension) + + # Boundaries in x-direction + if !periodicity[1] + for cell_y in 1:trees_per_dimension[2] + tree = linear_indices[1, cell_y] + boundary_names[1, tree] = :x_neg + + tree = linear_indices[end, cell_y] + boundary_names[2, tree] = :x_pos end end - end - - return element_node_matrix -end -function parse_node_sets(meshfile, boundary_symbols) - nodes_dict = Dict{Symbol, Vector{Int64}}() - current_symbol = nothing - current_nodes = Int64[] + # Boundaries in y-direction + if !periodicity[2] + for cell_x in 1:trees_per_dimension[1] + tree = linear_indices[cell_x, 1] + boundary_names[3, tree] = :y_neg - open(meshfile, "r") do file - for line in eachline(file) - # Check if the line contains nodes assembled in a special set, i.e., a physical boundary - if startswith(line, "*NSET,NSET=") - # Safe the previous nodeset - if current_symbol !== nothing - nodes_dict[current_symbol] = current_nodes - end - - current_symbol = Symbol(split(line, "=")[2]) - if current_symbol in boundary_symbols - # New nodeset - current_nodes = Int64[] - else # Read only boundary node sets - current_symbol = nothing - end - elseif current_symbol !== nothing # Read only if there was already a nodeset specified - try # Check if line contains nodes - # There is always a trailing comma, remove the corresponding empty string - append!(current_nodes, parse.(Int64, split(line, ",")[1:(end - 1)])) - catch # Something different, stop reading in nodes - # If parsing fails, set current_symbol to nothing - nodes_dict[current_symbol] = current_nodes - current_symbol = nothing - end + tree = linear_indices[cell_x, end] + boundary_names[4, tree] = :y_pos end end - # Safe the previous nodeset - if current_symbol !== nothing - nodes_dict[current_symbol] = current_nodes - end end - for symbol in boundary_symbols - if !haskey(nodes_dict, symbol) - @warn "No nodes found for nodeset :" * "$symbol" * " !" + # 3D version + function structured_boundary_names!( + boundary_names, trees_per_dimension::NTuple{3}, + periodicity + ) + linear_indices = LinearIndices(trees_per_dimension) + + # Boundaries in x-direction + if !periodicity[1] + for cell_z in 1:trees_per_dimension[3], cell_y in 1:trees_per_dimension[2] + tree = linear_indices[1, cell_y, cell_z] + boundary_names[1, tree] = :x_neg + + tree = linear_indices[end, cell_y, cell_z] + boundary_names[2, tree] = :x_pos + end end - end - return nodes_dict -end - -# This function assigns the edges of elements to boundaries by -# checking if the nodes that define the edges are part of nodesets which correspond to boundaries. -function assign_boundaries_standard_abaqus!(boundary_names, n_trees, - element_node_matrix, node_set_dict, - ::Val{2}) # 2D version - for tree in 1:n_trees - tree_nodes = element_node_matrix[tree, :] - # For node labeling, see - # https://docs.software.vt.edu/abaqusv2022/English/SIMACAEELMRefMap/simaelm-r-2delem.htm#simaelm-r-2delem-t-nodedef1 - # and search for "Node ordering and face numbering on elements" - for boundary in keys(node_set_dict) # Loop over specified boundaries - # Check bottom edge - if tree_nodes[1] in node_set_dict[boundary] && - tree_nodes[2] in node_set_dict[boundary] - # Bottom boundary is position 3 in p4est indexing - boundary_names[3, tree] = boundary - end - # Check right edge - if tree_nodes[2] in node_set_dict[boundary] && - tree_nodes[3] in node_set_dict[boundary] - # Right boundary is position 2 in p4est indexing - boundary_names[2, tree] = boundary - end - # Check top edge - if tree_nodes[3] in node_set_dict[boundary] && - tree_nodes[4] in node_set_dict[boundary] - # Top boundary is position 4 in p4est indexing - boundary_names[4, tree] = boundary - end - # Check left edge - if tree_nodes[4] in node_set_dict[boundary] && - tree_nodes[1] in node_set_dict[boundary] - # Left boundary is position 1 in p4est indexing - boundary_names[1, tree] = boundary + # Boundaries in y-direction + if !periodicity[2] + for cell_z in 1:trees_per_dimension[3], cell_x in 1:trees_per_dimension[1] + tree = linear_indices[cell_x, 1, cell_z] + boundary_names[3, tree] = :y_neg + + tree = linear_indices[cell_x, end, cell_z] + boundary_names[4, tree] = :y_pos end end - end - return boundary_names -end - -# This function assigns the edges of elements to boundaries by -# checking if the nodes that define the faces are part of nodesets which correspond to boundaries. -function assign_boundaries_standard_abaqus!(boundary_names, n_trees, - element_node_matrix, node_set_dict, - ::Val{3}) # 3D version - for tree in 1:n_trees - tree_nodes = element_node_matrix[tree, :] - # For node labeling, see - # https://web.mit.edu/calculix_v2.7/CalculiX/ccx_2.7/doc/ccx/node26.html - for boundary in keys(node_set_dict) # Loop over specified boundaries - # Check "front face" (y_min) - if tree_nodes[1] in node_set_dict[boundary] && - tree_nodes[2] in node_set_dict[boundary] && - tree_nodes[5] in node_set_dict[boundary] && - tree_nodes[6] in node_set_dict[boundary] - # Front face is position 3 in p4est indexing - boundary_names[3, tree] = boundary - end - # Check "back face" (y_max) - if tree_nodes[3] in node_set_dict[boundary] && - tree_nodes[4] in node_set_dict[boundary] && - tree_nodes[7] in node_set_dict[boundary] && - tree_nodes[8] in node_set_dict[boundary] - # Front face is position 4 in p4est indexing - boundary_names[4, tree] = boundary - end - # Check "left face" (x_min) - if tree_nodes[1] in node_set_dict[boundary] && - tree_nodes[4] in node_set_dict[boundary] && - tree_nodes[5] in node_set_dict[boundary] && - tree_nodes[8] in node_set_dict[boundary] - # Left face is position 1 in p4est indexing - boundary_names[1, tree] = boundary - end - # Check "right face" (x_max) - if tree_nodes[2] in node_set_dict[boundary] && - tree_nodes[3] in node_set_dict[boundary] && - tree_nodes[6] in node_set_dict[boundary] && - tree_nodes[7] in node_set_dict[boundary] - # Right face is position 2 in p4est indexing - boundary_names[2, tree] = boundary - end - # Check "bottom face" (z_min) - if tree_nodes[1] in node_set_dict[boundary] && - tree_nodes[2] in node_set_dict[boundary] && - tree_nodes[3] in node_set_dict[boundary] && - tree_nodes[4] in node_set_dict[boundary] - # Bottom face is position 5 in p4est indexing - boundary_names[5, tree] = boundary - end - # Check "top face" (z_max) - if tree_nodes[5] in node_set_dict[boundary] && - tree_nodes[6] in node_set_dict[boundary] && - tree_nodes[7] in node_set_dict[boundary] && - tree_nodes[8] in node_set_dict[boundary] - # Top face is position 6 in p4est indexing - boundary_names[6, tree] = boundary + # Boundaries in z-direction + if !periodicity[3] + for cell_y in 1:trees_per_dimension[2], cell_x in 1:trees_per_dimension[1] + tree = linear_indices[cell_x, cell_y, 1] + boundary_names[5, tree] = :z_neg + + tree = linear_indices[cell_x, cell_y, end] + boundary_names[6, tree] = :z_pos end end end - return boundary_names -end - -""" - P4estMeshCubedSphere(trees_per_face_dimension, layers, inner_radius, thickness; - polydeg, RealT=Float64, + """ + P4estMesh{NDIMS}(meshfile::String; + mapping=nothing, polydeg=1, RealT=Float64, initial_refinement_level=0, unsaved_changes=true, - p4est_partition_allow_for_coarsening=true) - -Build a "Cubed Sphere" mesh as `P4estMesh` with -`6 * trees_per_face_dimension^2 * layers` trees. - -The mesh will have two boundaries, `:inside` and `:outside`. - -# Arguments -- `trees_per_face_dimension::Integer`: the number of trees in the first two local dimensions of - each face. -- `layers::Integer`: the number of trees in the third local dimension of each face, i.e., the number - of layers of the sphere. -- `inner_radius::Integer`: the inner radius of the sphere. -- `thickness::Integer`: the thickness of the sphere. The outer radius will be `inner_radius + thickness`. -- `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. - The mapping will be approximated by an interpolation polynomial - of the specified degree for each tree. -- `RealT::Type`: the type that should be used for coordinates. -- `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. -- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file. -- `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity - independent of domain partitioning. Should be `false` for static meshes - to permit more fine-grained partitioning. -""" -function P4estMeshCubedSphere(trees_per_face_dimension, layers, inner_radius, thickness; - polydeg, RealT = Float64, - initial_refinement_level = 0, unsaved_changes = true, - p4est_partition_allow_for_coarsening = true) - connectivity = connectivity_cubed_sphere(trees_per_face_dimension, layers) - - n_trees = 6 * trees_per_face_dimension^2 * layers - - basis = LobattoLegendreBasis(RealT, polydeg) - nodes = basis.nodes - - tree_node_coordinates = Array{RealT, 5}(undef, 3, - ntuple(_ -> length(nodes), 3)..., - n_trees) - calc_tree_node_coordinates!(tree_node_coordinates, nodes, trees_per_face_dimension, - layers, - inner_radius, thickness) - - p4est = new_p4est(connectivity, initial_refinement_level) - - boundary_names = fill(Symbol("---"), 2 * 3, n_trees) - boundary_names[5, :] .= Symbol("inside") - boundary_names[6, :] .= Symbol("outside") - - return P4estMesh{3}(p4est, tree_node_coordinates, nodes, - boundary_names, "", unsaved_changes, - p4est_partition_allow_for_coarsening) -end - -# Create a new p4est_connectivity that represents a structured rectangle. -# Similar to p4est_connectivity_new_brick, but doesn't use Morton order. -# This order makes `calc_tree_node_coordinates!` below and the calculation -# of `boundary_names` above easier but is irrelevant otherwise. -# 2D version -function connectivity_structured(n_cells_x, n_cells_y, periodicity) - linear_indices = LinearIndices((n_cells_x, n_cells_y)) - - # Vertices represent the coordinates of the forest. This is used by `p4est` - # to write VTK files. - # Trixi.jl doesn't use the coordinates from `p4est`, so the vertices can be empty. - n_vertices = 0 - n_trees = n_cells_x * n_cells_y - # No corner connectivity is needed - n_corners = 0 - vertices = C_NULL - tree_to_vertex = C_NULL - - tree_to_tree = Array{p4est_topidx_t, 2}(undef, 4, n_trees) - tree_to_face = Array{Int8, 2}(undef, 4, n_trees) - - for cell_y in 1:n_cells_y, cell_x in 1:n_cells_x - tree = linear_indices[cell_x, cell_y] - - # Subtract 1 because `p4est` uses zero-based indexing - # Negative x-direction - if cell_x > 1 - tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y] - 1 - tree_to_face[1, tree] = 1 - elseif periodicity[1] - tree_to_tree[1, tree] = linear_indices[n_cells_x, cell_y] - 1 - tree_to_face[1, tree] = 1 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[1, tree] = tree - 1 - tree_to_face[1, tree] = 0 + p4est_partition_allow_for_coarsening=true, + boundary_symbols = nothing) + + Main mesh constructor for the `P4estMesh` that imports an unstructured, conforming + mesh from an Abaqus mesh file (`.inp`). Each element of the conforming mesh parsed + from the `meshfile` is created as a [`p4est`](https://github.com/cburstedde/p4est) + tree datatype. + + To create a curved unstructured mesh `P4estMesh` two strategies are available: + + - `p4est_mesh_from_hohqmesh_abaqus`: High-order, curved boundary information created by + [`HOHQMesh.jl`](https://github.com/trixi-framework/HOHQMesh.jl) is + available in the `meshfile`. The mesh polynomial degree `polydeg` + of the boundaries is provided from the `meshfile`. The computation of + the mapped tree coordinates is done with transfinite interpolation + with linear blending similar to [`UnstructuredMesh2D`](@ref). Boundary name + information is also parsed from the `meshfile` such that different boundary + conditions can be set at each named boundary on a given tree. + - `p4est_mesh_from_standard_abaqus`: By default, with `mapping=nothing` and `polydeg=1`, this creates a + straight-sided from the information parsed from the `meshfile`. If a mapping + function is specified then it computes the mapped tree coordinates via polynomial + interpolants with degree `polydeg`. The mesh created by this function will only + have one boundary `:all` if `boundary_symbols` is not specified. + If `boundary_symbols` is specified the mesh file will be parsed for nodesets defining + the boundary nodes from which boundary edges (2D) and faces (3D) will be assigned. + + Note that the `mapping` and `polydeg` keyword arguments are only used by the `p4est_mesh_from_standard_abaqus` + function. The `p4est_mesh_from_hohqmesh_abaqus` function obtains the mesh `polydeg` directly from the `meshfile` + and constructs the transfinite mapping internally. + + The particular strategy is selected according to the header present in the `meshfile` where + the constructor checks whether or not the `meshfile` was created with + [HOHQMesh.jl](https://github.com/trixi-framework/HOHQMesh.jl). + If the Abaqus file header is not present in the `meshfile` then the `P4estMesh` is created + with the function `p4est_mesh_from_standard_abaqus`. + + The default keyword argument `initial_refinement_level=0` corresponds to a forest + where the number of trees is the same as the number of elements in the original `meshfile`. + Increasing the `initial_refinement_level` allows one to uniformly refine the base mesh given + in the `meshfile` to create a forest with more trees before the simulation begins. + For example, if a two-dimensional base mesh contains 25 elements then setting + `initial_refinement_level=1` creates an initial forest of `2^2 * 25 = 100` trees. + + # Arguments + - `meshfile::String`: an uncurved Abaqus mesh file that can be imported by `p4est`. + - `mapping`: a function of `NDIMS` variables to describe the mapping that transforms + the imported mesh to the physical domain. Use `nothing` for the identity map. + - `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. + The mapping will be approximated by an interpolation polynomial + of the specified degree for each tree. + The default of `1` creates an uncurved geometry. Use a higher value if the mapping + will curve the imported uncurved mesh. + - `RealT::Type`: the type that should be used for coordinates. + - `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. + - `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file. + - `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity + independent of domain partitioning. Should be `false` for static meshes + to permit more fine-grained partitioning. + - `boundary_symbols::Vector{Symbol}`: A vector of symbols that correspond to the boundary names in the `meshfile`. + If `nothing` is passed then all boundaries are named `:all`. + """ + function P4estMesh{NDIMS}( + meshfile::String; + mapping = nothing, polydeg = 1, RealT = Float64, + initial_refinement_level = 0, unsaved_changes = true, + p4est_partition_allow_for_coarsening = true, + boundary_symbols = nothing + ) where {NDIMS} + # Prevent `p4est` from crashing Julia if the file doesn't exist + @assert isfile(meshfile) + + # Read in the Header of the meshfile to determine which constructor is appropriate + header = open(meshfile, "r") do io + readline(io) # *Header of the Abaqus file; discarded + readline(io) # Readin the actual header information end - # Positive x-direction - if cell_x < n_cells_x - tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y] - 1 - tree_to_face[2, tree] = 0 - elseif periodicity[1] - tree_to_tree[2, tree] = linear_indices[1, cell_y] - 1 - tree_to_face[2, tree] = 0 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[2, tree] = tree - 1 - tree_to_face[2, tree] = 1 + # Check if the meshfile was generated using HOHQMesh + if header == " File created by HOHQMesh" + # Mesh curvature and boundary naming is handled with additional information available in meshfile + p4est, tree_node_coordinates, nodes, boundary_names = p4est_mesh_from_hohqmesh_abaqus( + meshfile, + initial_refinement_level, + NDIMS, + RealT + ) + else + # Mesh curvature is handled directly by applying the mapping keyword argument + p4est, tree_node_coordinates, nodes, boundary_names = p4est_mesh_from_standard_abaqus( + meshfile, + mapping, + polydeg, + initial_refinement_level, + NDIMS, + RealT, + boundary_symbols + ) end - # Negative y-direction - if cell_y > 1 - tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1] - 1 - tree_to_face[3, tree] = 3 - elseif periodicity[2] - tree_to_tree[3, tree] = linear_indices[cell_x, n_cells_y] - 1 - tree_to_face[3, tree] = 3 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[3, tree] = tree - 1 - tree_to_face[3, tree] = 2 - end + return P4estMesh{NDIMS}( + p4est, tree_node_coordinates, nodes, + boundary_names, "", unsaved_changes, + p4est_partition_allow_for_coarsening + ) + end + + # Wrapper for `p4est_connectivity_from_hohqmesh_abaqus`. The latter is used + # by `T8codeMesh`, too. + function p4est_mesh_from_hohqmesh_abaqus( + meshfile, initial_refinement_level, + n_dimensions, RealT + ) + connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_hohqmesh_abaqus( + meshfile, + initial_refinement_level, + n_dimensions, + RealT + ) + + p4est = new_p4est(connectivity, initial_refinement_level) + + return p4est, tree_node_coordinates, nodes, boundary_names + end + + # Wrapper for `p4est_connectivity_from_standard_abaqus`. The latter is used + # by `T8codeMesh`, too. + function p4est_mesh_from_standard_abaqus( + meshfile, mapping, polydeg, + initial_refinement_level, n_dimensions, RealT, + boundary_symbols + ) + connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_standard_abaqus( + meshfile, + mapping, + polydeg, + initial_refinement_level, + n_dimensions, + RealT, + boundary_symbols + ) + + p4est = new_p4est(connectivity, initial_refinement_level) + + return p4est, tree_node_coordinates, nodes, boundary_names + end + + # Create the mesh connectivity, mapped node coordinates within each tree, reference nodes in [-1,1] + # and a list of boundary names for the `P4estMesh`. High-order boundary curve information as well as + # the boundary names on each tree are provided by the `meshfile` created by + # [`HOHQMesh.jl`](https://github.com/trixi-framework/HOHQMesh.jl). + function p4est_connectivity_from_hohqmesh_abaqus( + meshfile, initial_refinement_level, + n_dimensions, RealT + ) + # Create the mesh connectivity using `p4est` + connectivity = read_inp_p4est(meshfile, Val(n_dimensions)) + connectivity_pw = PointerWrapper(connectivity) + + # These need to be of the type Int for unsafe_wrap below to work + n_trees::Int = connectivity_pw.num_trees[] + n_vertices::Int = connectivity_pw.num_vertices[] + + # Extract a copy of the element vertices to compute the tree node coordinates + vertices = unsafe_wrap(Array, connectivity_pw.vertices, (3, n_vertices)) + + # Readin all the information from the mesh file into a string array + file_lines = readlines(open(meshfile)) - # Positive y-direction - if cell_y < n_cells_y - tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1] - 1 - tree_to_face[4, tree] = 2 - elseif periodicity[2] - tree_to_tree[4, tree] = linear_indices[cell_x, 1] - 1 - tree_to_face[4, tree] = 2 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[4, tree] = tree - 1 - tree_to_face[4, tree] = 3 + # Get the file index where the mesh polynomial degree is given in the meshfile + file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) + + # Get the polynomial order of the mesh boundary information + current_line = split(file_lines[file_idx]) + mesh_polydeg = parse(Int, current_line[6]) + mesh_nnodes = mesh_polydeg + 1 + + # Create the Chebyshev-Gauss-Lobatto nodes used by HOHQMesh to represent the boundaries + cheby_nodes, _ = chebyshev_gauss_lobatto_nodes_weights(mesh_nnodes) + nodes = SVector{mesh_nnodes}(cheby_nodes) + + # Allocate the memory for the tree node coordinates + tree_node_coordinates = Array{RealT, n_dimensions + 2}( + undef, n_dimensions, + ntuple( + _ -> length(nodes), + n_dimensions + )..., + n_trees + ) + + # Compute the tree node coordinates and return the updated file index + file_idx = calc_tree_node_coordinates!( + tree_node_coordinates, file_lines, nodes, + vertices, RealT + ) + + # Allocate the memory for the boundary labels + boundary_names = Array{Symbol}(undef, (2 * n_dimensions, n_trees)) + + # Read in the boundary names from the last portion of the meshfile + # Note here the boundary names where "---" means an internal connection + for tree in 1:n_trees + current_line = split(file_lines[file_idx]) + boundary_names[:, tree] = map(Symbol, current_line[2:end]) + file_idx += 1 end + + return connectivity, tree_node_coordinates, nodes, boundary_names end - tree_to_corner = C_NULL - # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." - # We don't need corner connectivity, so this is a trivial case. - ctt_offset = zeros(p4est_topidx_t, 1) - - corner_to_tree = C_NULL - corner_to_corner = C_NULL - - connectivity = p4est_connectivity_new_copy(n_vertices, n_trees, n_corners, - vertices, tree_to_vertex, - tree_to_tree, tree_to_face, - tree_to_corner, ctt_offset, - corner_to_tree, corner_to_corner) - - @assert p4est_connectivity_is_valid(connectivity) == 1 - - return connectivity -end - -# 3D version -function connectivity_structured(n_cells_x, n_cells_y, n_cells_z, periodicity) - linear_indices = LinearIndices((n_cells_x, n_cells_y, n_cells_z)) - - # Vertices represent the coordinates of the forest. This is used by `p4est` - # to write VTK files. - # Trixi.jl doesn't use the coordinates from `p4est`, so the vertices can be empty. - n_vertices = 0 - n_trees = n_cells_x * n_cells_y * n_cells_z - # No edge connectivity is needed - n_edges = 0 - # No corner connectivity is needed - n_corners = 0 - vertices = C_NULL - tree_to_vertex = C_NULL - - tree_to_tree = Array{p4est_topidx_t, 2}(undef, 6, n_trees) - tree_to_face = Array{Int8, 2}(undef, 6, n_trees) - - for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x - tree = linear_indices[cell_x, cell_y, cell_z] - - # Subtract 1 because `p4est` uses zero-based indexing - # Negative x-direction - if cell_x > 1 - tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y, cell_z] - 1 - tree_to_face[1, tree] = 1 - elseif periodicity[1] - tree_to_tree[1, tree] = linear_indices[n_cells_x, cell_y, cell_z] - 1 - tree_to_face[1, tree] = 1 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[1, tree] = tree - 1 - tree_to_face[1, tree] = 0 + # Create the mesh connectivity, mapped node coordinates within each tree, reference nodes in [-1,1] + # and a list of boundary names for the `P4estMesh`. The tree node coordinates are computed according to + # the `mapping` passed to this function using polynomial interpolants of degree `polydeg`. All boundary + # names are given the name `:all`. + function p4est_connectivity_from_standard_abaqus( + meshfile, mapping, polydeg, + initial_refinement_level, n_dimensions, + RealT, + boundary_symbols + ) + # Create the mesh connectivity using `p4est` + connectivity = read_inp_p4est(meshfile, Val(n_dimensions)) + connectivity_pw = PointerWrapper(connectivity) + + # These need to be of the type Int for unsafe_wrap below to work + n_trees::Int = connectivity_pw.num_trees[] + n_vertices::Int = connectivity_pw.num_vertices[] + + vertices = unsafe_wrap(Array, connectivity_pw.vertices, (3, n_vertices)) + tree_to_vertex = unsafe_wrap( + Array, connectivity_pw.tree_to_vertex, + (2^n_dimensions, n_trees) + ) + + basis = LobattoLegendreBasis(RealT, polydeg) + nodes = basis.nodes + + tree_node_coordinates = Array{RealT, n_dimensions + 2}( + undef, n_dimensions, + ntuple( + _ -> length(nodes), + n_dimensions + )..., + n_trees + ) + calc_tree_node_coordinates!( + tree_node_coordinates, nodes, mapping, vertices, + tree_to_vertex + ) + + if boundary_symbols === nothing + # There's no simple and generic way to distinguish boundaries without any information given. + # Name all of them :all. + boundary_names = fill(:all, 2 * n_dimensions, n_trees) + else # Boundary information given + # Read in nodes belonging to boundaries + node_set_dict = parse_node_sets(meshfile, boundary_symbols) + # Read in all elements with associated nodes to specify the boundaries + element_node_matrix = parse_elements(meshfile, n_trees, n_dimensions) + + # Initialize boundary information matrix with symbol for no boundary / internal connection + boundary_names = fill(Symbol("---"), 2 * n_dimensions, n_trees) + + # Fill `boundary_names` such that it can be processed by p4est + assign_boundaries_standard_abaqus!( + boundary_names, n_trees, + element_node_matrix, node_set_dict, + Val(n_dimensions) + ) end - # Positive x-direction - if cell_x < n_cells_x - tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y, cell_z] - 1 - tree_to_face[2, tree] = 0 - elseif periodicity[1] - tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z] - 1 - tree_to_face[2, tree] = 0 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[2, tree] = tree - 1 - tree_to_face[2, tree] = 1 + return connectivity, tree_node_coordinates, nodes, boundary_names + end + + function parse_elements(meshfile, n_trees, n_dims) + @assert n_dims in (2, 3) "Only 2D and 3D meshes are supported" + # Valid element types (that can be processed by p4est) based on dimension + element_types = n_dims == 2 ? + [ + "*ELEMENT, type=CPS4", "*ELEMENT, type=C2D4", + "*ELEMENT, type=S4", + ] : ["*ELEMENT, type=C3D8"] + # 2D quads: 4 nodes + element index, 3D hexes: 8 nodes + element index + expected_content_length = n_dims == 2 ? 5 : 9 + + element_node_matrix = Matrix{Int64}(undef, n_trees, expected_content_length - 1) + el_list_follows = false + tree_id = 1 + + open(meshfile, "r") do file + for line in eachline(file) + if any(startswith(line, el_type) for el_type in element_types) + el_list_follows = true + elseif el_list_follows + content = split(line, ",") + if length(content) == expected_content_length # Check that we still read in connectivity data + content_int = parse.(Int64, content) + # Add constituent nodes to the element_node_matrix. + # Important: Do not use index from the Abaqus file, but the one from p4est. + element_node_matrix[tree_id, :] = content_int[2:end] # First entry is element id + tree_id += 1 + else # Processed all elements for this ELSET + el_list_follows = false + end + end + end end - # Negative y-direction - if cell_y > 1 - tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1, cell_z] - 1 - tree_to_face[3, tree] = 3 - elseif periodicity[2] - tree_to_tree[3, tree] = linear_indices[cell_x, n_cells_y, cell_z] - 1 - tree_to_face[3, tree] = 3 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[3, tree] = tree - 1 - tree_to_face[3, tree] = 2 + return element_node_matrix + end + + function parse_node_sets(meshfile, boundary_symbols) + nodes_dict = Dict{Symbol, Vector{Int64}}() + current_symbol = nothing + current_nodes = Int64[] + + open(meshfile, "r") do file + for line in eachline(file) + # Check if the line contains nodes assembled in a special set, i.e., a physical boundary + if startswith(line, "*NSET,NSET=") + # Safe the previous nodeset + if current_symbol !== nothing + nodes_dict[current_symbol] = current_nodes + end + + current_symbol = Symbol(split(line, "=")[2]) + if current_symbol in boundary_symbols + # New nodeset + current_nodes = Int64[] + else # Read only boundary node sets + current_symbol = nothing + end + elseif current_symbol !== nothing # Read only if there was already a nodeset specified + try # Check if line contains nodes + # There is always a trailing comma, remove the corresponding empty string + append!(current_nodes, parse.(Int64, split(line, ",")[1:(end - 1)])) + catch # Something different, stop reading in nodes + # If parsing fails, set current_symbol to nothing + nodes_dict[current_symbol] = current_nodes + current_symbol = nothing + end + end + end + # Safe the previous nodeset + if current_symbol !== nothing + nodes_dict[current_symbol] = current_nodes + end end - # Positive y-direction - if cell_y < n_cells_y - tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1, cell_z] - 1 - tree_to_face[4, tree] = 2 - elseif periodicity[2] - tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z] - 1 - tree_to_face[4, tree] = 2 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[4, tree] = tree - 1 - tree_to_face[4, tree] = 3 + for symbol in boundary_symbols + if !haskey(nodes_dict, symbol) + @warn "No nodes found for nodeset :" * "$symbol" * " !" + end end - # Negative z-direction - if cell_z > 1 - tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, cell_z - 1] - 1 - tree_to_face[5, tree] = 5 - elseif periodicity[3] - tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, n_cells_z] - 1 - tree_to_face[5, tree] = 5 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[5, tree] = tree - 1 - tree_to_face[5, tree] = 4 + return nodes_dict + end + + # This function assigns the edges of elements to boundaries by + # checking if the nodes that define the edges are part of nodesets which correspond to boundaries. + function assign_boundaries_standard_abaqus!( + boundary_names, n_trees, + element_node_matrix, node_set_dict, + ::Val{2} + ) # 2D version + for tree in 1:n_trees + tree_nodes = element_node_matrix[tree, :] + # For node labeling, see + # https://docs.software.vt.edu/abaqusv2022/English/SIMACAEELMRefMap/simaelm-r-2delem.htm#simaelm-r-2delem-t-nodedef1 + # and search for "Node ordering and face numbering on elements" + for boundary in keys(node_set_dict) # Loop over specified boundaries + # Check bottom edge + if tree_nodes[1] in node_set_dict[boundary] && + tree_nodes[2] in node_set_dict[boundary] + # Bottom boundary is position 3 in p4est indexing + boundary_names[3, tree] = boundary + end + # Check right edge + if tree_nodes[2] in node_set_dict[boundary] && + tree_nodes[3] in node_set_dict[boundary] + # Right boundary is position 2 in p4est indexing + boundary_names[2, tree] = boundary + end + # Check top edge + if tree_nodes[3] in node_set_dict[boundary] && + tree_nodes[4] in node_set_dict[boundary] + # Top boundary is position 4 in p4est indexing + boundary_names[4, tree] = boundary + end + # Check left edge + if tree_nodes[4] in node_set_dict[boundary] && + tree_nodes[1] in node_set_dict[boundary] + # Left boundary is position 1 in p4est indexing + boundary_names[1, tree] = boundary + end + end end - # Positive z-direction - if cell_z < n_cells_z - tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, cell_z + 1] - 1 - tree_to_face[6, tree] = 4 - elseif periodicity[3] - tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, 1] - 1 - tree_to_face[6, tree] = 4 - else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) - tree_to_tree[6, tree] = tree - 1 - tree_to_face[6, tree] = 5 + return boundary_names + end + + # This function assigns the edges of elements to boundaries by + # checking if the nodes that define the faces are part of nodesets which correspond to boundaries. + function assign_boundaries_standard_abaqus!( + boundary_names, n_trees, + element_node_matrix, node_set_dict, + ::Val{3} + ) # 3D version + for tree in 1:n_trees + tree_nodes = element_node_matrix[tree, :] + # For node labeling, see + # https://web.mit.edu/calculix_v2.7/CalculiX/ccx_2.7/doc/ccx/node26.html + for boundary in keys(node_set_dict) # Loop over specified boundaries + # Check "front face" (y_min) + if tree_nodes[1] in node_set_dict[boundary] && + tree_nodes[2] in node_set_dict[boundary] && + tree_nodes[5] in node_set_dict[boundary] && + tree_nodes[6] in node_set_dict[boundary] + # Front face is position 3 in p4est indexing + boundary_names[3, tree] = boundary + end + # Check "back face" (y_max) + if tree_nodes[3] in node_set_dict[boundary] && + tree_nodes[4] in node_set_dict[boundary] && + tree_nodes[7] in node_set_dict[boundary] && + tree_nodes[8] in node_set_dict[boundary] + # Front face is position 4 in p4est indexing + boundary_names[4, tree] = boundary + end + # Check "left face" (x_min) + if tree_nodes[1] in node_set_dict[boundary] && + tree_nodes[4] in node_set_dict[boundary] && + tree_nodes[5] in node_set_dict[boundary] && + tree_nodes[8] in node_set_dict[boundary] + # Left face is position 1 in p4est indexing + boundary_names[1, tree] = boundary + end + # Check "right face" (x_max) + if tree_nodes[2] in node_set_dict[boundary] && + tree_nodes[3] in node_set_dict[boundary] && + tree_nodes[6] in node_set_dict[boundary] && + tree_nodes[7] in node_set_dict[boundary] + # Right face is position 2 in p4est indexing + boundary_names[2, tree] = boundary + end + # Check "bottom face" (z_min) + if tree_nodes[1] in node_set_dict[boundary] && + tree_nodes[2] in node_set_dict[boundary] && + tree_nodes[3] in node_set_dict[boundary] && + tree_nodes[4] in node_set_dict[boundary] + # Bottom face is position 5 in p4est indexing + boundary_names[5, tree] = boundary + end + # Check "top face" (z_max) + if tree_nodes[5] in node_set_dict[boundary] && + tree_nodes[6] in node_set_dict[boundary] && + tree_nodes[7] in node_set_dict[boundary] && + tree_nodes[8] in node_set_dict[boundary] + # Top face is position 6 in p4est indexing + boundary_names[6, tree] = boundary + end + end end + + return boundary_names end - tree_to_edge = C_NULL - # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." - # We don't need edge connectivity, so this is a trivial case. - ett_offset = zeros(p4est_topidx_t, 1) - edge_to_tree = C_NULL - edge_to_edge = C_NULL - - tree_to_corner = C_NULL - # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." - # We don't need corner connectivity, so this is a trivial case. - ctt_offset = zeros(p4est_topidx_t, 1) - - corner_to_tree = C_NULL - corner_to_corner = C_NULL - - connectivity = p8est_connectivity_new_copy(n_vertices, n_trees, n_corners, n_edges, - vertices, tree_to_vertex, - tree_to_tree, tree_to_face, - tree_to_edge, ett_offset, - edge_to_tree, edge_to_edge, - tree_to_corner, ctt_offset, - corner_to_tree, corner_to_corner) - - @assert p8est_connectivity_is_valid(connectivity) == 1 - - return connectivity -end - -function connectivity_cubed_sphere(trees_per_face_dimension, layers) - n_cells_x = n_cells_y = trees_per_face_dimension - n_cells_z = layers - - linear_indices = LinearIndices((trees_per_face_dimension, trees_per_face_dimension, - layers, 6)) - - # Vertices represent the coordinates of the forest. This is used by `p4est` - # to write VTK files. - # Trixi.jl doesn't use the coordinates from `p4est`, so the vertices can be empty. - n_vertices = 0 - n_trees = 6 * n_cells_x * n_cells_y * n_cells_z - # No edge connectivity is needed - n_edges = 0 - # No corner connectivity is needed - n_corners = 0 - vertices = C_NULL - tree_to_vertex = C_NULL - - tree_to_tree = Array{p4est_topidx_t, 2}(undef, 6, n_trees) - tree_to_face = Array{Int8, 2}(undef, 6, n_trees) - - # Illustration of the local coordinates of each face. ξ and η are the first - # local coordinates of each face. The third local coordinate ζ is always - # pointing outwards, which yields a right-handed coordinate system for each face. - # ┌────────────────────────────────────────────────────┐ - # ╱│ ╱│ - # ╱ │ ξ <───┐ ╱ │ - # ╱ │ ╱ ╱ │ - # ╱ │ 4 (+y) V ╱ │ - # ╱ │ η ╱ │ - # ╱ │ ╱ │ - # ╱ │ ╱ │ - # ╱ │ ╱ │ - # ╱ │ ╱ │ - # ╱ │ 5 (-z) η ╱ │ - # ╱ │ ↑ ╱ │ - # ╱ │ │ ╱ │ - # ╱ │ ξ <───┘ ╱ │ - # ┌────────────────────────────────────────────────────┐ 2 (+x) │ - # │ │ │ │ - # │ │ │ ξ │ - # │ │ │ ↑ │ - # │ 1 (-x) │ │ │ │ - # │ │ │ │ │ - # │ ╱│ │ │ ╱ │ - # │ V │ │ │ V │ - # │ η ↓ │ │ η │ - # │ ξ └──────────────────────────────────────│─────────────┘ - # │ ╱ η 6 (+z) │ ╱ - # │ ╱ ↑ │ ╱ - # │ ╱ │ │ ╱ - # │ ╱ └───> ξ │ ╱ - # │ ╱ │ ╱ - # │ ╱ │ ╱ Global coordinates: - # │ ╱ │ ╱ y - # │ ╱ ┌───> ξ │ ╱ ↑ - # │ ╱ ╱ │ ╱ │ - # │ ╱ V 3 (-y) │ ╱ │ - # │ ╱ η │ ╱ └─────> x - # │ ╱ │ ╱ ╱ - # │╱ │╱ V - # └────────────────────────────────────────────────────┘ z - for direction in 1:6 - for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x - tree = linear_indices[cell_x, cell_y, cell_z, direction] + """ + P4estMeshCubedSphere(trees_per_face_dimension, layers, inner_radius, thickness; + polydeg, RealT=Float64, + initial_refinement_level=0, unsaved_changes=true, + p4est_partition_allow_for_coarsening=true) + + Build a "Cubed Sphere" mesh as `P4estMesh` with + `6 * trees_per_face_dimension^2 * layers` trees. + + The mesh will have two boundaries, `:inside` and `:outside`. + + # Arguments + - `trees_per_face_dimension::Integer`: the number of trees in the first two local dimensions of + each face. + - `layers::Integer`: the number of trees in the third local dimension of each face, i.e., the number + of layers of the sphere. + - `inner_radius::Integer`: the inner radius of the sphere. + - `thickness::Integer`: the thickness of the sphere. The outer radius will be `inner_radius + thickness`. + - `polydeg::Integer`: polynomial degree used to store the geometry of the mesh. + The mapping will be approximated by an interpolation polynomial + of the specified degree for each tree. + - `RealT::Type`: the type that should be used for coordinates. + - `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. + - `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file. + - `p4est_partition_allow_for_coarsening::Bool`: Must be `true` when using AMR to make mesh adaptivity + independent of domain partitioning. Should be `false` for static meshes + to permit more fine-grained partitioning. + """ + function P4estMeshCubedSphere( + trees_per_face_dimension, layers, inner_radius, thickness; + polydeg, RealT = Float64, + initial_refinement_level = 0, unsaved_changes = true, + p4est_partition_allow_for_coarsening = true + ) + connectivity = connectivity_cubed_sphere(trees_per_face_dimension, layers) + + n_trees = 6 * trees_per_face_dimension^2 * layers + + basis = LobattoLegendreBasis(RealT, polydeg) + nodes = basis.nodes + + tree_node_coordinates = Array{RealT, 5}( + undef, 3, + ntuple(_ -> length(nodes), 3)..., + n_trees + ) + calc_tree_node_coordinates!( + tree_node_coordinates, nodes, trees_per_face_dimension, + layers, + inner_radius, thickness + ) + + p4est = new_p4est(connectivity, initial_refinement_level) + + boundary_names = fill(Symbol("---"), 2 * 3, n_trees) + boundary_names[5, :] .= Symbol("inside") + boundary_names[6, :] .= Symbol("outside") + + return P4estMesh{3}( + p4est, tree_node_coordinates, nodes, + boundary_names, "", unsaved_changes, + p4est_partition_allow_for_coarsening + ) + end + + # Create a new p4est_connectivity that represents a structured rectangle. + # Similar to p4est_connectivity_new_brick, but doesn't use Morton order. + # This order makes `calc_tree_node_coordinates!` below and the calculation + # of `boundary_names` above easier but is irrelevant otherwise. + # 2D version + function connectivity_structured(n_cells_x, n_cells_y, periodicity) + linear_indices = LinearIndices((n_cells_x, n_cells_y)) + + # Vertices represent the coordinates of the forest. This is used by `p4est` + # to write VTK files. + # Trixi.jl doesn't use the coordinates from `p4est`, so the vertices can be empty. + n_vertices = 0 + n_trees = n_cells_x * n_cells_y + # No corner connectivity is needed + n_corners = 0 + vertices = C_NULL + tree_to_vertex = C_NULL + + tree_to_tree = Array{p4est_topidx_t, 2}(undef, 4, n_trees) + tree_to_face = Array{Int8, 2}(undef, 4, n_trees) + + for cell_y in 1:n_cells_y, cell_x in 1:n_cells_x + tree = linear_indices[cell_x, cell_y] # Subtract 1 because `p4est` uses zero-based indexing # Negative x-direction - if cell_x > 1 # Connect to tree at the same face - tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y, cell_z, - direction] - 1 - tree_to_face[1, tree] = 1 - elseif direction == 1 # This is the -x face - target = 4 - tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1 - tree_to_face[1, tree] = 1 - elseif direction == 2 # This is the +x face - target = 3 - tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1 + if cell_x > 1 + tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y] - 1 tree_to_face[1, tree] = 1 - elseif direction == 3 # This is the -y face - target = 1 - tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1 + elseif periodicity[1] + tree_to_tree[1, tree] = linear_indices[n_cells_x, cell_y] - 1 tree_to_face[1, tree] = 1 - elseif direction == 4 # This is the +y face - target = 2 - tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1 - tree_to_face[1, tree] = 1 - elseif direction == 5 # This is the -z face - target = 2 - tree_to_tree[1, tree] = linear_indices[cell_y, 1, cell_z, target] - 1 - tree_to_face[1, tree] = 2 - else # direction == 6, this is the +z face - target = 1 - tree_to_tree[1, tree] = linear_indices[end - cell_y + 1, end, cell_z, - target] - 1 - tree_to_face[1, tree] = 9 # first face dimensions are oppositely oriented, add 6 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[1, tree] = tree - 1 + tree_to_face[1, tree] = 0 end # Positive x-direction - if cell_x < n_cells_x # Connect to tree at the same face - tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y, cell_z, - direction] - 1 - tree_to_face[2, tree] = 0 - elseif direction == 1 # This is the -x face - target = 3 - tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1 + if cell_x < n_cells_x + tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y] - 1 tree_to_face[2, tree] = 0 - elseif direction == 2 # This is the +x face - target = 4 - tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1 + elseif periodicity[1] + tree_to_tree[2, tree] = linear_indices[1, cell_y] - 1 tree_to_face[2, tree] = 0 - elseif direction == 3 # This is the -y face - target = 2 - tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[2, tree] = tree - 1 + tree_to_face[2, tree] = 1 + end + + # Negative y-direction + if cell_y > 1 + tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1] - 1 + tree_to_face[3, tree] = 3 + elseif periodicity[2] + tree_to_tree[3, tree] = linear_indices[cell_x, n_cells_y] - 1 + tree_to_face[3, tree] = 3 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[3, tree] = tree - 1 + tree_to_face[3, tree] = 2 + end + + # Positive y-direction + if cell_y < n_cells_y + tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1] - 1 + tree_to_face[4, tree] = 2 + elseif periodicity[2] + tree_to_tree[4, tree] = linear_indices[cell_x, 1] - 1 + tree_to_face[4, tree] = 2 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[4, tree] = tree - 1 + tree_to_face[4, tree] = 3 + end + end + + tree_to_corner = C_NULL + # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." + # We don't need corner connectivity, so this is a trivial case. + ctt_offset = zeros(p4est_topidx_t, 1) + + corner_to_tree = C_NULL + corner_to_corner = C_NULL + + connectivity = p4est_connectivity_new_copy( + n_vertices, n_trees, n_corners, + vertices, tree_to_vertex, + tree_to_tree, tree_to_face, + tree_to_corner, ctt_offset, + corner_to_tree, corner_to_corner + ) + + @assert p4est_connectivity_is_valid(connectivity) == 1 + + return connectivity + end + + # 3D version + function connectivity_structured(n_cells_x, n_cells_y, n_cells_z, periodicity) + linear_indices = LinearIndices((n_cells_x, n_cells_y, n_cells_z)) + + # Vertices represent the coordinates of the forest. This is used by `p4est` + # to write VTK files. + # Trixi.jl doesn't use the coordinates from `p4est`, so the vertices can be empty. + n_vertices = 0 + n_trees = n_cells_x * n_cells_y * n_cells_z + # No edge connectivity is needed + n_edges = 0 + # No corner connectivity is needed + n_corners = 0 + vertices = C_NULL + tree_to_vertex = C_NULL + + tree_to_tree = Array{p4est_topidx_t, 2}(undef, 6, n_trees) + tree_to_face = Array{Int8, 2}(undef, 6, n_trees) + + for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x + tree = linear_indices[cell_x, cell_y, cell_z] + + # Subtract 1 because `p4est` uses zero-based indexing + # Negative x-direction + if cell_x > 1 + tree_to_tree[1, tree] = linear_indices[cell_x - 1, cell_y, cell_z] - 1 + tree_to_face[1, tree] = 1 + elseif periodicity[1] + tree_to_tree[1, tree] = linear_indices[n_cells_x, cell_y, cell_z] - 1 + tree_to_face[1, tree] = 1 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[1, tree] = tree - 1 + tree_to_face[1, tree] = 0 + end + + # Positive x-direction + if cell_x < n_cells_x + tree_to_tree[2, tree] = linear_indices[cell_x + 1, cell_y, cell_z] - 1 tree_to_face[2, tree] = 0 - elseif direction == 4 # This is the +y face - target = 1 - tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1 + elseif periodicity[1] + tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z] - 1 tree_to_face[2, tree] = 0 - elseif direction == 5 # This is the -z face - target = 1 - tree_to_tree[2, tree] = linear_indices[end - cell_y + 1, 1, cell_z, - target] - 1 - tree_to_face[2, tree] = 8 # first face dimensions are oppositely oriented, add 6 - else # direction == 6, this is the +z face - target = 2 - tree_to_tree[2, tree] = linear_indices[cell_y, end, cell_z, target] - 1 - tree_to_face[2, tree] = 3 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[2, tree] = tree - 1 + tree_to_face[2, tree] = 1 end # Negative y-direction - if cell_y > 1 # Connect to tree at the same face - tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1, cell_z, - direction] - 1 - tree_to_face[3, tree] = 3 - elseif direction == 1 - target = 5 - tree_to_tree[3, tree] = linear_indices[end, end - cell_x + 1, cell_z, - target] - 1 - tree_to_face[3, tree] = 7 # first face dimensions are oppositely oriented, add 6 - elseif direction == 2 - target = 5 - tree_to_tree[3, tree] = linear_indices[1, cell_x, cell_z, target] - 1 - tree_to_face[3, tree] = 0 - elseif direction == 3 - target = 5 - tree_to_tree[3, tree] = linear_indices[end - cell_x + 1, 1, cell_z, - target] - 1 - tree_to_face[3, tree] = 8 # first face dimensions are oppositely oriented, add 6 - elseif direction == 4 - target = 5 - tree_to_tree[3, tree] = linear_indices[cell_x, end, cell_z, target] - 1 + if cell_y > 1 + tree_to_tree[3, tree] = linear_indices[cell_x, cell_y - 1, cell_z] - 1 tree_to_face[3, tree] = 3 - elseif direction == 5 - target = 3 - tree_to_tree[3, tree] = linear_indices[end - cell_x + 1, 1, cell_z, - target] - 1 - tree_to_face[3, tree] = 8 # first face dimensions are oppositely oriented, add 6 - else # direction == 6 - target = 3 - tree_to_tree[3, tree] = linear_indices[cell_x, end, cell_z, target] - 1 + elseif periodicity[2] + tree_to_tree[3, tree] = linear_indices[cell_x, n_cells_y, cell_z] - 1 tree_to_face[3, tree] = 3 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[3, tree] = tree - 1 + tree_to_face[3, tree] = 2 end # Positive y-direction - if cell_y < n_cells_y # Connect to tree at the same face - tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1, cell_z, - direction] - 1 + if cell_y < n_cells_y + tree_to_tree[4, tree] = linear_indices[cell_x, cell_y + 1, cell_z] - 1 tree_to_face[4, tree] = 2 - elseif direction == 1 - target = 6 - tree_to_tree[4, tree] = linear_indices[1, end - cell_x + 1, cell_z, - target] - 1 - tree_to_face[4, tree] = 6 # first face dimensions are oppositely oriented, add 6 - elseif direction == 2 - target = 6 - tree_to_tree[4, tree] = linear_indices[end, cell_x, cell_z, target] - 1 - tree_to_face[4, tree] = 1 - elseif direction == 3 - target = 6 - tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z, target] - 1 + elseif periodicity[2] + tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z] - 1 tree_to_face[4, tree] = 2 - elseif direction == 4 - target = 6 - tree_to_tree[4, tree] = linear_indices[end - cell_x + 1, end, cell_z, - target] - 1 - tree_to_face[4, tree] = 9 # first face dimensions are oppositely oriented, add 6 - elseif direction == 5 - target = 4 - tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z, target] - 1 - tree_to_face[4, tree] = 2 - else # direction == 6 - target = 4 - tree_to_tree[4, tree] = linear_indices[end - cell_x + 1, end, cell_z, - target] - 1 - tree_to_face[4, tree] = 9 # first face dimensions are oppositely oriented, add 6 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[4, tree] = tree - 1 + tree_to_face[4, tree] = 3 end # Negative z-direction if cell_z > 1 - tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, cell_z - 1, - direction] - 1 + tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, cell_z - 1] - 1 + tree_to_face[5, tree] = 5 + elseif periodicity[3] + tree_to_tree[5, tree] = linear_indices[cell_x, cell_y, n_cells_z] - 1 tree_to_face[5, tree] = 5 else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) tree_to_tree[5, tree] = tree - 1 @@ -1201,816 +1026,1094 @@ function connectivity_cubed_sphere(trees_per_face_dimension, layers) # Positive z-direction if cell_z < n_cells_z - tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, cell_z + 1, - direction] - 1 + tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, cell_z + 1] - 1 + tree_to_face[6, tree] = 4 + elseif periodicity[3] + tree_to_tree[6, tree] = linear_indices[cell_x, cell_y, 1] - 1 tree_to_face[6, tree] = 4 else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) tree_to_tree[6, tree] = tree - 1 tree_to_face[6, tree] = 5 end end + + tree_to_edge = C_NULL + # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." + # We don't need edge connectivity, so this is a trivial case. + ett_offset = zeros(p4est_topidx_t, 1) + edge_to_tree = C_NULL + edge_to_edge = C_NULL + + tree_to_corner = C_NULL + # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." + # We don't need corner connectivity, so this is a trivial case. + ctt_offset = zeros(p4est_topidx_t, 1) + + corner_to_tree = C_NULL + corner_to_corner = C_NULL + + connectivity = p8est_connectivity_new_copy( + n_vertices, n_trees, n_corners, n_edges, + vertices, tree_to_vertex, + tree_to_tree, tree_to_face, + tree_to_edge, ett_offset, + edge_to_tree, edge_to_edge, + tree_to_corner, ctt_offset, + corner_to_tree, corner_to_corner + ) + + @assert p8est_connectivity_is_valid(connectivity) == 1 + + return connectivity end - tree_to_edge = C_NULL - # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." - # We don't need edge connectivity, so this is a trivial case. - ett_offset = zeros(p4est_topidx_t, 1) - edge_to_tree = C_NULL - edge_to_edge = C_NULL - - tree_to_corner = C_NULL - # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." - # We don't need corner connectivity, so this is a trivial case. - ctt_offset = zeros(p4est_topidx_t, 1) - - corner_to_tree = C_NULL - corner_to_corner = C_NULL - - connectivity = p8est_connectivity_new_copy(n_vertices, n_trees, n_corners, n_edges, - vertices, tree_to_vertex, - tree_to_tree, tree_to_face, - tree_to_edge, ett_offset, - edge_to_tree, edge_to_edge, - tree_to_corner, ctt_offset, - corner_to_tree, corner_to_corner) - - @assert p8est_connectivity_is_valid(connectivity) == 1 - - return connectivity -end - -# Calculate physical coordinates of each node of a structured mesh. -# This function assumes a structured mesh with trees in row order. -# 2D version -function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, - nodes, mapping, trees_per_dimension) - linear_indices = LinearIndices(trees_per_dimension) - - # Get cell length in reference mesh - dx = 2 / trees_per_dimension[1] - dy = 2 / trees_per_dimension[2] - - for cell_y in 1:trees_per_dimension[2], cell_x in 1:trees_per_dimension[1] - tree_id = linear_indices[cell_x, cell_y] - - # Calculate node coordinates of reference mesh - cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 - cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 - - for j in eachindex(nodes), i in eachindex(nodes) - # node_coordinates are the mapped reference node coordinates - node_coordinates[:, i, j, tree_id] .= mapping(cell_x_offset + - dx / 2 * nodes[i], - cell_y_offset + - dy / 2 * nodes[j]) + function connectivity_cubed_sphere(trees_per_face_dimension, layers) + n_cells_x = n_cells_y = trees_per_face_dimension + n_cells_z = layers + + linear_indices = LinearIndices( + ( + trees_per_face_dimension, trees_per_face_dimension, + layers, 6, + ) + ) + + # Vertices represent the coordinates of the forest. This is used by `p4est` + # to write VTK files. + # Trixi.jl doesn't use the coordinates from `p4est`, so the vertices can be empty. + n_vertices = 0 + n_trees = 6 * n_cells_x * n_cells_y * n_cells_z + # No edge connectivity is needed + n_edges = 0 + # No corner connectivity is needed + n_corners = 0 + vertices = C_NULL + tree_to_vertex = C_NULL + + tree_to_tree = Array{p4est_topidx_t, 2}(undef, 6, n_trees) + tree_to_face = Array{Int8, 2}(undef, 6, n_trees) + + # Illustration of the local coordinates of each face. ξ and η are the first + # local coordinates of each face. The third local coordinate ζ is always + # pointing outwards, which yields a right-handed coordinate system for each face. + # ┌────────────────────────────────────────────────────┐ + # ╱│ ╱│ + # ╱ │ ξ <───┐ ╱ │ + # ╱ │ ╱ ╱ │ + # ╱ │ 4 (+y) V ╱ │ + # ╱ │ η ╱ │ + # ╱ │ ╱ │ + # ╱ │ ╱ │ + # ╱ │ ╱ │ + # ╱ │ ╱ │ + # ╱ │ 5 (-z) η ╱ │ + # ╱ │ ↑ ╱ │ + # ╱ │ │ ╱ │ + # ╱ │ ξ <───┘ ╱ │ + # ┌────────────────────────────────────────────────────┐ 2 (+x) │ + # │ │ │ │ + # │ │ │ ξ │ + # │ │ │ ↑ │ + # │ 1 (-x) │ │ │ │ + # │ │ │ │ │ + # │ ╱│ │ │ ╱ │ + # │ V │ │ │ V │ + # │ η ↓ │ │ η │ + # │ ξ └──────────────────────────────────────│─────────────┘ + # │ ╱ η 6 (+z) │ ╱ + # │ ╱ ↑ │ ╱ + # │ ╱ │ │ ╱ + # │ ╱ └───> ξ │ ╱ + # │ ╱ │ ╱ + # │ ╱ │ ╱ Global coordinates: + # │ ╱ │ ╱ y + # │ ╱ ┌───> ξ │ ╱ ↑ + # │ ╱ ╱ │ ╱ │ + # │ ╱ V 3 (-y) │ ╱ │ + # │ ╱ η │ ╱ └─────> x + # │ ╱ │ ╱ ╱ + # │╱ │╱ V + # └────────────────────────────────────────────────────┘ z + for direction in 1:6 + for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x + tree = linear_indices[cell_x, cell_y, cell_z, direction] + + # Subtract 1 because `p4est` uses zero-based indexing + # Negative x-direction + if cell_x > 1 # Connect to tree at the same face + tree_to_tree[1, tree] = linear_indices[ + cell_x - 1, cell_y, cell_z, + direction, + ] - 1 + tree_to_face[1, tree] = 1 + elseif direction == 1 # This is the -x face + target = 4 + tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1 + tree_to_face[1, tree] = 1 + elseif direction == 2 # This is the +x face + target = 3 + tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1 + tree_to_face[1, tree] = 1 + elseif direction == 3 # This is the -y face + target = 1 + tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1 + tree_to_face[1, tree] = 1 + elseif direction == 4 # This is the +y face + target = 2 + tree_to_tree[1, tree] = linear_indices[end, cell_y, cell_z, target] - 1 + tree_to_face[1, tree] = 1 + elseif direction == 5 # This is the -z face + target = 2 + tree_to_tree[1, tree] = linear_indices[cell_y, 1, cell_z, target] - 1 + tree_to_face[1, tree] = 2 + else # direction == 6, this is the +z face + target = 1 + tree_to_tree[1, tree] = linear_indices[ + end - cell_y + 1, end, cell_z, + target, + ] - 1 + tree_to_face[1, tree] = 9 # first face dimensions are oppositely oriented, add 6 + end + + # Positive x-direction + if cell_x < n_cells_x # Connect to tree at the same face + tree_to_tree[2, tree] = linear_indices[ + cell_x + 1, cell_y, cell_z, + direction, + ] - 1 + tree_to_face[2, tree] = 0 + elseif direction == 1 # This is the -x face + target = 3 + tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1 + tree_to_face[2, tree] = 0 + elseif direction == 2 # This is the +x face + target = 4 + tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1 + tree_to_face[2, tree] = 0 + elseif direction == 3 # This is the -y face + target = 2 + tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1 + tree_to_face[2, tree] = 0 + elseif direction == 4 # This is the +y face + target = 1 + tree_to_tree[2, tree] = linear_indices[1, cell_y, cell_z, target] - 1 + tree_to_face[2, tree] = 0 + elseif direction == 5 # This is the -z face + target = 1 + tree_to_tree[2, tree] = linear_indices[ + end - cell_y + 1, 1, cell_z, + target, + ] - 1 + tree_to_face[2, tree] = 8 # first face dimensions are oppositely oriented, add 6 + else # direction == 6, this is the +z face + target = 2 + tree_to_tree[2, tree] = linear_indices[cell_y, end, cell_z, target] - 1 + tree_to_face[2, tree] = 3 + end + + # Negative y-direction + if cell_y > 1 # Connect to tree at the same face + tree_to_tree[3, tree] = linear_indices[ + cell_x, cell_y - 1, cell_z, + direction, + ] - 1 + tree_to_face[3, tree] = 3 + elseif direction == 1 + target = 5 + tree_to_tree[3, tree] = linear_indices[ + end, end - cell_x + 1, cell_z, + target, + ] - 1 + tree_to_face[3, tree] = 7 # first face dimensions are oppositely oriented, add 6 + elseif direction == 2 + target = 5 + tree_to_tree[3, tree] = linear_indices[1, cell_x, cell_z, target] - 1 + tree_to_face[3, tree] = 0 + elseif direction == 3 + target = 5 + tree_to_tree[3, tree] = linear_indices[ + end - cell_x + 1, 1, cell_z, + target, + ] - 1 + tree_to_face[3, tree] = 8 # first face dimensions are oppositely oriented, add 6 + elseif direction == 4 + target = 5 + tree_to_tree[3, tree] = linear_indices[cell_x, end, cell_z, target] - 1 + tree_to_face[3, tree] = 3 + elseif direction == 5 + target = 3 + tree_to_tree[3, tree] = linear_indices[ + end - cell_x + 1, 1, cell_z, + target, + ] - 1 + tree_to_face[3, tree] = 8 # first face dimensions are oppositely oriented, add 6 + else # direction == 6 + target = 3 + tree_to_tree[3, tree] = linear_indices[cell_x, end, cell_z, target] - 1 + tree_to_face[3, tree] = 3 + end + + # Positive y-direction + if cell_y < n_cells_y # Connect to tree at the same face + tree_to_tree[4, tree] = linear_indices[ + cell_x, cell_y + 1, cell_z, + direction, + ] - 1 + tree_to_face[4, tree] = 2 + elseif direction == 1 + target = 6 + tree_to_tree[4, tree] = linear_indices[ + 1, end - cell_x + 1, cell_z, + target, + ] - 1 + tree_to_face[4, tree] = 6 # first face dimensions are oppositely oriented, add 6 + elseif direction == 2 + target = 6 + tree_to_tree[4, tree] = linear_indices[end, cell_x, cell_z, target] - 1 + tree_to_face[4, tree] = 1 + elseif direction == 3 + target = 6 + tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z, target] - 1 + tree_to_face[4, tree] = 2 + elseif direction == 4 + target = 6 + tree_to_tree[4, tree] = linear_indices[ + end - cell_x + 1, end, cell_z, + target, + ] - 1 + tree_to_face[4, tree] = 9 # first face dimensions are oppositely oriented, add 6 + elseif direction == 5 + target = 4 + tree_to_tree[4, tree] = linear_indices[cell_x, 1, cell_z, target] - 1 + tree_to_face[4, tree] = 2 + else # direction == 6 + target = 4 + tree_to_tree[4, tree] = linear_indices[ + end - cell_x + 1, end, cell_z, + target, + ] - 1 + tree_to_face[4, tree] = 9 # first face dimensions are oppositely oriented, add 6 + end + + # Negative z-direction + if cell_z > 1 + tree_to_tree[5, tree] = linear_indices[ + cell_x, cell_y, cell_z - 1, + direction, + ] - 1 + tree_to_face[5, tree] = 5 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[5, tree] = tree - 1 + tree_to_face[5, tree] = 4 + end + + # Positive z-direction + if cell_z < n_cells_z + tree_to_tree[6, tree] = linear_indices[ + cell_x, cell_y, cell_z + 1, + direction, + ] - 1 + tree_to_face[6, tree] = 4 + else # Non-periodic boundary, tree and face point to themselves (zero-based indexing) + tree_to_tree[6, tree] = tree - 1 + tree_to_face[6, tree] = 5 + end + end end + + tree_to_edge = C_NULL + # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." + # We don't need edge connectivity, so this is a trivial case. + ett_offset = zeros(p4est_topidx_t, 1) + edge_to_tree = C_NULL + edge_to_edge = C_NULL + + tree_to_corner = C_NULL + # `p4est` docs: "in trivial cases it is just a pointer to a p4est_topix value of 0." + # We don't need corner connectivity, so this is a trivial case. + ctt_offset = zeros(p4est_topidx_t, 1) + + corner_to_tree = C_NULL + corner_to_corner = C_NULL + + connectivity = p8est_connectivity_new_copy( + n_vertices, n_trees, n_corners, n_edges, + vertices, tree_to_vertex, + tree_to_tree, tree_to_face, + tree_to_edge, ett_offset, + edge_to_tree, edge_to_edge, + tree_to_corner, ctt_offset, + corner_to_tree, corner_to_corner + ) + + @assert p8est_connectivity_is_valid(connectivity) == 1 + + return connectivity end -end - -# 3D version -function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, - nodes, mapping, trees_per_dimension) - linear_indices = LinearIndices(trees_per_dimension) - - # Get cell length in reference mesh - dx = 2 / trees_per_dimension[1] - dy = 2 / trees_per_dimension[2] - dz = 2 / trees_per_dimension[3] - - for cell_z in 1:trees_per_dimension[3], - cell_y in 1:trees_per_dimension[2], - cell_x in 1:trees_per_dimension[1] - - tree_id = linear_indices[cell_x, cell_y, cell_z] - - # Calculate node coordinates of reference mesh - cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 - cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 - cell_z_offset = -1 + (cell_z - 1) * dz + dz / 2 - - for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes) - # node_coordinates are the mapped reference node coordinates - node_coordinates[:, i, j, k, tree_id] .= mapping(cell_x_offset + - dx / 2 * nodes[i], - cell_y_offset + - dy / 2 * nodes[j], - cell_z_offset + - dz / 2 * nodes[k]) + + # Calculate physical coordinates of each node of a structured mesh. + # This function assumes a structured mesh with trees in row order. + # 2D version + function calc_tree_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 4}, + nodes, mapping, trees_per_dimension + ) + linear_indices = LinearIndices(trees_per_dimension) + + # Get cell length in reference mesh + dx = 2 / trees_per_dimension[1] + dy = 2 / trees_per_dimension[2] + + for cell_y in 1:trees_per_dimension[2], cell_x in 1:trees_per_dimension[1] + tree_id = linear_indices[cell_x, cell_y] + + # Calculate node coordinates of reference mesh + cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 + cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 + + for j in eachindex(nodes), i in eachindex(nodes) + # node_coordinates are the mapped reference node coordinates + node_coordinates[:, i, j, tree_id] .= mapping( + cell_x_offset + + dx / 2 * nodes[i], + cell_y_offset + + dy / 2 * nodes[j] + ) + end end end -end - -# Calculate physical coordinates of each node of an unstructured mesh. -# Extract corners of each tree from the connectivity, -# interpolate to requested interpolation nodes, -# map the resulting coordinates with the specified mapping. -# 2D version -function calc_tree_node_coordinates!(node_coordinates::AbstractArray{RealT, 4}, - nodes, mapping, - vertices, tree_to_vertex) where {RealT} - nodes_in = [-1.0, 1.0] - matrix = polynomial_interpolation_matrix(nodes_in, nodes) - data_in = Array{RealT, 3}(undef, 2, 2, 2) - tmp1 = zeros(RealT, 2, length(nodes), length(nodes_in)) - - for tree in 1:size(tree_to_vertex, 2) - # Tree vertices are stored in Z-order, ignore z-coordinate in 2D, zero-based indexing - @views data_in[:, 1, 1] .= vertices[1:2, tree_to_vertex[1, tree] + 1] - @views data_in[:, 2, 1] .= vertices[1:2, tree_to_vertex[2, tree] + 1] - @views data_in[:, 1, 2] .= vertices[1:2, tree_to_vertex[3, tree] + 1] - @views data_in[:, 2, 2] .= vertices[1:2, tree_to_vertex[4, tree] + 1] - - # Interpolate corner coordinates to specified nodes - multiply_dimensionwise!(view(node_coordinates, :, :, :, tree), - matrix, matrix, - data_in, - tmp1) - end - - map_node_coordinates!(node_coordinates, mapping) -end - -function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, mapping) - for tree in axes(node_coordinates, 4), - j in axes(node_coordinates, 3), - i in axes(node_coordinates, 2) - - node_coordinates[:, i, j, tree] .= mapping(node_coordinates[1, i, j, tree], - node_coordinates[2, i, j, tree]) - end - - return node_coordinates -end - -function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, - mapping::Nothing) - return node_coordinates -end - -# 3D version -function calc_tree_node_coordinates!(node_coordinates::AbstractArray{RealT, 5}, - nodes, mapping, - vertices, tree_to_vertex) where {RealT} - nodes_in = [-1.0, 1.0] - matrix = polynomial_interpolation_matrix(nodes_in, nodes) - data_in = Array{RealT, 4}(undef, 3, 2, 2, 2) - - for tree in 1:size(tree_to_vertex, 2) - # Tree vertices are stored in Z-order, zero-based indexing - @views data_in[:, 1, 1, 1] .= vertices[:, tree_to_vertex[1, tree] + 1] - @views data_in[:, 2, 1, 1] .= vertices[:, tree_to_vertex[2, tree] + 1] - @views data_in[:, 1, 2, 1] .= vertices[:, tree_to_vertex[3, tree] + 1] - @views data_in[:, 2, 2, 1] .= vertices[:, tree_to_vertex[4, tree] + 1] - @views data_in[:, 1, 1, 2] .= vertices[:, tree_to_vertex[5, tree] + 1] - @views data_in[:, 2, 1, 2] .= vertices[:, tree_to_vertex[6, tree] + 1] - @views data_in[:, 1, 2, 2] .= vertices[:, tree_to_vertex[7, tree] + 1] - @views data_in[:, 2, 2, 2] .= vertices[:, tree_to_vertex[8, tree] + 1] - - # Interpolate corner coordinates to specified nodes - multiply_dimensionwise!(view(node_coordinates, :, :, :, :, tree), - matrix, matrix, matrix, - data_in) - end - - map_node_coordinates!(node_coordinates, mapping) -end - -function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, mapping) - for tree in axes(node_coordinates, 5), - k in axes(node_coordinates, 4), - j in axes(node_coordinates, 3), - i in axes(node_coordinates, 2) - - node_coordinates[:, i, j, k, tree] .= mapping(node_coordinates[1, i, j, k, - tree], - node_coordinates[2, i, j, k, - tree], - node_coordinates[3, i, j, k, - tree]) - end - - return node_coordinates -end - -function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, - mapping::Nothing) - return node_coordinates -end - -# Calculate physical coordinates of each node of a cubed sphere mesh. -function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, - nodes, trees_per_face_dimension, layers, - inner_radius, thickness) - n_cells_x = n_cells_y = trees_per_face_dimension - n_cells_z = layers - - linear_indices = LinearIndices((n_cells_x, n_cells_y, n_cells_z, 6)) - - # Get cell length in reference mesh - dx = 2 / n_cells_x - dy = 2 / n_cells_y - dz = 2 / n_cells_z - - for direction in 1:6 - for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x - tree = linear_indices[cell_x, cell_y, cell_z, direction] - x_offset = -1 + (cell_x - 1) * dx + dx / 2 - y_offset = -1 + (cell_y - 1) * dy + dy / 2 - z_offset = -1 + (cell_z - 1) * dz + dz / 2 + # 3D version + function calc_tree_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 5}, + nodes, mapping, trees_per_dimension + ) + linear_indices = LinearIndices(trees_per_dimension) + + # Get cell length in reference mesh + dx = 2 / trees_per_dimension[1] + dy = 2 / trees_per_dimension[2] + dz = 2 / trees_per_dimension[3] + + for cell_z in 1:trees_per_dimension[3], + cell_y in 1:trees_per_dimension[2], + cell_x in 1:trees_per_dimension[1] + + tree_id = linear_indices[cell_x, cell_y, cell_z] + + # Calculate node coordinates of reference mesh + cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 + cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 + cell_z_offset = -1 + (cell_z - 1) * dz + dz / 2 for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes) # node_coordinates are the mapped reference node coordinates - node_coordinates[:, i, j, k, tree] .= cubed_sphere_mapping(x_offset + - dx / 2 * - nodes[i], - y_offset + - dy / 2 * - nodes[j], - z_offset + - dz / 2 * - nodes[k], - inner_radius, - thickness, - direction) + node_coordinates[:, i, j, k, tree_id] .= mapping( + cell_x_offset + + dx / 2 * nodes[i], + cell_y_offset + + dy / 2 * nodes[j], + cell_z_offset + + dz / 2 * nodes[k] + ) end end end -end - -# Map the computational coordinates xi, eta, zeta to the specified side of a cubed sphere -# with the specified inner radius and thickness. -function cubed_sphere_mapping(xi, eta, zeta, inner_radius, thickness, direction) - alpha = xi * pi / 4 - beta = eta * pi / 4 - - # Equiangular projection - x = tan(alpha) - y = tan(beta) - - # Coordinates on unit cube per direction, see illustration above in the function connectivity_cubed_sphere - cube_coordinates = (SVector(-1, -x, y), - SVector(1, x, y), - SVector(x, -1, y), - SVector(-x, 1, y), - SVector(-x, y, -1), - SVector(x, y, 1)) - - # Radius on cube surface - r = sqrt(1 + x^2 + y^2) - - # Radius of the sphere - R = inner_radius + thickness * (0.5f0 * (zeta + 1)) - - # Projection onto the sphere - return R / r * cube_coordinates[direction] -end - -# Calculate physical coordinates of each element of an unstructured mesh read -# in from a HOHQMesh file. This calculation is done with the transfinite interpolation -# routines found in `mappings_geometry_curved_2d.jl` or `mappings_geometry_straight_2d.jl` -function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, - file_lines::Vector{String}, nodes, vertices, RealT) - # Get the number of trees and the number of interpolation nodes - n_trees = last(size(node_coordinates)) - nnodes = length(nodes) - - # Setup the starting file index to read in element indices and the additional - # curved boundary information provided by HOHQMesh. - file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) + 1 - - # Create a work set of Gamma curves to create the node coordinates - CurvedSurfaceT = CurvedSurface{RealT} - surface_curves = Array{CurvedSurfaceT}(undef, 4) - - # Create other work arrays to perform the mesh construction - element_node_ids = Array{Int}(undef, 4) - curved_check = Vector{Int}(undef, 4) - quad_vertices = Array{RealT}(undef, (4, 2)) - quad_vertices_flipped = Array{RealT}(undef, (4, 2)) - curve_values = Array{RealT}(undef, (nnodes, 2)) - - # Create the barycentric weights used for the surface interpolations - bary_weights_ = barycentric_weights(nodes) - bary_weights = SVector{nnodes}(bary_weights_) - - # Loop through all the trees, i.e., the elements generated by HOHQMesh and create the node coordinates. - # When we extract information from the `current_line` we start at index 2 in order to - # avoid the Abaqus comment character "** " - for tree in 1:n_trees - # Pull the vertex node IDs - current_line = split(file_lines[file_idx]) - element_node_ids[1] = parse(Int, current_line[2]) - element_node_ids[2] = parse(Int, current_line[3]) - element_node_ids[3] = parse(Int, current_line[4]) - element_node_ids[4] = parse(Int, current_line[5]) - - # Pull the (x,y) values of the four vertices of the current tree out of the global vertices array - for i in 1:4 - quad_vertices[i, :] .= vertices[1:2, element_node_ids[i]] + + # Calculate physical coordinates of each node of an unstructured mesh. + # Extract corners of each tree from the connectivity, + # interpolate to requested interpolation nodes, + # map the resulting coordinates with the specified mapping. + # 2D version + function calc_tree_node_coordinates!( + node_coordinates::AbstractArray{RealT, 4}, + nodes, mapping, + vertices, tree_to_vertex + ) where {RealT} + nodes_in = [-1.0, 1.0] + matrix = polynomial_interpolation_matrix(nodes_in, nodes) + data_in = Array{RealT, 3}(undef, 2, 2, 2) + tmp1 = zeros(RealT, 2, length(nodes), length(nodes_in)) + + for tree in 1:size(tree_to_vertex, 2) + # Tree vertices are stored in Z-order, ignore z-coordinate in 2D, zero-based indexing + @views data_in[:, 1, 1] .= vertices[1:2, tree_to_vertex[1, tree] + 1] + @views data_in[:, 2, 1] .= vertices[1:2, tree_to_vertex[2, tree] + 1] + @views data_in[:, 1, 2] .= vertices[1:2, tree_to_vertex[3, tree] + 1] + @views data_in[:, 2, 2] .= vertices[1:2, tree_to_vertex[4, tree] + 1] + + # Interpolate corner coordinates to specified nodes + multiply_dimensionwise!( + view(node_coordinates, :, :, :, tree), + matrix, matrix, + data_in, + tmp1 + ) end - # Pull the information to check if boundary is curved in order to read in additional data - file_idx += 1 - current_line = split(file_lines[file_idx]) - curved_check[1] = parse(Int, current_line[2]) - curved_check[2] = parse(Int, current_line[3]) - curved_check[3] = parse(Int, current_line[4]) - curved_check[4] = parse(Int, current_line[5]) - if sum(curved_check) == 0 - # Create the node coordinates on this particular element - calc_node_coordinates!(node_coordinates, tree, nodes, quad_vertices) - else - # Quadrilateral element has at least one curved side - # Flip node ordering to make sure the element is right-handed for the interpolations - m1 = 1 - m2 = 2 - @views quad_vertices_flipped[1, :] .= quad_vertices[4, :] - @views quad_vertices_flipped[2, :] .= quad_vertices[2, :] - @views quad_vertices_flipped[3, :] .= quad_vertices[3, :] - @views quad_vertices_flipped[4, :] .= quad_vertices[1, :] + + map_node_coordinates!(node_coordinates, mapping) + end + + function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, mapping) + for tree in axes(node_coordinates, 4), + j in axes(node_coordinates, 3), + i in axes(node_coordinates, 2) + + node_coordinates[:, i, j, tree] .= mapping( + node_coordinates[1, i, j, tree], + node_coordinates[2, i, j, tree] + ) + end + + return node_coordinates + end + + function map_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 4}, + mapping::Nothing + ) + return node_coordinates + end + + # 3D version + function calc_tree_node_coordinates!( + node_coordinates::AbstractArray{RealT, 5}, + nodes, mapping, + vertices, tree_to_vertex + ) where {RealT} + nodes_in = [-1.0, 1.0] + matrix = polynomial_interpolation_matrix(nodes_in, nodes) + data_in = Array{RealT, 4}(undef, 3, 2, 2, 2) + + for tree in 1:size(tree_to_vertex, 2) + # Tree vertices are stored in Z-order, zero-based indexing + @views data_in[:, 1, 1, 1] .= vertices[:, tree_to_vertex[1, tree] + 1] + @views data_in[:, 2, 1, 1] .= vertices[:, tree_to_vertex[2, tree] + 1] + @views data_in[:, 1, 2, 1] .= vertices[:, tree_to_vertex[3, tree] + 1] + @views data_in[:, 2, 2, 1] .= vertices[:, tree_to_vertex[4, tree] + 1] + @views data_in[:, 1, 1, 2] .= vertices[:, tree_to_vertex[5, tree] + 1] + @views data_in[:, 2, 1, 2] .= vertices[:, tree_to_vertex[6, tree] + 1] + @views data_in[:, 1, 2, 2] .= vertices[:, tree_to_vertex[7, tree] + 1] + @views data_in[:, 2, 2, 2] .= vertices[:, tree_to_vertex[8, tree] + 1] + + # Interpolate corner coordinates to specified nodes + multiply_dimensionwise!( + view(node_coordinates, :, :, :, :, tree), + matrix, matrix, matrix, + data_in + ) + end + + map_node_coordinates!(node_coordinates, mapping) + end + + function map_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, mapping) + for tree in axes(node_coordinates, 5), + k in axes(node_coordinates, 4), + j in axes(node_coordinates, 3), + i in axes(node_coordinates, 2) + + node_coordinates[:, i, j, k, tree] .= mapping( + node_coordinates[ + 1, i, j, k, + tree, + ], + node_coordinates[ + 2, i, j, k, + tree, + ], + node_coordinates[ + 3, i, j, k, + tree, + ] + ) + end + + return node_coordinates + end + + function map_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 5}, + mapping::Nothing + ) + return node_coordinates + end + + # Calculate physical coordinates of each node of a cubed sphere mesh. + function calc_tree_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 5}, + nodes, trees_per_face_dimension, layers, + inner_radius, thickness + ) + n_cells_x = n_cells_y = trees_per_face_dimension + n_cells_z = layers + + linear_indices = LinearIndices((n_cells_x, n_cells_y, n_cells_z, 6)) + + # Get cell length in reference mesh + dx = 2 / n_cells_x + dy = 2 / n_cells_y + dz = 2 / n_cells_z + + for direction in 1:6 + for cell_z in 1:n_cells_z, cell_y in 1:n_cells_y, cell_x in 1:n_cells_x + tree = linear_indices[cell_x, cell_y, cell_z, direction] + + x_offset = -1 + (cell_x - 1) * dx + dx / 2 + y_offset = -1 + (cell_y - 1) * dy + dy / 2 + z_offset = -1 + (cell_z - 1) * dz + dz / 2 + + for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes) + # node_coordinates are the mapped reference node coordinates + node_coordinates[:, i, j, k, tree] .= cubed_sphere_mapping( + x_offset + + dx / 2 * + nodes[i], + y_offset + + dy / 2 * + nodes[j], + z_offset + + dz / 2 * + nodes[k], + inner_radius, + thickness, + direction + ) + end + end + end + end + + # Map the computational coordinates xi, eta, zeta to the specified side of a cubed sphere + # with the specified inner radius and thickness. + function cubed_sphere_mapping(xi, eta, zeta, inner_radius, thickness, direction) + alpha = xi * pi / 4 + beta = eta * pi / 4 + + # Equiangular projection + x = tan(alpha) + y = tan(beta) + + # Coordinates on unit cube per direction, see illustration above in the function connectivity_cubed_sphere + cube_coordinates = ( + SVector(-1, -x, y), + SVector(1, x, y), + SVector(x, -1, y), + SVector(-x, 1, y), + SVector(-x, y, -1), + SVector(x, y, 1), + ) + + # Radius on cube surface + r = sqrt(1 + x^2 + y^2) + + # Radius of the sphere + R = inner_radius + thickness * (0.5f0 * (zeta + 1)) + + # Projection onto the sphere + return R / r * cube_coordinates[direction] + end + + # Calculate physical coordinates of each element of an unstructured mesh read + # in from a HOHQMesh file. This calculation is done with the transfinite interpolation + # routines found in `mappings_geometry_curved_2d.jl` or `mappings_geometry_straight_2d.jl` + function calc_tree_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 4}, + file_lines::Vector{String}, nodes, vertices, RealT + ) + # Get the number of trees and the number of interpolation nodes + n_trees = last(size(node_coordinates)) + nnodes = length(nodes) + + # Setup the starting file index to read in element indices and the additional + # curved boundary information provided by HOHQMesh. + file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) + 1 + + # Create a work set of Gamma curves to create the node coordinates + CurvedSurfaceT = CurvedSurface{RealT} + surface_curves = Array{CurvedSurfaceT}(undef, 4) + + # Create other work arrays to perform the mesh construction + element_node_ids = Array{Int}(undef, 4) + curved_check = Vector{Int}(undef, 4) + quad_vertices = Array{RealT}(undef, (4, 2)) + quad_vertices_flipped = Array{RealT}(undef, (4, 2)) + curve_values = Array{RealT}(undef, (nnodes, 2)) + + # Create the barycentric weights used for the surface interpolations + bary_weights_ = barycentric_weights(nodes) + bary_weights = SVector{nnodes}(bary_weights_) + + # Loop through all the trees, i.e., the elements generated by HOHQMesh and create the node coordinates. + # When we extract information from the `current_line` we start at index 2 in order to + # avoid the Abaqus comment character "** " + for tree in 1:n_trees + # Pull the vertex node IDs + current_line = split(file_lines[file_idx]) + element_node_ids[1] = parse(Int, current_line[2]) + element_node_ids[2] = parse(Int, current_line[3]) + element_node_ids[3] = parse(Int, current_line[4]) + element_node_ids[4] = parse(Int, current_line[5]) + + # Pull the (x,y) values of the four vertices of the current tree out of the global vertices array for i in 1:4 - if curved_check[i] == 0 - # When curved_check[i] is 0 then the "curve" from vertex `i` to vertex `i+1` is a straight line. - # Evaluate a linear interpolant between the two points at each of the nodes. - for k in 1:nnodes - curve_values[k, 1] = linear_interpolate(nodes[k], - quad_vertices_flipped[m1, - 1], - quad_vertices_flipped[m2, - 1]) - curve_values[k, 2] = linear_interpolate(nodes[k], - quad_vertices_flipped[m1, - 2], - quad_vertices_flipped[m2, - 2]) + quad_vertices[i, :] .= vertices[1:2, element_node_ids[i]] + end + # Pull the information to check if boundary is curved in order to read in additional data + file_idx += 1 + current_line = split(file_lines[file_idx]) + curved_check[1] = parse(Int, current_line[2]) + curved_check[2] = parse(Int, current_line[3]) + curved_check[3] = parse(Int, current_line[4]) + curved_check[4] = parse(Int, current_line[5]) + if sum(curved_check) == 0 + # Create the node coordinates on this particular element + calc_node_coordinates!(node_coordinates, tree, nodes, quad_vertices) + else + # Quadrilateral element has at least one curved side + # Flip node ordering to make sure the element is right-handed for the interpolations + m1 = 1 + m2 = 2 + @views quad_vertices_flipped[1, :] .= quad_vertices[4, :] + @views quad_vertices_flipped[2, :] .= quad_vertices[2, :] + @views quad_vertices_flipped[3, :] .= quad_vertices[3, :] + @views quad_vertices_flipped[4, :] .= quad_vertices[1, :] + for i in 1:4 + if curved_check[i] == 0 + # When curved_check[i] is 0 then the "curve" from vertex `i` to vertex `i+1` is a straight line. + # Evaluate a linear interpolant between the two points at each of the nodes. + for k in 1:nnodes + curve_values[k, 1] = linear_interpolate( + nodes[k], + quad_vertices_flipped[ + m1, + 1, + ], + quad_vertices_flipped[ + m2, + 1, + ] + ) + curve_values[k, 2] = linear_interpolate( + nodes[k], + quad_vertices_flipped[ + m1, + 2, + ], + quad_vertices_flipped[ + m2, + 2, + ] + ) + end + else + # When curved_check[i] is 1 this curved boundary information is supplied by the mesh + # generator. So we just read it into a work array + for k in 1:nnodes + file_idx += 1 + current_line = split(file_lines[file_idx]) + curve_values[k, 1] = parse(RealT, current_line[2]) + curve_values[k, 2] = parse(RealT, current_line[3]) + end end - else - # When curved_check[i] is 1 this curved boundary information is supplied by the mesh - # generator. So we just read it into a work array - for k in 1:nnodes - file_idx += 1 - current_line = split(file_lines[file_idx]) - curve_values[k, 1] = parse(RealT, current_line[2]) - curve_values[k, 2] = parse(RealT, current_line[3]) + # Construct the curve interpolant for the current side + surface_curves[i] = CurvedSurfaceT( + nodes, bary_weights, + copy(curve_values) + ) + # Indexing update that contains a "flip" to ensure correct element orientation. + # If we need to construct the straight line "curves" when curved_check[i] == 0 + m1 += 1 + if i == 3 + m2 = 1 + else + m2 += 1 end end - # Construct the curve interpolant for the current side - surface_curves[i] = CurvedSurfaceT(nodes, bary_weights, - copy(curve_values)) - # Indexing update that contains a "flip" to ensure correct element orientation. - # If we need to construct the straight line "curves" when curved_check[i] == 0 - m1 += 1 - if i == 3 - m2 = 1 - else - m2 += 1 - end + # Create the node coordinates on this particular element + calc_node_coordinates!(node_coordinates, tree, nodes, surface_curves) end - # Create the node coordinates on this particular element - calc_node_coordinates!(node_coordinates, tree, nodes, surface_curves) - end - # Move file index to the next tree - file_idx += 1 - end - - return file_idx -end - -# Calculate physical coordinates of each element of an unstructured mesh read -# in from a HOHQMesh file. This calculation is done with the transfinite interpolation -# routines found in `transfinite_mappings_3d.jl` -function calc_tree_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, - file_lines::Vector{String}, nodes, vertices, RealT) - # Get the number of trees and the number of interpolation nodes - n_trees = last(size(node_coordinates)) - nnodes = length(nodes) - - # Setup the starting file index to read in element indices and the additional - # curved boundary information provided by HOHQMesh. - file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) + 1 - - # Create a work set of Gamma curves to create the node coordinates - CurvedFaceT = CurvedFace{RealT} - face_curves = Array{CurvedFaceT}(undef, 6) - - # Create other work arrays to perform the mesh construction - element_node_ids = Array{Int}(undef, 8) - curved_check = Vector{Int}(undef, 6) - hex_vertices = Array{RealT}(undef, (3, 8)) - face_vertices = Array{RealT}(undef, (3, 4)) - curve_values = Array{RealT}(undef, (3, nnodes, nnodes)) - - # Create the barycentric weights used for the surface interpolations - bary_weights_ = barycentric_weights(nodes) - bary_weights = SVector{nnodes}(bary_weights_) - - # Loop through all the trees, i.e., the elements generated by HOHQMesh and create the node coordinates. - # When we extract information from the `current_line` we start at index 2 in order to - # avoid the Abaqus comment character "** " - for tree in 1:n_trees - # pull the vertex node IDs - current_line = split(file_lines[file_idx]) - element_node_ids[1] = parse(Int, current_line[2]) - element_node_ids[2] = parse(Int, current_line[3]) - element_node_ids[3] = parse(Int, current_line[4]) - element_node_ids[4] = parse(Int, current_line[5]) - element_node_ids[5] = parse(Int, current_line[6]) - element_node_ids[6] = parse(Int, current_line[7]) - element_node_ids[7] = parse(Int, current_line[8]) - element_node_ids[8] = parse(Int, current_line[9]) - - # Pull the (x, y, z) values of the eight vertices of the current tree out of the global vertices array - for i in 1:8 - hex_vertices[:, i] .= vertices[:, element_node_ids[i]] + # Move file index to the next tree + file_idx += 1 end - # Pull the information to check if boundary is curved in order to read in additional data - file_idx += 1 - current_line = split(file_lines[file_idx]) - curved_check[1] = parse(Int, current_line[2]) - curved_check[2] = parse(Int, current_line[3]) - curved_check[3] = parse(Int, current_line[4]) - curved_check[4] = parse(Int, current_line[5]) - curved_check[5] = parse(Int, current_line[6]) - curved_check[6] = parse(Int, current_line[7]) - if sum(curved_check) == 0 - # Create the node coordinates on this element - calc_node_coordinates!(node_coordinates, tree, nodes, hex_vertices) - else - # Hexahedral element has at least one curved side - for face in 1:6 - if curved_check[face] == 0 - # Face is a flat plane. - # Evaluate a bilinear interpolant between the four vertices - # of the face at each of the nodes. - get_vertices_for_bilinear_interpolant!(face_vertices, face, - hex_vertices) - for q in 1:nnodes, p in 1:nnodes - @views bilinear_interpolation!(curve_values[:, p, q], - face_vertices, nodes[p], - nodes[q]) - end - else # curved_check[face] == 1 - # Curved face boundary information is supplied by - # the mesh file. Just read it into a work array - for q in 1:nnodes, p in 1:nnodes - file_idx += 1 - current_line = split(file_lines[file_idx]) - curve_values[1, p, q] = parse(RealT, current_line[2]) - curve_values[2, p, q] = parse(RealT, current_line[3]) - curve_values[3, p, q] = parse(RealT, current_line[4]) + + return file_idx + end + + # Calculate physical coordinates of each element of an unstructured mesh read + # in from a HOHQMesh file. This calculation is done with the transfinite interpolation + # routines found in `transfinite_mappings_3d.jl` + function calc_tree_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 5}, + file_lines::Vector{String}, nodes, vertices, RealT + ) + # Get the number of trees and the number of interpolation nodes + n_trees = last(size(node_coordinates)) + nnodes = length(nodes) + + # Setup the starting file index to read in element indices and the additional + # curved boundary information provided by HOHQMesh. + file_idx = findfirst(contains("** mesh polynomial degree"), file_lines) + 1 + + # Create a work set of Gamma curves to create the node coordinates + CurvedFaceT = CurvedFace{RealT} + face_curves = Array{CurvedFaceT}(undef, 6) + + # Create other work arrays to perform the mesh construction + element_node_ids = Array{Int}(undef, 8) + curved_check = Vector{Int}(undef, 6) + hex_vertices = Array{RealT}(undef, (3, 8)) + face_vertices = Array{RealT}(undef, (3, 4)) + curve_values = Array{RealT}(undef, (3, nnodes, nnodes)) + + # Create the barycentric weights used for the surface interpolations + bary_weights_ = barycentric_weights(nodes) + bary_weights = SVector{nnodes}(bary_weights_) + + # Loop through all the trees, i.e., the elements generated by HOHQMesh and create the node coordinates. + # When we extract information from the `current_line` we start at index 2 in order to + # avoid the Abaqus comment character "** " + for tree in 1:n_trees + # pull the vertex node IDs + current_line = split(file_lines[file_idx]) + element_node_ids[1] = parse(Int, current_line[2]) + element_node_ids[2] = parse(Int, current_line[3]) + element_node_ids[3] = parse(Int, current_line[4]) + element_node_ids[4] = parse(Int, current_line[5]) + element_node_ids[5] = parse(Int, current_line[6]) + element_node_ids[6] = parse(Int, current_line[7]) + element_node_ids[7] = parse(Int, current_line[8]) + element_node_ids[8] = parse(Int, current_line[9]) + + # Pull the (x, y, z) values of the eight vertices of the current tree out of the global vertices array + for i in 1:8 + hex_vertices[:, i] .= vertices[:, element_node_ids[i]] + end + # Pull the information to check if boundary is curved in order to read in additional data + file_idx += 1 + current_line = split(file_lines[file_idx]) + curved_check[1] = parse(Int, current_line[2]) + curved_check[2] = parse(Int, current_line[3]) + curved_check[3] = parse(Int, current_line[4]) + curved_check[4] = parse(Int, current_line[5]) + curved_check[5] = parse(Int, current_line[6]) + curved_check[6] = parse(Int, current_line[7]) + if sum(curved_check) == 0 + # Create the node coordinates on this element + calc_node_coordinates!(node_coordinates, tree, nodes, hex_vertices) + else + # Hexahedral element has at least one curved side + for face in 1:6 + if curved_check[face] == 0 + # Face is a flat plane. + # Evaluate a bilinear interpolant between the four vertices + # of the face at each of the nodes. + get_vertices_for_bilinear_interpolant!( + face_vertices, face, + hex_vertices + ) + for q in 1:nnodes, p in 1:nnodes + @views bilinear_interpolation!( + curve_values[:, p, q], + face_vertices, nodes[p], + nodes[q] + ) + end + else # curved_check[face] == 1 + # Curved face boundary information is supplied by + # the mesh file. Just read it into a work array + for q in 1:nnodes, p in 1:nnodes + file_idx += 1 + current_line = split(file_lines[file_idx]) + curve_values[1, p, q] = parse(RealT, current_line[2]) + curve_values[2, p, q] = parse(RealT, current_line[3]) + curve_values[3, p, q] = parse(RealT, current_line[4]) + end end + # Construct the curve interpolant for the current side + face_curves[face] = CurvedFaceT(nodes, bary_weights, copy(curve_values)) end - # Construct the curve interpolant for the current side - face_curves[face] = CurvedFaceT(nodes, bary_weights, copy(curve_values)) + # Create the node coordinates on this particular element + calc_node_coordinates!(node_coordinates, tree, nodes, face_curves) end - # Create the node coordinates on this particular element - calc_node_coordinates!(node_coordinates, tree, nodes, face_curves) + # Move file index to the next tree + file_idx += 1 + end + + return file_idx + end + + # Given the eight `hex_vertices` for a hexahedral element extract + # the four `face_vertices` for a particular `face_index`. + function get_vertices_for_bilinear_interpolant!(face_vertices, face_index, hex_vertices) + if face_index == 1 + @views face_vertices[:, 1] .= hex_vertices[:, 1] + @views face_vertices[:, 2] .= hex_vertices[:, 2] + @views face_vertices[:, 3] .= hex_vertices[:, 6] + @views face_vertices[:, 4] .= hex_vertices[:, 5] + elseif face_index == 2 + @views face_vertices[:, 1] .= hex_vertices[:, 4] + @views face_vertices[:, 2] .= hex_vertices[:, 3] + @views face_vertices[:, 3] .= hex_vertices[:, 7] + @views face_vertices[:, 4] .= hex_vertices[:, 8] + elseif face_index == 3 + @views face_vertices[:, 1] .= hex_vertices[:, 1] + @views face_vertices[:, 2] .= hex_vertices[:, 2] + @views face_vertices[:, 3] .= hex_vertices[:, 3] + @views face_vertices[:, 4] .= hex_vertices[:, 4] + elseif face_index == 4 + @views face_vertices[:, 1] .= hex_vertices[:, 2] + @views face_vertices[:, 2] .= hex_vertices[:, 3] + @views face_vertices[:, 3] .= hex_vertices[:, 6] + @views face_vertices[:, 4] .= hex_vertices[:, 7] + elseif face_index == 5 + @views face_vertices[:, 1] .= hex_vertices[:, 5] + @views face_vertices[:, 2] .= hex_vertices[:, 6] + @views face_vertices[:, 3] .= hex_vertices[:, 7] + @views face_vertices[:, 4] .= hex_vertices[:, 8] + else # face_index == 6 + @views face_vertices[:, 1] .= hex_vertices[:, 1] + @views face_vertices[:, 2] .= hex_vertices[:, 4] + @views face_vertices[:, 3] .= hex_vertices[:, 8] + @views face_vertices[:, 4] .= hex_vertices[:, 5] + end + end + + # Evaluate a bilinear interpolant at a point (u,v) given the four vertices where the face is right-handed + # 4 3 + # o----------------o + # | | + # | | + # | | + # | | + # | | + # | | + # o----------------o + # 1 2 + # and return the 3D coordinate point (x, y, z) + function bilinear_interpolation!(coordinate, face_vertices, u, v) + for j in 1:3 + coordinate[j] = 0.25f0 * ( + face_vertices[j, 1] * (1 - u) * (1 - v) + + face_vertices[j, 2] * (1 + u) * (1 - v) + + face_vertices[j, 3] * (1 + u) * (1 + v) + + face_vertices[j, 4] * (1 - u) * (1 + v) + ) end - # Move file index to the next tree - file_idx += 1 - end - - return file_idx -end - -# Given the eight `hex_vertices` for a hexahedral element extract -# the four `face_vertices` for a particular `face_index`. -function get_vertices_for_bilinear_interpolant!(face_vertices, face_index, hex_vertices) - if face_index == 1 - @views face_vertices[:, 1] .= hex_vertices[:, 1] - @views face_vertices[:, 2] .= hex_vertices[:, 2] - @views face_vertices[:, 3] .= hex_vertices[:, 6] - @views face_vertices[:, 4] .= hex_vertices[:, 5] - elseif face_index == 2 - @views face_vertices[:, 1] .= hex_vertices[:, 4] - @views face_vertices[:, 2] .= hex_vertices[:, 3] - @views face_vertices[:, 3] .= hex_vertices[:, 7] - @views face_vertices[:, 4] .= hex_vertices[:, 8] - elseif face_index == 3 - @views face_vertices[:, 1] .= hex_vertices[:, 1] - @views face_vertices[:, 2] .= hex_vertices[:, 2] - @views face_vertices[:, 3] .= hex_vertices[:, 3] - @views face_vertices[:, 4] .= hex_vertices[:, 4] - elseif face_index == 4 - @views face_vertices[:, 1] .= hex_vertices[:, 2] - @views face_vertices[:, 2] .= hex_vertices[:, 3] - @views face_vertices[:, 3] .= hex_vertices[:, 6] - @views face_vertices[:, 4] .= hex_vertices[:, 7] - elseif face_index == 5 - @views face_vertices[:, 1] .= hex_vertices[:, 5] - @views face_vertices[:, 2] .= hex_vertices[:, 6] - @views face_vertices[:, 3] .= hex_vertices[:, 7] - @views face_vertices[:, 4] .= hex_vertices[:, 8] - else # face_index == 6 - @views face_vertices[:, 1] .= hex_vertices[:, 1] - @views face_vertices[:, 2] .= hex_vertices[:, 4] - @views face_vertices[:, 3] .= hex_vertices[:, 8] - @views face_vertices[:, 4] .= hex_vertices[:, 5] - end -end - -# Evaluate a bilinear interpolant at a point (u,v) given the four vertices where the face is right-handed -# 4 3 -# o----------------o -# | | -# | | -# | | -# | | -# | | -# | | -# o----------------o -# 1 2 -# and return the 3D coordinate point (x, y, z) -function bilinear_interpolation!(coordinate, face_vertices, u, v) - for j in 1:3 - coordinate[j] = 0.25f0 * (face_vertices[j, 1] * (1 - u) * (1 - v) - + face_vertices[j, 2] * (1 + u) * (1 - v) - + face_vertices[j, 3] * (1 + u) * (1 + v) - + face_vertices[j, 4] * (1 - u) * (1 + v)) - end -end - -function get_global_first_element_ids(mesh::P4estMesh) - return unsafe_wrap(Array, mesh.p4est.global_first_quadrant, mpi_nranks() + 1) -end - -function balance!(mesh::P4estMesh{2}, init_fn = C_NULL) - p4est_balance(mesh.p4est, P4EST_CONNECT_FACE, init_fn) - # Due to a bug in `p4est`, the forest needs to be rebalanced twice sometimes - # See https://github.com/cburstedde/p4est/issues/112 - p4est_balance(mesh.p4est, P4EST_CONNECT_FACE, init_fn) -end - -function balance!(mesh::P4estMesh{3}, init_fn = C_NULL) - p8est_balance(mesh.p4est, P8EST_CONNECT_FACE, init_fn) -end - -function partition!(mesh::P4estMesh{2}; weight_fn = C_NULL) - p4est_partition(mesh.p4est, Int(mesh.p4est_partition_allow_for_coarsening), - weight_fn) -end - -function partition!(mesh::P4estMesh{3}; weight_fn = C_NULL) - p8est_partition(mesh.p4est, Int(mesh.p4est_partition_allow_for_coarsening), - weight_fn) -end - -function update_ghost_layer!(mesh::P4estMesh) - ghost_destroy_p4est(mesh.ghost) - mesh.ghost = PointerWrapper(ghost_new_p4est(mesh.p4est)) -end - -function init_fn(p4est, which_tree, quadrant) - # Unpack quadrant's user data ([global quad ID, controller_value]) - # Use `unsafe_load` here since `quadrant.p.user_data isa Ptr{Ptr{Nothing}}` - # and we only need the first (only!) entry - pw = PointerWrapper(Int, unsafe_load(quadrant.p.user_data)) - - # Initialize quad ID as -1 and controller_value as 0 (don't refine or coarsen) - pw[1] = -1 - pw[2] = 0 - return nothing -end - -# 2D -function cfunction(::typeof(init_fn), ::Val{2}) - @cfunction(init_fn, Cvoid, - (Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{p4est_quadrant_t})) -end -# 3D -function cfunction(::typeof(init_fn), ::Val{3}) - @cfunction(init_fn, Cvoid, - (Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{p8est_quadrant_t})) -end - -function refine_fn(p4est, which_tree, quadrant) - # Controller value has been copied to the quadrant's user data storage before. - # Unpack quadrant's user data ([global quad ID, controller_value]). - # Use `unsafe_load` here since `quadrant.p.user_data isa Ptr{Ptr{Nothing}}` - # and we only need the first (only!) entry - pw = PointerWrapper(Int, unsafe_load(quadrant.p.user_data)) - controller_value = pw[2] - - if controller_value > 0 - # return true (refine) - return Cint(1) - else - # return false (don't refine) - return Cint(0) - end -end - -# 2D -function cfunction(::typeof(refine_fn), ::Val{2}) - @cfunction(refine_fn, Cint, - (Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{p4est_quadrant_t})) -end -# 3D -function cfunction(::typeof(refine_fn), ::Val{3}) - @cfunction(refine_fn, Cint, - (Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{p8est_quadrant_t})) -end - -# Refine marked cells and rebalance forest. -# Return a list of all cells that have been refined during refinement or rebalancing. -function refine!(mesh::P4estMesh) - # Copy original element IDs to quad user data storage - original_n_cells = ncells(mesh) - save_original_ids(mesh) - - init_fn_c = cfunction(init_fn, Val(ndims(mesh))) - refine_fn_c = cfunction(refine_fn, Val(ndims(mesh))) - - # Refine marked cells - @trixi_timeit timer() "refine" refine_p4est!(mesh.p4est, false, refine_fn_c, - init_fn_c) - - @trixi_timeit timer() "rebalance" balance!(mesh, init_fn_c) - - return collect_changed_cells(mesh, original_n_cells) -end - -function coarsen_fn(p4est, which_tree, quadrants_ptr) - quadrants = unsafe_wrap_quadrants(quadrants_ptr, p4est) - - # Controller value has been copied to the quadrant's user data storage before. - # Load controller value from quadrant's user data ([global quad ID, controller_value]). - # Use `unsafe_load` here since `quadrant.p.user_data isa Ptr{Ptr{Nothing}}` - # and we only need the first (only!) entry - controller_value(i) = PointerWrapper(Int, unsafe_load(quadrants[i].p.user_data))[2] - - # `p4est` calls this function for each 2^ndims quads that could be coarsened to a single one. - # Only coarsen if all these 2^ndims quads have been marked for coarsening. - if all(i -> controller_value(i) < 0, eachindex(quadrants)) - # return true (coarsen) - return Cint(1) - else - # return false (don't coarsen) - return Cint(0) - end -end - -# 2D -function unsafe_wrap_quadrants(quadrants_ptr, ::Ptr{p4est_t}) - unsafe_wrap(Array, quadrants_ptr, 4) -end -# 3D -function unsafe_wrap_quadrants(quadrants_ptr, ::Ptr{p8est_t}) - unsafe_wrap(Array, quadrants_ptr, 8) -end - -# 2D -function cfunction(::typeof(coarsen_fn), ::Val{2}) - @cfunction(coarsen_fn, Cint, - (Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{Ptr{p4est_quadrant_t}})) -end -# 3D -function cfunction(::typeof(coarsen_fn), ::Val{3}) - @cfunction(coarsen_fn, Cint, - (Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{Ptr{p8est_quadrant_t}})) -end - -# Coarsen marked cells if the forest will stay balanced. -# Return a list of all cells that have been coarsened. -function coarsen!(mesh::P4estMesh) - # Copy original element IDs to quad user data storage - original_n_cells = ncells(mesh) - save_original_ids(mesh) - - # Coarsen marked cells - coarsen_fn_c = cfunction(coarsen_fn, Val(ndims(mesh))) - init_fn_c = cfunction(init_fn, Val(ndims(mesh))) - - @trixi_timeit timer() "coarsen!" coarsen_p4est!(mesh.p4est, false, coarsen_fn_c, - init_fn_c) - - # IDs of newly created cells (one-based) - new_cells = collect_new_cells(mesh) - # Old IDs of cells that have been coarsened (one-based) - coarsened_cells_vec = collect_changed_cells(mesh, original_n_cells) - # 2^ndims changed cells should have been coarsened to one new cell. - # This matrix will store the IDs of all cells that have been coarsened to cell new_cells[i] - # in the i-th column. - coarsened_cells = reshape(coarsened_cells_vec, 2^ndims(mesh), length(new_cells)) - - # Save new original IDs to find out what changed after balancing - intermediate_n_cells = ncells(mesh) - save_original_ids(mesh) - - @trixi_timeit timer() "rebalance" balance!(mesh, init_fn_c) - - refined_cells = collect_changed_cells(mesh, intermediate_n_cells) - - # Some cells may have been coarsened even though they unbalanced the forest. - # These cells have now been refined again by p4est_balance. - # refined_cells contains the intermediate IDs (ID of coarse cell - # between coarsening and balancing) of these cells. - # Find original ID of each cell that has been coarsened and then refined again. - for refined_cell in refined_cells - # i-th cell of the ones that have been created by coarsening has been refined again - i = findfirst(==(refined_cell), new_cells) - - # Remove IDs of the 2^ndims cells that have been coarsened to this cell - coarsened_cells[:, i] .= -1 - end - - # Return all IDs of cells that have been coarsened but not refined again by balancing - return coarsened_cells_vec[coarsened_cells_vec .>= 0] -end - -# Copy global quad ID to quad's user data storage, will be called below -function save_original_id_iter_volume(info, user_data) - info_pw = PointerWrapper(info) - - # Load tree from global trees array, one-based indexing - tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1) - # Quadrant numbering offset of this quadrant - offset = tree_pw.quadrants_offset[] - # Global quad ID - quad_id = offset + info_pw.quadid[] - - # Unpack quadrant's user data ([global quad ID, controller_value]) - pw = PointerWrapper(Int, info_pw.quad.p.user_data[]) - # Save global quad ID - pw[1] = quad_id - return nothing -end - -# 2D -function cfunction(::typeof(save_original_id_iter_volume), ::Val{2}) - @cfunction(save_original_id_iter_volume, Cvoid, - (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(save_original_id_iter_volume), ::Val{3}) - @cfunction(save_original_id_iter_volume, Cvoid, - (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid})) -end - -# Copy old element IDs to each quad's user data storage -function save_original_ids(mesh::P4estMesh) - iter_volume_c = cfunction(save_original_id_iter_volume, Val(ndims(mesh))) - - iterate_p4est(mesh.p4est, C_NULL; iter_volume_c = iter_volume_c) -end - -# Extract information about which cells have been changed -function collect_changed_iter_volume(info, user_data) - info_pw = PointerWrapper(info) - - # The original element ID has been saved to user_data before. - # Load original quad ID from quad's user data ([global quad ID, controller_value]). - quad_data_pw = PointerWrapper(Int, info_pw.quad.p.user_data[]) - original_id = quad_data_pw[1] - - # original_id of cells that have been newly created is -1 - if original_id >= 0 - # Unpack user_data = original_cells - user_data_pw = PointerWrapper(Int, user_data) - - # If quad has an original_id, it existed before refinement/coarsening, - # and therefore wasn't changed. - # Mark original_id as "not changed during refinement/coarsening" in original_cells - user_data_pw[original_id + 1] = 0 - end - return nothing -end - -# 2D -function cfunction(::typeof(collect_changed_iter_volume), ::Val{2}) - @cfunction(collect_changed_iter_volume, Cvoid, - (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(collect_changed_iter_volume), ::Val{3}) - @cfunction(collect_changed_iter_volume, Cvoid, - (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid})) -end - -function collect_changed_cells(mesh::P4estMesh, original_n_cells) - original_cells = collect(1:original_n_cells) - - # Iterate over all quads and set original cells that haven't been changed to zero - iter_volume_c = cfunction(collect_changed_iter_volume, Val(ndims(mesh))) - - iterate_p4est(mesh.p4est, original_cells; iter_volume_c = iter_volume_c) - - # Changed cells are all that haven't been set to zero above - changed_original_cells = original_cells[original_cells .> 0] - - return changed_original_cells -end - -# Extract newly created cells -function collect_new_iter_volume(info, user_data) - info_pw = PointerWrapper(info) - - # The original element ID has been saved to user_data before. - # Unpack quadrant's user data ([global quad ID, controller_value]). - original_id = PointerWrapper(Int, info_pw.quad.p.user_data[])[1] - - # original_id of cells that have been newly created is -1 - if original_id < 0 + end + + function get_global_first_element_ids(mesh::P4estMesh) + return unsafe_wrap(Array, mesh.p4est.global_first_quadrant, mpi_nranks() + 1) + end + + function balance!(mesh::P4estMesh{2}, init_fn = C_NULL) + p4est_balance(mesh.p4est, P4EST_CONNECT_FACE, init_fn) + # Due to a bug in `p4est`, the forest needs to be rebalanced twice sometimes + # See https://github.com/cburstedde/p4est/issues/112 + p4est_balance(mesh.p4est, P4EST_CONNECT_FACE, init_fn) + end + + function balance!(mesh::P4estMesh{3}, init_fn = C_NULL) + p8est_balance(mesh.p4est, P8EST_CONNECT_FACE, init_fn) + end + + function partition!(mesh::P4estMesh{2}; weight_fn = C_NULL) + p4est_partition( + mesh.p4est, Int(mesh.p4est_partition_allow_for_coarsening), + weight_fn + ) + end + + function partition!(mesh::P4estMesh{3}; weight_fn = C_NULL) + p8est_partition( + mesh.p4est, Int(mesh.p4est_partition_allow_for_coarsening), + weight_fn + ) + end + + function update_ghost_layer!(mesh::P4estMesh) + ghost_destroy_p4est(mesh.ghost) + mesh.ghost = PointerWrapper(ghost_new_p4est(mesh.p4est)) + end + + function init_fn(p4est, which_tree, quadrant) + # Unpack quadrant's user data ([global quad ID, controller_value]) + # Use `unsafe_load` here since `quadrant.p.user_data isa Ptr{Ptr{Nothing}}` + # and we only need the first (only!) entry + pw = PointerWrapper(Int, unsafe_load(quadrant.p.user_data)) + + # Initialize quad ID as -1 and controller_value as 0 (don't refine or coarsen) + pw[1] = -1 + pw[2] = 0 + return nothing + end + + # 2D + function cfunction(::typeof(init_fn), ::Val{2}) + @cfunction( + init_fn, Cvoid, + (Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{p4est_quadrant_t}) + ) + end + # 3D + function cfunction(::typeof(init_fn), ::Val{3}) + @cfunction( + init_fn, Cvoid, + (Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{p8est_quadrant_t}) + ) + end + + function refine_fn(p4est, which_tree, quadrant) + # Controller value has been copied to the quadrant's user data storage before. + # Unpack quadrant's user data ([global quad ID, controller_value]). + # Use `unsafe_load` here since `quadrant.p.user_data isa Ptr{Ptr{Nothing}}` + # and we only need the first (only!) entry + pw = PointerWrapper(Int, unsafe_load(quadrant.p.user_data)) + controller_value = pw[2] + + if controller_value > 0 + # return true (refine) + return Cint(1) + else + # return false (don't refine) + return Cint(0) + end + end + + # 2D + function cfunction(::typeof(refine_fn), ::Val{2}) + @cfunction( + refine_fn, Cint, + (Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{p4est_quadrant_t}) + ) + end + # 3D + function cfunction(::typeof(refine_fn), ::Val{3}) + @cfunction( + refine_fn, Cint, + (Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{p8est_quadrant_t}) + ) + end + + # Refine marked cells and rebalance forest. + # Return a list of all cells that have been refined during refinement or rebalancing. + function refine!(mesh::P4estMesh) + # Copy original element IDs to quad user data storage + original_n_cells = ncells(mesh) + save_original_ids(mesh) + + init_fn_c = cfunction(init_fn, Val(ndims(mesh))) + refine_fn_c = cfunction(refine_fn, Val(ndims(mesh))) + + # Refine marked cells + @trixi_timeit timer() "refine" refine_p4est!( + mesh.p4est, false, refine_fn_c, + init_fn_c + ) + + @trixi_timeit timer() "rebalance" balance!(mesh, init_fn_c) + + return collect_changed_cells(mesh, original_n_cells) + end + + function coarsen_fn(p4est, which_tree, quadrants_ptr) + quadrants = unsafe_wrap_quadrants(quadrants_ptr, p4est) + + # Controller value has been copied to the quadrant's user data storage before. + # Load controller value from quadrant's user data ([global quad ID, controller_value]). + # Use `unsafe_load` here since `quadrant.p.user_data isa Ptr{Ptr{Nothing}}` + # and we only need the first (only!) entry + controller_value(i) = PointerWrapper(Int, unsafe_load(quadrants[i].p.user_data))[2] + + # `p4est` calls this function for each 2^ndims quads that could be coarsened to a single one. + # Only coarsen if all these 2^ndims quads have been marked for coarsening. + if all(i -> controller_value(i) < 0, eachindex(quadrants)) + # return true (coarsen) + return Cint(1) + else + # return false (don't coarsen) + return Cint(0) + end + end + + # 2D + function unsafe_wrap_quadrants(quadrants_ptr, ::Ptr{p4est_t}) + unsafe_wrap(Array, quadrants_ptr, 4) + end + # 3D + function unsafe_wrap_quadrants(quadrants_ptr, ::Ptr{p8est_t}) + unsafe_wrap(Array, quadrants_ptr, 8) + end + + # 2D + function cfunction(::typeof(coarsen_fn), ::Val{2}) + @cfunction( + coarsen_fn, Cint, + (Ptr{p4est_t}, Ptr{p4est_topidx_t}, Ptr{Ptr{p4est_quadrant_t}}) + ) + end + # 3D + function cfunction(::typeof(coarsen_fn), ::Val{3}) + @cfunction( + coarsen_fn, Cint, + (Ptr{p8est_t}, Ptr{p4est_topidx_t}, Ptr{Ptr{p8est_quadrant_t}}) + ) + end + + # Coarsen marked cells if the forest will stay balanced. + # Return a list of all cells that have been coarsened. + function coarsen!(mesh::P4estMesh) + # Copy original element IDs to quad user data storage + original_n_cells = ncells(mesh) + save_original_ids(mesh) + + # Coarsen marked cells + coarsen_fn_c = cfunction(coarsen_fn, Val(ndims(mesh))) + init_fn_c = cfunction(init_fn, Val(ndims(mesh))) + + @trixi_timeit timer() "coarsen!" coarsen_p4est!( + mesh.p4est, false, coarsen_fn_c, + init_fn_c + ) + + # IDs of newly created cells (one-based) + new_cells = collect_new_cells(mesh) + # Old IDs of cells that have been coarsened (one-based) + coarsened_cells_vec = collect_changed_cells(mesh, original_n_cells) + # 2^ndims changed cells should have been coarsened to one new cell. + # This matrix will store the IDs of all cells that have been coarsened to cell new_cells[i] + # in the i-th column. + coarsened_cells = reshape(coarsened_cells_vec, 2^ndims(mesh), length(new_cells)) + + # Save new original IDs to find out what changed after balancing + intermediate_n_cells = ncells(mesh) + save_original_ids(mesh) + + @trixi_timeit timer() "rebalance" balance!(mesh, init_fn_c) + + refined_cells = collect_changed_cells(mesh, intermediate_n_cells) + + # Some cells may have been coarsened even though they unbalanced the forest. + # These cells have now been refined again by p4est_balance. + # refined_cells contains the intermediate IDs (ID of coarse cell + # between coarsening and balancing) of these cells. + # Find original ID of each cell that has been coarsened and then refined again. + for refined_cell in refined_cells + # i-th cell of the ones that have been created by coarsening has been refined again + i = findfirst(==(refined_cell), new_cells) + + # Remove IDs of the 2^ndims cells that have been coarsened to this cell + coarsened_cells[:, i] .= -1 + end + + # Return all IDs of cells that have been coarsened but not refined again by balancing + return coarsened_cells_vec[coarsened_cells_vec .>= 0] + end + + # Copy global quad ID to quad's user data storage, will be called below + function save_original_id_iter_volume(info, user_data) + info_pw = PointerWrapper(info) + # Load tree from global trees array, one-based indexing tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1) # Quadrant numbering offset of this quadrant @@ -2018,37 +2121,138 @@ function collect_new_iter_volume(info, user_data) # Global quad ID quad_id = offset + info_pw.quadid[] - # Unpack user_data = original_cells - user_data_pw = PointerWrapper(Int, user_data) + # Unpack quadrant's user data ([global quad ID, controller_value]) + pw = PointerWrapper(Int, info_pw.quad.p.user_data[]) + # Save global quad ID + pw[1] = quad_id + return nothing + end + + # 2D + function cfunction(::typeof(save_original_id_iter_volume), ::Val{2}) + @cfunction( + save_original_id_iter_volume, Cvoid, + (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}) + ) + end + # 3D + function cfunction(::typeof(save_original_id_iter_volume), ::Val{3}) + @cfunction( + save_original_id_iter_volume, Cvoid, + (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}) + ) + end + + # Copy old element IDs to each quad's user data storage + function save_original_ids(mesh::P4estMesh) + iter_volume_c = cfunction(save_original_id_iter_volume, Val(ndims(mesh))) + + iterate_p4est(mesh.p4est, C_NULL; iter_volume_c = iter_volume_c) + end + + # Extract information about which cells have been changed + function collect_changed_iter_volume(info, user_data) + info_pw = PointerWrapper(info) + + # The original element ID has been saved to user_data before. + # Load original quad ID from quad's user data ([global quad ID, controller_value]). + quad_data_pw = PointerWrapper(Int, info_pw.quad.p.user_data[]) + original_id = quad_data_pw[1] + + # original_id of cells that have been newly created is -1 + if original_id >= 0 + # Unpack user_data = original_cells + user_data_pw = PointerWrapper(Int, user_data) + + # If quad has an original_id, it existed before refinement/coarsening, + # and therefore wasn't changed. + # Mark original_id as "not changed during refinement/coarsening" in original_cells + user_data_pw[original_id + 1] = 0 + end + return nothing + end + + # 2D + function cfunction(::typeof(collect_changed_iter_volume), ::Val{2}) + @cfunction( + collect_changed_iter_volume, Cvoid, + (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}) + ) + end + # 3D + function cfunction(::typeof(collect_changed_iter_volume), ::Val{3}) + @cfunction( + collect_changed_iter_volume, Cvoid, + (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}) + ) + end + + function collect_changed_cells(mesh::P4estMesh, original_n_cells) + original_cells = collect(1:original_n_cells) + + # Iterate over all quads and set original cells that haven't been changed to zero + iter_volume_c = cfunction(collect_changed_iter_volume, Val(ndims(mesh))) + + iterate_p4est(mesh.p4est, original_cells; iter_volume_c = iter_volume_c) + + # Changed cells are all that haven't been set to zero above + changed_original_cells = original_cells[original_cells .> 0] + + return changed_original_cells + end + + # Extract newly created cells + function collect_new_iter_volume(info, user_data) + info_pw = PointerWrapper(info) + + # The original element ID has been saved to user_data before. + # Unpack quadrant's user data ([global quad ID, controller_value]). + original_id = PointerWrapper(Int, info_pw.quad.p.user_data[])[1] + + # original_id of cells that have been newly created is -1 + if original_id < 0 + # Load tree from global trees array, one-based indexing + tree_pw = load_pointerwrapper_tree(info_pw.p4est, info_pw.treeid[] + 1) + # Quadrant numbering offset of this quadrant + offset = tree_pw.quadrants_offset[] + # Global quad ID + quad_id = offset + info_pw.quadid[] + + # Unpack user_data = original_cells + user_data_pw = PointerWrapper(Int, user_data) - # Mark cell as "newly created during refinement/coarsening/balancing" - user_data_pw[quad_id + 1] = 1 + # Mark cell as "newly created during refinement/coarsening/balancing" + user_data_pw[quad_id + 1] = 1 + end + return nothing end - return nothing -end -# 2D -function cfunction(::typeof(collect_new_iter_volume), ::Val{2}) - @cfunction(collect_new_iter_volume, Cvoid, - (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(collect_new_iter_volume), ::Val{3}) - @cfunction(collect_new_iter_volume, Cvoid, - (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid})) -end + # 2D + function cfunction(::typeof(collect_new_iter_volume), ::Val{2}) + @cfunction( + collect_new_iter_volume, Cvoid, + (Ptr{p4est_iter_volume_info_t}, Ptr{Cvoid}) + ) + end + # 3D + function cfunction(::typeof(collect_new_iter_volume), ::Val{3}) + @cfunction( + collect_new_iter_volume, Cvoid, + (Ptr{p8est_iter_volume_info_t}, Ptr{Cvoid}) + ) + end -function collect_new_cells(mesh::P4estMesh) - cell_is_new = zeros(Int, ncells(mesh)) + function collect_new_cells(mesh::P4estMesh) + cell_is_new = zeros(Int, ncells(mesh)) - # Iterate over all quads and set original cells that have been changed to one - iter_volume_c = cfunction(collect_new_iter_volume, Val(ndims(mesh))) + # Iterate over all quads and set original cells that have been changed to one + iter_volume_c = cfunction(collect_new_iter_volume, Val(ndims(mesh))) - iterate_p4est(mesh.p4est, cell_is_new; iter_volume_c = iter_volume_c) + iterate_p4est(mesh.p4est, cell_is_new; iter_volume_c = iter_volume_c) - # Changed cells are all that haven't been set to zero above - new_cells = findall(==(1), cell_is_new) + # Changed cells are all that haven't been set to zero above + new_cells = findall(==(1), cell_is_new) - return new_cells -end + return new_cells + end end # @muladd diff --git a/src/meshes/parallel_tree.jl b/src/meshes/parallel_tree.jl index 7175ed4774..da3c97369a 100644 --- a/src/meshes/parallel_tree.jl +++ b/src/meshes/parallel_tree.jl @@ -3,234 +3,256 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Composite type that represents a NDIMS-dimensional tree (parallel version). -# -# Implements everything required for AbstractContainer. -# -# Note: The way the data structures are set up and the way most algorithms -# work, it is *always* assumed that -# a) we have a balanced tree (= at most one level difference between -# neighboring cells, or 2:1 rule) -# b) we may not have all children (= some children may not exist) -# c) the tree is stored depth-first -# -# However, the way the refinement/coarsening algorithms are currently -# implemented, we only have fully refined cells. That is, a cell either has 2^NDIMS children or -# no children at all (= leaf cell). This restriction is also assumed at -# multiple positions in the refinement/coarsening algorithms. -# -# An exception to the 2:1 rule exists for the low-level `refine_unbalanced!` -# function, which is required for implementing level-wise refinement in a sane -# way. Also, depth-first ordering *might* not be guaranteed during -# refinement/coarsening operations. -mutable struct ParallelTree{NDIMS} <: AbstractTree{NDIMS} - parent_ids::Vector{Int} - child_ids::Matrix{Int} - neighbor_ids::Matrix{Int} - levels::Vector{Int} - coordinates::Matrix{Float64} - original_cell_ids::Vector{Int} - mpi_ranks::Vector{Int} - - capacity::Int - length::Int - dummy::Int - - center_level_0::SVector{NDIMS, Float64} - length_level_0::Float64 - periodicity::NTuple{NDIMS, Bool} - - function ParallelTree{NDIMS}(capacity::Integer) where {NDIMS} - # Verify that NDIMS is an integer - @assert NDIMS isa Integer - - # Create instance - t = new() + #! format: noindent + + # Composite type that represents a NDIMS-dimensional tree (parallel version). + # + # Implements everything required for AbstractContainer. + # + # Note: The way the data structures are set up and the way most algorithms + # work, it is *always* assumed that + # a) we have a balanced tree (= at most one level difference between + # neighboring cells, or 2:1 rule) + # b) we may not have all children (= some children may not exist) + # c) the tree is stored depth-first + # + # However, the way the refinement/coarsening algorithms are currently + # implemented, we only have fully refined cells. That is, a cell either has 2^NDIMS children or + # no children at all (= leaf cell). This restriction is also assumed at + # multiple positions in the refinement/coarsening algorithms. + # + # An exception to the 2:1 rule exists for the low-level `refine_unbalanced!` + # function, which is required for implementing level-wise refinement in a sane + # way. Also, depth-first ordering *might* not be guaranteed during + # refinement/coarsening operations. + mutable struct ParallelTree{NDIMS} <: AbstractTree{NDIMS} + parent_ids::Vector{Int} + child_ids::Matrix{Int} + neighbor_ids::Matrix{Int} + levels::Vector{Int} + coordinates::Matrix{Float64} + original_cell_ids::Vector{Int} + mpi_ranks::Vector{Int} + + capacity::Int + length::Int + dummy::Int + + center_level_0::SVector{NDIMS, Float64} + length_level_0::Float64 + periodicity::NTuple{NDIMS, Bool} + + function ParallelTree{NDIMS}(capacity::Integer) where {NDIMS} + # Verify that NDIMS is an integer + @assert NDIMS isa Integer + + # Create instance + t = new() + + # Initialize fields with defaults + # Note: length as capacity + 1 is to use `capacity + 1` as temporary storage for swap operations + t.parent_ids = fill(typemin(Int), capacity + 1) + t.child_ids = fill(typemin(Int), 2^NDIMS, capacity + 1) + t.neighbor_ids = fill(typemin(Int), 2 * NDIMS, capacity + 1) + t.levels = fill(typemin(Int), capacity + 1) + t.coordinates = fill(NaN, NDIMS, capacity + 1) + t.original_cell_ids = fill(typemin(Int), capacity + 1) + t.mpi_ranks = fill(typemin(Int), capacity + 1) + + t.capacity = capacity + t.length = 0 + t.dummy = capacity + 1 + + t.center_level_0 = SVector(ntuple(_ -> NaN, NDIMS)) + t.length_level_0 = NaN + + return t + end + end - # Initialize fields with defaults - # Note: length as capacity + 1 is to use `capacity + 1` as temporary storage for swap operations - t.parent_ids = fill(typemin(Int), capacity + 1) - t.child_ids = fill(typemin(Int), 2^NDIMS, capacity + 1) - t.neighbor_ids = fill(typemin(Int), 2 * NDIMS, capacity + 1) - t.levels = fill(typemin(Int), capacity + 1) - t.coordinates = fill(NaN, NDIMS, capacity + 1) - t.original_cell_ids = fill(typemin(Int), capacity + 1) - t.mpi_ranks = fill(typemin(Int), capacity + 1) + # Constructor for passing the dimension as an argument + ParallelTree(::Val{NDIMS}, args...) where {NDIMS} = ParallelTree{NDIMS}(args...) - t.capacity = capacity - t.length = 0 - t.dummy = capacity + 1 + # Create and initialize tree + function ParallelTree{NDIMS}( + capacity::Int, center::AbstractArray{Float64}, + length::Real, periodicity = true + ) where {NDIMS} + # Create instance + t = ParallelTree{NDIMS}(capacity) - t.center_level_0 = SVector(ntuple(_ -> NaN, NDIMS)) - t.length_level_0 = NaN + # Initialize root cell + init!(t, center, length, periodicity) return t end -end - -# Constructor for passing the dimension as an argument -ParallelTree(::Val{NDIMS}, args...) where {NDIMS} = ParallelTree{NDIMS}(args...) - -# Create and initialize tree -function ParallelTree{NDIMS}(capacity::Int, center::AbstractArray{Float64}, - length::Real, periodicity = true) where {NDIMS} - # Create instance - t = ParallelTree{NDIMS}(capacity) - - # Initialize root cell - init!(t, center, length, periodicity) - - return t -end - -# Constructor accepting a single number as center (as opposed to an array) for 1D -function ParallelTree{1}(cap::Int, center::Real, len::Real, periodicity = true) - ParallelTree{1}(cap, [convert(Float64, center)], len, periodicity) -end - -# Clear tree with deleting data structures, store center and length, and create root cell -function init!(t::ParallelTree, center::AbstractArray{Float64}, length::Real, - periodicity = true) - clear!(t) - - # Set domain information - t.center_level_0 = center - t.length_level_0 = length - - # Create root cell - t.length += 1 - t.parent_ids[1] = 0 - t.child_ids[:, 1] .= 0 - t.levels[1] = 0 - set_cell_coordinates!(t, t.center_level_0, 1) - t.original_cell_ids[1] = 0 - t.mpi_ranks[1] = typemin(Int) - - # Set neighbor ids: for each periodic direction, the level-0 cell is its own neighbor - if all(periodicity) - # Also catches case where periodicity = true - t.neighbor_ids[:, 1] .= 1 - t.periodicity = ntuple(x -> true, ndims(t)) - elseif !any(periodicity) - # Also catches case where periodicity = false - t.neighbor_ids[:, 1] .= 0 - t.periodicity = ntuple(x -> false, ndims(t)) - else - # Default case if periodicity is an iterable - for dimension in 1:ndims(t) - if periodicity[dimension] - t.neighbor_ids[2 * dimension - 1, 1] = 1 - t.neighbor_ids[2 * dimension - 0, 1] = 1 - else - t.neighbor_ids[2 * dimension - 1, 1] = 0 - t.neighbor_ids[2 * dimension - 0, 1] = 0 + + # Constructor accepting a single number as center (as opposed to an array) for 1D + function ParallelTree{1}(cap::Int, center::Real, len::Real, periodicity = true) + ParallelTree{1}(cap, [convert(Float64, center)], len, periodicity) + end + + # Clear tree with deleting data structures, store center and length, and create root cell + function init!( + t::ParallelTree, center::AbstractArray{Float64}, length::Real, + periodicity = true + ) + clear!(t) + + # Set domain information + t.center_level_0 = center + t.length_level_0 = length + + # Create root cell + t.length += 1 + t.parent_ids[1] = 0 + t.child_ids[:, 1] .= 0 + t.levels[1] = 0 + set_cell_coordinates!(t, t.center_level_0, 1) + t.original_cell_ids[1] = 0 + t.mpi_ranks[1] = typemin(Int) + + # Set neighbor ids: for each periodic direction, the level-0 cell is its own neighbor + if all(periodicity) + # Also catches case where periodicity = true + t.neighbor_ids[:, 1] .= 1 + t.periodicity = ntuple(x -> true, ndims(t)) + elseif !any(periodicity) + # Also catches case where periodicity = false + t.neighbor_ids[:, 1] .= 0 + t.periodicity = ntuple(x -> false, ndims(t)) + else + # Default case if periodicity is an iterable + for dimension in 1:ndims(t) + if periodicity[dimension] + t.neighbor_ids[2 * dimension - 1, 1] = 1 + t.neighbor_ids[2 * dimension - 0, 1] = 1 + else + t.neighbor_ids[2 * dimension - 1, 1] = 0 + t.neighbor_ids[2 * dimension - 0, 1] = 0 + end end + + t.periodicity = Tuple(periodicity) end + end - t.periodicity = Tuple(periodicity) + # Convenience output for debugging + function Base.show(io::IO, ::MIME"text/plain", t::ParallelTree) + @nospecialize t # reduce precompilation time + + l = t.length + println(io, '*'^20) + println(io, "t.parent_ids[1:l] = $(t.parent_ids[1:l])") + println(io, "transpose(t.child_ids[:, 1:l]) = $(transpose(t.child_ids[:, 1:l]))") + println( + io, + "transpose(t.neighbor_ids[:, 1:l]) = $(transpose(t.neighbor_ids[:, 1:l]))" + ) + println(io, "t.levels[1:l] = $(t.levels[1:l])") + println( + io, + "transpose(t.coordinates[:, 1:l]) = $(transpose(t.coordinates[:, 1:l]))" + ) + println(io, "t.original_cell_ids[1:l] = $(t.original_cell_ids[1:l])") + println(io, "t.mpi_ranks[1:l] = $(t.mpi_ranks[1:l])") + println(io, "t.capacity = $(t.capacity)") + println(io, "t.length = $(t.length)") + println(io, "t.dummy = $(t.dummy)") + println(io, "t.center_level_0 = $(t.center_level_0)") + println(io, "t.length_level_0 = $(t.length_level_0)") + println(io, '*'^20) end -end - -# Convenience output for debugging -function Base.show(io::IO, ::MIME"text/plain", t::ParallelTree) - @nospecialize t # reduce precompilation time - - l = t.length - println(io, '*'^20) - println(io, "t.parent_ids[1:l] = $(t.parent_ids[1:l])") - println(io, "transpose(t.child_ids[:, 1:l]) = $(transpose(t.child_ids[:, 1:l]))") - println(io, - "transpose(t.neighbor_ids[:, 1:l]) = $(transpose(t.neighbor_ids[:, 1:l]))") - println(io, "t.levels[1:l] = $(t.levels[1:l])") - println(io, - "transpose(t.coordinates[:, 1:l]) = $(transpose(t.coordinates[:, 1:l]))") - println(io, "t.original_cell_ids[1:l] = $(t.original_cell_ids[1:l])") - println(io, "t.mpi_ranks[1:l] = $(t.mpi_ranks[1:l])") - println(io, "t.capacity = $(t.capacity)") - println(io, "t.length = $(t.length)") - println(io, "t.dummy = $(t.dummy)") - println(io, "t.center_level_0 = $(t.center_level_0)") - println(io, "t.length_level_0 = $(t.length_level_0)") - println(io, '*'^20) -end - -# Check if cell is own cell, i.e., belongs to this MPI rank -is_own_cell(t::ParallelTree, cell_id) = t.mpi_ranks[cell_id] == mpi_rank() - -# Return an array with the ids of all leaf cells for a given rank -leaf_cells_by_rank(t::ParallelTree, rank) = - filter_leaf_cells(t) do cell_id + + # Check if cell is own cell, i.e., belongs to this MPI rank + is_own_cell(t::ParallelTree, cell_id) = t.mpi_ranks[cell_id] == mpi_rank() + + # Return an array with the ids of all leaf cells for a given rank + leaf_cells_by_rank(t::ParallelTree, rank) = + filter_leaf_cells(t) do cell_id t.mpi_ranks[cell_id] == rank end -# Return an array with the ids of all local leaf cells -local_leaf_cells(t::ParallelTree) = leaf_cells_by_rank(t, mpi_rank()) - -# Set information for child cell `child_id` based on parent cell `cell_id` (except neighbors) -function init_child!(t::ParallelTree, cell_id, child, child_id) - t.parent_ids[child_id] = cell_id - t.child_ids[child, cell_id] = child_id - t.child_ids[:, child_id] .= 0 - t.levels[child_id] = t.levels[cell_id] + 1 - set_cell_coordinates!(t, - child_coordinates(t, cell_coordinates(t, cell_id), - length_at_cell(t, cell_id), child), - child_id) - t.original_cell_ids[child_id] = 0 - t.mpi_ranks[child_id] = t.mpi_ranks[cell_id] - - return nothing -end - -# Reset range of cells to values that are prone to cause errors as soon as they are used. -# -# Rationale: If an invalid cell is accidentally used, we want to know it as soon as possible. -function invalidate!(t::ParallelTree, first::Int, last::Int) - @assert first > 0 - @assert last <= t.capacity + 1 - - # Integer values are set to smallest negative value, floating point values to NaN - t.parent_ids[first:last] .= typemin(Int) - t.child_ids[:, first:last] .= typemin(Int) - t.neighbor_ids[:, first:last] .= typemin(Int) - t.levels[first:last] .= typemin(Int) - t.coordinates[:, first:last] .= NaN - t.original_cell_ids[first:last] .= typemin(Int) - t.mpi_ranks[first:last] .= typemin(Int) - - return nothing -end - -# Raw copy operation for ranges of cells. -# -# This method is used by the higher-level copy operations for AbstractContainer -function raw_copy!(target::ParallelTree, source::ParallelTree, first::Int, last::Int, - destination::Int) - copy_data!(target.parent_ids, source.parent_ids, first, last, destination) - copy_data!(target.child_ids, source.child_ids, first, last, destination, - n_children_per_cell(target)) - copy_data!(target.neighbor_ids, source.neighbor_ids, first, last, - destination, n_directions(target)) - copy_data!(target.levels, source.levels, first, last, destination) - copy_data!(target.coordinates, source.coordinates, first, last, destination, - ndims(target)) - copy_data!(target.original_cell_ids, source.original_cell_ids, first, last, - destination) - copy_data!(target.mpi_ranks, source.mpi_ranks, first, last, destination) -end - -# Reset data structures by recreating all internal storage containers and invalidating all elements -function reset_data_structures!(t::ParallelTree{NDIMS}) where {NDIMS} - t.parent_ids = Vector{Int}(undef, t.capacity + 1) - t.child_ids = Matrix{Int}(undef, 2^NDIMS, t.capacity + 1) - t.neighbor_ids = Matrix{Int}(undef, 2 * NDIMS, t.capacity + 1) - t.levels = Vector{Int}(undef, t.capacity + 1) - t.coordinates = Matrix{Float64}(undef, NDIMS, t.capacity + 1) - t.original_cell_ids = Vector{Int}(undef, t.capacity + 1) - t.mpi_ranks = Vector{Int}(undef, t.capacity + 1) - - invalidate!(t, 1, capacity(t) + 1) -end + # Return an array with the ids of all local leaf cells + local_leaf_cells(t::ParallelTree) = leaf_cells_by_rank(t, mpi_rank()) + + # Set information for child cell `child_id` based on parent cell `cell_id` (except neighbors) + function init_child!(t::ParallelTree, cell_id, child, child_id) + t.parent_ids[child_id] = cell_id + t.child_ids[child, cell_id] = child_id + t.child_ids[:, child_id] .= 0 + t.levels[child_id] = t.levels[cell_id] + 1 + set_cell_coordinates!( + t, + child_coordinates( + t, cell_coordinates(t, cell_id), + length_at_cell(t, cell_id), child + ), + child_id + ) + t.original_cell_ids[child_id] = 0 + t.mpi_ranks[child_id] = t.mpi_ranks[cell_id] + + return nothing + end + + # Reset range of cells to values that are prone to cause errors as soon as they are used. + # + # Rationale: If an invalid cell is accidentally used, we want to know it as soon as possible. + function invalidate!(t::ParallelTree, first::Int, last::Int) + @assert first > 0 + @assert last <= t.capacity + 1 + + # Integer values are set to smallest negative value, floating point values to NaN + t.parent_ids[first:last] .= typemin(Int) + t.child_ids[:, first:last] .= typemin(Int) + t.neighbor_ids[:, first:last] .= typemin(Int) + t.levels[first:last] .= typemin(Int) + t.coordinates[:, first:last] .= NaN + t.original_cell_ids[first:last] .= typemin(Int) + t.mpi_ranks[first:last] .= typemin(Int) + + return nothing + end + + # Raw copy operation for ranges of cells. + # + # This method is used by the higher-level copy operations for AbstractContainer + function raw_copy!( + target::ParallelTree, source::ParallelTree, first::Int, last::Int, + destination::Int + ) + copy_data!(target.parent_ids, source.parent_ids, first, last, destination) + copy_data!( + target.child_ids, source.child_ids, first, last, destination, + n_children_per_cell(target) + ) + copy_data!( + target.neighbor_ids, source.neighbor_ids, first, last, + destination, n_directions(target) + ) + copy_data!(target.levels, source.levels, first, last, destination) + copy_data!( + target.coordinates, source.coordinates, first, last, destination, + ndims(target) + ) + copy_data!( + target.original_cell_ids, source.original_cell_ids, first, last, + destination + ) + copy_data!(target.mpi_ranks, source.mpi_ranks, first, last, destination) + end + + # Reset data structures by recreating all internal storage containers and invalidating all elements + function reset_data_structures!(t::ParallelTree{NDIMS}) where {NDIMS} + t.parent_ids = Vector{Int}(undef, t.capacity + 1) + t.child_ids = Matrix{Int}(undef, 2^NDIMS, t.capacity + 1) + t.neighbor_ids = Matrix{Int}(undef, 2 * NDIMS, t.capacity + 1) + t.levels = Vector{Int}(undef, t.capacity + 1) + t.coordinates = Matrix{Float64}(undef, NDIMS, t.capacity + 1) + t.original_cell_ids = Vector{Int}(undef, t.capacity + 1) + t.mpi_ranks = Vector{Int}(undef, t.capacity + 1) + + invalidate!(t, 1, capacity(t) + 1) + end end # @muladd diff --git a/src/meshes/parallel_tree_mesh.jl b/src/meshes/parallel_tree_mesh.jl index 2b1f137755..bb331f8acc 100644 --- a/src/meshes/parallel_tree_mesh.jl +++ b/src/meshes/parallel_tree_mesh.jl @@ -3,105 +3,111 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - partition!(mesh::ParallelTreeMesh, allow_coarsening=true) - -Partition `mesh` using a static domain decomposition algorithm -based on leaf cell count and tree structure. -If `allow_coarsening` is `true`, the algorithm will keep leaf cells together -on one rank when needed for local coarsening (i.e. when all children of a cell are leaves). -""" -function partition!(mesh::ParallelTreeMesh; allow_coarsening = true) - # Determine number of leaf cells per rank - leaves = leaf_cells(mesh.tree) - @assert length(leaves)>mpi_nranks() "Too many ranks to properly partition the mesh!" - n_leaves_per_rank = OffsetArray(fill(div(length(leaves), mpi_nranks()), - mpi_nranks()), - 0:(mpi_nranks() - 1)) - for rank in 0:(rem(length(leaves), mpi_nranks()) - 1) - n_leaves_per_rank[rank] += 1 - end - @assert sum(n_leaves_per_rank) == length(leaves) - - # Assign MPI ranks to all cells such that all ancestors of each cell - if not yet assigned to a - # rank - belong to the same rank - mesh.first_cell_by_rank = similar(n_leaves_per_rank) - mesh.n_cells_by_rank = similar(n_leaves_per_rank) - - leaf_count = 0 - # Assign first cell to rank 0 (employ depth-first indexing of cells) - mesh.first_cell_by_rank[0] = 1 - # Iterate over all ranks - for rank in 0:(mpi_nranks() - 1) - leaf_count += n_leaves_per_rank[rank] - last_id = leaves[leaf_count] - parent_id = mesh.tree.parent_ids[last_id] - - # If coarsening is allowed, we need to make sure that parents of leaves - # are on the same rank as the leaves when coarsened. - if allow_coarsening && - # Check if all children of the last parent are leaves - all(id -> is_leaf(mesh.tree, id), @view mesh.tree.child_ids[:, parent_id]) && - rank < length(n_leaves_per_rank) - 1 # Make sure there is another rank - - # To keep children of parent together if they are all leaves, - # all children are added to this rank - additional_cells = (last_id + 1):mesh.tree.child_ids[end, parent_id] - if length(additional_cells) > 0 - last_id = additional_cells[end] - - additional_leaves = count(id -> is_leaf(mesh.tree, id), - additional_cells) - leaf_count += additional_leaves - # Add leaves to this rank, remove from next rank - n_leaves_per_rank[rank] += additional_leaves - n_leaves_per_rank[rank + 1] -= additional_leaves - end + #! format: noindent + + """ + partition!(mesh::ParallelTreeMesh, allow_coarsening=true) + + Partition `mesh` using a static domain decomposition algorithm + based on leaf cell count and tree structure. + If `allow_coarsening` is `true`, the algorithm will keep leaf cells together + on one rank when needed for local coarsening (i.e. when all children of a cell are leaves). + """ + function partition!(mesh::ParallelTreeMesh; allow_coarsening = true) + # Determine number of leaf cells per rank + leaves = leaf_cells(mesh.tree) + @assert length(leaves) > mpi_nranks() "Too many ranks to properly partition the mesh!" + n_leaves_per_rank = OffsetArray( + fill( + div(length(leaves), mpi_nranks()), + mpi_nranks() + ), + 0:(mpi_nranks() - 1) + ) + for rank in 0:(rem(length(leaves), mpi_nranks()) - 1) + n_leaves_per_rank[rank] += 1 end + @assert sum(n_leaves_per_rank) == length(leaves) + + # Assign MPI ranks to all cells such that all ancestors of each cell - if not yet assigned to a + # rank - belong to the same rank + mesh.first_cell_by_rank = similar(n_leaves_per_rank) + mesh.n_cells_by_rank = similar(n_leaves_per_rank) + + leaf_count = 0 + # Assign first cell to rank 0 (employ depth-first indexing of cells) + mesh.first_cell_by_rank[0] = 1 + # Iterate over all ranks + for rank in 0:(mpi_nranks() - 1) + leaf_count += n_leaves_per_rank[rank] + last_id = leaves[leaf_count] + parent_id = mesh.tree.parent_ids[last_id] + + # If coarsening is allowed, we need to make sure that parents of leaves + # are on the same rank as the leaves when coarsened. + if allow_coarsening && + # Check if all children of the last parent are leaves + all(id -> is_leaf(mesh.tree, id), @view mesh.tree.child_ids[:, parent_id]) && + rank < length(n_leaves_per_rank) - 1 # Make sure there is another rank + + # To keep children of parent together if they are all leaves, + # all children are added to this rank + additional_cells = (last_id + 1):mesh.tree.child_ids[end, parent_id] + if length(additional_cells) > 0 + last_id = additional_cells[end] + + additional_leaves = count( + id -> is_leaf(mesh.tree, id), + additional_cells + ) + leaf_count += additional_leaves + # Add leaves to this rank, remove from next rank + n_leaves_per_rank[rank] += additional_leaves + n_leaves_per_rank[rank + 1] -= additional_leaves + end + end - @assert all(n -> n > 0, n_leaves_per_rank) "Too many ranks to properly partition the mesh!" + @assert all(n -> n > 0, n_leaves_per_rank) "Too many ranks to properly partition the mesh!" - mesh.n_cells_by_rank[rank] = last_id - mesh.first_cell_by_rank[rank] + 1 - # Use depth-first indexing of cells again to assign also non leaf cells - mesh.tree.mpi_ranks[mesh.first_cell_by_rank[rank]:last_id] .= rank + mesh.n_cells_by_rank[rank] = last_id - mesh.first_cell_by_rank[rank] + 1 + # Use depth-first indexing of cells again to assign also non leaf cells + mesh.tree.mpi_ranks[mesh.first_cell_by_rank[rank]:last_id] .= rank - # Set first cell of next rank - if rank < length(n_leaves_per_rank) - 1 # Make sure there is another rank - mesh.first_cell_by_rank[rank + 1] = mesh.first_cell_by_rank[rank] + - mesh.n_cells_by_rank[rank] + # Set first cell of next rank + if rank < length(n_leaves_per_rank) - 1 # Make sure there is another rank + mesh.first_cell_by_rank[rank + 1] = mesh.first_cell_by_rank[rank] + + mesh.n_cells_by_rank[rank] + end end - end - @assert all(x -> x >= 0, mesh.tree.mpi_ranks[1:length(mesh.tree)]) - @assert sum(mesh.n_cells_by_rank) == length(mesh.tree) + @assert all(x -> x >= 0, mesh.tree.mpi_ranks[1:length(mesh.tree)]) + @assert sum(mesh.n_cells_by_rank) == length(mesh.tree) - return nothing -end + return nothing + end + + function get_restart_mesh_filename(restart_filename, mpi_parallel::True) + # Get directory name + dirname, _ = splitdir(restart_filename) -function get_restart_mesh_filename(restart_filename, mpi_parallel::True) - # Get directory name - dirname, _ = splitdir(restart_filename) + if mpi_isroot() + # Read mesh filename from restart file + mesh_file = "" + h5open(restart_filename, "r") do file + mesh_file = read(attributes(file)["mesh_file"]) + end - if mpi_isroot() - # Read mesh filename from restart file - mesh_file = "" - h5open(restart_filename, "r") do file - mesh_file = read(attributes(file)["mesh_file"]) + buffer = Vector{UInt8}(mesh_file) + MPI.Bcast!(Ref(length(buffer)), mpi_root(), mpi_comm()) + MPI.Bcast!(buffer, mpi_root(), mpi_comm()) + else # non-root ranks + count = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm()) + buffer = Vector{UInt8}(undef, count[]) + MPI.Bcast!(buffer, mpi_root(), mpi_comm()) + mesh_file = String(buffer) end - buffer = Vector{UInt8}(mesh_file) - MPI.Bcast!(Ref(length(buffer)), mpi_root(), mpi_comm()) - MPI.Bcast!(buffer, mpi_root(), mpi_comm()) - else # non-root ranks - count = MPI.Bcast!(Ref(0), mpi_root(), mpi_comm()) - buffer = Vector{UInt8}(undef, count[]) - MPI.Bcast!(buffer, mpi_root(), mpi_comm()) - mesh_file = String(buffer) + # Construct and return filename + return joinpath(dirname, mesh_file) end - - # Construct and return filename - return joinpath(dirname, mesh_file) -end end # @muladd diff --git a/src/meshes/serial_tree.jl b/src/meshes/serial_tree.jl index 143ac19f6e..3fb11b585d 100644 --- a/src/meshes/serial_tree.jl +++ b/src/meshes/serial_tree.jl @@ -3,214 +3,236 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Composite type that represents a NDIMS-dimensional tree (serial version). -# -# Implements everything required for AbstractContainer. -# -# Note: The way the data structures are set up and the way most algorithms -# work, it is *always* assumed that -# a) we have a balanced tree (= at most one level difference between -# neighboring cells, or 2:1 rule) -# b) we may not have all children (= some children may not exist) -# c) the tree is stored depth-first -# -# However, the way the refinement/coarsening algorithms are currently -# implemented, we only have fully refined cells. That is, a cell either has 2^NDIMS children or -# no children at all (= leaf cell). This restriction is also assumed at -# multiple positions in the refinement/coarsening algorithms. -# -# An exception to the 2:1 rule exists for the low-level `refine_unbalanced!` -# function, which is required for implementing level-wise refinement in a sane -# way. Also, depth-first ordering *might* not by guaranteed during -# refinement/coarsening operations. -mutable struct SerialTree{NDIMS} <: AbstractTree{NDIMS} - parent_ids::Vector{Int} - child_ids::Matrix{Int} - neighbor_ids::Matrix{Int} - levels::Vector{Int} - coordinates::Matrix{Float64} - original_cell_ids::Vector{Int} - - capacity::Int - length::Int - dummy::Int - - center_level_0::SVector{NDIMS, Float64} - length_level_0::Float64 - periodicity::NTuple{NDIMS, Bool} - - function SerialTree{NDIMS}(capacity::Integer) where {NDIMS} - # Verify that NDIMS is an integer - @assert NDIMS isa Integer - - # Create instance - t = new() + #! format: noindent + + # Composite type that represents a NDIMS-dimensional tree (serial version). + # + # Implements everything required for AbstractContainer. + # + # Note: The way the data structures are set up and the way most algorithms + # work, it is *always* assumed that + # a) we have a balanced tree (= at most one level difference between + # neighboring cells, or 2:1 rule) + # b) we may not have all children (= some children may not exist) + # c) the tree is stored depth-first + # + # However, the way the refinement/coarsening algorithms are currently + # implemented, we only have fully refined cells. That is, a cell either has 2^NDIMS children or + # no children at all (= leaf cell). This restriction is also assumed at + # multiple positions in the refinement/coarsening algorithms. + # + # An exception to the 2:1 rule exists for the low-level `refine_unbalanced!` + # function, which is required for implementing level-wise refinement in a sane + # way. Also, depth-first ordering *might* not by guaranteed during + # refinement/coarsening operations. + mutable struct SerialTree{NDIMS} <: AbstractTree{NDIMS} + parent_ids::Vector{Int} + child_ids::Matrix{Int} + neighbor_ids::Matrix{Int} + levels::Vector{Int} + coordinates::Matrix{Float64} + original_cell_ids::Vector{Int} + + capacity::Int + length::Int + dummy::Int + + center_level_0::SVector{NDIMS, Float64} + length_level_0::Float64 + periodicity::NTuple{NDIMS, Bool} + + function SerialTree{NDIMS}(capacity::Integer) where {NDIMS} + # Verify that NDIMS is an integer + @assert NDIMS isa Integer + + # Create instance + t = new() + + # Initialize fields with defaults + # Note: length as capacity + 1 is to use `capacity + 1` as temporary storage for swap operations + t.parent_ids = fill(typemin(Int), capacity + 1) + t.child_ids = fill(typemin(Int), 2^NDIMS, capacity + 1) + t.neighbor_ids = fill(typemin(Int), 2 * NDIMS, capacity + 1) + t.levels = fill(typemin(Int), capacity + 1) + t.coordinates = fill(NaN, NDIMS, capacity + 1) + t.original_cell_ids = fill(typemin(Int), capacity + 1) + + t.capacity = capacity + t.length = 0 + t.dummy = capacity + 1 + + t.center_level_0 = SVector(ntuple(_ -> NaN, NDIMS)) + t.length_level_0 = NaN + + return t + end + end - # Initialize fields with defaults - # Note: length as capacity + 1 is to use `capacity + 1` as temporary storage for swap operations - t.parent_ids = fill(typemin(Int), capacity + 1) - t.child_ids = fill(typemin(Int), 2^NDIMS, capacity + 1) - t.neighbor_ids = fill(typemin(Int), 2 * NDIMS, capacity + 1) - t.levels = fill(typemin(Int), capacity + 1) - t.coordinates = fill(NaN, NDIMS, capacity + 1) - t.original_cell_ids = fill(typemin(Int), capacity + 1) + # Constructor for passing the dimension as an argument + SerialTree(::Val{NDIMS}, args...) where {NDIMS} = SerialTree{NDIMS}(args...) - t.capacity = capacity - t.length = 0 - t.dummy = capacity + 1 + # Create and initialize tree + function SerialTree{NDIMS}( + capacity::Int, center::AbstractArray{Float64}, + length::Real, periodicity = true + ) where {NDIMS} + # Create instance + t = SerialTree{NDIMS}(capacity) - t.center_level_0 = SVector(ntuple(_ -> NaN, NDIMS)) - t.length_level_0 = NaN + # Initialize root cell + init!(t, center, length, periodicity) return t end -end - -# Constructor for passing the dimension as an argument -SerialTree(::Val{NDIMS}, args...) where {NDIMS} = SerialTree{NDIMS}(args...) - -# Create and initialize tree -function SerialTree{NDIMS}(capacity::Int, center::AbstractArray{Float64}, - length::Real, periodicity = true) where {NDIMS} - # Create instance - t = SerialTree{NDIMS}(capacity) - - # Initialize root cell - init!(t, center, length, periodicity) - - return t -end - -# Constructor accepting a single number as center (as opposed to an array) for 1D -function SerialTree{1}(cap::Int, center::Real, len::Real, periodicity = true) - SerialTree{1}(cap, [convert(Float64, center)], len, periodicity) -end - -# Clear tree with deleting data structures, store center and length, and create root cell -function init!(t::SerialTree, center::AbstractArray{Float64}, length::Real, - periodicity = true) - clear!(t) - - # Set domain information - t.center_level_0 = center - t.length_level_0 = length - - # Create root cell - t.length += 1 - t.parent_ids[1] = 0 - t.child_ids[:, 1] .= 0 - t.levels[1] = 0 - set_cell_coordinates!(t, t.center_level_0, 1) - t.original_cell_ids[1] = 0 - - # Set neighbor ids: for each periodic direction, the level-0 cell is its own neighbor - if all(periodicity) - # Also catches case where periodicity = true - t.neighbor_ids[:, 1] .= 1 - t.periodicity = ntuple(x -> true, ndims(t)) - elseif !any(periodicity) - # Also catches case where periodicity = false - t.neighbor_ids[:, 1] .= 0 - t.periodicity = ntuple(x -> false, ndims(t)) - else - # Default case if periodicity is an iterable - for dimension in 1:ndims(t) - if periodicity[dimension] - t.neighbor_ids[2 * dimension - 1, 1] = 1 - t.neighbor_ids[2 * dimension - 0, 1] = 1 - else - t.neighbor_ids[2 * dimension - 1, 1] = 0 - t.neighbor_ids[2 * dimension - 0, 1] = 0 + + # Constructor accepting a single number as center (as opposed to an array) for 1D + function SerialTree{1}(cap::Int, center::Real, len::Real, periodicity = true) + SerialTree{1}(cap, [convert(Float64, center)], len, periodicity) + end + + # Clear tree with deleting data structures, store center and length, and create root cell + function init!( + t::SerialTree, center::AbstractArray{Float64}, length::Real, + periodicity = true + ) + clear!(t) + + # Set domain information + t.center_level_0 = center + t.length_level_0 = length + + # Create root cell + t.length += 1 + t.parent_ids[1] = 0 + t.child_ids[:, 1] .= 0 + t.levels[1] = 0 + set_cell_coordinates!(t, t.center_level_0, 1) + t.original_cell_ids[1] = 0 + + # Set neighbor ids: for each periodic direction, the level-0 cell is its own neighbor + if all(periodicity) + # Also catches case where periodicity = true + t.neighbor_ids[:, 1] .= 1 + t.periodicity = ntuple(x -> true, ndims(t)) + elseif !any(periodicity) + # Also catches case where periodicity = false + t.neighbor_ids[:, 1] .= 0 + t.periodicity = ntuple(x -> false, ndims(t)) + else + # Default case if periodicity is an iterable + for dimension in 1:ndims(t) + if periodicity[dimension] + t.neighbor_ids[2 * dimension - 1, 1] = 1 + t.neighbor_ids[2 * dimension - 0, 1] = 1 + else + t.neighbor_ids[2 * dimension - 1, 1] = 0 + t.neighbor_ids[2 * dimension - 0, 1] = 0 + end end + + t.periodicity = Tuple(periodicity) end + end + + # Convenience output for debugging + function Base.show(io::IO, ::MIME"text/plain", t::SerialTree) + @nospecialize t # reduce precompilation time + + l = t.length + println(io, '*'^20) + println(io, "t.parent_ids[1:l] = $(t.parent_ids[1:l])") + println(io, "transpose(t.child_ids[:, 1:l]) = $(transpose(t.child_ids[:, 1:l]))") + println( + io, + "transpose(t.neighbor_ids[:, 1:l]) = $(transpose(t.neighbor_ids[:, 1:l]))" + ) + println(io, "t.levels[1:l] = $(t.levels[1:l])") + println( + io, + "transpose(t.coordinates[:, 1:l]) = $(transpose(t.coordinates[:, 1:l]))" + ) + println(io, "t.original_cell_ids[1:l] = $(t.original_cell_ids[1:l])") + println(io, "t.capacity = $(t.capacity)") + println(io, "t.length = $(t.length)") + println(io, "t.dummy = $(t.dummy)") + println(io, "t.center_level_0 = $(t.center_level_0)") + println(io, "t.length_level_0 = $(t.length_level_0)") + println(io, '*'^20) + end + + # Set information for child cell `child_id` based on parent cell `cell_id` (except neighbors) + function init_child!(t::SerialTree, cell_id, child, child_id) + t.parent_ids[child_id] = cell_id + t.child_ids[child, cell_id] = child_id + t.child_ids[:, child_id] .= 0 + t.levels[child_id] = t.levels[cell_id] + 1 + set_cell_coordinates!( + t, + child_coordinates( + t, cell_coordinates(t, cell_id), + length_at_cell(t, cell_id), child + ), + child_id + ) + t.original_cell_ids[child_id] = 0 + + return nothing + end + + # Reset range of cells to values that are prone to cause errors as soon as they are used. + # + # Rationale: If an invalid cell is accidentally used, we want to know it as soon as possible. + function invalidate!(t::SerialTree, first::Int, last::Int) + @assert first > 0 + @assert last <= t.capacity + 1 + + # Integer values are set to smallest negative value, floating point values to NaN + t.parent_ids[first:last] .= typemin(Int) + t.child_ids[:, first:last] .= typemin(Int) + t.neighbor_ids[:, first:last] .= typemin(Int) + t.levels[first:last] .= typemin(Int) + t.coordinates[:, first:last] .= NaN + t.original_cell_ids[first:last] .= typemin(Int) + + return nothing + end + + # Raw copy operation for ranges of cells. + # + # This method is used by the higher-level copy operations for AbstractContainer + function raw_copy!( + target::SerialTree, source::SerialTree, first::Int, last::Int, + destination::Int + ) + copy_data!(target.parent_ids, source.parent_ids, first, last, destination) + copy_data!( + target.child_ids, source.child_ids, first, last, destination, + n_children_per_cell(target) + ) + copy_data!( + target.neighbor_ids, source.neighbor_ids, first, last, + destination, n_directions(target) + ) + copy_data!(target.levels, source.levels, first, last, destination) + copy_data!( + target.coordinates, source.coordinates, first, last, destination, + ndims(target) + ) + copy_data!( + target.original_cell_ids, source.original_cell_ids, first, last, + destination + ) + end + + # Reset data structures by recreating all internal storage containers and invalidating all elements + function reset_data_structures!(t::SerialTree{NDIMS}) where {NDIMS} + t.parent_ids = Vector{Int}(undef, t.capacity + 1) + t.child_ids = Matrix{Int}(undef, 2^NDIMS, t.capacity + 1) + t.neighbor_ids = Matrix{Int}(undef, 2 * NDIMS, t.capacity + 1) + t.levels = Vector{Int}(undef, t.capacity + 1) + t.coordinates = Matrix{Float64}(undef, NDIMS, t.capacity + 1) + t.original_cell_ids = Vector{Int}(undef, t.capacity + 1) - t.periodicity = Tuple(periodicity) + invalidate!(t, 1, capacity(t) + 1) end -end - -# Convenience output for debugging -function Base.show(io::IO, ::MIME"text/plain", t::SerialTree) - @nospecialize t # reduce precompilation time - - l = t.length - println(io, '*'^20) - println(io, "t.parent_ids[1:l] = $(t.parent_ids[1:l])") - println(io, "transpose(t.child_ids[:, 1:l]) = $(transpose(t.child_ids[:, 1:l]))") - println(io, - "transpose(t.neighbor_ids[:, 1:l]) = $(transpose(t.neighbor_ids[:, 1:l]))") - println(io, "t.levels[1:l] = $(t.levels[1:l])") - println(io, - "transpose(t.coordinates[:, 1:l]) = $(transpose(t.coordinates[:, 1:l]))") - println(io, "t.original_cell_ids[1:l] = $(t.original_cell_ids[1:l])") - println(io, "t.capacity = $(t.capacity)") - println(io, "t.length = $(t.length)") - println(io, "t.dummy = $(t.dummy)") - println(io, "t.center_level_0 = $(t.center_level_0)") - println(io, "t.length_level_0 = $(t.length_level_0)") - println(io, '*'^20) -end - -# Set information for child cell `child_id` based on parent cell `cell_id` (except neighbors) -function init_child!(t::SerialTree, cell_id, child, child_id) - t.parent_ids[child_id] = cell_id - t.child_ids[child, cell_id] = child_id - t.child_ids[:, child_id] .= 0 - t.levels[child_id] = t.levels[cell_id] + 1 - set_cell_coordinates!(t, - child_coordinates(t, cell_coordinates(t, cell_id), - length_at_cell(t, cell_id), child), - child_id) - t.original_cell_ids[child_id] = 0 - - return nothing -end - -# Reset range of cells to values that are prone to cause errors as soon as they are used. -# -# Rationale: If an invalid cell is accidentally used, we want to know it as soon as possible. -function invalidate!(t::SerialTree, first::Int, last::Int) - @assert first > 0 - @assert last <= t.capacity + 1 - - # Integer values are set to smallest negative value, floating point values to NaN - t.parent_ids[first:last] .= typemin(Int) - t.child_ids[:, first:last] .= typemin(Int) - t.neighbor_ids[:, first:last] .= typemin(Int) - t.levels[first:last] .= typemin(Int) - t.coordinates[:, first:last] .= NaN - t.original_cell_ids[first:last] .= typemin(Int) - - return nothing -end - -# Raw copy operation for ranges of cells. -# -# This method is used by the higher-level copy operations for AbstractContainer -function raw_copy!(target::SerialTree, source::SerialTree, first::Int, last::Int, - destination::Int) - copy_data!(target.parent_ids, source.parent_ids, first, last, destination) - copy_data!(target.child_ids, source.child_ids, first, last, destination, - n_children_per_cell(target)) - copy_data!(target.neighbor_ids, source.neighbor_ids, first, last, - destination, n_directions(target)) - copy_data!(target.levels, source.levels, first, last, destination) - copy_data!(target.coordinates, source.coordinates, first, last, destination, - ndims(target)) - copy_data!(target.original_cell_ids, source.original_cell_ids, first, last, - destination) -end - -# Reset data structures by recreating all internal storage containers and invalidating all elements -function reset_data_structures!(t::SerialTree{NDIMS}) where {NDIMS} - t.parent_ids = Vector{Int}(undef, t.capacity + 1) - t.child_ids = Matrix{Int}(undef, 2^NDIMS, t.capacity + 1) - t.neighbor_ids = Matrix{Int}(undef, 2 * NDIMS, t.capacity + 1) - t.levels = Vector{Int}(undef, t.capacity + 1) - t.coordinates = Matrix{Float64}(undef, NDIMS, t.capacity + 1) - t.original_cell_ids = Vector{Int}(undef, t.capacity + 1) - - invalidate!(t, 1, capacity(t) + 1) -end end # @muladd diff --git a/src/meshes/structured_mesh.jl b/src/meshes/structured_mesh.jl index 155bad8464..71e48ed1ff 100644 --- a/src/meshes/structured_mesh.jl +++ b/src/meshes/structured_mesh.jl @@ -3,356 +3,406 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - StructuredMesh{NDIMS} <: AbstractMesh{NDIMS} - -A structured curved mesh. - -Different numbers of cells per dimension are possible and arbitrary functions -can be used as domain faces. -""" -mutable struct StructuredMesh{NDIMS, RealT <: Real} <: AbstractMesh{NDIMS} - cells_per_dimension::NTuple{NDIMS, Int} - mapping::Any # Not relevant for performance - mapping_as_string::String - periodicity::NTuple{NDIMS, Bool} - current_filename::String - unsaved_changes::Bool -end - -""" - StructuredMesh(cells_per_dimension, mapping; RealT=Float64, unsaved_changes=true, mapping_as_string=mapping2string(mapping, length(cells_per_dimension))) - -Create a StructuredMesh of the given size and shape that uses `RealT` as coordinate type. - -# Arguments -- `cells_per_dimension::NTupleE{NDIMS, Int}`: the number of cells in each dimension. -- `mapping`: a function of `NDIMS` variables to describe the mapping, which transforms - the reference mesh to the physical domain. - If no `mapping_as_string` is defined, this function must be defined with the name `mapping` - to allow for restarts. - This will be changed in the future, see [https://github.com/trixi-framework/Trixi.jl/issues/541](https://github.com/trixi-framework/Trixi.jl/issues/541). -- `RealT::Type`: the type that should be used for coordinates. -- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` - deciding for each dimension if the boundaries in this dimension are periodic. -- `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file. -- `mapping_as_string::String`: the code that defines the `mapping`. - If `CodeTracking` can't find the function definition, it can be passed directly here. - The code string must define the mapping function with the name `mapping`. - This will be changed in the future, see [https://github.com/trixi-framework/Trixi.jl/issues/541](https://github.com/trixi-framework/Trixi.jl/issues/541). -""" -function StructuredMesh(cells_per_dimension, mapping; RealT = Float64, - periodicity = true, unsaved_changes = true, - mapping_as_string = mapping2string(mapping, - length(cells_per_dimension))) - NDIMS = length(cells_per_dimension) - - # Convert periodicity to a Tuple of a Bool for every dimension - if all(periodicity) - # Also catches case where periodicity = true - periodicity = ntuple(_ -> true, NDIMS) - elseif !any(periodicity) - # Also catches case where periodicity = false - periodicity = ntuple(_ -> false, NDIMS) - else - # Default case if periodicity is an iterable - periodicity = Tuple(periodicity) + #! format: noindent + + """ + StructuredMesh{NDIMS} <: AbstractMesh{NDIMS} + + A structured curved mesh. + + Different numbers of cells per dimension are possible and arbitrary functions + can be used as domain faces. + """ + mutable struct StructuredMesh{NDIMS, RealT <: Real} <: AbstractMesh{NDIMS} + cells_per_dimension::NTuple{NDIMS, Int} + mapping::Any # Not relevant for performance + mapping_as_string::String + periodicity::NTuple{NDIMS, Bool} + current_filename::String + unsaved_changes::Bool end - return StructuredMesh{NDIMS, RealT}(Tuple(cells_per_dimension), mapping, - mapping_as_string, periodicity, "", - unsaved_changes) -end - -""" - StructuredMesh(cells_per_dimension, faces; RealT=Float64, unsaved_changes=true, faces_as_string=faces2string(faces)) - -Create a StructuredMesh of the given size and shape that uses `RealT` as coordinate type. - -# Arguments -- `cells_per_dimension::NTupleE{NDIMS, Int}`: the number of cells in each dimension. -- `faces::NTuple{2*NDIMS}`: a tuple of `2 * NDIMS` functions that describe the faces of the domain. - Each function must take `NDIMS-1` arguments. - `faces[1]` describes the face onto which the face in negative x-direction - of the unit hypercube is mapped. The face in positive x-direction of - the unit hypercube will be mapped onto the face described by `faces[2]`. - `faces[3:4]` describe the faces in positive and negative y-direction respectively - (in 2D and 3D). - `faces[5:6]` describe the faces in positive and negative z-direction respectively (in 3D). -- `RealT::Type`: the type that should be used for coordinates. -- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` deciding for - each dimension if the boundaries in this dimension are periodic. -""" -function StructuredMesh(cells_per_dimension, faces::Tuple; RealT = Float64, - periodicity = true) - NDIMS = length(cells_per_dimension) - - validate_faces(faces) - - # Use the transfinite mapping with the correct number of arguments - mapping = transfinite_mapping(faces) - - # Collect definitions of face functions in one string (separated by semicolons) - face2substring(face) = code_string(face, ntuple(_ -> Float64, NDIMS - 1)) - join_newline(strings) = join(strings, "\n") - - faces_definition = faces .|> face2substring .|> string |> join_newline - - # Include faces definition in `mapping_as_string` to allow for evaluation - # without knowing the face functions - mapping_as_string = """ + """ + StructuredMesh(cells_per_dimension, mapping; RealT=Float64, unsaved_changes=true, mapping_as_string=mapping2string(mapping, length(cells_per_dimension))) + + Create a StructuredMesh of the given size and shape that uses `RealT` as coordinate type. + + # Arguments + - `cells_per_dimension::NTupleE{NDIMS, Int}`: the number of cells in each dimension. + - `mapping`: a function of `NDIMS` variables to describe the mapping, which transforms + the reference mesh to the physical domain. + If no `mapping_as_string` is defined, this function must be defined with the name `mapping` + to allow for restarts. + This will be changed in the future, see [https://github.com/trixi-framework/Trixi.jl/issues/541](https://github.com/trixi-framework/Trixi.jl/issues/541). + - `RealT::Type`: the type that should be used for coordinates. + - `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` + deciding for each dimension if the boundaries in this dimension are periodic. + - `unsaved_changes::Bool`: if set to `true`, the mesh will be saved to a mesh file. + - `mapping_as_string::String`: the code that defines the `mapping`. + If `CodeTracking` can't find the function definition, it can be passed directly here. + The code string must define the mapping function with the name `mapping`. + This will be changed in the future, see [https://github.com/trixi-framework/Trixi.jl/issues/541](https://github.com/trixi-framework/Trixi.jl/issues/541). + """ + function StructuredMesh( + cells_per_dimension, mapping; RealT = Float64, + periodicity = true, unsaved_changes = true, + mapping_as_string = mapping2string( + mapping, + length(cells_per_dimension) + ) + ) + NDIMS = length(cells_per_dimension) + + # Convert periodicity to a Tuple of a Bool for every dimension + if all(periodicity) + # Also catches case where periodicity = true + periodicity = ntuple(_ -> true, NDIMS) + elseif !any(periodicity) + # Also catches case where periodicity = false + periodicity = ntuple(_ -> false, NDIMS) + else + # Default case if periodicity is an iterable + periodicity = Tuple(periodicity) + end + + return StructuredMesh{NDIMS, RealT}( + Tuple(cells_per_dimension), mapping, + mapping_as_string, periodicity, "", + unsaved_changes + ) + end + + """ + StructuredMesh(cells_per_dimension, faces; RealT=Float64, unsaved_changes=true, faces_as_string=faces2string(faces)) + + Create a StructuredMesh of the given size and shape that uses `RealT` as coordinate type. + + # Arguments + - `cells_per_dimension::NTupleE{NDIMS, Int}`: the number of cells in each dimension. + - `faces::NTuple{2*NDIMS}`: a tuple of `2 * NDIMS` functions that describe the faces of the domain. + Each function must take `NDIMS-1` arguments. + `faces[1]` describes the face onto which the face in negative x-direction + of the unit hypercube is mapped. The face in positive x-direction of + the unit hypercube will be mapped onto the face described by `faces[2]`. + `faces[3:4]` describe the faces in positive and negative y-direction respectively + (in 2D and 3D). + `faces[5:6]` describe the faces in positive and negative z-direction respectively (in 3D). + - `RealT::Type`: the type that should be used for coordinates. + - `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` deciding for + each dimension if the boundaries in this dimension are periodic. + """ + function StructuredMesh( + cells_per_dimension, faces::Tuple; RealT = Float64, + periodicity = true + ) + NDIMS = length(cells_per_dimension) + + validate_faces(faces) + + # Use the transfinite mapping with the correct number of arguments + mapping = transfinite_mapping(faces) + + # Collect definitions of face functions in one string (separated by semicolons) + face2substring(face) = code_string(face, ntuple(_ -> Float64, NDIMS - 1)) + join_newline(strings) = join(strings, "\n") + + faces_definition = faces .|> face2substring .|> string |> join_newline + + # Include faces definition in `mapping_as_string` to allow for evaluation + # without knowing the face functions + mapping_as_string = """ $faces_definition faces = $(string(faces)) mapping = transfinite_mapping(faces) """ - return StructuredMesh(cells_per_dimension, mapping; RealT = RealT, - periodicity = periodicity, - mapping_as_string = mapping_as_string) -end - -""" - StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; periodicity=true) - -Create a StructuredMesh that represents a uncurved structured mesh with a rectangular domain. - -# Arguments -- `cells_per_dimension::NTuple{NDIMS, Int}`: the number of cells in each dimension. -- `coordinates_min::NTuple{NDIMS, RealT}`: coordinate of the corner in the negative direction of each dimension. -- `coordinates_max::NTuple{NDIMS, RealT}`: coordinate of the corner in the positive direction of each dimension. -- `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` deciding for - each dimension if the boundaries in this dimension are periodic. -""" -function StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; - periodicity = true) - RealT = promote_type(eltype(coordinates_min), eltype(coordinates_max)) - - mapping = coordinates2mapping(coordinates_min, coordinates_max) - mapping_as_string = """ + return StructuredMesh( + cells_per_dimension, mapping; RealT = RealT, + periodicity = periodicity, + mapping_as_string = mapping_as_string + ) + end + + """ + StructuredMesh(cells_per_dimension, coordinates_min, coordinates_max; periodicity=true) + + Create a StructuredMesh that represents a uncurved structured mesh with a rectangular domain. + + # Arguments + - `cells_per_dimension::NTuple{NDIMS, Int}`: the number of cells in each dimension. + - `coordinates_min::NTuple{NDIMS, RealT}`: coordinate of the corner in the negative direction of each dimension. + - `coordinates_max::NTuple{NDIMS, RealT}`: coordinate of the corner in the positive direction of each dimension. + - `periodicity`: either a `Bool` deciding if all of the boundaries are periodic or an `NTuple{NDIMS, Bool}` deciding for + each dimension if the boundaries in this dimension are periodic. + """ + function StructuredMesh( + cells_per_dimension, coordinates_min, coordinates_max; + periodicity = true + ) + RealT = promote_type(eltype(coordinates_min), eltype(coordinates_max)) + + mapping = coordinates2mapping(coordinates_min, coordinates_max) + mapping_as_string = """ coordinates_min = $coordinates_min coordinates_max = $coordinates_max mapping = coordinates2mapping(coordinates_min, coordinates_max) """ - return StructuredMesh(cells_per_dimension, mapping; RealT = RealT, - periodicity = periodicity, - mapping_as_string = mapping_as_string) -end - -# Extract a string of the code that defines the mapping function -function mapping2string(mapping, ndims) - string(code_string(mapping, ntuple(_ -> Float64, ndims))) -end - -# An internal function wrapping `CodeTracking.code_string` with additional -# error checking to avoid some problems when calling this function in -# Jupyter notebooks or Documenter.jl environments. See -# - https://github.com/trixi-framework/Trixi.jl/issues/931 -# - https://github.com/trixi-framework/Trixi.jl/pull/1084 -function code_string(f, t) - try - return CodeTracking.code_string(f, t) - catch e - return "" + return StructuredMesh( + cells_per_dimension, mapping; RealT = RealT, + periodicity = periodicity, + mapping_as_string = mapping_as_string + ) + end + + # Extract a string of the code that defines the mapping function + function mapping2string(mapping, ndims) + string(code_string(mapping, ntuple(_ -> Float64, ndims))) + end + + # An internal function wrapping `CodeTracking.code_string` with additional + # error checking to avoid some problems when calling this function in + # Jupyter notebooks or Documenter.jl environments. See + # - https://github.com/trixi-framework/Trixi.jl/issues/931 + # - https://github.com/trixi-framework/Trixi.jl/pull/1084 + function code_string(f, t) + try + return CodeTracking.code_string(f, t) + catch e + return "" + end + end + + # Interpolate linearly between left and right value where s should be between -1 and 1 + function linear_interpolate(s, left_value, right_value) + 0.5f0 * ((1 - s) * left_value + (1 + s) * right_value) end -end - -# Interpolate linearly between left and right value where s should be between -1 and 1 -function linear_interpolate(s, left_value, right_value) - 0.5f0 * ((1 - s) * left_value + (1 + s) * right_value) -end - -# Convert min and max coordinates of a rectangle to the corresponding transformation mapping -function coordinates2mapping(coordinates_min::NTuple{1}, coordinates_max::NTuple{1}) - mapping(xi) = linear_interpolate(xi, coordinates_min[1], coordinates_max[1]) -end - -function coordinates2mapping(coordinates_min::NTuple{2}, coordinates_max::NTuple{2}) - function mapping(xi, eta) - SVector(linear_interpolate(xi, coordinates_min[1], coordinates_max[1]), - linear_interpolate(eta, coordinates_min[2], coordinates_max[2])) + + # Convert min and max coordinates of a rectangle to the corresponding transformation mapping + function coordinates2mapping(coordinates_min::NTuple{1}, coordinates_max::NTuple{1}) + mapping(xi) = linear_interpolate(xi, coordinates_min[1], coordinates_max[1]) + end + + function coordinates2mapping(coordinates_min::NTuple{2}, coordinates_max::NTuple{2}) + function mapping(xi, eta) + SVector( + linear_interpolate(xi, coordinates_min[1], coordinates_max[1]), + linear_interpolate(eta, coordinates_min[2], coordinates_max[2]) + ) + end end -end -function coordinates2mapping(coordinates_min::NTuple{3}, coordinates_max::NTuple{3}) - function mapping(xi, eta, zeta) - SVector(linear_interpolate(xi, coordinates_min[1], coordinates_max[1]), + function coordinates2mapping(coordinates_min::NTuple{3}, coordinates_max::NTuple{3}) + function mapping(xi, eta, zeta) + SVector( + linear_interpolate(xi, coordinates_min[1], coordinates_max[1]), linear_interpolate(eta, coordinates_min[2], coordinates_max[2]), - linear_interpolate(zeta, coordinates_min[3], coordinates_max[3])) + linear_interpolate(zeta, coordinates_min[3], coordinates_max[3]) + ) + end + end + + # In 1D + # Linear mapping from the reference element to the domain described by the faces + function linear_mapping(x, faces) + return linear_interpolate(x, faces[1](), faces[2]()) + end + + # In 2D + # Bilinear mapping from the reference element to the domain described by the faces + function bilinear_mapping(x, y, faces) + x1 = faces[1](-1) # Bottom left + x2 = faces[2](-1) # Bottom right + x3 = faces[1](1) # Top left + x4 = faces[2](1) # Top right + + return 0.25f0 * ( + x1 * (1 - x) * (1 - y) + + x2 * (1 + x) * (1 - y) + + x3 * (1 - x) * (1 + y) + + x4 * (1 + x) * (1 + y) + ) + end + + # In 3D + # Trilinear mapping from the reference element to the domain described by the faces + function trilinear_mapping(x, y, z, faces) + x1 = faces[1](-1, -1) # mapped from (-1,-1,-1) + x2 = faces[2](-1, -1) # mapped from ( 1,-1,-1) + x3 = faces[1](1, -1) # mapped from (-1, 1,-1) + x4 = faces[2](1, -1) # mapped from ( 1, 1,-1) + x5 = faces[1](-1, 1) # mapped from (-1,-1, 1) + x6 = faces[2](-1, 1) # mapped from ( 1,-1, 1) + x7 = faces[1](1, 1) # mapped from (-1, 1, 1) + x8 = faces[2](1, 1) # mapped from ( 1, 1, 1) + + return 0.125f0 * ( + x1 * (1 - x) * (1 - y) * (1 - z) + + x2 * (1 + x) * (1 - y) * (1 - z) + + x3 * (1 - x) * (1 + y) * (1 - z) + + x4 * (1 + x) * (1 + y) * (1 - z) + + x5 * (1 - x) * (1 - y) * (1 + z) + + x6 * (1 + x) * (1 - y) * (1 + z) + + x7 * (1 - x) * (1 + y) * (1 + z) + + x8 * (1 + x) * (1 + y) * (1 + z) + ) + end + + # Use linear mapping in 1D + transfinite_mapping(faces::NTuple{2, Any}) = x -> linear_mapping(x, faces) + + # In 2D + # Transfinite mapping from the reference element to the domain described by the faces + function transfinite_mapping(faces::NTuple{4, Any}) + function mapping(x, y) + ( + linear_interpolate(x, faces[1](y), faces[2](y)) + + linear_interpolate(y, faces[3](x), faces[4](x)) - + bilinear_mapping(x, y, faces) + ) + end + end + + # In 3D + # Correction term for the Transfinite mapping + function correction_term_3d(x, y, z, faces) + # Correction for x-terms + c_x = linear_interpolate( + x, + linear_interpolate(y, faces[3](-1, z), faces[4](-1, z)) + + linear_interpolate(z, faces[5](-1, y), faces[6](-1, y)), + linear_interpolate(y, faces[3](1, z), faces[4](1, z)) + + linear_interpolate(z, faces[5](1, y), faces[6](1, y)) + ) + + # Correction for y-terms + c_y = linear_interpolate( + y, + linear_interpolate(x, faces[1](-1, z), faces[2](-1, z)) + + linear_interpolate(z, faces[5](x, -1), faces[6](x, -1)), + linear_interpolate(x, faces[1](1, z), faces[2](1, z)) + + linear_interpolate(z, faces[5](x, 1), faces[6](x, 1)) + ) + + # Correction for z-terms + c_z = linear_interpolate( + z, + linear_interpolate(x, faces[1](y, -1), faces[2](y, -1)) + + linear_interpolate(y, faces[3](x, -1), faces[4](x, -1)), + linear_interpolate(x, faces[1](y, 1), faces[2](y, 1)) + + linear_interpolate(y, faces[3](x, 1), faces[4](x, 1)) + ) + + # Each of the 12 edges are counted twice above + # so we divide the correction term by two + return 0.5f0 * (c_x + c_y + c_z) + end + + # In 3D + # Transfinite mapping from the reference element to the domain described by the faces + function transfinite_mapping(faces::NTuple{6, Any}) + function mapping(x, y, z) + ( + linear_interpolate(x, faces[1](y, z), faces[2](y, z)) + + linear_interpolate(y, faces[3](x, z), faces[4](x, z)) + + linear_interpolate(z, faces[5](x, y), faces[6](x, y)) - + correction_term_3d(x, y, z, faces) + + trilinear_mapping(x, y, z, faces) + ) + end + end + + function validate_faces(faces::NTuple{2, Any}) end + + function validate_faces(faces::NTuple{4, Any}) + @assert faces[1](-1) ≈ faces[3](-1) "faces[1](-1) needs to match faces[3](-1) (bottom left corner)" + @assert faces[2](-1) ≈ faces[3](1) "faces[2](-1) needs to match faces[3](1) (bottom right corner)" + @assert faces[1](1) ≈ faces[4](-1) "faces[1](1) needs to match faces[4](-1) (top left corner)" + @assert faces[2](1) ≈ faces[4](1) "faces[2](1) needs to match faces[4](1) (top right corner)" end -end - -# In 1D -# Linear mapping from the reference element to the domain described by the faces -function linear_mapping(x, faces) - return linear_interpolate(x, faces[1](), faces[2]()) -end - -# In 2D -# Bilinear mapping from the reference element to the domain described by the faces -function bilinear_mapping(x, y, faces) - x1 = faces[1](-1) # Bottom left - x2 = faces[2](-1) # Bottom right - x3 = faces[1](1) # Top left - x4 = faces[2](1) # Top right - - return 0.25f0 * (x1 * (1 - x) * (1 - y) + - x2 * (1 + x) * (1 - y) + - x3 * (1 - x) * (1 + y) + - x4 * (1 + x) * (1 + y)) -end - -# In 3D -# Trilinear mapping from the reference element to the domain described by the faces -function trilinear_mapping(x, y, z, faces) - x1 = faces[1](-1, -1) # mapped from (-1,-1,-1) - x2 = faces[2](-1, -1) # mapped from ( 1,-1,-1) - x3 = faces[1](1, -1) # mapped from (-1, 1,-1) - x4 = faces[2](1, -1) # mapped from ( 1, 1,-1) - x5 = faces[1](-1, 1) # mapped from (-1,-1, 1) - x6 = faces[2](-1, 1) # mapped from ( 1,-1, 1) - x7 = faces[1](1, 1) # mapped from (-1, 1, 1) - x8 = faces[2](1, 1) # mapped from ( 1, 1, 1) - - return 0.125f0 * (x1 * (1 - x) * (1 - y) * (1 - z) + - x2 * (1 + x) * (1 - y) * (1 - z) + - x3 * (1 - x) * (1 + y) * (1 - z) + - x4 * (1 + x) * (1 + y) * (1 - z) + - x5 * (1 - x) * (1 - y) * (1 + z) + - x6 * (1 + x) * (1 - y) * (1 + z) + - x7 * (1 - x) * (1 + y) * (1 + z) + - x8 * (1 + x) * (1 + y) * (1 + z)) -end - -# Use linear mapping in 1D -transfinite_mapping(faces::NTuple{2, Any}) = x -> linear_mapping(x, faces) - -# In 2D -# Transfinite mapping from the reference element to the domain described by the faces -function transfinite_mapping(faces::NTuple{4, Any}) - function mapping(x, y) - (linear_interpolate(x, faces[1](y), faces[2](y)) + - linear_interpolate(y, faces[3](x), faces[4](x)) - - bilinear_mapping(x, y, faces)) + + function validate_faces(faces::NTuple{6, Any}) + @assert ( + faces[1](-1, -1) ≈ + faces[3](-1, -1) ≈ + faces[5](-1, -1) + ) "faces[1](-1, -1), faces[3](-1, -1) and faces[5](-1, -1) need to match at (-1, -1, -1) corner" + + @assert ( + faces[2](-1, -1) ≈ + faces[3](1, -1) ≈ + faces[5](1, -1) + ) "faces[2](-1, -1), faces[3](1, -1) and faces[5](1, -1) need to match at (1, -1, -1) corner" + + @assert ( + faces[1](1, -1) ≈ + faces[4](-1, -1) ≈ + faces[5](-1, 1) + ) "faces[1](1, -1), faces[4](-1, -1) and faces[5](-1, 1) need to match at (-1, 1, -1) corner" + + @assert ( + faces[2](1, -1) ≈ + faces[4](1, -1) ≈ + faces[5](1, 1) + ) "faces[2](1, -1), faces[4](1, -1) and faces[5](1, 1) need to match at (1, 1, -1) corner" + + @assert ( + faces[1](-1, 1) ≈ + faces[3](-1, 1) ≈ + faces[6](-1, -1) + ) "faces[1](-1, 1), faces[3](-1, 1) and faces[6](-1, -1) need to match at (-1, -1, 1) corner" + + @assert ( + faces[2](-1, 1) ≈ + faces[3](1, 1) ≈ + faces[6](1, -1) + ) "faces[2](-1, 1), faces[3](1, 1) and faces[6](1, -1) need to match at (1, -1, 1) corner" + + @assert ( + faces[1](1, 1) ≈ + faces[4](-1, 1) ≈ + faces[6](-1, 1) + ) "faces[1](1, 1), faces[4](-1, 1) and faces[6](-1, 1) need to match at (-1, 1, 1) corner" + + @assert ( + faces[2](1, 1) ≈ + faces[4](1, 1) ≈ + faces[6](1, 1) + ) "faces[2](1, 1), faces[4](1, 1) and faces[6](1, 1) need to match at (1, 1, 1) corner" end -end - -# In 3D -# Correction term for the Transfinite mapping -function correction_term_3d(x, y, z, faces) - # Correction for x-terms - c_x = linear_interpolate(x, - linear_interpolate(y, faces[3](-1, z), faces[4](-1, z)) + - linear_interpolate(z, faces[5](-1, y), faces[6](-1, y)), - linear_interpolate(y, faces[3](1, z), faces[4](1, z)) + - linear_interpolate(z, faces[5](1, y), faces[6](1, y))) - - # Correction for y-terms - c_y = linear_interpolate(y, - linear_interpolate(x, faces[1](-1, z), faces[2](-1, z)) + - linear_interpolate(z, faces[5](x, -1), faces[6](x, -1)), - linear_interpolate(x, faces[1](1, z), faces[2](1, z)) + - linear_interpolate(z, faces[5](x, 1), faces[6](x, 1))) - - # Correction for z-terms - c_z = linear_interpolate(z, - linear_interpolate(x, faces[1](y, -1), faces[2](y, -1)) + - linear_interpolate(y, faces[3](x, -1), faces[4](x, -1)), - linear_interpolate(x, faces[1](y, 1), faces[2](y, 1)) + - linear_interpolate(y, faces[3](x, 1), faces[4](x, 1))) - - # Each of the 12 edges are counted twice above - # so we divide the correction term by two - return 0.5f0 * (c_x + c_y + c_z) -end - -# In 3D -# Transfinite mapping from the reference element to the domain described by the faces -function transfinite_mapping(faces::NTuple{6, Any}) - function mapping(x, y, z) - (linear_interpolate(x, faces[1](y, z), faces[2](y, z)) + - linear_interpolate(y, faces[3](x, z), faces[4](x, z)) + - linear_interpolate(z, faces[5](x, y), faces[6](x, y)) - - correction_term_3d(x, y, z, faces) + - trilinear_mapping(x, y, z, faces)) + + # Check if mesh is periodic + isperiodic(mesh::StructuredMesh) = all(mesh.periodicity) + isperiodic(mesh::StructuredMesh, dimension) = mesh.periodicity[dimension] + + @inline Base.ndims(::StructuredMesh{NDIMS}) where {NDIMS} = NDIMS + @inline Base.real(::StructuredMesh{NDIMS, RealT}) where {NDIMS, RealT} = RealT + Base.size(mesh::StructuredMesh) = mesh.cells_per_dimension + Base.size(mesh::StructuredMesh, i) = mesh.cells_per_dimension[i] + Base.axes(mesh::StructuredMesh) = map(Base.OneTo, mesh.cells_per_dimension) + Base.axes(mesh::StructuredMesh, i) = Base.OneTo(mesh.cells_per_dimension[i]) + + function Base.show(io::IO, mesh::StructuredMesh) + print(io, "StructuredMesh{", ndims(mesh), ", ", real(mesh), "}") end -end - -function validate_faces(faces::NTuple{2, Any}) end - -function validate_faces(faces::NTuple{4, Any}) - @assert faces[1](-1)≈faces[3](-1) "faces[1](-1) needs to match faces[3](-1) (bottom left corner)" - @assert faces[2](-1)≈faces[3](1) "faces[2](-1) needs to match faces[3](1) (bottom right corner)" - @assert faces[1](1)≈faces[4](-1) "faces[1](1) needs to match faces[4](-1) (top left corner)" - @assert faces[2](1)≈faces[4](1) "faces[2](1) needs to match faces[4](1) (top right corner)" -end - -function validate_faces(faces::NTuple{6, Any}) - @assert (faces[1](-1, -1)≈ - faces[3](-1, -1)≈ - faces[5](-1, -1)) "faces[1](-1, -1), faces[3](-1, -1) and faces[5](-1, -1) need to match at (-1, -1, -1) corner" - - @assert (faces[2](-1, -1)≈ - faces[3](1, -1)≈ - faces[5](1, -1)) "faces[2](-1, -1), faces[3](1, -1) and faces[5](1, -1) need to match at (1, -1, -1) corner" - - @assert (faces[1](1, -1)≈ - faces[4](-1, -1)≈ - faces[5](-1, 1)) "faces[1](1, -1), faces[4](-1, -1) and faces[5](-1, 1) need to match at (-1, 1, -1) corner" - - @assert (faces[2](1, -1)≈ - faces[4](1, -1)≈ - faces[5](1, 1)) "faces[2](1, -1), faces[4](1, -1) and faces[5](1, 1) need to match at (1, 1, -1) corner" - - @assert (faces[1](-1, 1)≈ - faces[3](-1, 1)≈ - faces[6](-1, -1)) "faces[1](-1, 1), faces[3](-1, 1) and faces[6](-1, -1) need to match at (-1, -1, 1) corner" - - @assert (faces[2](-1, 1)≈ - faces[3](1, 1)≈ - faces[6](1, -1)) "faces[2](-1, 1), faces[3](1, 1) and faces[6](1, -1) need to match at (1, -1, 1) corner" - - @assert (faces[1](1, 1)≈ - faces[4](-1, 1)≈ - faces[6](-1, 1)) "faces[1](1, 1), faces[4](-1, 1) and faces[6](-1, 1) need to match at (-1, 1, 1) corner" - - @assert (faces[2](1, 1)≈ - faces[4](1, 1)≈ - faces[6](1, 1)) "faces[2](1, 1), faces[4](1, 1) and faces[6](1, 1) need to match at (1, 1, 1) corner" -end - -# Check if mesh is periodic -isperiodic(mesh::StructuredMesh) = all(mesh.periodicity) -isperiodic(mesh::StructuredMesh, dimension) = mesh.periodicity[dimension] - -@inline Base.ndims(::StructuredMesh{NDIMS}) where {NDIMS} = NDIMS -@inline Base.real(::StructuredMesh{NDIMS, RealT}) where {NDIMS, RealT} = RealT -Base.size(mesh::StructuredMesh) = mesh.cells_per_dimension -Base.size(mesh::StructuredMesh, i) = mesh.cells_per_dimension[i] -Base.axes(mesh::StructuredMesh) = map(Base.OneTo, mesh.cells_per_dimension) -Base.axes(mesh::StructuredMesh, i) = Base.OneTo(mesh.cells_per_dimension[i]) - -function Base.show(io::IO, mesh::StructuredMesh) - print(io, "StructuredMesh{", ndims(mesh), ", ", real(mesh), "}") -end - -function Base.show(io::IO, ::MIME"text/plain", mesh::StructuredMesh) - if get(io, :compact, false) - show(io, mesh) - else - summary_header(io, - "StructuredMesh{" * string(ndims(mesh)) * ", " * - string(real(mesh)) * "}") - summary_line(io, "size", size(mesh)) - - summary_line(io, "mapping", "") - # Print code lines of mapping_as_string - mapping_lines = split(mesh.mapping_as_string, ";") - for i in eachindex(mapping_lines) - summary_line(increment_indent(io), "line $i", strip(mapping_lines[i])) + + function Base.show(io::IO, ::MIME"text/plain", mesh::StructuredMesh) + if get(io, :compact, false) + show(io, mesh) + else + summary_header( + io, + "StructuredMesh{" * string(ndims(mesh)) * ", " * + string(real(mesh)) * "}" + ) + summary_line(io, "size", size(mesh)) + + summary_line(io, "mapping", "") + # Print code lines of mapping_as_string + mapping_lines = split(mesh.mapping_as_string, ";") + for i in eachindex(mapping_lines) + summary_line(increment_indent(io), "line $i", strip(mapping_lines[i])) + end + summary_footer(io) end - summary_footer(io) end -end end # @muladd diff --git a/src/meshes/structured_mesh_view.jl b/src/meshes/structured_mesh_view.jl index 0b0cccfc7f..9ba60e9f14 100644 --- a/src/meshes/structured_mesh_view.jl +++ b/src/meshes/structured_mesh_view.jl @@ -3,130 +3,142 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - StructuredMeshView{NDIMS, RealT <: Real} <: AbstractMesh{NDIMS} - -A view on a structured curved mesh. -""" -mutable struct StructuredMeshView{NDIMS, RealT <: Real} <: AbstractMesh{NDIMS} - parent::StructuredMesh{NDIMS, RealT} - cells_per_dimension::NTuple{NDIMS, Int} - mapping::Any # Not relevant for performance - mapping_as_string::String - current_filename::String - indices_min::NTuple{NDIMS, Int} - indices_max::NTuple{NDIMS, Int} - unsaved_changes::Bool -end - -""" - StructuredMeshView(parent; indices_min, indices_max) - -Create a StructuredMeshView on a StructuredMesh parent. - -# Arguments -- `parent`: the parent StructuredMesh. -- `indices_min`: starting indices of the parent mesh. -- `indices_max`: ending indices of the parent mesh. -""" -function StructuredMeshView(parent::StructuredMesh{NDIMS, RealT}; - indices_min = ntuple(_ -> 1, Val(NDIMS)), - indices_max = size(parent)) where {NDIMS, RealT} - @assert indices_min <= indices_max - @assert all(indices_min .> 0) - @assert indices_max <= size(parent) - - cells_per_dimension = indices_max .- indices_min .+ 1 - - # Compute cell sizes `deltas` - deltas = (parent.mapping.coordinates_max .- parent.mapping.coordinates_min) ./ - parent.cells_per_dimension - # Calculate the domain boundaries. - coordinates_min = parent.mapping.coordinates_min .+ deltas .* (indices_min .- 1) - coordinates_max = parent.mapping.coordinates_min .+ deltas .* indices_max - mapping = coordinates2mapping(coordinates_min, coordinates_max) - mapping_as_string = """ + #! format: noindent + + """ + StructuredMeshView{NDIMS, RealT <: Real} <: AbstractMesh{NDIMS} + + A view on a structured curved mesh. + """ + mutable struct StructuredMeshView{NDIMS, RealT <: Real} <: AbstractMesh{NDIMS} + parent::StructuredMesh{NDIMS, RealT} + cells_per_dimension::NTuple{NDIMS, Int} + mapping::Any # Not relevant for performance + mapping_as_string::String + current_filename::String + indices_min::NTuple{NDIMS, Int} + indices_max::NTuple{NDIMS, Int} + unsaved_changes::Bool + end + + """ + StructuredMeshView(parent; indices_min, indices_max) + + Create a StructuredMeshView on a StructuredMesh parent. + + # Arguments + - `parent`: the parent StructuredMesh. + - `indices_min`: starting indices of the parent mesh. + - `indices_max`: ending indices of the parent mesh. + """ + function StructuredMeshView( + parent::StructuredMesh{NDIMS, RealT}; + indices_min = ntuple(_ -> 1, Val(NDIMS)), + indices_max = size(parent) + ) where {NDIMS, RealT} + @assert indices_min <= indices_max + @assert all(indices_min .> 0) + @assert indices_max <= size(parent) + + cells_per_dimension = indices_max .- indices_min .+ 1 + + # Compute cell sizes `deltas` + deltas = (parent.mapping.coordinates_max .- parent.mapping.coordinates_min) ./ + parent.cells_per_dimension + # Calculate the domain boundaries. + coordinates_min = parent.mapping.coordinates_min .+ deltas .* (indices_min .- 1) + coordinates_max = parent.mapping.coordinates_min .+ deltas .* indices_max + mapping = coordinates2mapping(coordinates_min, coordinates_max) + mapping_as_string = """ coordinates_min = $coordinates_min coordinates_max = $coordinates_max mapping = coordinates2mapping(coordinates_min, coordinates_max) """ - return StructuredMeshView{NDIMS, RealT}(parent, cells_per_dimension, mapping, - mapping_as_string, - parent.current_filename, - indices_min, indices_max, - parent.unsaved_changes) -end - -# Check if mesh is periodic -function isperiodic(mesh::StructuredMeshView) - @unpack parent = mesh - return isperiodic(parent) && size(parent) == size(mesh) -end - -function isperiodic(mesh::StructuredMeshView, dimension) - @unpack parent, indices_min, indices_max = mesh - return (isperiodic(parent, dimension) && - indices_min[dimension] == 1 && - indices_max[dimension] == size(parent, dimension)) -end - -@inline Base.ndims(::StructuredMeshView{NDIMS}) where {NDIMS} = NDIMS -@inline Base.real(::StructuredMeshView{NDIMS, RealT}) where {NDIMS, RealT} = RealT -function Base.size(mesh::StructuredMeshView) - @unpack indices_min, indices_max = mesh - return indices_max .- indices_min .+ 1 -end -function Base.size(mesh::StructuredMeshView, i) - @unpack indices_min, indices_max = mesh - return indices_max[i] - indices_min[i] + 1 -end -Base.axes(mesh::StructuredMeshView) = map(Base.OneTo, size(mesh)) -Base.axes(mesh::StructuredMeshView, i) = Base.OneTo(size(mesh, i)) - -function calc_node_coordinates!(node_coordinates, element, - cell_x, cell_y, mapping, - mesh::StructuredMeshView{2}, - basis) - @unpack nodes = basis - - # Get cell length in reference mesh - dx = 2 / size(mesh, 1) - dy = 2 / size(mesh, 2) - - # Calculate node coordinates of reference mesh - cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 - cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 - - for j in eachnode(basis), i in eachnode(basis) - # node_coordinates are the mapped reference node_coordinates - node_coordinates[:, i, j, element] .= mapping(cell_x_offset + dx / 2 * nodes[i], - cell_y_offset + dy / 2 * nodes[j]) + return StructuredMeshView{NDIMS, RealT}( + parent, cells_per_dimension, mapping, + mapping_as_string, + parent.current_filename, + indices_min, indices_max, + parent.unsaved_changes + ) end -end - -# Does not save the mesh itself to an HDF5 file. Instead saves important attributes -# of the mesh, like its size and the type of boundary mapping function. -# Then, within Trixi2Vtk, the StructuredMesh and its node coordinates are reconstructured from -# these attributes for plotting purposes. -function save_mesh_file(mesh::StructuredMeshView, output_directory; system = "", - timestep = 0) - # Create output directory (if it does not exist) - mkpath(output_directory) - - filename = joinpath(output_directory, @sprintf("mesh_%s_%09d.h5", system, timestep)) - - # Open file (clobber existing content) - h5open(filename, "w") do file - # Add context information as attributes - attributes(file)["mesh_type"] = get_name(mesh) - attributes(file)["ndims"] = ndims(mesh) - attributes(file)["size"] = collect(size(mesh)) - attributes(file)["mapping"] = mesh.mapping_as_string + + # Check if mesh is periodic + function isperiodic(mesh::StructuredMeshView) + @unpack parent = mesh + return isperiodic(parent) && size(parent) == size(mesh) + end + + function isperiodic(mesh::StructuredMeshView, dimension) + @unpack parent, indices_min, indices_max = mesh + return ( + isperiodic(parent, dimension) && + indices_min[dimension] == 1 && + indices_max[dimension] == size(parent, dimension) + ) end - return filename -end + @inline Base.ndims(::StructuredMeshView{NDIMS}) where {NDIMS} = NDIMS + @inline Base.real(::StructuredMeshView{NDIMS, RealT}) where {NDIMS, RealT} = RealT + function Base.size(mesh::StructuredMeshView) + @unpack indices_min, indices_max = mesh + return indices_max .- indices_min .+ 1 + end + function Base.size(mesh::StructuredMeshView, i) + @unpack indices_min, indices_max = mesh + return indices_max[i] - indices_min[i] + 1 + end + Base.axes(mesh::StructuredMeshView) = map(Base.OneTo, size(mesh)) + Base.axes(mesh::StructuredMeshView, i) = Base.OneTo(size(mesh, i)) + + function calc_node_coordinates!( + node_coordinates, element, + cell_x, cell_y, mapping, + mesh::StructuredMeshView{2}, + basis + ) + @unpack nodes = basis + + # Get cell length in reference mesh + dx = 2 / size(mesh, 1) + dy = 2 / size(mesh, 2) + + # Calculate node coordinates of reference mesh + cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 + cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 + + for j in eachnode(basis), i in eachnode(basis) + # node_coordinates are the mapped reference node_coordinates + node_coordinates[:, i, j, element] .= mapping( + cell_x_offset + dx / 2 * nodes[i], + cell_y_offset + dy / 2 * nodes[j] + ) + end + end + + # Does not save the mesh itself to an HDF5 file. Instead saves important attributes + # of the mesh, like its size and the type of boundary mapping function. + # Then, within Trixi2Vtk, the StructuredMesh and its node coordinates are reconstructured from + # these attributes for plotting purposes. + function save_mesh_file( + mesh::StructuredMeshView, output_directory; system = "", + timestep = 0 + ) + # Create output directory (if it does not exist) + mkpath(output_directory) + + filename = joinpath(output_directory, @sprintf("mesh_%s_%09d.h5", system, timestep)) + + # Open file (clobber existing content) + h5open(filename, "w") do file + # Add context information as attributes + attributes(file)["mesh_type"] = get_name(mesh) + attributes(file)["ndims"] = ndims(mesh) + attributes(file)["size"] = collect(size(mesh)) + attributes(file)["mapping"] = mesh.mapping_as_string + end + + return filename + end end # @muladd diff --git a/src/meshes/surface_interpolant.jl b/src/meshes/surface_interpolant.jl index 20a6611668..729e907676 100644 --- a/src/meshes/surface_interpolant.jl +++ b/src/meshes/surface_interpolant.jl @@ -3,128 +3,144 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# CurvedSurface{RealT<:Real} -# -# Contains the data needed to represent a curve with data points (x,y) as a Lagrange polynomial -# interpolant written in barycentric form at a given set of nodes. -struct CurvedSurface{RealT <: Real} - nodes :: Vector{RealT} - barycentric_weights :: Vector{RealT} - coordinates :: Array{RealT, 2} #[nnodes, ndims] -end - -# evaluate the Gamma curve interpolant at a particular point s and return the (x,y) coordinate -function evaluate_at(s, boundary_curve::CurvedSurface) - @unpack nodes, barycentric_weights, coordinates = boundary_curve - - x_coordinate_at_s_on_boundary_curve = lagrange_interpolation(s, nodes, - view(coordinates, :, - 1), - barycentric_weights) - y_coordinate_at_s_on_boundary_curve = lagrange_interpolation(s, nodes, - view(coordinates, :, - 2), - barycentric_weights) - - return x_coordinate_at_s_on_boundary_curve, y_coordinate_at_s_on_boundary_curve -end - -# evaluate the derivative of a Gamma curve interpolant at a particular point s -# and return the (x,y) coordinate -function derivative_at(s, boundary_curve::CurvedSurface) - @unpack nodes, barycentric_weights, coordinates = boundary_curve - - x_coordinate_at_s_on_boundary_curve_prime = lagrange_interpolation_derivative(s, - nodes, - view(coordinates, - :, - 1), - barycentric_weights) - y_coordinate_at_s_on_boundary_curve_prime = lagrange_interpolation_derivative(s, - nodes, - view(coordinates, - :, - 2), - barycentric_weights) - return x_coordinate_at_s_on_boundary_curve_prime, - y_coordinate_at_s_on_boundary_curve_prime -end - -# Chebyshev-Gauss-Lobatto nodes and weights for use with curved boundaries -function chebyshev_gauss_lobatto_nodes_weights(n_nodes::Integer) - - # Initialize output - nodes = zeros(n_nodes) - weights = zeros(n_nodes) - - # Get polynomial degree for convenience - N = n_nodes - 1 - - for j in 1:n_nodes - nodes[j] = -cospi((j - 1) / N) - weights[j] = pi / N + #! format: noindent + + # CurvedSurface{RealT<:Real} + # + # Contains the data needed to represent a curve with data points (x,y) as a Lagrange polynomial + # interpolant written in barycentric form at a given set of nodes. + struct CurvedSurface{RealT <: Real} + nodes::Vector{RealT} + barycentric_weights::Vector{RealT} + coordinates::Array{RealT, 2} #[nnodes, ndims] end - weights[1] = 0.5f0 * weights[1] - weights[end] = 0.5f0 * weights[end] - - return nodes, weights -end - -# Calculate Lagrange interpolating polynomial of a function f(x) at a given point x for a given -# node distribution. -function lagrange_interpolation(x, nodes, fvals, wbary) - # Barycentric two formulation of Lagrange interpolant - numerator = zero(eltype(fvals)) - denominator = zero(eltype(fvals)) - - for j in eachindex(nodes) - # using eps(nodes[j]) instead of eps(x) allows us to use integer - # coordinates for the target location x - if isapprox(x, nodes[j], rtol = eps(nodes[j])) - return fvals[j] - end - t = wbary[j] / (x - nodes[j]) - numerator += t * fvals[j] - denominator += t + + # evaluate the Gamma curve interpolant at a particular point s and return the (x,y) coordinate + function evaluate_at(s, boundary_curve::CurvedSurface) + @unpack nodes, barycentric_weights, coordinates = boundary_curve + + x_coordinate_at_s_on_boundary_curve = lagrange_interpolation( + s, nodes, + view( + coordinates, :, + 1 + ), + barycentric_weights + ) + y_coordinate_at_s_on_boundary_curve = lagrange_interpolation( + s, nodes, + view( + coordinates, :, + 2 + ), + barycentric_weights + ) + + return x_coordinate_at_s_on_boundary_curve, y_coordinate_at_s_on_boundary_curve end - return numerator / denominator -end - -# Calculate derivative of a Lagrange interpolating polynomial of a function f(x) at a given -# point x for a given node distribution. -function lagrange_interpolation_derivative(x, nodes, fvals, wbary) - at_node = false - numerator = zero(eltype(fvals)) - i = 0 - - for j in eachindex(nodes) - if isapprox(x, nodes[j]) - at_node = true - p = fvals[j] - denominator = -wbary[j] - i = j - end + # evaluate the derivative of a Gamma curve interpolant at a particular point s + # and return the (x,y) coordinate + function derivative_at(s, boundary_curve::CurvedSurface) + @unpack nodes, barycentric_weights, coordinates = boundary_curve + + x_coordinate_at_s_on_boundary_curve_prime = lagrange_interpolation_derivative( + s, + nodes, + view( + coordinates, + :, + 1 + ), + barycentric_weights + ) + y_coordinate_at_s_on_boundary_curve_prime = lagrange_interpolation_derivative( + s, + nodes, + view( + coordinates, + :, + 2 + ), + barycentric_weights + ) + return x_coordinate_at_s_on_boundary_curve_prime, + y_coordinate_at_s_on_boundary_curve_prime end - if at_node - for j in eachindex(nodes) - if j != i - numerator += wbary[j] * (p - fvals[j]) / (x - nodes[j]) - end + # Chebyshev-Gauss-Lobatto nodes and weights for use with curved boundaries + function chebyshev_gauss_lobatto_nodes_weights(n_nodes::Integer) + + # Initialize output + nodes = zeros(n_nodes) + weights = zeros(n_nodes) + + # Get polynomial degree for convenience + N = n_nodes - 1 + + for j in 1:n_nodes + nodes[j] = -cospi((j - 1) / N) + weights[j] = pi / N end - else + weights[1] = 0.5f0 * weights[1] + weights[end] = 0.5f0 * weights[end] + + return nodes, weights + end + + # Calculate Lagrange interpolating polynomial of a function f(x) at a given point x for a given + # node distribution. + function lagrange_interpolation(x, nodes, fvals, wbary) + # Barycentric two formulation of Lagrange interpolant + numerator = zero(eltype(fvals)) denominator = zero(eltype(fvals)) - p = lagrange_interpolation(x, nodes, fvals, wbary) + for j in eachindex(nodes) + # using eps(nodes[j]) instead of eps(x) allows us to use integer + # coordinates for the target location x + if isapprox(x, nodes[j], rtol = eps(nodes[j])) + return fvals[j] + end t = wbary[j] / (x - nodes[j]) - numerator += t * (p - fvals[j]) / (x - nodes[j]) + numerator += t * fvals[j] denominator += t end + + return numerator / denominator end - return numerator / denominator # p_prime -end + # Calculate derivative of a Lagrange interpolating polynomial of a function f(x) at a given + # point x for a given node distribution. + function lagrange_interpolation_derivative(x, nodes, fvals, wbary) + at_node = false + numerator = zero(eltype(fvals)) + i = 0 + + for j in eachindex(nodes) + if isapprox(x, nodes[j]) + at_node = true + p = fvals[j] + denominator = -wbary[j] + i = j + end + end + + if at_node + for j in eachindex(nodes) + if j != i + numerator += wbary[j] * (p - fvals[j]) / (x - nodes[j]) + end + end + else + denominator = zero(eltype(fvals)) + p = lagrange_interpolation(x, nodes, fvals, wbary) + for j in eachindex(nodes) + t = wbary[j] / (x - nodes[j]) + numerator += t * (p - fvals[j]) / (x - nodes[j]) + denominator += t + end + end + + return numerator / denominator # p_prime + end end # @muladd diff --git a/src/meshes/t8code_mesh.jl b/src/meshes/t8code_mesh.jl index 9b0e0b741a..c32b208ac5 100644 --- a/src/meshes/t8code_mesh.jl +++ b/src/meshes/t8code_mesh.jl @@ -6,9 +6,9 @@ An unstructured curved mesh based on trees that uses the C library to manage trees and mesh refinement. """ mutable struct T8codeMesh{NDIMS, RealT <: Real, IsParallel, NDIMSP2, NNODES} <: - AbstractMesh{NDIMS} - forest :: Ptr{t8_forest} # cpointer to forest - is_parallel :: IsParallel + AbstractMesh{NDIMS} + forest::Ptr{t8_forest} # cpointer to forest + is_parallel::IsParallel # This specifies the geometry interpolation for each tree. tree_node_coordinates::Array{RealT, NDIMSP2} # [dimension, i, j, k, tree] @@ -16,23 +16,27 @@ mutable struct T8codeMesh{NDIMS, RealT <: Real, IsParallel, NDIMSP2, NNODES} <: # Stores the quadrature nodes. nodes::SVector{NNODES, RealT} - boundary_names :: Array{Symbol, 2} # [face direction, tree] - current_filename :: String + boundary_names::Array{Symbol, 2} # [face direction, tree] + current_filename::String - ninterfaces :: Int - nmortars :: Int - nboundaries :: Int + ninterfaces::Int + nmortars::Int + nboundaries::Int - nmpiinterfaces :: Int - nmpimortars :: Int + nmpiinterfaces::Int + nmpimortars::Int - function T8codeMesh{NDIMS}(forest::Ptr{t8_forest}, tree_node_coordinates, nodes, - boundary_names, - current_filename) where {NDIMS} + function T8codeMesh{NDIMS}( + forest::Ptr{t8_forest}, tree_node_coordinates, nodes, + boundary_names, + current_filename + ) where {NDIMS} is_parallel = mpi_isparallel() ? True() : False() - mesh = new{NDIMS, Float64, typeof(is_parallel), NDIMS + 2, length(nodes)}(forest, - is_parallel) + mesh = new{NDIMS, Float64, typeof(is_parallel), NDIMS + 2, length(nodes)}( + forest, + is_parallel + ) mesh.nodes = nodes mesh.boundary_names = boundary_names @@ -95,9 +99,11 @@ function Base.show(io::IO, ::MIME"text/plain", mesh::T8codeMesh) "current #cells" => ncellsglobal(mesh), "polydeg" => length(mesh.nodes) - 1, ] - summary_box(io, - "T8codeMesh{" * string(ndims(mesh)) * ", " * string(real(mesh)) * "}", - setup) + summary_box( + io, + "T8codeMesh{" * string(ndims(mesh)) * ", " * string(real(mesh)) * "}", + setup + ) end end @@ -117,8 +123,10 @@ constructors. - `mapping`: A function of `NDIMS` variables to describe the mapping that transforms the imported mesh to the physical domain. Use `nothing` for the identity map. """ -function T8codeMesh{NDIMS, RealT}(forest::Ptr{t8_forest}, boundary_names; polydeg = 1, - mapping = nothing) where {NDIMS, RealT} +function T8codeMesh{NDIMS, RealT}( + forest::Ptr{t8_forest}, boundary_names; polydeg = 1, + mapping = nothing + ) where {NDIMS, RealT} # In t8code reference space is [0,1]. basis = LobattoLegendreBasis(RealT, polydeg) nodes = 0.5f0 .* (basis.nodes .+ 1) @@ -126,9 +134,11 @@ function T8codeMesh{NDIMS, RealT}(forest::Ptr{t8_forest}, boundary_names; polyde cmesh = t8_forest_get_cmesh(forest) number_of_trees = t8_forest_get_num_global_trees(forest) - tree_node_coordinates = Array{RealT, NDIMS + 2}(undef, NDIMS, - ntuple(_ -> length(nodes), NDIMS)..., - number_of_trees) + tree_node_coordinates = Array{RealT, NDIMS + 2}( + undef, NDIMS, + ntuple(_ -> length(nodes), NDIMS)..., + number_of_trees + ) reference_coordinates = Vector{Float64}(undef, 3) @@ -168,8 +178,10 @@ function T8codeMesh{NDIMS, RealT}(forest::Ptr{t8_forest}, boundary_names; polyde reference_coordinates[1] = nodes[i] reference_coordinates[2] = nodes[j] reference_coordinates[3] = 0.0 - t8_geometry_evaluate(cmesh, itree - 1, reference_coordinates, 1, - @view(tree_node_coordinates[:, i, j, itree])) + t8_geometry_evaluate( + cmesh, itree - 1, reference_coordinates, 1, + @view(tree_node_coordinates[:, i, j, itree]) + ) end end @@ -209,8 +221,10 @@ function T8codeMesh{NDIMS, RealT}(forest::Ptr{t8_forest}, boundary_names; polyde reference_coordinates[1] = nodes[i] reference_coordinates[2] = nodes[j] reference_coordinates[3] = nodes[k] - t8_geometry_evaluate(cmesh, itree - 1, reference_coordinates, 1, - @view(tree_node_coordinates[:, i, j, k, itree])) + t8_geometry_evaluate( + cmesh, itree - 1, reference_coordinates, 1, + @view(tree_node_coordinates[:, i, j, k, itree]) + ) end end else @@ -220,8 +234,10 @@ function T8codeMesh{NDIMS, RealT}(forest::Ptr{t8_forest}, boundary_names; polyde # Apply user defined mapping. map_node_coordinates!(tree_node_coordinates, mapping) - return T8codeMesh{NDIMS}(forest, tree_node_coordinates, basis.nodes, - boundary_names, "") + return T8codeMesh{NDIMS}( + forest, tree_node_coordinates, basis.nodes, + boundary_names, "" + ) end """ @@ -260,15 +276,19 @@ Non-periodic boundaries will be called ':x_neg', ':x_pos', ':y_neg', ':y_pos', ' - 'periodicity': either a 'Bool' deciding if all of the boundaries are periodic or an 'NTuple{NDIMS, Bool}' deciding for each dimension if the boundaries in this dimension are periodic. """ -function T8codeMesh(trees_per_dimension; polydeg = 1, - mapping = nothing, faces = nothing, coordinates_min = nothing, - coordinates_max = nothing, - RealT = Float64, initial_refinement_level = 0, - periodicity = true) - @assert ((coordinates_min === nothing)===(coordinates_max === nothing)) "Either both or none of coordinates_min and coordinates_max must be specified" - - @assert count(i -> i !== nothing, - (mapping, faces, coordinates_min))==1 "Exactly one of mapping, faces and coordinates_min/max must be specified" +function T8codeMesh( + trees_per_dimension; polydeg = 1, + mapping = nothing, faces = nothing, coordinates_min = nothing, + coordinates_max = nothing, + RealT = Float64, initial_refinement_level = 0, + periodicity = true + ) + @assert ((coordinates_min === nothing) === (coordinates_max === nothing)) "Either both or none of coordinates_min and coordinates_max must be specified" + + @assert count( + i -> i !== nothing, + (mapping, faces, coordinates_min) + ) == 1 "Exactly one of mapping, faces and coordinates_min/max must be specified" # Extract mapping if faces !== nothing @@ -295,21 +315,27 @@ function T8codeMesh(trees_per_dimension; polydeg = 1, do_partition = 0 if NDIMS == 2 - conn = T8code.Libt8.p4est_connectivity_new_brick(trees_per_dimension..., - periodicity...) + conn = T8code.Libt8.p4est_connectivity_new_brick( + trees_per_dimension..., + periodicity... + ) cmesh = t8_cmesh_new_from_p4est(conn, mpi_comm(), do_partition) T8code.Libt8.p4est_connectivity_destroy(conn) elseif NDIMS == 3 - conn = T8code.Libt8.p8est_connectivity_new_brick(trees_per_dimension..., - periodicity...) + conn = T8code.Libt8.p8est_connectivity_new_brick( + trees_per_dimension..., + periodicity... + ) cmesh = t8_cmesh_new_from_p8est(conn, mpi_comm(), do_partition) T8code.Libt8.p8est_connectivity_destroy(conn) end do_face_ghost = mpi_isparallel() scheme = t8_scheme_new_default_cxx() - forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, do_face_ghost, - mpi_comm()) + forest = t8_forest_new_uniform( + cmesh, scheme, initial_refinement_level, do_face_ghost, + mpi_comm() + ) # Non-periodic boundaries. boundary_names = fill(Symbol("---"), 2 * NDIMS, prod(trees_per_dimension)) @@ -335,11 +361,19 @@ function T8codeMesh(trees_per_dimension; polydeg = 1, # Note, `p*est_connectivity_new_brick` converts a domain of `[0,nx] x [0,ny] x ....`. # Hence, transform mesh coordinates to reference space [-1,1]^NDIMS before applying user defined mapping. - mapping_(xyz...) = mapping((x * 2.0 / tpd - 1.0 for (x, tpd) in zip(xyz, - trees_per_dimension))...) - - return T8codeMesh{NDIMS, RealT}(forest, boundary_names; polydeg = polydeg, - mapping = mapping_) + mapping_(xyz...) = mapping( + ( + x * 2.0 / tpd - 1.0 for (x, tpd) in zip( + xyz, + trees_per_dimension + ) + )... + ) + + return T8codeMesh{NDIMS, RealT}( + forest, boundary_names; polydeg = polydeg, + mapping = mapping_ + ) end """ @@ -362,10 +396,12 @@ conforming mesh from a `t8_cmesh` data structure. - `RealT::Type`: the type that should be used for coordinates. - `initial_refinement_level::Integer`: refine the mesh uniformly to this level before the simulation starts. """ -function T8codeMesh(cmesh::Ptr{t8_cmesh}; - mapping = nothing, polydeg = 1, RealT = Float64, - initial_refinement_level = 0) - @assert (t8_cmesh_get_num_trees(cmesh)>0) "Given `cmesh` does not contain any trees." +function T8codeMesh( + cmesh::Ptr{t8_cmesh}; + mapping = nothing, polydeg = 1, RealT = Float64, + initial_refinement_level = 0 + ) + @assert (t8_cmesh_get_num_trees(cmesh) > 0) "Given `cmesh` does not contain any trees." # Infer NDIMS from the geometry of the first tree. NDIMS = Int(t8_geom_get_dimension(t8_cmesh_get_tree_geometry(cmesh, 0))) @@ -374,14 +410,18 @@ function T8codeMesh(cmesh::Ptr{t8_cmesh}; do_face_ghost = mpi_isparallel() scheme = t8_scheme_new_default_cxx() - forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, do_face_ghost, - mpi_comm()) + forest = t8_forest_new_uniform( + cmesh, scheme, initial_refinement_level, do_face_ghost, + mpi_comm() + ) # There's no simple and generic way to distinguish boundaries, yet. Name all of them :all. boundary_names = fill(:all, 2 * NDIMS, t8_cmesh_get_num_trees(cmesh)) - return T8codeMesh{NDIMS, RealT}(forest, boundary_names; polydeg = polydeg, - mapping = mapping) + return T8codeMesh{NDIMS, RealT}( + forest, boundary_names; polydeg = polydeg, + mapping = mapping + ) end """ @@ -574,10 +614,12 @@ For example, if a two-dimensional base mesh contains 25 elements then setting - `boundary_symbols::Vector{Symbol}`: A vector of symbols that correspond to the boundary names in the `meshfile`. If `nothing` is passed then all boundaries are named `:all`. """ -function T8codeMesh(meshfile::AbaqusFile{NDIMS}; - mapping = nothing, polydeg = 1, RealT = Float64, - initial_refinement_level = 0, - boundary_symbols = nothing) where {NDIMS} +function T8codeMesh( + meshfile::AbaqusFile{NDIMS}; + mapping = nothing, polydeg = 1, RealT = Float64, + initial_refinement_level = 0, + boundary_symbols = nothing + ) where {NDIMS} # Prevent `t8code` from crashing Julia if the file doesn't exist. @assert isfile(meshfile.path) @@ -590,21 +632,25 @@ function T8codeMesh(meshfile::AbaqusFile{NDIMS}; # Check if the meshfile was generated using HOHQMesh. if header == " File created by HOHQMesh" # Mesh curvature and boundary naming is handled with additional information available in meshfile - connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_hohqmesh_abaqus(meshfile.path, - initial_refinement_level, - NDIMS, - RealT) + connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_hohqmesh_abaqus( + meshfile.path, + initial_refinement_level, + NDIMS, + RealT + ) # Apply user defined mapping. map_node_coordinates!(tree_node_coordinates, mapping) else # Mesh curvature is handled directly by applying the mapping keyword argument. - connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_standard_abaqus(meshfile.path, - mapping, - polydeg, - initial_refinement_level, - NDIMS, - RealT, - boundary_symbols) + connectivity, tree_node_coordinates, nodes, boundary_names = p4est_connectivity_from_standard_abaqus( + meshfile.path, + mapping, + polydeg, + initial_refinement_level, + NDIMS, + RealT, + boundary_symbols + ) end cmesh = t8_cmesh_new_from_connectivity(connectivity, mpi_comm()) @@ -612,11 +658,15 @@ function T8codeMesh(meshfile::AbaqusFile{NDIMS}; do_face_ghost = mpi_isparallel() scheme = t8_scheme_new_default_cxx() - forest = t8_forest_new_uniform(cmesh, scheme, initial_refinement_level, do_face_ghost, - mpi_comm()) - - return T8codeMesh{NDIMS}(forest, tree_node_coordinates, nodes, - boundary_names, "") + forest = t8_forest_new_uniform( + cmesh, scheme, initial_refinement_level, do_face_ghost, + mpi_comm() + ) + + return T8codeMesh{NDIMS}( + forest, tree_node_coordinates, nodes, + boundary_names, "" + ) end function t8_cmesh_new_from_connectivity(connectivity::Ptr{p4est_connectivity}, comm) @@ -651,20 +701,24 @@ end # \return greater zero if the first entry in `elements` should be refined, # smaller zero if the family `elements` shall be coarsened, # zero else. -function adapt_callback_wrapper(forest, - forest_from, - which_tree, - lelement_id, - ts, - is_family, - num_elements, - elements_ptr)::Cint +function adapt_callback_wrapper( + forest, + forest_from, + which_tree, + lelement_id, + ts, + is_family, + num_elements, + elements_ptr + )::Cint passthrough = unsafe_pointer_to_objref(t8_forest_get_user_data(forest))[] elements = unsafe_wrap(Array, elements_ptr, num_elements) - return passthrough.adapt_callback(forest_from, which_tree, ts, lelement_id, elements, - Bool(is_family), passthrough.user_data) + return passthrough.adapt_callback( + forest_from, which_tree, ts, lelement_id, elements, + Bool(is_family), passthrough.user_data + ) end """ @@ -702,8 +756,10 @@ Adapt a `T8codeMesh` according to a user-defined `adapt_callback`. - `ghost = true`: Create a ghost layer for MPI data exchange. - `user_data = C_NULL`: Pointer to some arbitrary user-defined data. """ -function adapt!(mesh::T8codeMesh, adapt_callback; recursive = true, balance = true, - partition = true, ghost = true, user_data = C_NULL) +function adapt!( + mesh::T8codeMesh, adapt_callback; recursive = true, balance = true, + partition = true, ghost = true, user_data = C_NULL + ) # Check that forest is a committed, that is valid and usable, forest. @assert t8_forest_is_committed(mesh.forest) != 0 @@ -715,12 +771,22 @@ function adapt!(mesh::T8codeMesh, adapt_callback; recursive = true, balance = tr # Check out `examples/t8_step4_partition_balance_ghost.jl` in # https://github.com/DLR-AMR/T8code.jl for detailed explanations. let set_from = C_NULL, set_for_coarsening = 0, no_repartition = !partition - t8_forest_set_user_data(new_forest, - pointer_from_objref(Ref(adapt_callback_passthrough(adapt_callback, - user_data)))) - t8_forest_set_adapt(new_forest, mesh.forest, - @t8_adapt_callback(adapt_callback_wrapper), - recursive) + t8_forest_set_user_data( + new_forest, + pointer_from_objref( + Ref( + adapt_callback_passthrough( + adapt_callback, + user_data + ) + ) + ) + ) + t8_forest_set_adapt( + new_forest, mesh.forest, + @t8_adapt_callback(adapt_callback_wrapper), + recursive + ) if balance t8_forest_set_balance(new_forest, set_from, no_repartition) end @@ -823,12 +889,18 @@ function count_interfaces(mesh::T8codeMesh) if mpi_isparallel() ghost_num_trees = t8_forest_ghost_num_trees(mesh.forest) - ghost_tree_element_offsets = [num_local_elements + - t8_forest_ghost_get_tree_element_offset(mesh.forest, - itree) - for itree in 0:(ghost_num_trees - 1)] - ghost_global_treeids = [t8_forest_ghost_get_global_treeid(mesh.forest, itree) - for itree in 0:(ghost_num_trees - 1)] + ghost_tree_element_offsets = [ + num_local_elements + + t8_forest_ghost_get_tree_element_offset( + mesh.forest, + itree + ) + for itree in 0:(ghost_num_trees - 1) + ] + ghost_global_treeids = [ + t8_forest_ghost_get_global_treeid(mesh.forest, itree) + for itree in 0:(ghost_num_trees - 1) + ] end for itree in 0:(num_local_trees - 1) @@ -848,7 +920,7 @@ function count_interfaces(mesh::T8codeMesh) # Note: This works only for forests of one element class. current_linear_id = global_itree * max_tree_num_elements + - t8_element_get_linear_id(eclass_scheme, element, max_level) + t8_element_get_linear_id(eclass_scheme, element, max_level) for iface in 0:(num_faces - 1) pelement_indices_ref = Ref{Ptr{t8_locidx_t}}() @@ -860,16 +932,20 @@ function count_interfaces(mesh::T8codeMesh) forest_is_balanced = Cint(1) - t8_forest_leaf_face_neighbors(mesh.forest, itree, element, - pneighbor_leaves_ref, iface, dual_faces_ref, - num_neighbors_ref, - pelement_indices_ref, pneigh_scheme_ref, - forest_is_balanced) + t8_forest_leaf_face_neighbors( + mesh.forest, itree, element, + pneighbor_leaves_ref, iface, dual_faces_ref, + num_neighbors_ref, + pelement_indices_ref, pneigh_scheme_ref, + forest_is_balanced + ) num_neighbors = num_neighbors_ref[] dual_faces = unsafe_wrap(Array, dual_faces_ref[], num_neighbors) - neighbor_ielements = unsafe_wrap(Array, pelement_indices_ref[], - num_neighbors) + neighbor_ielements = unsafe_wrap( + Array, pelement_indices_ref[], + num_neighbors + ) neighbor_leaves = unsafe_wrap(Array, pneighbor_leaves_ref[], num_neighbors) neighbor_scheme = pneigh_scheme_ref[] @@ -897,15 +973,21 @@ function count_interfaces(mesh::T8codeMesh) global_mortar_id = 2 * ndims(mesh) * current_linear_id + iface else # level > neighbor_level - neighbor_global_ghost_itree = ghost_global_treeids[findlast(ghost_tree_element_offsets .<= - neighbor_ielements[1])] + neighbor_global_ghost_itree = ghost_global_treeids[ + findlast( + ghost_tree_element_offsets .<= + neighbor_ielements[1] + ), + ] neighbor_linear_id = neighbor_global_ghost_itree * - max_tree_num_elements + - t8_element_get_linear_id(neighbor_scheme, - neighbor_leaves[1], - max_level) + max_tree_num_elements + + t8_element_get_linear_id( + neighbor_scheme, + neighbor_leaves[1], + max_level + ) global_mortar_id = 2 * ndims(mesh) * neighbor_linear_id + - dual_faces[1] + dual_faces[1] if !(global_mortar_id in visited_global_mortar_ids) push!(visited_global_mortar_ids, global_mortar_id) @@ -924,11 +1006,13 @@ function count_interfaces(mesh::T8codeMesh) end # for end # for - return (interfaces = local_num_conform, - mortars = local_num_mortars, - boundaries = local_num_boundary, - mpi_interfaces = local_num_mpi_conform, - mpi_mortars = local_num_mpi_mortars) + return ( + interfaces = local_num_conform, + mortars = local_num_mortars, + boundaries = local_num_boundary, + mpi_interfaces = local_num_mpi_conform, + mpi_mortars = local_num_mpi_mortars, + ) end # I know this routine is an unmaintainable behemoth. However, I see no real @@ -937,8 +1021,10 @@ end # makes sense to query it only once per face per element and extract all the # information needed at once in order to fill the connectivity information. # Instead, I opted for good documentation. -function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, - boundary_names; mpi_mesh_info = nothing) +function fill_mesh_info!( + mesh::T8codeMesh, interfaces, mortars, boundaries, + boundary_names; mpi_mesh_info = nothing + ) @assert t8_forest_is_committed(mesh.forest) != 0 num_local_elements = t8_forest_get_local_num_elements(mesh.forest) @@ -1013,7 +1099,7 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, # Note: This works only for forests of one element class. current_linear_id = global_itree * max_tree_num_elements + - t8_element_get_linear_id(eclass_scheme, element, max_level) + t8_element_get_linear_id(eclass_scheme, element, max_level) # Loop over all faces of the current local element. for iface in 0:(num_faces - 1) @@ -1024,8 +1110,10 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, iface_in_tree = t8_element_tree_face(eclass_scheme, element, iface) orientation_ref = Ref{Cint}() - t8_cmesh_get_face_neighbor(cmesh, itree_in_cmesh, iface_in_tree, C_NULL, - orientation_ref) + t8_cmesh_get_face_neighbor( + cmesh, itree_in_cmesh, iface_in_tree, C_NULL, + orientation_ref + ) orientation = orientation_ref[] else orientation = zero(Cint) @@ -1041,16 +1129,20 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, forest_is_balanced = Cint(1) # Query neighbor information from t8code. - t8_forest_leaf_face_neighbors(mesh.forest, itree, element, - pneighbor_leaves_ref, iface, dual_faces_ref, - num_neighbors_ref, - pelement_indices_ref, pneigh_scheme_ref, - forest_is_balanced) + t8_forest_leaf_face_neighbors( + mesh.forest, itree, element, + pneighbor_leaves_ref, iface, dual_faces_ref, + num_neighbors_ref, + pelement_indices_ref, pneigh_scheme_ref, + forest_is_balanced + ) num_neighbors = num_neighbors_ref[] dual_faces = unsafe_wrap(Array, dual_faces_ref[], num_neighbors) - neighbor_ielements = unsafe_wrap(Array, pelement_indices_ref[], - num_neighbors) + neighbor_ielements = unsafe_wrap( + Array, pelement_indices_ref[], + num_neighbors + ) neighbor_leaves = unsafe_wrap(Array, pneighbor_leaves_ref[], num_neighbors) neighbor_scheme = pneigh_scheme_ref[] @@ -1105,13 +1197,15 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, local_num_conform += 1 interfaces.neighbor_ids[1, local_num_conform] = current_index + - 1 + 1 interfaces.neighbor_ids[2, local_num_conform] = neighbor_ielements[1] + - 1 + 1 - init_interface_node_indices!(interfaces, (iface, dual_faces[1]), - orientation, - local_num_conform) + init_interface_node_indices!( + interfaces, (iface, dual_faces[1]), + orientation, + local_num_conform + ) # Local mortar. elseif level < neighbor_level local_num_mortars += 1 @@ -1119,12 +1213,16 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, # Last entry is the large element. mortars.neighbor_ids[end, local_num_mortars] = current_index + 1 - init_mortar_neighbor_ids!(mortars, iface, dual_faces[1], - orientation, neighbor_ielements, - local_num_mortars) + init_mortar_neighbor_ids!( + mortars, iface, dual_faces[1], + orientation, neighbor_ielements, + local_num_mortars + ) - init_mortar_node_indices!(mortars, (dual_faces[1], iface), - orientation, local_num_mortars) + init_mortar_node_indices!( + mortars, (dual_faces[1], iface), + orientation, local_num_mortars + ) # else: `level > neighbor_level` is skipped since we visit the mortar interface only once. end @@ -1136,14 +1234,20 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, if level == neighbor_level local_num_mpi_conform += 1 - neighbor_global_ghost_itree = ghost_global_treeids[findlast(ghost_tree_element_offsets .<= - neighbor_ielements[1])] + neighbor_global_ghost_itree = ghost_global_treeids[ + findlast( + ghost_tree_element_offsets .<= + neighbor_ielements[1] + ), + ] neighbor_linear_id = neighbor_global_ghost_itree * - max_tree_num_elements + - t8_element_get_linear_id(neighbor_scheme, - neighbor_leaves[1], - max_level) + max_tree_num_elements + + t8_element_get_linear_id( + neighbor_scheme, + neighbor_leaves[1], + max_level + ) if current_linear_id < neighbor_linear_id local_side = 1 @@ -1158,18 +1262,24 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, end global_interface_id = 2 * ndims(mesh) * smaller_linear_id + - smaller_iface + smaller_iface mpi_mesh_info.mpi_interfaces.local_neighbor_ids[local_num_mpi_conform] = current_index + - 1 + 1 mpi_mesh_info.mpi_interfaces.local_sides[local_num_mpi_conform] = local_side - init_mpi_interface_node_indices!(mpi_mesh_info.mpi_interfaces, - faces, local_side, orientation, - local_num_mpi_conform) - - neighbor_rank = remotes[findlast(ghost_remote_first_elem .<= - neighbor_ielements[1])] + init_mpi_interface_node_indices!( + mpi_mesh_info.mpi_interfaces, + faces, local_side, orientation, + local_num_mpi_conform + ) + + neighbor_rank = remotes[ + findlast( + ghost_remote_first_elem .<= + neighbor_ielements[1] + ), + ] mpi_mesh_info.neighbor_ranks_interface[local_num_mpi_conform] = neighbor_rank mpi_mesh_info.global_interface_ids[local_num_mpi_conform] = global_interface_id @@ -1182,12 +1292,18 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, neighbor_ids = neighbor_ielements .+ 1 - local_neighbor_positions = findall(neighbor_ids .<= - num_local_elements) - local_neighbor_ids = [neighbor_ids[i] - for i in local_neighbor_positions] - local_neighbor_positions = [map_iface_to_ichild_to_position[dual_faces[1] + 1][t8_element_child_id(neighbor_scheme, neighbor_leaves[i]) + 1] - for i in local_neighbor_positions] + local_neighbor_positions = findall( + neighbor_ids .<= + num_local_elements + ) + local_neighbor_ids = [ + neighbor_ids[i] + for i in local_neighbor_positions + ] + local_neighbor_positions = [ + map_iface_to_ichild_to_position[dual_faces[1] + 1][t8_element_child_id(neighbor_scheme, neighbor_leaves[i]) + 1] + for i in local_neighbor_positions + ] # Last entry is the large element. push!(local_neighbor_ids, current_index + 1) @@ -1196,38 +1312,58 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, mpi_mesh_info.mpi_mortars.local_neighbor_ids[local_num_mpi_mortars] = local_neighbor_ids mpi_mesh_info.mpi_mortars.local_neighbor_positions[local_num_mpi_mortars] = local_neighbor_positions - init_mortar_node_indices!(mpi_mesh_info.mpi_mortars, - (dual_faces[1], iface), orientation, - local_num_mpi_mortars) - - neighbor_ranks = [remotes[findlast(ghost_remote_first_elem .<= - ineighbor_ghost)] - for ineighbor_ghost in filter(x -> x >= - num_local_elements, - neighbor_ielements)] + init_mortar_node_indices!( + mpi_mesh_info.mpi_mortars, + (dual_faces[1], iface), orientation, + local_num_mpi_mortars + ) + + neighbor_ranks = [ + remotes[ + findlast( + ghost_remote_first_elem .<= + ineighbor_ghost + ), + ] + for ineighbor_ghost in filter( + x -> x >= + num_local_elements, + neighbor_ielements + ) + ] mpi_mesh_info.neighbor_ranks_mortar[local_num_mpi_mortars] = neighbor_ranks mpi_mesh_info.global_mortar_ids[local_num_mpi_mortars] = global_mortar_id # MPI Mortar: from smaller elements point of view else - neighbor_global_ghost_itree = ghost_global_treeids[findlast(ghost_tree_element_offsets .<= - neighbor_ielements[1])] + neighbor_global_ghost_itree = ghost_global_treeids[ + findlast( + ghost_tree_element_offsets .<= + neighbor_ielements[1] + ), + ] neighbor_linear_id = neighbor_global_ghost_itree * - max_tree_num_elements + - t8_element_get_linear_id(neighbor_scheme, - neighbor_leaves[1], - max_level) + max_tree_num_elements + + t8_element_get_linear_id( + neighbor_scheme, + neighbor_leaves[1], + max_level + ) global_mortar_id = 2 * ndims(mesh) * neighbor_linear_id + - dual_faces[1] + dual_faces[1] if global_mortar_id in visited_global_mortar_ids local_mpi_mortar_id = global_mortar_id_to_local[global_mortar_id] - push!(mpi_mesh_info.mpi_mortars.local_neighbor_ids[local_mpi_mortar_id], - current_index + 1) - push!(mpi_mesh_info.mpi_mortars.local_neighbor_positions[local_mpi_mortar_id], - map_iface_to_ichild_to_position[iface + 1][t8_element_child_id(eclass_scheme, element) + 1]) + push!( + mpi_mesh_info.mpi_mortars.local_neighbor_ids[local_mpi_mortar_id], + current_index + 1 + ) + push!( + mpi_mesh_info.mpi_mortars.local_neighbor_positions[local_mpi_mortar_id], + map_iface_to_ichild_to_position[iface + 1][t8_element_child_id(eclass_scheme, element) + 1] + ) else local_num_mpi_mortars += 1 local_mpi_mortar_id = local_num_mpi_mortars @@ -1240,13 +1376,19 @@ function fill_mesh_info!(mesh::T8codeMesh, interfaces, mortars, boundaries, mpi_mesh_info.mpi_mortars.local_neighbor_positions[local_mpi_mortar_id] = [ map_iface_to_ichild_to_position[iface + 1][t8_element_child_id(eclass_scheme, element) + 1], ] - init_mortar_node_indices!(mpi_mesh_info.mpi_mortars, - (iface, dual_faces[1]), - orientation, local_mpi_mortar_id) + init_mortar_node_indices!( + mpi_mesh_info.mpi_mortars, + (iface, dual_faces[1]), + orientation, local_mpi_mortar_id + ) neighbor_ranks = [ - remotes[findlast(ghost_remote_first_elem .<= - neighbor_ielements[1])], + remotes[ + findlast( + ghost_remote_first_elem .<= + neighbor_ielements[1] + ), + ], ] mpi_mesh_info.neighbor_ranks_mortar[local_mpi_mortar_id] = neighbor_ranks diff --git a/src/meshes/transfinite_mappings_3d.jl b/src/meshes/transfinite_mappings_3d.jl index a82526bc1d..be483fe4c7 100644 --- a/src/meshes/transfinite_mappings_3d.jl +++ b/src/meshes/transfinite_mappings_3d.jl @@ -3,172 +3,192 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Illustration of the corner (circled), edge (braces), and face index numbering convention -# used in these functions. -# -# ⑧────────────────────────{7}────────────────────────⑦ -# ╱│ ╱│ -# ╱ │ ╱ │ -# ╱ │ ╱ │ -# ╱ │ 5 (+z) ╱ │ -# ╱ │ ╱ │ -# ╱ │ ╱ │ -# {12} │ {11} │ -# ╱ │ ╱ │ -# ╱ │ ╱ │ -# ╱ │ 2 (+y) ╱ │ -# ╱ │ ╱ │ -# ╱ {8} ╱ {6} -# ╱ │ ╱ │ -# ⑤─────────────────────────{3}───────────────────────⑥ 4 (+x) │ -# │ │ │ │ -# │ │ │ │ -# │ │ │ │ -# │ 6 (-x) │ │ │ -# │ │ │ │ -# │ │ │ │ -# │ │ │ │ -# │ │ │ │ -# │ ④────────────────────────{5}──────────│─────────────③ -# │ ╱ │ ╱ -# │ ╱ 1 (-y) │ ╱ -# {4} ╱ {2} ╱ -# │ ╱ │ ╱ -# │ ╱ │ ╱ -# │ {9} │ {10} -# │ ╱ │ ╱ -# │ ╱ │ ╱ Global coordinates: -# │ ╱ │ ╱ z -# │ ╱ 3 (-z) │ ╱ ↑ y -# │ ╱ │ ╱ │ ╱ -# │ ╱ │ ╱ │ ╱ -# │╱ │╱ └─────> x -# ①───────────────────────{1}─────────────────────────② - -# Transfinite mapping formula from a point (xi, eta, zeta) in reference space [-1,1]^3 to a -# physical coordinate (x, y, z) for a hexahedral element with straight sides -function straight_side_hex_map(xi, eta, zeta, corner_points) - coordinate = zeros(eltype(xi), 3) - for j in 1:3 - coordinate[j] += (0.125f0 * - (corner_points[j, 1] * (1 - xi) * (1 - eta) * (1 - zeta) - + corner_points[j, 2] * (1 + xi) * (1 - eta) * (1 - zeta) - + corner_points[j, 3] * (1 + xi) * (1 + eta) * (1 - zeta) - + corner_points[j, 4] * (1 - xi) * (1 + eta) * (1 - zeta) - + corner_points[j, 5] * (1 - xi) * (1 - eta) * (1 + zeta) - + corner_points[j, 6] * (1 + xi) * (1 - eta) * (1 + zeta) - + corner_points[j, 7] * (1 + xi) * (1 + eta) * (1 + zeta) - + corner_points[j, 8] * (1 - xi) * (1 + eta) * (1 + zeta))) + #! format: noindent + + # Illustration of the corner (circled), edge (braces), and face index numbering convention + # used in these functions. + # + # ⑧────────────────────────{7}────────────────────────⑦ + # ╱│ ╱│ + # ╱ │ ╱ │ + # ╱ │ ╱ │ + # ╱ │ 5 (+z) ╱ │ + # ╱ │ ╱ │ + # ╱ │ ╱ │ + # {12} │ {11} │ + # ╱ │ ╱ │ + # ╱ │ ╱ │ + # ╱ │ 2 (+y) ╱ │ + # ╱ │ ╱ │ + # ╱ {8} ╱ {6} + # ╱ │ ╱ │ + # ⑤─────────────────────────{3}───────────────────────⑥ 4 (+x) │ + # │ │ │ │ + # │ │ │ │ + # │ │ │ │ + # │ 6 (-x) │ │ │ + # │ │ │ │ + # │ │ │ │ + # │ │ │ │ + # │ │ │ │ + # │ ④────────────────────────{5}──────────│─────────────③ + # │ ╱ │ ╱ + # │ ╱ 1 (-y) │ ╱ + # {4} ╱ {2} ╱ + # │ ╱ │ ╱ + # │ ╱ │ ╱ + # │ {9} │ {10} + # │ ╱ │ ╱ + # │ ╱ │ ╱ Global coordinates: + # │ ╱ │ ╱ z + # │ ╱ 3 (-z) │ ╱ ↑ y + # │ ╱ │ ╱ │ ╱ + # │ ╱ │ ╱ │ ╱ + # │╱ │╱ └─────> x + # ①───────────────────────{1}─────────────────────────② + + # Transfinite mapping formula from a point (xi, eta, zeta) in reference space [-1,1]^3 to a + # physical coordinate (x, y, z) for a hexahedral element with straight sides + function straight_side_hex_map(xi, eta, zeta, corner_points) + coordinate = zeros(eltype(xi), 3) + for j in 1:3 + coordinate[j] += ( + 0.125f0 * + ( + corner_points[j, 1] * (1 - xi) * (1 - eta) * (1 - zeta) + + corner_points[j, 2] * (1 + xi) * (1 - eta) * (1 - zeta) + + corner_points[j, 3] * (1 + xi) * (1 + eta) * (1 - zeta) + + corner_points[j, 4] * (1 - xi) * (1 + eta) * (1 - zeta) + + corner_points[j, 5] * (1 - xi) * (1 - eta) * (1 + zeta) + + corner_points[j, 6] * (1 + xi) * (1 - eta) * (1 + zeta) + + corner_points[j, 7] * (1 + xi) * (1 + eta) * (1 + zeta) + + corner_points[j, 8] * (1 - xi) * (1 + eta) * (1 + zeta) + ) + ) + end + + return coordinate end - return coordinate -end - -# Construct the (x, y, z) node coordinates in the volume of a straight sided hexahedral element -function calc_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, element, - nodes, corners) - for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes) - node_coordinates[:, i, j, k, element] .= straight_side_hex_map(nodes[i], - nodes[j], - nodes[k], - corners) + # Construct the (x, y, z) node coordinates in the volume of a straight sided hexahedral element + function calc_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 5}, element, + nodes, corners + ) + for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes) + node_coordinates[:, i, j, k, element] .= straight_side_hex_map( + nodes[i], + nodes[j], + nodes[k], + corners + ) + end + + return node_coordinates end - return node_coordinates -end - -# Transfinite mapping formula from a point (xi, eta, zeta) in reference space [-1,1]^3 to a point -# (x,y,z) in physical coordinate space for a hexahedral element with general curved sides -# See Section 4.3 -# - Andrew R. Winters (2014) -# Discontinuous Galerkin spectral element approximations for the reflection and -# transmission of waves from moving material interfaces -# [PhD thesis, Florida State University](https://diginole.lib.fsu.edu/islandora/object/fsu%3A185342) -function transfinite_hex_map(xi, eta, zeta, face_curves::AbstractVector{<:CurvedFace}) - coordinate = zeros(eltype(xi), 3) - face_values = zeros(eltype(xi), (3, 6)) - edge_values = zeros(eltype(xi), (3, 12)) - corners = zeros(eltype(xi), (3, 8)) - - # Compute values along the face edges - edge_values[:, 1] .= evaluate_at(SVector(xi, -1), face_curves[1]) - edge_values[:, 2] .= evaluate_at(SVector(1, zeta), face_curves[1]) - edge_values[:, 3] .= evaluate_at(SVector(xi, 1), face_curves[1]) - edge_values[:, 4] .= evaluate_at(SVector(-1, zeta), face_curves[1]) - - edge_values[:, 5] .= evaluate_at(SVector(xi, -1), face_curves[2]) - edge_values[:, 6] .= evaluate_at(SVector(1, zeta), face_curves[2]) - edge_values[:, 7] .= evaluate_at(SVector(xi, 1), face_curves[2]) - edge_values[:, 8] .= evaluate_at(SVector(-1, zeta), face_curves[2]) - - edge_values[:, 9] .= evaluate_at(SVector(eta, -1), face_curves[6]) - edge_values[:, 10] .= evaluate_at(SVector(eta, -1), face_curves[4]) - edge_values[:, 11] .= evaluate_at(SVector(eta, 1), face_curves[4]) - edge_values[:, 12] .= evaluate_at(SVector(eta, 1), face_curves[6]) - - # Compute values on the face - face_values[:, 1] .= evaluate_at(SVector(xi, zeta), face_curves[1]) - face_values[:, 2] .= evaluate_at(SVector(xi, zeta), face_curves[2]) - face_values[:, 3] .= evaluate_at(SVector(xi, eta), face_curves[3]) - face_values[:, 4] .= evaluate_at(SVector(eta, zeta), face_curves[4]) - face_values[:, 5] .= evaluate_at(SVector(xi, eta), face_curves[5]) - face_values[:, 6] .= evaluate_at(SVector(eta, zeta), face_curves[6]) - - # Pull the eight corner values and compute the straight sided hex mapping - corners[:, 1] .= face_curves[1].coordinates[:, 1, 1] - corners[:, 2] .= face_curves[1].coordinates[:, end, 1] - corners[:, 3] .= face_curves[2].coordinates[:, end, 1] - corners[:, 4] .= face_curves[2].coordinates[:, 1, 1] - corners[:, 5] .= face_curves[1].coordinates[:, 1, end] - corners[:, 6] .= face_curves[1].coordinates[:, end, end] - corners[:, 7] .= face_curves[2].coordinates[:, end, end] - corners[:, 8] .= face_curves[2].coordinates[:, 1, end] - - coordinate_straight = straight_side_hex_map(xi, eta, zeta, corners) - - # Compute the transfinite mapping - for j in 1:3 - # Linear interpolation between opposite faces - coordinate[j] = (0.5f0 * - (face_values[j, 6] * (1 - xi) + face_values[j, 4] * (1 + xi) - + face_values[j, 1] * (1 - eta) + - face_values[j, 2] * (1 + eta) - + face_values[j, 3] * (1 - zeta) + - face_values[j, 5] * (1 + zeta))) - - # Edge corrections to ensure faces match - coordinate[j] -= (0.25f0 * (edge_values[j, 1] * (1 - eta) * (1 - zeta) - + edge_values[j, 2] * (1 + xi) * (1 - eta) - + edge_values[j, 3] * (1 - eta) * (1 + zeta) - + edge_values[j, 4] * (1 - xi) * (1 - eta) - + edge_values[j, 5] * (1 + eta) * (1 - zeta) - + edge_values[j, 6] * (1 + xi) * (1 + eta) - + edge_values[j, 7] * (1 + eta) * (1 + zeta) - + edge_values[j, 8] * (1 - xi) * (1 + eta) - + edge_values[j, 9] * (1 - xi) * (1 - zeta) - + edge_values[j, 10] * (1 + xi) * (1 - zeta) - + edge_values[j, 11] * (1 + xi) * (1 + zeta) - + edge_values[j, 12] * (1 - xi) * (1 + zeta))) - - # Subtracted interior twice, so add back the straight-sided hexahedral mapping - coordinate[j] += coordinate_straight[j] + # Transfinite mapping formula from a point (xi, eta, zeta) in reference space [-1,1]^3 to a point + # (x,y,z) in physical coordinate space for a hexahedral element with general curved sides + # See Section 4.3 + # - Andrew R. Winters (2014) + # Discontinuous Galerkin spectral element approximations for the reflection and + # transmission of waves from moving material interfaces + # [PhD thesis, Florida State University](https://diginole.lib.fsu.edu/islandora/object/fsu%3A185342) + function transfinite_hex_map(xi, eta, zeta, face_curves::AbstractVector{<:CurvedFace}) + coordinate = zeros(eltype(xi), 3) + face_values = zeros(eltype(xi), (3, 6)) + edge_values = zeros(eltype(xi), (3, 12)) + corners = zeros(eltype(xi), (3, 8)) + + # Compute values along the face edges + edge_values[:, 1] .= evaluate_at(SVector(xi, -1), face_curves[1]) + edge_values[:, 2] .= evaluate_at(SVector(1, zeta), face_curves[1]) + edge_values[:, 3] .= evaluate_at(SVector(xi, 1), face_curves[1]) + edge_values[:, 4] .= evaluate_at(SVector(-1, zeta), face_curves[1]) + + edge_values[:, 5] .= evaluate_at(SVector(xi, -1), face_curves[2]) + edge_values[:, 6] .= evaluate_at(SVector(1, zeta), face_curves[2]) + edge_values[:, 7] .= evaluate_at(SVector(xi, 1), face_curves[2]) + edge_values[:, 8] .= evaluate_at(SVector(-1, zeta), face_curves[2]) + + edge_values[:, 9] .= evaluate_at(SVector(eta, -1), face_curves[6]) + edge_values[:, 10] .= evaluate_at(SVector(eta, -1), face_curves[4]) + edge_values[:, 11] .= evaluate_at(SVector(eta, 1), face_curves[4]) + edge_values[:, 12] .= evaluate_at(SVector(eta, 1), face_curves[6]) + + # Compute values on the face + face_values[:, 1] .= evaluate_at(SVector(xi, zeta), face_curves[1]) + face_values[:, 2] .= evaluate_at(SVector(xi, zeta), face_curves[2]) + face_values[:, 3] .= evaluate_at(SVector(xi, eta), face_curves[3]) + face_values[:, 4] .= evaluate_at(SVector(eta, zeta), face_curves[4]) + face_values[:, 5] .= evaluate_at(SVector(xi, eta), face_curves[5]) + face_values[:, 6] .= evaluate_at(SVector(eta, zeta), face_curves[6]) + + # Pull the eight corner values and compute the straight sided hex mapping + corners[:, 1] .= face_curves[1].coordinates[:, 1, 1] + corners[:, 2] .= face_curves[1].coordinates[:, end, 1] + corners[:, 3] .= face_curves[2].coordinates[:, end, 1] + corners[:, 4] .= face_curves[2].coordinates[:, 1, 1] + corners[:, 5] .= face_curves[1].coordinates[:, 1, end] + corners[:, 6] .= face_curves[1].coordinates[:, end, end] + corners[:, 7] .= face_curves[2].coordinates[:, end, end] + corners[:, 8] .= face_curves[2].coordinates[:, 1, end] + + coordinate_straight = straight_side_hex_map(xi, eta, zeta, corners) + + # Compute the transfinite mapping + for j in 1:3 + # Linear interpolation between opposite faces + coordinate[j] = ( + 0.5f0 * + ( + face_values[j, 6] * (1 - xi) + face_values[j, 4] * (1 + xi) + + face_values[j, 1] * (1 - eta) + + face_values[j, 2] * (1 + eta) + + face_values[j, 3] * (1 - zeta) + + face_values[j, 5] * (1 + zeta) + ) + ) + + # Edge corrections to ensure faces match + coordinate[j] -= ( + 0.25f0 * ( + edge_values[j, 1] * (1 - eta) * (1 - zeta) + + edge_values[j, 2] * (1 + xi) * (1 - eta) + + edge_values[j, 3] * (1 - eta) * (1 + zeta) + + edge_values[j, 4] * (1 - xi) * (1 - eta) + + edge_values[j, 5] * (1 + eta) * (1 - zeta) + + edge_values[j, 6] * (1 + xi) * (1 + eta) + + edge_values[j, 7] * (1 + eta) * (1 + zeta) + + edge_values[j, 8] * (1 - xi) * (1 + eta) + + edge_values[j, 9] * (1 - xi) * (1 - zeta) + + edge_values[j, 10] * (1 + xi) * (1 - zeta) + + edge_values[j, 11] * (1 + xi) * (1 + zeta) + + edge_values[j, 12] * (1 - xi) * (1 + zeta) + ) + ) + + # Subtracted interior twice, so add back the straight-sided hexahedral mapping + coordinate[j] += coordinate_straight[j] + end + + return coordinate end - return coordinate -end - -# Construct the (x, y, z) node coordinates in the volume of a curved sided hexahedral element -function calc_node_coordinates!(node_coordinates::AbstractArray{<:Any, 5}, element, - nodes, - face_curves::AbstractVector{<:CurvedFace}) - for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes) - node_coordinates[:, i, j, k, element] .= transfinite_hex_map(nodes[i], nodes[j], - nodes[k], - face_curves) + # Construct the (x, y, z) node coordinates in the volume of a curved sided hexahedral element + function calc_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 5}, element, + nodes, + face_curves::AbstractVector{<:CurvedFace} + ) + for k in eachindex(nodes), j in eachindex(nodes), i in eachindex(nodes) + node_coordinates[:, i, j, k, element] .= transfinite_hex_map( + nodes[i], nodes[j], + nodes[k], + face_curves + ) + end + + return node_coordinates end - - return node_coordinates -end end # @muladd diff --git a/src/meshes/tree_mesh.jl b/src/meshes/tree_mesh.jl index 1092fc54cc..0a6634518f 100644 --- a/src/meshes/tree_mesh.jl +++ b/src/meshes/tree_mesh.jl @@ -3,233 +3,263 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -include("abstract_tree.jl") -include("serial_tree.jl") -include("parallel_tree.jl") - -get_name(mesh::AbstractMesh) = mesh |> typeof |> nameof |> string - -# Composite type to hold the actual tree in addition to other mesh-related data -# that is not strictly part of the tree. -# The mesh is really just about the connectivity, size, and location of the individual -# tree nodes. Neighbor information between interfaces or the large sides for mortars is -# something that is solver-specific and that might not be needed by all solvers (or in a -# different form). Also, these data values can be performance critical, so a mesh would -# have to store them for all solvers in an efficient way - OTOH, different solvers might -# use different cells of a shared mesh, so "efficient" is again solver dependent. -""" - TreeMesh{NDIMS} <: AbstractMesh{NDIMS} - -A Cartesian mesh based on trees of hypercubes to support adaptive mesh refinement. -""" -mutable struct TreeMesh{NDIMS, TreeType <: AbstractTree{NDIMS}} <: AbstractMesh{NDIMS} - tree::TreeType - current_filename::String - unsaved_changes::Bool - first_cell_by_rank::OffsetVector{Int, Vector{Int}} - n_cells_by_rank::OffsetVector{Int, Vector{Int}} - - function TreeMesh{NDIMS, TreeType}(n_cells_max::Integer) where {NDIMS, - TreeType <: - AbstractTree{NDIMS}} - # Create mesh - m = new() - m.tree = TreeType(n_cells_max) - m.current_filename = "" - m.unsaved_changes = true - m.first_cell_by_rank = OffsetVector(Int[], 0) - m.n_cells_by_rank = OffsetVector(Int[], 0) - - return m + #! format: noindent + + include("abstract_tree.jl") + include("serial_tree.jl") + include("parallel_tree.jl") + + get_name(mesh::AbstractMesh) = mesh |> typeof |> nameof |> string + + # Composite type to hold the actual tree in addition to other mesh-related data + # that is not strictly part of the tree. + # The mesh is really just about the connectivity, size, and location of the individual + # tree nodes. Neighbor information between interfaces or the large sides for mortars is + # something that is solver-specific and that might not be needed by all solvers (or in a + # different form). Also, these data values can be performance critical, so a mesh would + # have to store them for all solvers in an efficient way - OTOH, different solvers might + # use different cells of a shared mesh, so "efficient" is again solver dependent. + """ + TreeMesh{NDIMS} <: AbstractMesh{NDIMS} + + A Cartesian mesh based on trees of hypercubes to support adaptive mesh refinement. + """ + mutable struct TreeMesh{NDIMS, TreeType <: AbstractTree{NDIMS}} <: AbstractMesh{NDIMS} + tree::TreeType + current_filename::String + unsaved_changes::Bool + first_cell_by_rank::OffsetVector{Int, Vector{Int}} + n_cells_by_rank::OffsetVector{Int, Vector{Int}} + + function TreeMesh{NDIMS, TreeType}(n_cells_max::Integer) where { + NDIMS, + TreeType <: + AbstractTree{NDIMS}, + } + # Create mesh + m = new() + m.tree = TreeType(n_cells_max) + m.current_filename = "" + m.unsaved_changes = true + m.first_cell_by_rank = OffsetVector(Int[], 0) + m.n_cells_by_rank = OffsetVector(Int[], 0) + + return m + end + + # TODO: Taal refactor, order of important arguments, use of n_cells_max? + # TODO: Taal refactor, allow other RealT for the mesh, not just Float64 + # TODO: Taal refactor, use NTuple instead of domain_center::AbstractArray{Float64} + function TreeMesh{NDIMS, TreeType}( + n_cells_max::Integer, + domain_center::AbstractArray{Float64}, + domain_length, + periodicity = true + ) where { + NDIMS, + TreeType <: + AbstractTree{NDIMS}, + } + @assert NDIMS isa Integer && NDIMS > 0 + + # Create mesh + m = new() + m.tree = TreeType(n_cells_max, domain_center, domain_length, periodicity) + m.current_filename = "" + m.unsaved_changes = true + m.first_cell_by_rank = OffsetVector(Int[], 0) + m.n_cells_by_rank = OffsetVector(Int[], 0) + + return m + end end - # TODO: Taal refactor, order of important arguments, use of n_cells_max? - # TODO: Taal refactor, allow other RealT for the mesh, not just Float64 - # TODO: Taal refactor, use NTuple instead of domain_center::AbstractArray{Float64} - function TreeMesh{NDIMS, TreeType}(n_cells_max::Integer, - domain_center::AbstractArray{Float64}, - domain_length, - periodicity = true) where {NDIMS, - TreeType <: - AbstractTree{NDIMS}} - @assert NDIMS isa Integer && NDIMS > 0 + const TreeMesh1D = TreeMesh{1, TreeType} where {TreeType <: AbstractTree{1}} + const TreeMesh2D = TreeMesh{2, TreeType} where {TreeType <: AbstractTree{2}} + const TreeMesh3D = TreeMesh{3, TreeType} where {TreeType <: AbstractTree{3}} - # Create mesh - m = new() - m.tree = TreeType(n_cells_max, domain_center, domain_length, periodicity) - m.current_filename = "" - m.unsaved_changes = true - m.first_cell_by_rank = OffsetVector(Int[], 0) - m.n_cells_by_rank = OffsetVector(Int[], 0) - - return m + const SerialTreeMesh{NDIMS} = TreeMesh{NDIMS, <:SerialTree{NDIMS}} + const ParallelTreeMesh{NDIMS} = TreeMesh{NDIMS, <:ParallelTree{NDIMS}} + + @inline mpi_parallel(mesh::SerialTreeMesh) = False() + @inline mpi_parallel(mesh::ParallelTreeMesh) = True() + + partition!(mesh::SerialTreeMesh) = nothing + + # Constructor for passing the dimension and mesh type as an argument + function TreeMesh( + ::Type{TreeType}, + args... + ) where {NDIMS, TreeType <: AbstractTree{NDIMS}} + TreeMesh{NDIMS, TreeType}(args...) end -end - -const TreeMesh1D = TreeMesh{1, TreeType} where {TreeType <: AbstractTree{1}} -const TreeMesh2D = TreeMesh{2, TreeType} where {TreeType <: AbstractTree{2}} -const TreeMesh3D = TreeMesh{3, TreeType} where {TreeType <: AbstractTree{3}} - -const SerialTreeMesh{NDIMS} = TreeMesh{NDIMS, <:SerialTree{NDIMS}} -const ParallelTreeMesh{NDIMS} = TreeMesh{NDIMS, <:ParallelTree{NDIMS}} - -@inline mpi_parallel(mesh::SerialTreeMesh) = False() -@inline mpi_parallel(mesh::ParallelTreeMesh) = True() - -partition!(mesh::SerialTreeMesh) = nothing - -# Constructor for passing the dimension and mesh type as an argument -function TreeMesh(::Type{TreeType}, - args...) where {NDIMS, TreeType <: AbstractTree{NDIMS}} - TreeMesh{NDIMS, TreeType}(args...) -end - -# Constructor accepting a single number as center (as opposed to an array) for 1D -function TreeMesh{1, TreeType}(n::Int, center::Real, len::Real, - periodicity = true) where {TreeType <: AbstractTree{1}} - # TODO: Taal refactor, allow other RealT for the mesh, not just Float64 - return TreeMesh{1, TreeType}(n, SVector{1, Float64}(center), len, periodicity) -end - -function TreeMesh{NDIMS, TreeType}(n_cells_max::Integer, - domain_center::NTuple{NDIMS, Real}, - domain_length::Real, - periodicity = true) where {NDIMS, - TreeType <: - AbstractTree{NDIMS}} - # TODO: Taal refactor, allow other RealT for the mesh, not just Float64 - TreeMesh{NDIMS, TreeType}(n_cells_max, SVector{NDIMS, Float64}(domain_center), - convert(Float64, domain_length), periodicity) -end - -function TreeMesh(coordinates_min::NTuple{NDIMS, Real}, - coordinates_max::NTuple{NDIMS, Real}; - n_cells_max, - periodicity = true, - initial_refinement_level, - refinement_patches = (), - coarsening_patches = ()) where {NDIMS} - # check arguments - if !(n_cells_max isa Integer && n_cells_max > 0) - throw(ArgumentError("`n_cells_max` must be a positive integer (provided `n_cells_max = $n_cells_max`)")) + + # Constructor accepting a single number as center (as opposed to an array) for 1D + function TreeMesh{1, TreeType}( + n::Int, center::Real, len::Real, + periodicity = true + ) where {TreeType <: AbstractTree{1}} + # TODO: Taal refactor, allow other RealT for the mesh, not just Float64 + return TreeMesh{1, TreeType}(n, SVector{1, Float64}(center), len, periodicity) end - if !(initial_refinement_level isa Integer && initial_refinement_level >= 0) - throw(ArgumentError("`initial_refinement_level` must be a non-negative integer (provided `initial_refinement_level = $initial_refinement_level`)")) + + function TreeMesh{NDIMS, TreeType}( + n_cells_max::Integer, + domain_center::NTuple{NDIMS, Real}, + domain_length::Real, + periodicity = true + ) where { + NDIMS, + TreeType <: + AbstractTree{NDIMS}, + } + # TODO: Taal refactor, allow other RealT for the mesh, not just Float64 + TreeMesh{NDIMS, TreeType}( + n_cells_max, SVector{NDIMS, Float64}(domain_center), + convert(Float64, domain_length), periodicity + ) end - # Domain length is calculated as the maximum length in any axis direction - domain_center = @. (coordinates_min + coordinates_max) / 2 - domain_length = maximum(coordinates_max .- coordinates_min) + function TreeMesh( + coordinates_min::NTuple{NDIMS, Real}, + coordinates_max::NTuple{NDIMS, Real}; + n_cells_max, + periodicity = true, + initial_refinement_level, + refinement_patches = (), + coarsening_patches = () + ) where {NDIMS} + # check arguments + if !(n_cells_max isa Integer && n_cells_max > 0) + throw(ArgumentError("`n_cells_max` must be a positive integer (provided `n_cells_max = $n_cells_max`)")) + end + if !(initial_refinement_level isa Integer && initial_refinement_level >= 0) + throw(ArgumentError("`initial_refinement_level` must be a non-negative integer (provided `initial_refinement_level = $initial_refinement_level`)")) + end - # TODO: MPI, create nice interface for a parallel tree/mesh - if mpi_isparallel() - if mpi_isroot() && NDIMS != 2 - println(stderr, - "ERROR: The TreeMesh supports parallel execution with MPI only in 2 dimensions") - MPI.Abort(mpi_comm(), 1) + # Domain length is calculated as the maximum length in any axis direction + domain_center = @. (coordinates_min + coordinates_max) / 2 + domain_length = maximum(coordinates_max .- coordinates_min) + + # TODO: MPI, create nice interface for a parallel tree/mesh + if mpi_isparallel() + if mpi_isroot() && NDIMS != 2 + println( + stderr, + "ERROR: The TreeMesh supports parallel execution with MPI only in 2 dimensions" + ) + MPI.Abort(mpi_comm(), 1) + end + TreeType = ParallelTree{NDIMS} + else + TreeType = SerialTree{NDIMS} end - TreeType = ParallelTree{NDIMS} - else - TreeType = SerialTree{NDIMS} + + # Create mesh + mesh = @trixi_timeit timer() "creation" TreeMesh{NDIMS, TreeType}( + n_cells_max, + domain_center, + domain_length, + periodicity + ) + + # Initialize mesh + initialize!(mesh, initial_refinement_level, refinement_patches, coarsening_patches) + + return mesh end - # Create mesh - mesh = @trixi_timeit timer() "creation" TreeMesh{NDIMS, TreeType}(n_cells_max, - domain_center, - domain_length, - periodicity) - - # Initialize mesh - initialize!(mesh, initial_refinement_level, refinement_patches, coarsening_patches) - - return mesh -end - -function initialize!(mesh::TreeMesh, initial_refinement_level, - refinement_patches, coarsening_patches) - # Create initial refinement - @trixi_timeit timer() "initial refinement" refine_uniformly!(mesh.tree, - initial_refinement_level) - - # Apply refinement patches - @trixi_timeit timer() "refinement patches" for patch in refinement_patches - # TODO: Taal refactor, use multiple dispatch? - if patch.type == "box" - refine_box!(mesh.tree, patch.coordinates_min, patch.coordinates_max) - elseif patch.type == "sphere" - refine_sphere!(mesh.tree, patch.center, patch.radius) - else - error("unknown refinement patch type '$(patch.type)'") + function initialize!( + mesh::TreeMesh, initial_refinement_level, + refinement_patches, coarsening_patches + ) + # Create initial refinement + @trixi_timeit timer() "initial refinement" refine_uniformly!( + mesh.tree, + initial_refinement_level + ) + + # Apply refinement patches + @trixi_timeit timer() "refinement patches" for patch in refinement_patches + # TODO: Taal refactor, use multiple dispatch? + if patch.type == "box" + refine_box!(mesh.tree, patch.coordinates_min, patch.coordinates_max) + elseif patch.type == "sphere" + refine_sphere!(mesh.tree, patch.center, patch.radius) + else + error("unknown refinement patch type '$(patch.type)'") + end + end + + # Apply coarsening patches + @trixi_timeit timer() "coarsening patches" for patch in coarsening_patches + # TODO: Taal refactor, use multiple dispatch + if patch.type == "box" + coarsen_box!(mesh.tree, patch.coordinates_min, patch.coordinates_max) + else + error("unknown coarsening patch type '$(patch.type)'") + end end + + # Partition the mesh among multiple MPI ranks (does nothing if run in serial) + partition!(mesh) + + return nothing end - # Apply coarsening patches - @trixi_timeit timer() "coarsening patches" for patch in coarsening_patches - # TODO: Taal refactor, use multiple dispatch - if patch.type == "box" - coarsen_box!(mesh.tree, patch.coordinates_min, patch.coordinates_max) + function TreeMesh(coordinates_min::Real, coordinates_max::Real; kwargs...) + TreeMesh((coordinates_min,), (coordinates_max,); kwargs...) + end + + function Base.show(io::IO, mesh::TreeMesh{NDIMS, TreeType}) where {NDIMS, TreeType} + print(io, "TreeMesh{", NDIMS, ", ", TreeType, "} with length ", mesh.tree.length) + end + + function Base.show( + io::IO, ::MIME"text/plain", + mesh::TreeMesh{NDIMS, TreeType} + ) where {NDIMS, TreeType} + if get(io, :compact, false) + show(io, mesh) else - error("unknown coarsening patch type '$(patch.type)'") + setup = [ + "center" => mesh.tree.center_level_0, + "length" => mesh.tree.length_level_0, + "periodicity" => mesh.tree.periodicity, + "current #cells" => mesh.tree.length, + "#leaf-cells" => count_leaf_cells(mesh.tree), + "maximum #cells" => mesh.tree.capacity, + ] + summary_box( + io, "TreeMesh{" * string(NDIMS) * ", " * string(TreeType) * "}", + setup + ) end end - # Partition the mesh among multiple MPI ranks (does nothing if run in serial) - partition!(mesh) - - return nothing -end - -function TreeMesh(coordinates_min::Real, coordinates_max::Real; kwargs...) - TreeMesh((coordinates_min,), (coordinates_max,); kwargs...) -end - -function Base.show(io::IO, mesh::TreeMesh{NDIMS, TreeType}) where {NDIMS, TreeType} - print(io, "TreeMesh{", NDIMS, ", ", TreeType, "} with length ", mesh.tree.length) -end - -function Base.show(io::IO, ::MIME"text/plain", - mesh::TreeMesh{NDIMS, TreeType}) where {NDIMS, TreeType} - if get(io, :compact, false) - show(io, mesh) - else - setup = [ - "center" => mesh.tree.center_level_0, - "length" => mesh.tree.length_level_0, - "periodicity" => mesh.tree.periodicity, - "current #cells" => mesh.tree.length, - "#leaf-cells" => count_leaf_cells(mesh.tree), - "maximum #cells" => mesh.tree.capacity, - ] - summary_box(io, "TreeMesh{" * string(NDIMS) * ", " * string(TreeType) * "}", - setup) - end -end + @inline Base.ndims(mesh::TreeMesh) = ndims(mesh.tree) -@inline Base.ndims(mesh::TreeMesh) = ndims(mesh.tree) + # Obtain the mesh filename from a restart file + function get_restart_mesh_filename(restart_filename, mpi_parallel::False) + # Get directory name + dirname, _ = splitdir(restart_filename) -# Obtain the mesh filename from a restart file -function get_restart_mesh_filename(restart_filename, mpi_parallel::False) - # Get directory name - dirname, _ = splitdir(restart_filename) + # Read mesh filename from restart file + mesh_file = "" + h5open(restart_filename, "r") do file + mesh_file = read(attributes(file)["mesh_file"]) + end - # Read mesh filename from restart file - mesh_file = "" - h5open(restart_filename, "r") do file - mesh_file = read(attributes(file)["mesh_file"]) + # Construct and return filename + return joinpath(dirname, mesh_file) end - # Construct and return filename - return joinpath(dirname, mesh_file) -end - -function total_volume(mesh::TreeMesh) - return mesh.tree.length_level_0^ndims(mesh) -end + function total_volume(mesh::TreeMesh) + return mesh.tree.length_level_0^ndims(mesh) + end -isperiodic(mesh::TreeMesh) = isperiodic(mesh.tree) -isperiodic(mesh::TreeMesh, dimension) = isperiodic(mesh.tree, dimension) + isperiodic(mesh::TreeMesh) = isperiodic(mesh.tree) + isperiodic(mesh::TreeMesh, dimension) = isperiodic(mesh.tree, dimension) -include("parallel_tree_mesh.jl") + include("parallel_tree_mesh.jl") end # @muladd diff --git a/src/meshes/unstructured_mesh.jl b/src/meshes/unstructured_mesh.jl index fae52f834b..9ee55f7141 100644 --- a/src/meshes/unstructured_mesh.jl +++ b/src/meshes/unstructured_mesh.jl @@ -3,271 +3,308 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - UnstructuredMesh2D <: AbstractMesh{2} + """ + UnstructuredMesh2D <: AbstractMesh{2} -An unstructured (possibly curved) quadrilateral mesh. + An unstructured (possibly curved) quadrilateral mesh. - UnstructuredMesh2D(filename; RealT=Float64, periodicity=false) + UnstructuredMesh2D(filename; RealT=Float64, periodicity=false) -All mesh information, neighbour coupling, and boundary curve information is read in -from a mesh file `filename`. -""" -mutable struct UnstructuredMesh2D{RealT <: Real, - CurvedSurfaceT <: CurvedSurface{RealT}} <: - AbstractMesh{2} - filename :: String - n_corners :: Int - n_surfaces :: Int # total number of surfaces - n_interfaces :: Int # number of interior surfaces - n_boundaries :: Int # number of surfaces on the physical boundary - n_elements :: Int - polydeg :: Int - corners :: Array{RealT, 2} # [ndims, n_corners] - neighbour_information :: Array{Int, 2} # [neighbour node/element/edge ids, n_surfaces] - boundary_names :: Array{Symbol, 2} # [local sides, n_elements] - periodicity :: Bool - element_node_ids :: Array{Int, 2} # [node ids, n_elements] - element_is_curved :: Vector{Bool} - surface_curves :: Array{CurvedSurfaceT, 2} # [local sides, n_elements] - current_filename :: String - unsaved_changes :: Bool # if true, the mesh will be saved for plotting -end + All mesh information, neighbour coupling, and boundary curve information is read in + from a mesh file `filename`. + """ + mutable struct UnstructuredMesh2D{ + RealT <: Real, + CurvedSurfaceT <: CurvedSurface{RealT}, + } <: + AbstractMesh{2} + filename::String + n_corners::Int + n_surfaces::Int # total number of surfaces + n_interfaces::Int # number of interior surfaces + n_boundaries::Int # number of surfaces on the physical boundary + n_elements::Int + polydeg::Int + corners::Array{RealT, 2} # [ndims, n_corners] + neighbour_information::Array{Int, 2} # [neighbour node/element/edge ids, n_surfaces] + boundary_names::Array{Symbol, 2} # [local sides, n_elements] + periodicity::Bool + element_node_ids::Array{Int, 2} # [node ids, n_elements] + element_is_curved::Vector{Bool} + surface_curves::Array{CurvedSurfaceT, 2} # [local sides, n_elements] + current_filename::String + unsaved_changes::Bool # if true, the mesh will be saved for plotting + end -# constructor for an unstructured mesh read in from a file -# TODO: this mesh file parsing and construction of the mesh skeleton can likely be improved in terms -# of performance -function UnstructuredMesh2D(filename; RealT = Float64, periodicity = false, - unsaved_changes = true) + # constructor for an unstructured mesh read in from a file + # TODO: this mesh file parsing and construction of the mesh skeleton can likely be improved in terms + # of performance + function UnstructuredMesh2D( + filename; RealT = Float64, periodicity = false, + unsaved_changes = true + ) - # readin all the information from the mesh file into a string array - file_lines = readlines(open(filename)) + # readin all the information from the mesh file into a string array + file_lines = readlines(open(filename)) - # readin the number of nodes, number of interfaces, number of elements and local polynomial degree - current_line = split(file_lines[2]) - n_corners = parse(Int, current_line[1]) - n_surfaces = parse(Int, current_line[2]) - n_elements = parse(Int, current_line[3]) - mesh_polydeg = parse(Int, current_line[4]) + # readin the number of nodes, number of interfaces, number of elements and local polynomial degree + current_line = split(file_lines[2]) + n_corners = parse(Int, current_line[1]) + n_surfaces = parse(Int, current_line[2]) + n_elements = parse(Int, current_line[3]) + mesh_polydeg = parse(Int, current_line[4]) - mesh_nnodes = mesh_polydeg + 1 + mesh_nnodes = mesh_polydeg + 1 - # The types of structs used in the following depend on information read from - # the mesh file. Thus, this cannot be type stable at all. Hence, we allocate - # the memory now and introduce a function barrier before continuing to read - # data from the file. - corner_nodes = Array{RealT}(undef, (2, n_corners)) - interface_info = Array{Int}(undef, (6, n_surfaces)) - element_node_ids = Array{Int}(undef, (4, n_elements)) - curved_check = Vector{Int}(undef, 4) - quad_corners = Array{RealT}(undef, (4, 2)) - quad_corners_flipped = Array{RealT}(undef, (4, 2)) - curve_values = Array{RealT}(undef, (mesh_nnodes, 2)) - element_is_curved = Array{Bool}(undef, n_elements) - CurvedSurfaceT = CurvedSurface{RealT} - surface_curves = Array{CurvedSurfaceT}(undef, (4, n_elements)) - boundary_names = Array{Symbol}(undef, (4, n_elements)) + # The types of structs used in the following depend on information read from + # the mesh file. Thus, this cannot be type stable at all. Hence, we allocate + # the memory now and introduce a function barrier before continuing to read + # data from the file. + corner_nodes = Array{RealT}(undef, (2, n_corners)) + interface_info = Array{Int}(undef, (6, n_surfaces)) + element_node_ids = Array{Int}(undef, (4, n_elements)) + curved_check = Vector{Int}(undef, 4) + quad_corners = Array{RealT}(undef, (4, 2)) + quad_corners_flipped = Array{RealT}(undef, (4, 2)) + curve_values = Array{RealT}(undef, (mesh_nnodes, 2)) + element_is_curved = Array{Bool}(undef, n_elements) + CurvedSurfaceT = CurvedSurface{RealT} + surface_curves = Array{CurvedSurfaceT}(undef, (4, n_elements)) + boundary_names = Array{Symbol}(undef, (4, n_elements)) - # create the Chebyshev-Gauss-Lobatto nodes used to represent any curved boundaries that are - # required to construct the sides - cheby_nodes_, _ = chebyshev_gauss_lobatto_nodes_weights(mesh_nnodes) - bary_weights_ = barycentric_weights(cheby_nodes_) - cheby_nodes = SVector{mesh_nnodes}(cheby_nodes_) - bary_weights = SVector{mesh_nnodes}(bary_weights_) + # create the Chebyshev-Gauss-Lobatto nodes used to represent any curved boundaries that are + # required to construct the sides + cheby_nodes_, _ = chebyshev_gauss_lobatto_nodes_weights(mesh_nnodes) + bary_weights_ = barycentric_weights(cheby_nodes_) + cheby_nodes = SVector{mesh_nnodes}(cheby_nodes_) + bary_weights = SVector{mesh_nnodes}(bary_weights_) - arrays = (; corner_nodes, interface_info, element_node_ids, curved_check, - quad_corners, quad_corners_flipped, curve_values, - element_is_curved, surface_curves, boundary_names) - counters = (; n_corners, n_surfaces, n_elements) + arrays = (; + corner_nodes, interface_info, element_node_ids, curved_check, + quad_corners, quad_corners_flipped, curve_values, + element_is_curved, surface_curves, boundary_names, + ) + counters = (; n_corners, n_surfaces, n_elements) - n_boundaries = parse_mesh_file!(arrays, RealT, CurvedSurfaceT, file_lines, counters, - cheby_nodes, bary_weights) + n_boundaries = parse_mesh_file!( + arrays, RealT, CurvedSurfaceT, file_lines, counters, + cheby_nodes, bary_weights + ) - # get the number of internal interfaces in the mesh - if periodicity - n_interfaces = n_surfaces - n_boundaries = 0 - else - n_interfaces = n_surfaces - n_boundaries - end + # get the number of internal interfaces in the mesh + if periodicity + n_interfaces = n_surfaces + n_boundaries = 0 + else + n_interfaces = n_surfaces - n_boundaries + end - return UnstructuredMesh2D{RealT, CurvedSurfaceT}(filename, n_corners, n_surfaces, - n_interfaces, n_boundaries, - n_elements, mesh_polydeg, - corner_nodes, - interface_info, boundary_names, - periodicity, - element_node_ids, - element_is_curved, surface_curves, - "", unsaved_changes) -end + return UnstructuredMesh2D{RealT, CurvedSurfaceT}( + filename, n_corners, n_surfaces, + n_interfaces, n_boundaries, + n_elements, mesh_polydeg, + corner_nodes, + interface_info, boundary_names, + periodicity, + element_node_ids, + element_is_curved, surface_curves, + "", unsaved_changes + ) + end -function parse_mesh_file!(arrays, RealT, CurvedSurfaceT, file_lines, counters, - cheby_nodes, bary_weights) - @unpack (corner_nodes, interface_info, element_node_ids, curved_check, - quad_corners, quad_corners_flipped, curve_values, - element_is_curved, surface_curves, boundary_names) = arrays - @unpack n_corners, n_surfaces, n_elements = counters - mesh_nnodes = length(cheby_nodes) + function parse_mesh_file!( + arrays, RealT, CurvedSurfaceT, file_lines, counters, + cheby_nodes, bary_weights + ) + @unpack ( + corner_nodes, interface_info, element_node_ids, curved_check, + quad_corners, quad_corners_flipped, curve_values, + element_is_curved, surface_curves, boundary_names, + ) = arrays + @unpack n_corners, n_surfaces, n_elements = counters + mesh_nnodes = length(cheby_nodes) - # counter to step through the mesh file line by line - file_idx = 3 + # counter to step through the mesh file line by line + file_idx = 3 - # readin an store the nodes that dictate the corners of the elements needed to construct the - # element geometry terms - for j in 1:n_corners - current_line = split(file_lines[file_idx]) - corner_nodes[1, j] = parse(RealT, current_line[1]) - corner_nodes[2, j] = parse(RealT, current_line[2]) - file_idx += 1 - end + # readin an store the nodes that dictate the corners of the elements needed to construct the + # element geometry terms + for j in 1:n_corners + current_line = split(file_lines[file_idx]) + corner_nodes[1, j] = parse(RealT, current_line[1]) + corner_nodes[2, j] = parse(RealT, current_line[2]) + file_idx += 1 + end - # readin an store the nodes that dictate the interfaces, neighbour data, and orientations contains - # the following: - # interface_info[1] = start node ID - # interface_info[2] = end node ID - # interface_info[3] = ID of the primary element - # interface_info[4] = ID of the secondary element (if 0 then it is a physical boundary) - # interface_info[5] = local side ID on the primary element - # interface_info[6] = local side ID on the secondary element - # container to for the interface neighbour information and connectivity - n_boundaries = 0 - for j in 1:n_surfaces - current_line = split(file_lines[file_idx]) - interface_info[1, j] = parse(Int, current_line[1]) - interface_info[2, j] = parse(Int, current_line[2]) - interface_info[3, j] = parse(Int, current_line[3]) - interface_info[4, j] = parse(Int, current_line[4]) - interface_info[5, j] = parse(Int, current_line[5]) - interface_info[6, j] = parse(Int, current_line[6]) + # readin an store the nodes that dictate the interfaces, neighbour data, and orientations contains + # the following: + # interface_info[1] = start node ID + # interface_info[2] = end node ID + # interface_info[3] = ID of the primary element + # interface_info[4] = ID of the secondary element (if 0 then it is a physical boundary) + # interface_info[5] = local side ID on the primary element + # interface_info[6] = local side ID on the secondary element + # container to for the interface neighbour information and connectivity + n_boundaries = 0 + for j in 1:n_surfaces + current_line = split(file_lines[file_idx]) + interface_info[1, j] = parse(Int, current_line[1]) + interface_info[2, j] = parse(Int, current_line[2]) + interface_info[3, j] = parse(Int, current_line[3]) + interface_info[4, j] = parse(Int, current_line[4]) + interface_info[5, j] = parse(Int, current_line[5]) + interface_info[6, j] = parse(Int, current_line[6]) - # count the number of physical boundaries - if interface_info[4, j] == 0 - n_boundaries += 1 + # count the number of physical boundaries + if interface_info[4, j] == 0 + n_boundaries += 1 + end + file_idx += 1 end - file_idx += 1 - end - # work arrays to pull to correct corners of a given element (agnostic to curvature) and local - # copies of the curved boundary information + # work arrays to pull to correct corners of a given element (agnostic to curvature) and local + # copies of the curved boundary information - # readin an store the curved boundary information of the elements + # readin an store the curved boundary information of the elements - for j in 1:n_elements - # pull the corner node IDs - current_line = split(file_lines[file_idx]) - element_node_ids[1, j] = parse(Int, current_line[1]) - element_node_ids[2, j] = parse(Int, current_line[2]) - element_node_ids[3, j] = parse(Int, current_line[3]) - element_node_ids[4, j] = parse(Int, current_line[4]) - for i in 1:4 - # pull the (x,y) values of these corners out of the nodes array - quad_corners[i, :] .= corner_nodes[:, element_node_ids[i, j]] - end - # pull the information to check if boundary is curved in order to read in additional data - file_idx += 1 - current_line = split(file_lines[file_idx]) - curved_check[1] = parse(Int, current_line[1]) - curved_check[2] = parse(Int, current_line[2]) - curved_check[3] = parse(Int, current_line[3]) - curved_check[4] = parse(Int, current_line[4]) - if sum(curved_check) == 0 - # quadrilateral element is straight sided - element_is_curved[j] = false + for j in 1:n_elements + # pull the corner node IDs + current_line = split(file_lines[file_idx]) + element_node_ids[1, j] = parse(Int, current_line[1]) + element_node_ids[2, j] = parse(Int, current_line[2]) + element_node_ids[3, j] = parse(Int, current_line[3]) + element_node_ids[4, j] = parse(Int, current_line[4]) + for i in 1:4 + # pull the (x,y) values of these corners out of the nodes array + quad_corners[i, :] .= corner_nodes[:, element_node_ids[i, j]] + end + # pull the information to check if boundary is curved in order to read in additional data file_idx += 1 - # read all the boundary names - boundary_names[:, j] = map(Symbol, split(file_lines[file_idx])) - else - # quadrilateral element has at least one curved side - element_is_curved[j] = true + current_line = split(file_lines[file_idx]) + curved_check[1] = parse(Int, current_line[1]) + curved_check[2] = parse(Int, current_line[2]) + curved_check[3] = parse(Int, current_line[3]) + curved_check[4] = parse(Int, current_line[4]) + if sum(curved_check) == 0 + # quadrilateral element is straight sided + element_is_curved[j] = false + file_idx += 1 + # read all the boundary names + boundary_names[:, j] = map(Symbol, split(file_lines[file_idx])) + else + # quadrilateral element has at least one curved side + element_is_curved[j] = true - # flip node ordering to make sure the element is right-handed for the interpolations - m1 = 1 - m2 = 2 - @views quad_corners_flipped[1, :] .= quad_corners[4, :] - @views quad_corners_flipped[2, :] .= quad_corners[2, :] - @views quad_corners_flipped[3, :] .= quad_corners[3, :] - @views quad_corners_flipped[4, :] .= quad_corners[1, :] - for i in 1:4 - if curved_check[i] == 0 - # when curved_check[i] is 0 then the "curve" from corner `i` to corner `i+1` is a - # straight line. So we must construct the interpolant for this line - for k in 1:mesh_nnodes - curve_values[k, 1] = linear_interpolate(cheby_nodes[k], - quad_corners_flipped[m1, - 1], - quad_corners_flipped[m2, - 1]) - curve_values[k, 2] = linear_interpolate(cheby_nodes[k], - quad_corners_flipped[m1, - 2], - quad_corners_flipped[m2, - 2]) + # flip node ordering to make sure the element is right-handed for the interpolations + m1 = 1 + m2 = 2 + @views quad_corners_flipped[1, :] .= quad_corners[4, :] + @views quad_corners_flipped[2, :] .= quad_corners[2, :] + @views quad_corners_flipped[3, :] .= quad_corners[3, :] + @views quad_corners_flipped[4, :] .= quad_corners[1, :] + for i in 1:4 + if curved_check[i] == 0 + # when curved_check[i] is 0 then the "curve" from corner `i` to corner `i+1` is a + # straight line. So we must construct the interpolant for this line + for k in 1:mesh_nnodes + curve_values[k, 1] = linear_interpolate( + cheby_nodes[k], + quad_corners_flipped[ + m1, + 1, + ], + quad_corners_flipped[ + m2, + 1, + ] + ) + curve_values[k, 2] = linear_interpolate( + cheby_nodes[k], + quad_corners_flipped[ + m1, + 2, + ], + quad_corners_flipped[ + m2, + 2, + ] + ) + end + else + # when curved_check[i] is 1 this curved boundary information is supplied by the mesh + # generator. So we just read it into a work array + for k in 1:mesh_nnodes + file_idx += 1 + current_line = split(file_lines[file_idx]) + curve_values[k, 1] = parse(RealT, current_line[1]) + curve_values[k, 2] = parse(RealT, current_line[2]) + end end - else - # when curved_check[i] is 1 this curved boundary information is supplied by the mesh - # generator. So we just read it into a work array - for k in 1:mesh_nnodes - file_idx += 1 - current_line = split(file_lines[file_idx]) - curve_values[k, 1] = parse(RealT, current_line[1]) - curve_values[k, 2] = parse(RealT, current_line[2]) + # construct the curve interpolant for the current side + surface_curves[i, j] = CurvedSurfaceT( + cheby_nodes, bary_weights, + copy(curve_values) + ) + # indexing update that contains a "flip" to ensure correct element orientation + # if we need to construct the straight line "curves" when curved_check[i] == 0 + m1 += 1 + if i == 3 + m2 = 1 + else + m2 += 1 end end - # construct the curve interpolant for the current side - surface_curves[i, j] = CurvedSurfaceT(cheby_nodes, bary_weights, - copy(curve_values)) - # indexing update that contains a "flip" to ensure correct element orientation - # if we need to construct the straight line "curves" when curved_check[i] == 0 - m1 += 1 - if i == 3 - m2 = 1 - else - m2 += 1 - end + # finally read in the boundary names where "---" means an internal connection + file_idx += 1 + boundary_names[:, j] = map(Symbol, split(file_lines[file_idx])) end - # finally read in the boundary names where "---" means an internal connection + # one last increment to the global index to read the next piece of element information file_idx += 1 - boundary_names[:, j] = map(Symbol, split(file_lines[file_idx])) end - # one last increment to the global index to read the next piece of element information - file_idx += 1 - end - return n_boundaries -end + return n_boundaries + end -@inline Base.ndims(::UnstructuredMesh2D) = 2 -@inline Base.real(::UnstructuredMesh2D{RealT}) where {RealT} = RealT + @inline Base.ndims(::UnstructuredMesh2D) = 2 + @inline Base.real(::UnstructuredMesh2D{RealT}) where {RealT} = RealT -# Check if mesh is periodic -isperiodic(mesh::UnstructuredMesh2D) = mesh.periodicity + # Check if mesh is periodic + isperiodic(mesh::UnstructuredMesh2D) = mesh.periodicity -Base.length(mesh::UnstructuredMesh2D) = mesh.n_elements + Base.length(mesh::UnstructuredMesh2D) = mesh.n_elements -function Base.show(io::IO, - ::UnstructuredMesh2D{RealT, CurvedSurfaceT}) where {RealT, - CurvedSurfaceT} - print(io, "UnstructuredMesh2D{2, ", RealT, ", ", CurvedSurfaceT, "}") -end + function Base.show( + io::IO, + ::UnstructuredMesh2D{RealT, CurvedSurfaceT} + ) where { + RealT, + CurvedSurfaceT, + } + print(io, "UnstructuredMesh2D{2, ", RealT, ", ", CurvedSurfaceT, "}") + end -function Base.show(io::IO, ::MIME"text/plain", - mesh::UnstructuredMesh2D{RealT, CurvedSurfaceT}) where {RealT, - CurvedSurfaceT - } - if get(io, :compact, false) - show(io, mesh) - else - summary_header(io, - "UnstructuredMesh2D{" * string(2) * ", " * string(RealT) * ", " * - string(CurvedSurfaceT) * "}") - summary_line(io, "mesh file", mesh.filename) - summary_line(io, "number of elements", length(mesh)) - summary_line(io, "faces", mesh.n_surfaces) - summary_line(io, "mesh polynomial degree", mesh.polydeg) - summary_footer(io) + function Base.show( + io::IO, ::MIME"text/plain", + mesh::UnstructuredMesh2D{RealT, CurvedSurfaceT} + ) where { + RealT, + CurvedSurfaceT, + } + if get(io, :compact, false) + show(io, mesh) + else + summary_header( + io, + "UnstructuredMesh2D{" * string(2) * ", " * string(RealT) * ", " * + string(CurvedSurfaceT) * "}" + ) + summary_line(io, "mesh file", mesh.filename) + summary_line(io, "number of elements", length(mesh)) + summary_line(io, "faces", mesh.n_surfaces) + summary_line(io, "mesh polynomial degree", mesh.polydeg) + summary_footer(io) + end end -end end # @muladd diff --git a/src/semidiscretization/semidiscretization.jl b/src/semidiscretization/semidiscretization.jl index c6b82d5f37..51c77ef34c 100644 --- a/src/semidiscretization/semidiscretization.jl +++ b/src/semidiscretization/semidiscretization.jl @@ -3,425 +3,465 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - ndofs(semi::AbstractSemidiscretization) - -Return the number of degrees of freedom associated with each scalar variable. -""" -@inline function ndofs(semi::AbstractSemidiscretization) - mesh, _, solver, cache = mesh_equations_solver_cache(semi) - ndofs(mesh, solver, cache) -end - -""" - ndofsglobal(semi::AbstractSemidiscretization) - -Return the global number of degrees of freedom associated with each scalar variable across all MPI ranks. -This is the same as [`ndofs`](@ref) for simulations running in serial or -parallelized via threads. It will in general be different for simulations -running in parallel with MPI. -""" -@inline function ndofsglobal(semi::AbstractSemidiscretization) - mesh, _, solver, cache = mesh_equations_solver_cache(semi) - ndofsglobal(mesh, solver, cache) -end - -""" - integrate_via_indices(func, u_ode, semi::AbstractSemidiscretization, args...; normalize=true) - -Call `func(u, i..., element, equations, solver, args...)` for all nodal indices `i..., element` -and integrate the result using a quadrature associated with the semidiscretization `semi`. - -If `normalize` is true, the result is divided by the total volume of the computational domain. -""" -function integrate_via_indices(func::Func, u_ode, semi::AbstractSemidiscretization, - args...; normalize = true) where {Func} - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - - u = wrap_array(u_ode, mesh, equations, solver, cache) - integrate_via_indices(func, u, mesh, equations, solver, cache, args..., - normalize = normalize) -end - -""" - integrate([func=(u_node,equations)->u_node,] u_ode, semi::AbstractSemidiscretization; normalize=true) - -Call `func(u_node, equations)` for each vector of nodal variables `u_node` in `u_ode` -and integrate the result using a quadrature associated with the semidiscretization `semi`. - -If `normalize` is true, the result is divided by the total volume of the computational domain. -""" -function integrate(func::Func, u_ode, semi::AbstractSemidiscretization; - normalize = true) where {Func} - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - - u = wrap_array(u_ode, mesh, equations, solver, cache) - integrate(func, u, mesh, equations, solver, cache, normalize = normalize) -end - -function integrate(u, semi::AbstractSemidiscretization; normalize = true) - integrate(cons2cons, u, semi; normalize = normalize) -end - -""" - calc_error_norms([func=(u_node,equations)->u_node,] u_ode, t, analyzer, semi::AbstractSemidiscretization, cache_analysis) - -Calculate discrete L2 and L∞ error norms of `func` applied to each nodal variable `u_node` in `u_ode`. -If no exact solution is available, "errors" are calculated using some reference state and can be useful -for regression tests. -""" -function calc_error_norms(u_ode, t, analyzer, semi::AbstractSemidiscretization, - cache_analysis) - calc_error_norms(cons2cons, u_ode, t, analyzer, semi, cache_analysis) -end - -""" - semidiscretize(semi::AbstractSemidiscretization, tspan) - -Wrap the semidiscretization `semi` as an ODE problem in the time interval `tspan` -that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/). -""" -function semidiscretize(semi::AbstractSemidiscretization, tspan; - reset_threads = true) - # Optionally reset Polyester.jl threads. See - # https://github.com/trixi-framework/Trixi.jl/issues/1583 - # https://github.com/JuliaSIMD/Polyester.jl/issues/30 - if reset_threads - Polyester.reset_threads!() + #! format: noindent + + """ + ndofs(semi::AbstractSemidiscretization) + + Return the number of degrees of freedom associated with each scalar variable. + """ + @inline function ndofs(semi::AbstractSemidiscretization) + mesh, _, solver, cache = mesh_equations_solver_cache(semi) + ndofs(mesh, solver, cache) + end + + """ + ndofsglobal(semi::AbstractSemidiscretization) + + Return the global number of degrees of freedom associated with each scalar variable across all MPI ranks. + This is the same as [`ndofs`](@ref) for simulations running in serial or + parallelized via threads. It will in general be different for simulations + running in parallel with MPI. + """ + @inline function ndofsglobal(semi::AbstractSemidiscretization) + mesh, _, solver, cache = mesh_equations_solver_cache(semi) + ndofsglobal(mesh, solver, cache) + end + + """ + integrate_via_indices(func, u_ode, semi::AbstractSemidiscretization, args...; normalize=true) + + Call `func(u, i..., element, equations, solver, args...)` for all nodal indices `i..., element` + and integrate the result using a quadrature associated with the semidiscretization `semi`. + + If `normalize` is true, the result is divided by the total volume of the computational domain. + """ + function integrate_via_indices( + func::Func, u_ode, semi::AbstractSemidiscretization, + args...; normalize = true + ) where {Func} + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + + u = wrap_array(u_ode, mesh, equations, solver, cache) + integrate_via_indices( + func, u, mesh, equations, solver, cache, args..., + normalize = normalize + ) + end + + """ + integrate([func=(u_node,equations)->u_node,] u_ode, semi::AbstractSemidiscretization; normalize=true) + + Call `func(u_node, equations)` for each vector of nodal variables `u_node` in `u_ode` + and integrate the result using a quadrature associated with the semidiscretization `semi`. + + If `normalize` is true, the result is divided by the total volume of the computational domain. + """ + function integrate( + func::Func, u_ode, semi::AbstractSemidiscretization; + normalize = true + ) where {Func} + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + + u = wrap_array(u_ode, mesh, equations, solver, cache) + integrate(func, u, mesh, equations, solver, cache, normalize = normalize) + end + + function integrate(u, semi::AbstractSemidiscretization; normalize = true) + integrate(cons2cons, u, semi; normalize = normalize) + end + + """ + calc_error_norms([func=(u_node,equations)->u_node,] u_ode, t, analyzer, semi::AbstractSemidiscretization, cache_analysis) + + Calculate discrete L2 and L∞ error norms of `func` applied to each nodal variable `u_node` in `u_ode`. + If no exact solution is available, "errors" are calculated using some reference state and can be useful + for regression tests. + """ + function calc_error_norms( + u_ode, t, analyzer, semi::AbstractSemidiscretization, + cache_analysis + ) + calc_error_norms(cons2cons, u_ode, t, analyzer, semi, cache_analysis) end - u0_ode = compute_coefficients(first(tspan), semi) - # TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using - # mpi_isparallel() && MPI.Barrier(mpi_comm()) - # See https://github.com/trixi-framework/Trixi.jl/issues/328 - iip = true # is-inplace, i.e., we modify a vector when calling rhs! - specialize = SciMLBase.FullSpecialize # specialize on rhs! and parameters (semi) - return ODEProblem{iip, specialize}(rhs!, u0_ode, tspan, semi) -end - -""" - semidiscretize(semi::AbstractSemidiscretization, tspan, restart_file::AbstractString) - -Wrap the semidiscretization `semi` as an ODE problem in the time interval `tspan` -that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/). -The initial condition etc. is taken from the `restart_file`. -""" -function semidiscretize(semi::AbstractSemidiscretization, tspan, - restart_file::AbstractString; - reset_threads = true) - # Optionally reset Polyester.jl threads. See - # https://github.com/trixi-framework/Trixi.jl/issues/1583 - # https://github.com/JuliaSIMD/Polyester.jl/issues/30 - if reset_threads - Polyester.reset_threads!() + """ + semidiscretize(semi::AbstractSemidiscretization, tspan) + + Wrap the semidiscretization `semi` as an ODE problem in the time interval `tspan` + that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/). + """ + function semidiscretize( + semi::AbstractSemidiscretization, tspan; + reset_threads = true + ) + # Optionally reset Polyester.jl threads. See + # https://github.com/trixi-framework/Trixi.jl/issues/1583 + # https://github.com/JuliaSIMD/Polyester.jl/issues/30 + if reset_threads + Polyester.reset_threads!() + end + + u0_ode = compute_coefficients(first(tspan), semi) + # TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using + # mpi_isparallel() && MPI.Barrier(mpi_comm()) + # See https://github.com/trixi-framework/Trixi.jl/issues/328 + iip = true # is-inplace, i.e., we modify a vector when calling rhs! + specialize = SciMLBase.FullSpecialize # specialize on rhs! and parameters (semi) + return ODEProblem{iip, specialize}(rhs!, u0_ode, tspan, semi) end - u0_ode = load_restart_file(semi, restart_file) - # TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using - # mpi_isparallel() && MPI.Barrier(mpi_comm()) - # See https://github.com/trixi-framework/Trixi.jl/issues/328 - iip = true # is-inplace, i.e., we modify a vector when calling rhs! - specialize = SciMLBase.FullSpecialize # specialize on rhs! and parameters (semi) - return ODEProblem{iip, specialize}(rhs!, u0_ode, tspan, semi) -end - -""" - compute_coefficients(func, t, semi::AbstractSemidiscretization) - -Compute the discrete coefficients of the continuous function `func` at time `t` -associated with the semidiscretization `semi`. -For example, the discrete coefficients of `func` for a discontinuous Galerkin -spectral element method ([`DGSEM`](@ref)) are the values of `func` at the -Lobatto-Legendre nodes. Similarly, a classical finite difference method will use -the values of `func` at the nodes of the grid assoociated with the semidiscretization -`semi`. - -For semidiscretizations `semi` associated with an initial condition, `func` can be omitted -to use the given initial condition at time `t`. -""" -function compute_coefficients(func, t, semi::AbstractSemidiscretization) - u_ode = allocate_coefficients(mesh_equations_solver_cache(semi)...) - # Call `compute_coefficients` defined below - compute_coefficients!(u_ode, func, t, semi) - return u_ode -end - -""" - compute_coefficients!(u_ode, func, t, semi::AbstractSemidiscretization) - -Same as [`compute_coefficients`](@ref) but stores the result in `u_ode`. -""" -function compute_coefficients!(u_ode, func, t, semi::AbstractSemidiscretization) - u = wrap_array(u_ode, semi) - # Call `compute_coefficients` defined by the solver - compute_coefficients!(u, func, t, mesh_equations_solver_cache(semi)...) -end - -""" - linear_structure(semi::AbstractSemidiscretization; - t0=zero(real(semi))) - -Wraps the right-hand side operator of the semidiscretization `semi` -at time `t0` as an affine-linear operator given by a linear operator `A` -and a vector `b`. -""" -function linear_structure(semi::AbstractSemidiscretization; - t0 = zero(real(semi))) - # allocate memory - u_ode = allocate_coefficients(mesh_equations_solver_cache(semi)...) - du_ode = similar(u_ode) - - # get the right hand side from possible source terms - u_ode .= zero(eltype(u_ode)) - rhs!(du_ode, u_ode, semi, t0) - # Create a copy of `b` used internally to extract the linear part of `semi`. - # This is necessary to get everything correct when the users updates the - # returned vector `b`. - b = -du_ode - b_tmp = copy(b) - - # wrap the linear operator - A = LinearMap(length(u_ode), ismutating = true) do dest, src - rhs!(dest, src, semi, t0) - @. dest += b_tmp - dest + """ + semidiscretize(semi::AbstractSemidiscretization, tspan, restart_file::AbstractString) + + Wrap the semidiscretization `semi` as an ODE problem in the time interval `tspan` + that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/). + The initial condition etc. is taken from the `restart_file`. + """ + function semidiscretize( + semi::AbstractSemidiscretization, tspan, + restart_file::AbstractString; + reset_threads = true + ) + # Optionally reset Polyester.jl threads. See + # https://github.com/trixi-framework/Trixi.jl/issues/1583 + # https://github.com/JuliaSIMD/Polyester.jl/issues/30 + if reset_threads + Polyester.reset_threads!() + end + + u0_ode = load_restart_file(semi, restart_file) + # TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using + # mpi_isparallel() && MPI.Barrier(mpi_comm()) + # See https://github.com/trixi-framework/Trixi.jl/issues/328 + iip = true # is-inplace, i.e., we modify a vector when calling rhs! + specialize = SciMLBase.FullSpecialize # specialize on rhs! and parameters (semi) + return ODEProblem{iip, specialize}(rhs!, u0_ode, tspan, semi) + end + + """ + compute_coefficients(func, t, semi::AbstractSemidiscretization) + + Compute the discrete coefficients of the continuous function `func` at time `t` + associated with the semidiscretization `semi`. + For example, the discrete coefficients of `func` for a discontinuous Galerkin + spectral element method ([`DGSEM`](@ref)) are the values of `func` at the + Lobatto-Legendre nodes. Similarly, a classical finite difference method will use + the values of `func` at the nodes of the grid assoociated with the semidiscretization + `semi`. + + For semidiscretizations `semi` associated with an initial condition, `func` can be omitted + to use the given initial condition at time `t`. + """ + function compute_coefficients(func, t, semi::AbstractSemidiscretization) + u_ode = allocate_coefficients(mesh_equations_solver_cache(semi)...) + # Call `compute_coefficients` defined below + compute_coefficients!(u_ode, func, t, semi) + return u_ode + end + + """ + compute_coefficients!(u_ode, func, t, semi::AbstractSemidiscretization) + + Same as [`compute_coefficients`](@ref) but stores the result in `u_ode`. + """ + function compute_coefficients!(u_ode, func, t, semi::AbstractSemidiscretization) + u = wrap_array(u_ode, semi) + # Call `compute_coefficients` defined by the solver + compute_coefficients!(u, func, t, mesh_equations_solver_cache(semi)...) end - return A, b -end - -""" - jacobian_fd(semi::AbstractSemidiscretization; - t0=zero(real(semi)), - u0_ode=compute_coefficients(t0, semi)) - -Uses the right-hand side operator of the semidiscretization `semi` -and simple second order finite difference to compute the Jacobian `J` -of the semidiscretization `semi` at state `u0_ode`. -""" -function jacobian_fd(semi::AbstractSemidiscretization; - t0 = zero(real(semi)), - u0_ode = compute_coefficients(t0, semi)) - # copy the initial state since it will be modified in the following - u_ode = copy(u0_ode) - du0_ode = similar(u_ode) - dup_ode = similar(u_ode) - dum_ode = similar(u_ode) - - # compute residual of linearization state - rhs!(du0_ode, u_ode, semi, t0) - - # initialize Jacobian matrix - J = zeros(eltype(u_ode), length(u_ode), length(u_ode)) - - # use second order finite difference to estimate Jacobian matrix - for idx in eachindex(u0_ode) - # determine size of fluctuation - epsilon = sqrt(eps(u0_ode[idx])) - - # plus fluctuation - u_ode[idx] = u0_ode[idx] + epsilon - rhs!(dup_ode, u_ode, semi, t0) - - # minus fluctuation - u_ode[idx] = u0_ode[idx] - epsilon - rhs!(dum_ode, u_ode, semi, t0) - - # restore linearisation state - u_ode[idx] = u0_ode[idx] - - # central second order finite difference - @. J[:, idx] = (dup_ode - dum_ode) / (2 * epsilon) + """ + linear_structure(semi::AbstractSemidiscretization; + t0=zero(real(semi))) + + Wraps the right-hand side operator of the semidiscretization `semi` + at time `t0` as an affine-linear operator given by a linear operator `A` + and a vector `b`. + """ + function linear_structure( + semi::AbstractSemidiscretization; + t0 = zero(real(semi)) + ) + # allocate memory + u_ode = allocate_coefficients(mesh_equations_solver_cache(semi)...) + du_ode = similar(u_ode) + + # get the right hand side from possible source terms + u_ode .= zero(eltype(u_ode)) + rhs!(du_ode, u_ode, semi, t0) + # Create a copy of `b` used internally to extract the linear part of `semi`. + # This is necessary to get everything correct when the users updates the + # returned vector `b`. + b = -du_ode + b_tmp = copy(b) + + # wrap the linear operator + A = LinearMap(length(u_ode), ismutating = true) do dest, src + rhs!(dest, src, semi, t0) + @. dest += b_tmp + dest + end + + return A, b end - return J -end - -""" - jacobian_ad_forward(semi::AbstractSemidiscretization; - t0=zero(real(semi)), - u0_ode=compute_coefficients(t0, semi)) - -Uses the right-hand side operator of the semidiscretization `semi` -and forward mode automatic differentiation to compute the Jacobian `J` -of the semidiscretization `semi` at state `u0_ode`. -""" -function jacobian_ad_forward(semi::AbstractSemidiscretization; - t0 = zero(real(semi)), - u0_ode = compute_coefficients(t0, semi)) - jacobian_ad_forward(semi, t0, u0_ode) -end - -# The following version is for plain arrays -function jacobian_ad_forward(semi::AbstractSemidiscretization, t0, u0_ode) - du_ode = similar(u0_ode) - config = ForwardDiff.JacobianConfig(nothing, du_ode, u0_ode) - - # Use a function barrier since the generation of the `config` we use above - # is not type-stable - _jacobian_ad_forward(semi, t0, u0_ode, du_ode, config) -end - -function _jacobian_ad_forward(semi, t0, u0_ode, du_ode, config) - new_semi = remake(semi, uEltype = eltype(config)) - # Create anonymous function passed as first argument to `ForwardDiff.jacobian` to match - # `ForwardDiff.jacobian(f!, y::AbstractArray, x::AbstractArray, - # cfg::JacobianConfig = JacobianConfig(f!, y, x), check=Val{true}())` - J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode - Trixi.rhs!(du_ode, u_ode, new_semi, t0) + """ + jacobian_fd(semi::AbstractSemidiscretization; + t0=zero(real(semi)), + u0_ode=compute_coefficients(t0, semi)) + + Uses the right-hand side operator of the semidiscretization `semi` + and simple second order finite difference to compute the Jacobian `J` + of the semidiscretization `semi` at state `u0_ode`. + """ + function jacobian_fd( + semi::AbstractSemidiscretization; + t0 = zero(real(semi)), + u0_ode = compute_coefficients(t0, semi) + ) + # copy the initial state since it will be modified in the following + u_ode = copy(u0_ode) + du0_ode = similar(u_ode) + dup_ode = similar(u_ode) + dum_ode = similar(u_ode) + + # compute residual of linearization state + rhs!(du0_ode, u_ode, semi, t0) + + # initialize Jacobian matrix + J = zeros(eltype(u_ode), length(u_ode), length(u_ode)) + + # use second order finite difference to estimate Jacobian matrix + for idx in eachindex(u0_ode) + # determine size of fluctuation + epsilon = sqrt(eps(u0_ode[idx])) + + # plus fluctuation + u_ode[idx] = u0_ode[idx] + epsilon + rhs!(dup_ode, u_ode, semi, t0) + + # minus fluctuation + u_ode[idx] = u0_ode[idx] - epsilon + rhs!(dum_ode, u_ode, semi, t0) + + # restore linearisation state + u_ode[idx] = u0_ode[idx] + + # central second order finite difference + @. J[:, idx] = (dup_ode - dum_ode) / (2 * epsilon) + end + + return J end - return J -end - -# This version is specialized to `StructArray`s used by some `DGMulti` solvers. -# We need to convert the numerical solution vectors since ForwardDiff cannot -# handle arrays of `SVector`s. -function jacobian_ad_forward(semi::AbstractSemidiscretization, t0, _u0_ode::StructArray) - u0_ode_plain = similar(_u0_ode, eltype(eltype(_u0_ode)), - (size(_u0_ode)..., nvariables(semi))) - for (v, u_v) in enumerate(StructArrays.components(_u0_ode)) - u0_ode_plain[.., v] = u_v + """ + jacobian_ad_forward(semi::AbstractSemidiscretization; + t0=zero(real(semi)), + u0_ode=compute_coefficients(t0, semi)) + + Uses the right-hand side operator of the semidiscretization `semi` + and forward mode automatic differentiation to compute the Jacobian `J` + of the semidiscretization `semi` at state `u0_ode`. + """ + function jacobian_ad_forward( + semi::AbstractSemidiscretization; + t0 = zero(real(semi)), + u0_ode = compute_coefficients(t0, semi) + ) + jacobian_ad_forward(semi, t0, u0_ode) end - du_ode_plain = similar(u0_ode_plain) - config = ForwardDiff.JacobianConfig(nothing, du_ode_plain, u0_ode_plain) - - # Use a function barrier since the generation of the `config` we use above - # is not type-stable - _jacobian_ad_forward_structarrays(semi, t0, u0_ode_plain, du_ode_plain, config) -end - -function _jacobian_ad_forward_structarrays(semi, t0, u0_ode_plain, du_ode_plain, config) - new_semi = remake(semi, uEltype = eltype(config)) - # Create anonymous function passed as first argument to `ForwardDiff.jacobian` to match - # `ForwardDiff.jacobian(f!, y::AbstractArray, x::AbstractArray, - # cfg::JacobianConfig = JacobianConfig(f!, y, x), check=Val{true}())` - J = ForwardDiff.jacobian(du_ode_plain, u0_ode_plain, - config) do du_ode_plain, u_ode_plain - u_ode = StructArray{SVector{nvariables(semi), eltype(config)}}(ntuple(v -> view(u_ode_plain, - :, - :, - v), - nvariables(semi))) - du_ode = StructArray{SVector{nvariables(semi), eltype(config)}}(ntuple(v -> view(du_ode_plain, - :, - :, - v), - nvariables(semi))) - Trixi.rhs!(du_ode, u_ode, new_semi, t0) + + # The following version is for plain arrays + function jacobian_ad_forward(semi::AbstractSemidiscretization, t0, u0_ode) + du_ode = similar(u0_ode) + config = ForwardDiff.JacobianConfig(nothing, du_ode, u0_ode) + + # Use a function barrier since the generation of the `config` we use above + # is not type-stable + _jacobian_ad_forward(semi, t0, u0_ode, du_ode, config) + end + + function _jacobian_ad_forward(semi, t0, u0_ode, du_ode, config) + new_semi = remake(semi, uEltype = eltype(config)) + # Create anonymous function passed as first argument to `ForwardDiff.jacobian` to match + # `ForwardDiff.jacobian(f!, y::AbstractArray, x::AbstractArray, + # cfg::JacobianConfig = JacobianConfig(f!, y, x), check=Val{true}())` + J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode + Trixi.rhs!(du_ode, u_ode, new_semi, t0) + end + + return J + end + + # This version is specialized to `StructArray`s used by some `DGMulti` solvers. + # We need to convert the numerical solution vectors since ForwardDiff cannot + # handle arrays of `SVector`s. + function jacobian_ad_forward(semi::AbstractSemidiscretization, t0, _u0_ode::StructArray) + u0_ode_plain = similar( + _u0_ode, eltype(eltype(_u0_ode)), + (size(_u0_ode)..., nvariables(semi)) + ) + for (v, u_v) in enumerate(StructArrays.components(_u0_ode)) + u0_ode_plain[.., v] = u_v + end + du_ode_plain = similar(u0_ode_plain) + config = ForwardDiff.JacobianConfig(nothing, du_ode_plain, u0_ode_plain) + + # Use a function barrier since the generation of the `config` we use above + # is not type-stable + _jacobian_ad_forward_structarrays(semi, t0, u0_ode_plain, du_ode_plain, config) + end + + function _jacobian_ad_forward_structarrays(semi, t0, u0_ode_plain, du_ode_plain, config) + new_semi = remake(semi, uEltype = eltype(config)) + # Create anonymous function passed as first argument to `ForwardDiff.jacobian` to match + # `ForwardDiff.jacobian(f!, y::AbstractArray, x::AbstractArray, + # cfg::JacobianConfig = JacobianConfig(f!, y, x), check=Val{true}())` + J = ForwardDiff.jacobian( + du_ode_plain, u0_ode_plain, + config + ) do du_ode_plain, u_ode_plain + u_ode = StructArray{SVector{nvariables(semi), eltype(config)}}( + ntuple( + v -> view( + u_ode_plain, + :, + :, + v + ), + nvariables(semi) + ) + ) + du_ode = StructArray{SVector{nvariables(semi), eltype(config)}}( + ntuple( + v -> view( + du_ode_plain, + :, + :, + v + ), + nvariables(semi) + ) + ) + Trixi.rhs!(du_ode, u_ode, new_semi, t0) + end + + return J + end + + # This version is specialized to arrays of `StaticArray`s used by some `DGMulti` solvers. + # We need to convert the numerical solution vectors since ForwardDiff cannot + # handle arrays of `SVector`s. + function jacobian_ad_forward( + semi::AbstractSemidiscretization, t0, + _u0_ode::AbstractArray{<:SVector} + ) + u0_ode_plain = reinterpret(eltype(eltype(_u0_ode)), _u0_ode) + du_ode_plain = similar(u0_ode_plain) + config = ForwardDiff.JacobianConfig(nothing, du_ode_plain, u0_ode_plain) + + # Use a function barrier since the generation of the `config` we use above + # is not type-stable + _jacobian_ad_forward_staticarrays(semi, t0, u0_ode_plain, du_ode_plain, config) + end + + function _jacobian_ad_forward_staticarrays(semi, t0, u0_ode_plain, du_ode_plain, config) + new_semi = remake(semi, uEltype = eltype(config)) + J = ForwardDiff.jacobian( + du_ode_plain, u0_ode_plain, + config + ) do du_ode_plain, u_ode_plain + u_ode = reinterpret(SVector{nvariables(semi), eltype(config)}, u_ode_plain) + du_ode = reinterpret(SVector{nvariables(semi), eltype(config)}, du_ode_plain) + Trixi.rhs!(du_ode, u_ode, new_semi, t0) + end + + return J + end + + # Sometimes, it can be useful to save some (scalar) variables associated with each element, + # e.g. AMR indicators or shock indicators. Since these usually have to be re-computed + # directly before IO and do not necessarily need to be stored in memory before, + # get_element_variables!(element_variables, ..) + # is used to retrieve such up to date element variables, modifying + # `element_variables::Dict{Symbol,Any}` in place. + function get_element_variables!( + element_variables, u_ode, + semi::AbstractSemidiscretization + ) + u = wrap_array(u_ode, semi) + get_element_variables!(element_variables, u, mesh_equations_solver_cache(semi)...) + end + + function get_node_variables!(node_variables, semi::AbstractSemidiscretization) + get_node_variables!(node_variables, mesh_equations_solver_cache(semi)...) + end + + # To implement AMR and use OrdinaryDiffEq.jl etc., we have to be a bit creative. + # Since the caches of the SciML ecosystem are immutable structs, we cannot simply + # change the underlying arrays therein. Hence, to support changing the number of + # DOFs, we need to use `resize!`. In some sense, this will force us to write more + # efficient code, since `resize!` will make use of previously allocated memory + # instead of allocating memory from scratch every time. + # + # However, multidimensional `Array`s don't support `resize!`. One option might be + # to use ElasticArrays.jl. But I don't really like that approach. Needing to use + # ElasticArray doesn't feel completely good to me, since we also want to experiment + # with other array types such as PaddedMatrices.jl, see trixi-framework/Trixi.jl#166. + # Then, we would need to wrap an Array inside something from PaddedMatrices.jl inside + # something from ElasticArrays.jl - or the other way round? Is that possible at all? + # If we go further, this looks like it could easily explode. + # + # Currently, the best option seems to be to let OrdinaryDiffEq.jl use `Vector`s, + # which can be `resize!`ed for AMR. Then, we have to wrap these `Vector`s inside + # Trixi.jl as our favorite multidimensional array type. We need to do this wrapping + # in every method exposed to OrdinaryDiffEq, i.e. in the first levels of things like + # rhs!, AMRCallback, StepsizeCallback, AnalysisCallback, SaveSolutionCallback + # + # This wrapping will also allow us to experiment more easily with additional + # kinds of wrapping, e.g. HybridArrays.jl or PaddedMatrices.jl to inform the + # compiler about the sizes of the first few dimensions in DG methods, i.e. + # nvariables(equations) and nnodes(dg). + # + # In some sense, having plain multidimensional `Array`s not support `resize!` + # isn't necessarily a bug (although it would be nice to add this possibility to + # base Julia) but can turn out to be a feature for us, because it will allow us + # more specializations. + # Since we can use multiple dispatch, these kinds of specializations can be + # tailored specifically to each combinations of mesh/solver etc. + # + # Under the hood, `wrap_array(u_ode, mesh, equations, solver, cache)` might + # (and probably will) use `unsafe_wrap`. Hence, you have to remember to + # `GC.@preserve` temporaries that are only used indirectly via `wrap_array` + # to avoid stochastic memory errors. + # + # Xref https://github.com/SciML/OrdinaryDiffEq.jl/pull/1275 + function wrap_array(u_ode, semi::AbstractSemidiscretization) + wrap_array(u_ode, mesh_equations_solver_cache(semi)...) end - return J -end - -# This version is specialized to arrays of `StaticArray`s used by some `DGMulti` solvers. -# We need to convert the numerical solution vectors since ForwardDiff cannot -# handle arrays of `SVector`s. -function jacobian_ad_forward(semi::AbstractSemidiscretization, t0, - _u0_ode::AbstractArray{<:SVector}) - u0_ode_plain = reinterpret(eltype(eltype(_u0_ode)), _u0_ode) - du_ode_plain = similar(u0_ode_plain) - config = ForwardDiff.JacobianConfig(nothing, du_ode_plain, u0_ode_plain) - - # Use a function barrier since the generation of the `config` we use above - # is not type-stable - _jacobian_ad_forward_staticarrays(semi, t0, u0_ode_plain, du_ode_plain, config) -end - -function _jacobian_ad_forward_staticarrays(semi, t0, u0_ode_plain, du_ode_plain, config) - new_semi = remake(semi, uEltype = eltype(config)) - J = ForwardDiff.jacobian(du_ode_plain, u0_ode_plain, - config) do du_ode_plain, u_ode_plain - u_ode = reinterpret(SVector{nvariables(semi), eltype(config)}, u_ode_plain) - du_ode = reinterpret(SVector{nvariables(semi), eltype(config)}, du_ode_plain) - Trixi.rhs!(du_ode, u_ode, new_semi, t0) + # Like `wrap_array`, but guarantees to return a plain `Array`, which can be better + # for writing solution files etc. + function wrap_array_native(u_ode, semi::AbstractSemidiscretization) + wrap_array_native(u_ode, mesh_equations_solver_cache(semi)...) end - return J -end - -# Sometimes, it can be useful to save some (scalar) variables associated with each element, -# e.g. AMR indicators or shock indicators. Since these usually have to be re-computed -# directly before IO and do not necessarily need to be stored in memory before, -# get_element_variables!(element_variables, ..) -# is used to retrieve such up to date element variables, modifying -# `element_variables::Dict{Symbol,Any}` in place. -function get_element_variables!(element_variables, u_ode, - semi::AbstractSemidiscretization) - u = wrap_array(u_ode, semi) - get_element_variables!(element_variables, u, mesh_equations_solver_cache(semi)...) -end - -function get_node_variables!(node_variables, semi::AbstractSemidiscretization) - get_node_variables!(node_variables, mesh_equations_solver_cache(semi)...) -end - -# To implement AMR and use OrdinaryDiffEq.jl etc., we have to be a bit creative. -# Since the caches of the SciML ecosystem are immutable structs, we cannot simply -# change the underlying arrays therein. Hence, to support changing the number of -# DOFs, we need to use `resize!`. In some sense, this will force us to write more -# efficient code, since `resize!` will make use of previously allocated memory -# instead of allocating memory from scratch every time. -# -# However, multidimensional `Array`s don't support `resize!`. One option might be -# to use ElasticArrays.jl. But I don't really like that approach. Needing to use -# ElasticArray doesn't feel completely good to me, since we also want to experiment -# with other array types such as PaddedMatrices.jl, see trixi-framework/Trixi.jl#166. -# Then, we would need to wrap an Array inside something from PaddedMatrices.jl inside -# something from ElasticArrays.jl - or the other way round? Is that possible at all? -# If we go further, this looks like it could easily explode. -# -# Currently, the best option seems to be to let OrdinaryDiffEq.jl use `Vector`s, -# which can be `resize!`ed for AMR. Then, we have to wrap these `Vector`s inside -# Trixi.jl as our favorite multidimensional array type. We need to do this wrapping -# in every method exposed to OrdinaryDiffEq, i.e. in the first levels of things like -# rhs!, AMRCallback, StepsizeCallback, AnalysisCallback, SaveSolutionCallback -# -# This wrapping will also allow us to experiment more easily with additional -# kinds of wrapping, e.g. HybridArrays.jl or PaddedMatrices.jl to inform the -# compiler about the sizes of the first few dimensions in DG methods, i.e. -# nvariables(equations) and nnodes(dg). -# -# In some sense, having plain multidimensional `Array`s not support `resize!` -# isn't necessarily a bug (although it would be nice to add this possibility to -# base Julia) but can turn out to be a feature for us, because it will allow us -# more specializations. -# Since we can use multiple dispatch, these kinds of specializations can be -# tailored specifically to each combinations of mesh/solver etc. -# -# Under the hood, `wrap_array(u_ode, mesh, equations, solver, cache)` might -# (and probably will) use `unsafe_wrap`. Hence, you have to remember to -# `GC.@preserve` temporaries that are only used indirectly via `wrap_array` -# to avoid stochastic memory errors. -# -# Xref https://github.com/SciML/OrdinaryDiffEq.jl/pull/1275 -function wrap_array(u_ode, semi::AbstractSemidiscretization) - wrap_array(u_ode, mesh_equations_solver_cache(semi)...) -end - -# Like `wrap_array`, but guarantees to return a plain `Array`, which can be better -# for writing solution files etc. -function wrap_array_native(u_ode, semi::AbstractSemidiscretization) - wrap_array_native(u_ode, mesh_equations_solver_cache(semi)...) -end - -# TODO: Taal, document interface? -# New mesh/solver combinations have to implement -# - ndofs(mesh, solver, cache) -# - ndofsgloabal(mesh, solver, cache) -# - ndims(mesh) -# - nnodes(solver) -# - real(solver) -# - create_cache(mesh, equations, solver, RealT) -# - wrap_array(u_ode, mesh, equations, solver, cache) -# - integrate(func, u, mesh, equations, solver, cache; normalize=true) -# - integrate_via_indices(func, u, mesh, equations, solver, cache, args...; normalize=true) -# - calc_error_norms(func, u, t, analyzer, mesh, equations, initial_condition, solver, cache, cache_analysis) -# - allocate_coefficients(mesh, equations, solver, cache) -# - compute_coefficients!(u, func, mesh, equations, solver, cache) -# - rhs!(du, u, t, mesh, equations, initial_condition, boundary_conditions, source_terms, solver, cache) -# + # TODO: Taal, document interface? + # New mesh/solver combinations have to implement + # - ndofs(mesh, solver, cache) + # - ndofsgloabal(mesh, solver, cache) + # - ndims(mesh) + # - nnodes(solver) + # - real(solver) + # - create_cache(mesh, equations, solver, RealT) + # - wrap_array(u_ode, mesh, equations, solver, cache) + # - integrate(func, u, mesh, equations, solver, cache; normalize=true) + # - integrate_via_indices(func, u, mesh, equations, solver, cache, args...; normalize=true) + # - calc_error_norms(func, u, t, analyzer, mesh, equations, initial_condition, solver, cache, cache_analysis) + # - allocate_coefficients(mesh, equations, solver, cache) + # - compute_coefficients!(u, func, mesh, equations, solver, cache) + # - rhs!(du, u, t, mesh, equations, initial_condition, boundary_conditions, source_terms, solver, cache) + # end # @muladd diff --git a/src/semidiscretization/semidiscretization_coupled.jl b/src/semidiscretization/semidiscretization_coupled.jl index 7c1fbef972..fed61fcfa4 100644 --- a/src/semidiscretization/semidiscretization_coupled.jl +++ b/src/semidiscretization/semidiscretization_coupled.jl @@ -3,735 +3,833 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent + + """ + SemidiscretizationCoupled + + A struct used to bundle multiple semidiscretizations. + [`semidiscretize`](@ref) will return an `ODEProblem` that synchronizes time steps between the semidiscretizations. + Each call of `rhs!` will call `rhs!` for each semidiscretization individually. + The semidiscretizations can be coupled by gluing meshes together using [`BoundaryConditionCoupled`](@ref). + + !!! warning "Experimental code" + This is an experimental feature and can change any time. + """ + struct SemidiscretizationCoupled{S, Indices, EquationList} <: AbstractSemidiscretization + semis::S + u_indices::Indices # u_ode[u_indices[i]] is the part of u_ode corresponding to semis[i] + performance_counter::PerformanceCounter + end + + """ + SemidiscretizationCoupled(semis...) -""" - SemidiscretizationCoupled + Create a coupled semidiscretization that consists of the semidiscretizations passed as arguments. + """ + function SemidiscretizationCoupled(semis...) + @assert all(semi -> ndims(semi) == ndims(semis[1]), semis) "All semidiscretizations must have the same dimension!" -A struct used to bundle multiple semidiscretizations. -[`semidiscretize`](@ref) will return an `ODEProblem` that synchronizes time steps between the semidiscretizations. -Each call of `rhs!` will call `rhs!` for each semidiscretization individually. -The semidiscretizations can be coupled by gluing meshes together using [`BoundaryConditionCoupled`](@ref). + # Number of coefficients for each semidiscretization + n_coefficients = zeros(Int, length(semis)) + for i in 1:length(semis) + _, equations, _, _ = mesh_equations_solver_cache(semis[i]) + n_coefficients[i] = ndofs(semis[i]) * nvariables(equations) + end -!!! warning "Experimental code" - This is an experimental feature and can change any time. -""" -struct SemidiscretizationCoupled{S, Indices, EquationList} <: AbstractSemidiscretization - semis::S - u_indices::Indices # u_ode[u_indices[i]] is the part of u_ode corresponding to semis[i] - performance_counter::PerformanceCounter -end + # Compute range of coefficients associated with each semidiscretization and allocate coupled BCs + u_indices = Vector{UnitRange{Int}}(undef, length(semis)) + for i in 1:length(semis) + offset = sum(n_coefficients[1:(i - 1)]) + 1 + u_indices[i] = range(offset, length = n_coefficients[i]) -""" - SemidiscretizationCoupled(semis...) + allocate_coupled_boundary_conditions(semis[i]) + end -Create a coupled semidiscretization that consists of the semidiscretizations passed as arguments. -""" -function SemidiscretizationCoupled(semis...) - @assert all(semi -> ndims(semi) == ndims(semis[1]), semis) "All semidiscretizations must have the same dimension!" + performance_counter = PerformanceCounter() - # Number of coefficients for each semidiscretization - n_coefficients = zeros(Int, length(semis)) - for i in 1:length(semis) - _, equations, _, _ = mesh_equations_solver_cache(semis[i]) - n_coefficients[i] = ndofs(semis[i]) * nvariables(equations) + SemidiscretizationCoupled{ + typeof(semis), typeof(u_indices), + typeof(performance_counter), + }( + semis, u_indices, + performance_counter + ) end - # Compute range of coefficients associated with each semidiscretization and allocate coupled BCs - u_indices = Vector{UnitRange{Int}}(undef, length(semis)) - for i in 1:length(semis) - offset = sum(n_coefficients[1:(i - 1)]) + 1 - u_indices[i] = range(offset, length = n_coefficients[i]) + function Base.show(io::IO, semi::SemidiscretizationCoupled) + @nospecialize semi # reduce precompilation time - allocate_coupled_boundary_conditions(semis[i]) + print(io, "SemidiscretizationCoupled($(semi.semis))") end - performance_counter = PerformanceCounter() + function Base.show(io::IO, ::MIME"text/plain", semi::SemidiscretizationCoupled) + @nospecialize semi # reduce precompilation time + + if get(io, :compact, false) + show(io, semi) + else + summary_header(io, "SemidiscretizationCoupled") + summary_line(io, "#spatial dimensions", ndims(semi.semis[1])) + summary_line(io, "#systems", nsystems(semi)) + for i in eachsystem(semi) + summary_line(io, "system", i) + mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i]) + summary_line(increment_indent(io), "mesh", mesh |> typeof |> nameof) + summary_line( + increment_indent(io), "equations", + equations |> typeof |> nameof + ) + summary_line( + increment_indent(io), "initial condition", + semi.semis[i].initial_condition + ) + # no boundary conditions since that could be too much + summary_line( + increment_indent(io), "source terms", + semi.semis[i].source_terms + ) + summary_line(increment_indent(io), "solver", solver |> typeof |> nameof) + end + summary_line(io, "total #DOFs per field", ndofsglobal(semi)) + summary_footer(io) + end + end - SemidiscretizationCoupled{typeof(semis), typeof(u_indices), - typeof(performance_counter)}(semis, u_indices, - performance_counter) -end + function print_summary_semidiscretization(io::IO, semi::SemidiscretizationCoupled) + show(io, MIME"text/plain"(), semi) + println(io, "\n") + for i in eachsystem(semi) + mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i]) + summary_header(io, "System #$i") -function Base.show(io::IO, semi::SemidiscretizationCoupled) - @nospecialize semi # reduce precompilation time + summary_line(io, "mesh", mesh |> typeof |> nameof) + show(increment_indent(io), MIME"text/plain"(), mesh) - print(io, "SemidiscretizationCoupled($(semi.semis))") -end + summary_line(io, "equations", equations |> typeof |> nameof) + show(increment_indent(io), MIME"text/plain"(), equations) -function Base.show(io::IO, ::MIME"text/plain", semi::SemidiscretizationCoupled) - @nospecialize semi # reduce precompilation time + summary_line(io, "solver", solver |> typeof |> nameof) + show(increment_indent(io), MIME"text/plain"(), solver) - if get(io, :compact, false) - show(io, semi) - else - summary_header(io, "SemidiscretizationCoupled") - summary_line(io, "#spatial dimensions", ndims(semi.semis[1])) - summary_line(io, "#systems", nsystems(semi)) - for i in eachsystem(semi) - summary_line(io, "system", i) - mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i]) - summary_line(increment_indent(io), "mesh", mesh |> typeof |> nameof) - summary_line(increment_indent(io), "equations", - equations |> typeof |> nameof) - summary_line(increment_indent(io), "initial condition", - semi.semis[i].initial_condition) - # no boundary conditions since that could be too much - summary_line(increment_indent(io), "source terms", - semi.semis[i].source_terms) - summary_line(increment_indent(io), "solver", solver |> typeof |> nameof) + summary_footer(io) + println(io, "\n") end - summary_line(io, "total #DOFs per field", ndofsglobal(semi)) - summary_footer(io) end -end -function print_summary_semidiscretization(io::IO, semi::SemidiscretizationCoupled) - show(io, MIME"text/plain"(), semi) - println(io, "\n") - for i in eachsystem(semi) - mesh, equations, solver, _ = mesh_equations_solver_cache(semi.semis[i]) - summary_header(io, "System #$i") + @inline Base.ndims(semi::SemidiscretizationCoupled) = ndims(semi.semis[1]) - summary_line(io, "mesh", mesh |> typeof |> nameof) - show(increment_indent(io), MIME"text/plain"(), mesh) + @inline nsystems(semi::SemidiscretizationCoupled) = length(semi.semis) - summary_line(io, "equations", equations |> typeof |> nameof) - show(increment_indent(io), MIME"text/plain"(), equations) + @inline eachsystem(semi::SemidiscretizationCoupled) = Base.OneTo(nsystems(semi)) - summary_line(io, "solver", solver |> typeof |> nameof) - show(increment_indent(io), MIME"text/plain"(), solver) + @inline Base.real(semi::SemidiscretizationCoupled) = promote_type(real.(semi.semis)...) - summary_footer(io) - println(io, "\n") + @inline function Base.eltype(semi::SemidiscretizationCoupled) + promote_type(eltype.(semi.semis)...) end -end -@inline Base.ndims(semi::SemidiscretizationCoupled) = ndims(semi.semis[1]) + @inline function ndofs(semi::SemidiscretizationCoupled) + sum(ndofs, semi.semis) + end -@inline nsystems(semi::SemidiscretizationCoupled) = length(semi.semis) + """ + ndofsglobal(semi::SemidiscretizationCoupled) + + Return the global number of degrees of freedom associated with each scalar variable across all MPI ranks, and summed up over all coupled systems. + This is the same as [`ndofs`](@ref) for simulations running in serial or + parallelized via threads. It will in general be different for simulations + running in parallel with MPI. + """ + @inline function ndofsglobal(semi::SemidiscretizationCoupled) + sum(ndofsglobal, semi.semis) + end -@inline eachsystem(semi::SemidiscretizationCoupled) = Base.OneTo(nsystems(semi)) + function compute_coefficients(t, semi::SemidiscretizationCoupled) + @unpack u_indices = semi -@inline Base.real(semi::SemidiscretizationCoupled) = promote_type(real.(semi.semis)...) + u_ode = Vector{real(semi)}(undef, u_indices[end][end]) -@inline function Base.eltype(semi::SemidiscretizationCoupled) - promote_type(eltype.(semi.semis)...) -end + for i in eachsystem(semi) + # Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl` + u_ode[u_indices[i]] .= compute_coefficients(t, semi.semis[i]) + end -@inline function ndofs(semi::SemidiscretizationCoupled) - sum(ndofs, semi.semis) -end + return u_ode + end -""" - ndofsglobal(semi::SemidiscretizationCoupled) + @inline function get_system_u_ode(u_ode, index, semi::SemidiscretizationCoupled) + @view u_ode[semi.u_indices[index]] + end -Return the global number of degrees of freedom associated with each scalar variable across all MPI ranks, and summed up over all coupled systems. -This is the same as [`ndofs`](@ref) for simulations running in serial or -parallelized via threads. It will in general be different for simulations -running in parallel with MPI. -""" -@inline function ndofsglobal(semi::SemidiscretizationCoupled) - sum(ndofsglobal, semi.semis) -end + # Same as `foreach(enumerate(something))`, but without allocations. + # + # Note that compile times may increase if this is used with big tuples. + @inline foreach_enumerate(func, collection) = foreach_enumerate(func, collection, 1) + @inline foreach_enumerate(func, collection::Tuple{}, index) = nothing -function compute_coefficients(t, semi::SemidiscretizationCoupled) - @unpack u_indices = semi + @inline function foreach_enumerate(func, collection, index) + element = first(collection) + remaining_collection = Base.tail(collection) - u_ode = Vector{real(semi)}(undef, u_indices[end][end]) + func((index, element)) - for i in eachsystem(semi) - # Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl` - u_ode[u_indices[i]] .= compute_coefficients(t, semi.semis[i]) + # Process remaining collection + foreach_enumerate(func, remaining_collection, index + 1) end - return u_ode -end + function rhs!(du_ode, u_ode, semi::SemidiscretizationCoupled, t) + @unpack u_indices = semi + + time_start = time_ns() -@inline function get_system_u_ode(u_ode, index, semi::SemidiscretizationCoupled) - @view u_ode[semi.u_indices[index]] -end + @trixi_timeit timer() "copy to coupled boundaries" begin + foreach(semi.semis) do semi_ + copy_to_coupled_boundary!(semi_.boundary_conditions, u_ode, semi, semi_) + end + end -# Same as `foreach(enumerate(something))`, but without allocations. -# -# Note that compile times may increase if this is used with big tuples. -@inline foreach_enumerate(func, collection) = foreach_enumerate(func, collection, 1) -@inline foreach_enumerate(func, collection::Tuple{}, index) = nothing + # Call rhs! for each semidiscretization + foreach_enumerate(semi.semis) do (i, semi_) + u_loc = get_system_u_ode(u_ode, i, semi) + du_loc = get_system_u_ode(du_ode, i, semi) + rhs!(du_loc, u_loc, semi_, t) + end -@inline function foreach_enumerate(func, collection, index) - element = first(collection) - remaining_collection = Base.tail(collection) + runtime = time_ns() - time_start + put!(semi.performance_counter, runtime) - func((index, element)) + return nothing + end - # Process remaining collection - foreach_enumerate(func, remaining_collection, index + 1) -end + ################################################################################ + ### AnalysisCallback + ################################################################################ -function rhs!(du_ode, u_ode, semi::SemidiscretizationCoupled, t) - @unpack u_indices = semi + """ + AnalysisCallbackCoupled(semi, callbacks...) - time_start = time_ns() + Combine multiple analysis callbacks for coupled simulations with a + [`SemidiscretizationCoupled`](@ref). For each coupled system, an indididual + [`AnalysisCallback`](@ref) **must** be created and passed to the `AnalysisCallbackCoupled` **in + order**, i.e., in the same sequence as the indidvidual semidiscretizations are stored in the + `SemidiscretizationCoupled`. - @trixi_timeit timer() "copy to coupled boundaries" begin - foreach(semi.semis) do semi_ - copy_to_coupled_boundary!(semi_.boundary_conditions, u_ode, semi, semi_) - end + !!! warning "Experimental code" + This is an experimental feature and can change any time. + """ + struct AnalysisCallbackCoupled{CB} + callbacks::CB end - # Call rhs! for each semidiscretization - foreach_enumerate(semi.semis) do (i, semi_) - u_loc = get_system_u_ode(u_ode, i, semi) - du_loc = get_system_u_ode(du_ode, i, semi) - rhs!(du_loc, u_loc, semi_, t) + function Base.show( + io::IO, ::MIME"text/plain", + cb_coupled::DiscreteCallback{<:Any, <:AnalysisCallbackCoupled} + ) + @nospecialize cb_coupled # reduce precompilation time + + if get(io, :compact, false) + show(io, cb_coupled) + else + analysis_callback_coupled = cb_coupled.affect! + + summary_header(io, "AnalysisCallbackCoupled") + for (i, cb) in enumerate(analysis_callback_coupled.callbacks) + summary_line(io, "Callback #$i", "") + show(increment_indent(io), MIME"text/plain"(), cb) + end + summary_footer(io) + end end - runtime = time_ns() - time_start - put!(semi.performance_counter, runtime) - - return nothing -end + # Convenience constructor for the coupled callback that gets called directly from the elixirs + function AnalysisCallbackCoupled(semi_coupled, callbacks...) + if length(callbacks) != nsystems(semi_coupled) + error("an AnalysisCallbackCoupled requires one AnalysisCallback for each semidiscretization") + end -################################################################################ -### AnalysisCallback -################################################################################ + analysis_callback_coupled = AnalysisCallbackCoupled{typeof(callbacks)}(callbacks) -""" - AnalysisCallbackCoupled(semi, callbacks...) + # This callback is triggered if any of its subsidiary callbacks' condition is triggered + condition = (u, t, integrator) -> any(callbacks) do callback + callback.condition(u, t, integrator) + end -Combine multiple analysis callbacks for coupled simulations with a -[`SemidiscretizationCoupled`](@ref). For each coupled system, an indididual -[`AnalysisCallback`](@ref) **must** be created and passed to the `AnalysisCallbackCoupled` **in -order**, i.e., in the same sequence as the indidvidual semidiscretizations are stored in the -`SemidiscretizationCoupled`. + DiscreteCallback( + condition, analysis_callback_coupled, + save_positions = (false, false), + initialize = initialize! + ) + end -!!! warning "Experimental code" - This is an experimental feature and can change any time. -""" -struct AnalysisCallbackCoupled{CB} - callbacks::CB -end + # This method gets called during initialization from OrdinaryDiffEq's `solve(...)` + function initialize!( + cb_coupled::DiscreteCallback{Condition, Affect!}, u_ode_coupled, t, + integrator + ) where {Condition, Affect! <: AnalysisCallbackCoupled} + analysis_callback_coupled = cb_coupled.affect! + semi_coupled = integrator.p + du_ode_coupled = first(get_tmp_cache(integrator)) + + # Loop over coupled systems' callbacks and initialize them individually + for i in eachsystem(semi_coupled) + cb = analysis_callback_coupled.callbacks[i] + semi = semi_coupled.semis[i] + u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled) + du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled) + initialize!(cb, u_ode, du_ode, t, integrator, semi) + end + end -function Base.show(io::IO, ::MIME"text/plain", - cb_coupled::DiscreteCallback{<:Any, <:AnalysisCallbackCoupled}) - @nospecialize cb_coupled # reduce precompilation time + # This method gets called from OrdinaryDiffEq's `solve(...)` + function (analysis_callback_coupled::AnalysisCallbackCoupled)(integrator) + semi_coupled = integrator.p + u_ode_coupled = integrator.u + du_ode_coupled = first(get_tmp_cache(integrator)) + + # Loop over coupled systems' callbacks and call them individually + for i in eachsystem(semi_coupled) + @unpack condition = analysis_callback_coupled.callbacks[i] + analysis_callback = analysis_callback_coupled.callbacks[i].affect! + u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled) + + # Check condition and skip callback if it is not yet its turn + if !condition(u_ode, integrator.t, integrator) + continue + end - if get(io, :compact, false) - show(io, cb_coupled) - else - analysis_callback_coupled = cb_coupled.affect! + semi = semi_coupled.semis[i] + du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled) + analysis_callback(u_ode, du_ode, integrator, semi) + end + end - summary_header(io, "AnalysisCallbackCoupled") - for (i, cb) in enumerate(analysis_callback_coupled.callbacks) - summary_line(io, "Callback #$i", "") - show(increment_indent(io), MIME"text/plain"(), cb) + # used for error checks and EOC analysis + function (cb::DiscreteCallback{Condition, Affect!})(sol) where { + Condition, + Affect! <: + AnalysisCallbackCoupled, + } + semi_coupled = sol.prob.p + u_ode_coupled = sol.u[end] + @unpack callbacks = cb.affect! + + uEltype = real(semi_coupled) + l2_error_collection = uEltype[] + linf_error_collection = uEltype[] + for i in eachsystem(semi_coupled) + analysis_callback = callbacks[i].affect! + @unpack analyzer = analysis_callback + cache_analysis = analysis_callback.cache + + semi = semi_coupled.semis[i] + u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled) + + l2_error, linf_error = calc_error_norms( + u_ode, sol.t[end], analyzer, semi, + cache_analysis + ) + append!(l2_error_collection, l2_error) + append!(linf_error_collection, linf_error) end - summary_footer(io) + + (; l2 = l2_error_collection, linf = linf_error_collection) end -end -# Convenience constructor for the coupled callback that gets called directly from the elixirs -function AnalysisCallbackCoupled(semi_coupled, callbacks...) - if length(callbacks) != nsystems(semi_coupled) - error("an AnalysisCallbackCoupled requires one AnalysisCallback for each semidiscretization") + ################################################################################ + ### SaveSolutionCallback + ################################################################################ + + # Save mesh for a coupled semidiscretization, which contains multiple meshes internally + function save_mesh(semi::SemidiscretizationCoupled, output_directory, timestep = 0) + for i in eachsystem(semi) + mesh, _, _, _ = mesh_equations_solver_cache(semi.semis[i]) + + if mesh.unsaved_changes + mesh.current_filename = save_mesh_file( + mesh, output_directory; system = i, + timestep = timestep + ) + mesh.unsaved_changes = false + end + end end - analysis_callback_coupled = AnalysisCallbackCoupled{typeof(callbacks)}(callbacks) + @inline function save_solution_file( + semi::SemidiscretizationCoupled, u_ode, + solution_callback, + integrator + ) + @unpack semis = semi - # This callback is triggered if any of its subsidiary callbacks' condition is triggered - condition = (u, t, integrator) -> any(callbacks) do callback - callback.condition(u, t, integrator) + for i in eachsystem(semi) + u_ode_slice = get_system_u_ode(u_ode, i, semi) + save_solution_file( + semis[i], u_ode_slice, solution_callback, integrator, + system = i + ) + end end - DiscreteCallback(condition, analysis_callback_coupled, - save_positions = (false, false), - initialize = initialize!) -end + ################################################################################ + ### StepsizeCallback + ################################################################################ -# This method gets called during initialization from OrdinaryDiffEq's `solve(...)` -function initialize!(cb_coupled::DiscreteCallback{Condition, Affect!}, u_ode_coupled, t, - integrator) where {Condition, Affect! <: AnalysisCallbackCoupled} - analysis_callback_coupled = cb_coupled.affect! - semi_coupled = integrator.p - du_ode_coupled = first(get_tmp_cache(integrator)) + # In case of coupled system, use minimum timestep over all systems + function calculate_dt(u_ode, t, cfl_number, semi::SemidiscretizationCoupled) + dt = minimum(eachsystem(semi)) do i + u_ode_slice = get_system_u_ode(u_ode, i, semi) + calculate_dt(u_ode_slice, t, cfl_number, semi.semis[i]) + end - # Loop over coupled systems' callbacks and initialize them individually - for i in eachsystem(semi_coupled) - cb = analysis_callback_coupled.callbacks[i] - semi = semi_coupled.semis[i] - u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled) - du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled) - initialize!(cb, u_ode, du_ode, t, integrator, semi) + return dt end -end -# This method gets called from OrdinaryDiffEq's `solve(...)` -function (analysis_callback_coupled::AnalysisCallbackCoupled)(integrator) - semi_coupled = integrator.p - u_ode_coupled = integrator.u - du_ode_coupled = first(get_tmp_cache(integrator)) + function update_cleaning_speed!( + semi_coupled::SemidiscretizationCoupled, + glm_speed_callback, dt + ) + @unpack glm_scale, cfl, semi_indices = glm_speed_callback - # Loop over coupled systems' callbacks and call them individually - for i in eachsystem(semi_coupled) - @unpack condition = analysis_callback_coupled.callbacks[i] - analysis_callback = analysis_callback_coupled.callbacks[i].affect! - u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled) + if length(semi_indices) == 0 + throw("Since you have more than one semidiscretization you need to specify the 'semi_indices' for which the GLM speed needs to be calculated.") + end - # Check condition and skip callback if it is not yet its turn - if !condition(u_ode, integrator.t, integrator) - continue + # Check that all MHD semidiscretizations received a GLM cleaning speed update. + for (semi_index, semi) in enumerate(semi_coupled.semis) + if ( + typeof(semi.equations) <: AbstractIdealGlmMhdEquations && + !(semi_index in semi_indices) + ) + error("Equation of semidiscretization $semi_index needs to be included in 'semi_indices' of 'GlmSpeedCallback'.") + end end - semi = semi_coupled.semis[i] - du_ode = get_system_u_ode(du_ode_coupled, i, semi_coupled) - analysis_callback(u_ode, du_ode, integrator, semi) - end -end - -# used for error checks and EOC analysis -function (cb::DiscreteCallback{Condition, Affect!})(sol) where {Condition, - Affect! <: - AnalysisCallbackCoupled} - semi_coupled = sol.prob.p - u_ode_coupled = sol.u[end] - @unpack callbacks = cb.affect! - - uEltype = real(semi_coupled) - l2_error_collection = uEltype[] - linf_error_collection = uEltype[] - for i in eachsystem(semi_coupled) - analysis_callback = callbacks[i].affect! - @unpack analyzer = analysis_callback - cache_analysis = analysis_callback.cache - - semi = semi_coupled.semis[i] - u_ode = get_system_u_ode(u_ode_coupled, i, semi_coupled) - - l2_error, linf_error = calc_error_norms(u_ode, sol.t[end], analyzer, semi, - cache_analysis) - append!(l2_error_collection, l2_error) - append!(linf_error_collection, linf_error) - end - - (; l2 = l2_error_collection, linf = linf_error_collection) -end - -################################################################################ -### SaveSolutionCallback -################################################################################ - -# Save mesh for a coupled semidiscretization, which contains multiple meshes internally -function save_mesh(semi::SemidiscretizationCoupled, output_directory, timestep = 0) - for i in eachsystem(semi) - mesh, _, _, _ = mesh_equations_solver_cache(semi.semis[i]) - - if mesh.unsaved_changes - mesh.current_filename = save_mesh_file(mesh, output_directory; system = i, - timestep = timestep) - mesh.unsaved_changes = false + for semi_index in semi_indices + semi = semi_coupled.semis[semi_index] + mesh, equations, solver, cache = mesh_equations_solver_cache(semi) + + # compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR) + c_h_deltat = calc_dt_for_cleaning_speed(cfl, mesh, equations, solver, cache) + + # c_h is proportional to its own time step divided by the complete MHD time step + equations.c_h = glm_scale * c_h_deltat / dt end + + return semi_coupled end -end -@inline function save_solution_file(semi::SemidiscretizationCoupled, u_ode, - solution_callback, - integrator) - @unpack semis = semi + ################################################################################ + ### Equations + ################################################################################ + + """ + BoundaryConditionCoupled(other_semi_index, indices, uEltype, coupling_converter) + + Boundary condition to glue two meshes together. Solution values at the boundary + of another mesh will be used as boundary values. This requires the use + of [`SemidiscretizationCoupled`](@ref). The other mesh is specified by `other_semi_index`, + which is the index of the mesh in the tuple of semidiscretizations. + + Note that the elements and nodes of the two meshes at the coupled boundary must coincide. + This is currently only implemented for [`StructuredMesh`](@ref). + + # Arguments + - `other_semi_index`: the index in `SemidiscretizationCoupled` of the semidiscretization + from which the values are copied + - `indices::Tuple`: node/cell indices at the boundary of the mesh in the other + semidiscretization. See examples below. + - `uEltype::Type`: element type of solution + - `coupling_converter::CouplingConverter`: function to call for converting the solution + state of one system to the other system + + # Examples + ```julia + # Connect the left boundary of mesh 2 to our boundary such that our positive + # boundary direction will match the positive y direction of the other boundary + BoundaryConditionCoupled(2, (:begin, :i), Float64, fun) + + # Connect the same two boundaries oppositely oriented + BoundaryConditionCoupled(2, (:begin, :i_backwards), Float64, fun) + + # Using this as y_neg boundary will connect `our_cells[i, 1, j]` to `other_cells[j, end-i, end]` + BoundaryConditionCoupled(2, (:j, :i_backwards, :end), Float64, fun) + ``` + + !!! warning "Experimental code" + This is an experimental feature and can change any time. + """ + mutable struct BoundaryConditionCoupled{ + NDIMS, + # Store the other semi index as type parameter, + # so that retrieving the other semidiscretization + # is type-stable. + # x-ref: https://github.com/trixi-framework/Trixi.jl/pull/1979 + other_semi_index, NDIMST2M1, + uEltype <: Real, Indices, CouplingConverter, + } + # NDIMST2M1 == NDIMS * 2 - 1 + # Buffer for boundary values: [variable, nodes_i, nodes_j, cell_i, cell_j] + u_boundary::Array{uEltype, NDIMST2M1} # NDIMS * 2 - 1 + other_orientation::Int + indices::Indices + coupling_converter::CouplingConverter + + function BoundaryConditionCoupled( + other_semi_index, indices, uEltype, + coupling_converter + ) + NDIMS = length(indices) + u_boundary = Array{uEltype, NDIMS * 2 - 1}(undef, ntuple(_ -> 0, NDIMS * 2 - 1)) + + if indices[1] in (:begin, :end) + other_orientation = 1 + elseif indices[2] in (:begin, :end) + other_orientation = 2 + else # indices[3] in (:begin, :end) + other_orientation = 3 + end - for i in eachsystem(semi) - u_ode_slice = get_system_u_ode(u_ode, i, semi) - save_solution_file(semis[i], u_ode_slice, solution_callback, integrator, - system = i) + new{ + NDIMS, other_semi_index, NDIMS * 2 - 1, uEltype, typeof(indices), + typeof(coupling_converter), + }( + u_boundary, + other_orientation, + indices, coupling_converter + ) + end + end + + function Base.eltype(boundary_condition::BoundaryConditionCoupled) + eltype(boundary_condition.u_boundary) end -end -################################################################################ -### StepsizeCallback -################################################################################ + function (boundary_condition::BoundaryConditionCoupled)( + u_inner, orientation, direction, + cell_indices, + surface_node_indices, + surface_flux_function, + equations + ) + # get_node_vars(boundary_condition.u_boundary, equations, solver, surface_node_indices..., cell_indices...), + # but we don't have a solver here + u_boundary = SVector( + ntuple( + v -> boundary_condition.u_boundary[ + v, + surface_node_indices..., + cell_indices..., + ], + Val(nvariables(equations)) + ) + ) + + # Calculate boundary flux + if surface_flux_function isa Tuple + # In case of conservative (index 1) and non-conservative (index 2) fluxes, + # add the non-conservative one with a factor of 1/2. + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = ( + surface_flux_function[1]( + u_inner, u_boundary, orientation, + equations + ) + + 0.5f0 * + surface_flux_function[2]( + u_inner, u_boundary, orientation, + equations + ) + ) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = ( + surface_flux_function[1]( + u_boundary, u_inner, orientation, + equations + ) + + 0.5f0 * + surface_flux_function[2]( + u_boundary, u_inner, orientation, + equations + ) + ) + end + else + if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary + flux = surface_flux_function(u_inner, u_boundary, orientation, equations) + else # u_boundary is "left" of boundary, u_inner is "right" of boundary + flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + end + end -# In case of coupled system, use minimum timestep over all systems -function calculate_dt(u_ode, t, cfl_number, semi::SemidiscretizationCoupled) - dt = minimum(eachsystem(semi)) do i - u_ode_slice = get_system_u_ode(u_ode, i, semi) - calculate_dt(u_ode_slice, t, cfl_number, semi.semis[i]) + return flux end - return dt -end + function allocate_coupled_boundary_conditions(semi::AbstractSemidiscretization) + n_boundaries = 2 * ndims(semi) + mesh, equations, solver, _ = mesh_equations_solver_cache(semi) -function update_cleaning_speed!(semi_coupled::SemidiscretizationCoupled, - glm_speed_callback, dt) - @unpack glm_scale, cfl, semi_indices = glm_speed_callback + for direction in 1:n_boundaries + boundary_condition = semi.boundary_conditions[direction] - if length(semi_indices) == 0 - throw("Since you have more than one semidiscretization you need to specify the 'semi_indices' for which the GLM speed needs to be calculated.") + allocate_coupled_boundary_condition( + boundary_condition, direction, mesh, + equations, + solver + ) + end end - # Check that all MHD semidiscretizations received a GLM cleaning speed update. - for (semi_index, semi) in enumerate(semi_coupled.semis) - if (typeof(semi.equations) <: AbstractIdealGlmMhdEquations && - !(semi_index in semi_indices)) - error("Equation of semidiscretization $semi_index needs to be included in 'semi_indices' of 'GlmSpeedCallback'.") + # Don't do anything for other BCs than BoundaryConditionCoupled + function allocate_coupled_boundary_condition( + boundary_condition, direction, mesh, + equations, + solver + ) + return nothing + end + + # In 2D + function allocate_coupled_boundary_condition( + boundary_condition::BoundaryConditionCoupled{2}, + direction, mesh, equations, dg::DGSEM + ) + if direction in (1, 2) + cell_size = size(mesh, 2) + else + cell_size = size(mesh, 1) end + + uEltype = eltype(boundary_condition) + boundary_condition.u_boundary = Array{uEltype, 3}( + undef, nvariables(equations), + nnodes(dg), + cell_size + ) end - for semi_index in semi_indices - semi = semi_coupled.semis[semi_index] - mesh, equations, solver, cache = mesh_equations_solver_cache(semi) - - # compute time step for GLM linear advection equation with c_h=1 (redone due to the possible AMR) - c_h_deltat = calc_dt_for_cleaning_speed(cfl, mesh, equations, solver, cache) - - # c_h is proportional to its own time step divided by the complete MHD time step - equations.c_h = glm_scale * c_h_deltat / dt - end - - return semi_coupled -end - -################################################################################ -### Equations -################################################################################ - -""" - BoundaryConditionCoupled(other_semi_index, indices, uEltype, coupling_converter) - -Boundary condition to glue two meshes together. Solution values at the boundary -of another mesh will be used as boundary values. This requires the use -of [`SemidiscretizationCoupled`](@ref). The other mesh is specified by `other_semi_index`, -which is the index of the mesh in the tuple of semidiscretizations. - -Note that the elements and nodes of the two meshes at the coupled boundary must coincide. -This is currently only implemented for [`StructuredMesh`](@ref). - -# Arguments -- `other_semi_index`: the index in `SemidiscretizationCoupled` of the semidiscretization - from which the values are copied -- `indices::Tuple`: node/cell indices at the boundary of the mesh in the other - semidiscretization. See examples below. -- `uEltype::Type`: element type of solution -- `coupling_converter::CouplingConverter`: function to call for converting the solution - state of one system to the other system - -# Examples -```julia -# Connect the left boundary of mesh 2 to our boundary such that our positive -# boundary direction will match the positive y direction of the other boundary -BoundaryConditionCoupled(2, (:begin, :i), Float64, fun) - -# Connect the same two boundaries oppositely oriented -BoundaryConditionCoupled(2, (:begin, :i_backwards), Float64, fun) - -# Using this as y_neg boundary will connect `our_cells[i, 1, j]` to `other_cells[j, end-i, end]` -BoundaryConditionCoupled(2, (:j, :i_backwards, :end), Float64, fun) -``` - -!!! warning "Experimental code" - This is an experimental feature and can change any time. -""" -mutable struct BoundaryConditionCoupled{NDIMS, - # Store the other semi index as type parameter, - # so that retrieving the other semidiscretization - # is type-stable. - # x-ref: https://github.com/trixi-framework/Trixi.jl/pull/1979 - other_semi_index, NDIMST2M1, - uEltype <: Real, Indices, CouplingConverter} - # NDIMST2M1 == NDIMS * 2 - 1 - # Buffer for boundary values: [variable, nodes_i, nodes_j, cell_i, cell_j] - u_boundary :: Array{uEltype, NDIMST2M1} # NDIMS * 2 - 1 - other_orientation :: Int - indices :: Indices - coupling_converter :: CouplingConverter - - function BoundaryConditionCoupled(other_semi_index, indices, uEltype, - coupling_converter) - NDIMS = length(indices) - u_boundary = Array{uEltype, NDIMS * 2 - 1}(undef, ntuple(_ -> 0, NDIMS * 2 - 1)) - - if indices[1] in (:begin, :end) - other_orientation = 1 - elseif indices[2] in (:begin, :end) - other_orientation = 2 - else # indices[3] in (:begin, :end) - other_orientation = 3 + # Don't do anything for other BCs than BoundaryConditionCoupled + function copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi) + return nothing + end + + function copy_to_coupled_boundary!( + u_ode, semi_coupled, semi, i, n_boundaries, + boundary_condition, boundary_conditions... + ) + copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi) + if i < n_boundaries + copy_to_coupled_boundary!( + u_ode, semi_coupled, semi, i + 1, n_boundaries, + boundary_conditions... + ) end + end + + function copy_to_coupled_boundary!( + boundary_conditions::Union{Tuple, NamedTuple}, u_ode, + semi_coupled, semi + ) + copy_to_coupled_boundary!( + u_ode, semi_coupled, semi, 1, length(boundary_conditions), + boundary_conditions... + ) + end - new{NDIMS, other_semi_index, NDIMS * 2 - 1, uEltype, typeof(indices), - typeof(coupling_converter)}(u_boundary, - other_orientation, - indices, coupling_converter) - end -end - -function Base.eltype(boundary_condition::BoundaryConditionCoupled) - eltype(boundary_condition.u_boundary) -end - -function (boundary_condition::BoundaryConditionCoupled)(u_inner, orientation, direction, - cell_indices, - surface_node_indices, - surface_flux_function, - equations) - # get_node_vars(boundary_condition.u_boundary, equations, solver, surface_node_indices..., cell_indices...), - # but we don't have a solver here - u_boundary = SVector(ntuple(v -> boundary_condition.u_boundary[v, - surface_node_indices..., - cell_indices...], - Val(nvariables(equations)))) - - # Calculate boundary flux - if surface_flux_function isa Tuple - # In case of conservative (index 1) and non-conservative (index 2) fluxes, - # add the non-conservative one with a factor of 1/2. - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = (surface_flux_function[1](u_inner, u_boundary, orientation, - equations) + - 0.5f0 * - surface_flux_function[2](u_inner, u_boundary, orientation, - equations)) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = (surface_flux_function[1](u_boundary, u_inner, orientation, - equations) + - 0.5f0 * - surface_flux_function[2](u_boundary, u_inner, orientation, - equations)) + # In 2D + function copy_to_coupled_boundary!( + boundary_condition::BoundaryConditionCoupled{ + 2, + other_semi_index, + }, + u_ode, semi_coupled, semi + ) where {other_semi_index} + @unpack u_indices = semi_coupled + @unpack other_orientation, indices = boundary_condition + @unpack coupling_converter, u_boundary = boundary_condition + + mesh_own, equations_own, solver_own, cache_own = mesh_equations_solver_cache(semi) + other_semi = semi_coupled.semis[other_semi_index] + mesh_other, equations_other, solver_other, cache_other = mesh_equations_solver_cache(other_semi) + + node_coordinates_other = cache_other.elements.node_coordinates + u_ode_other = get_system_u_ode(u_ode, other_semi_index, semi_coupled) + u_other = wrap_array( + u_ode_other, mesh_other, equations_other, solver_other, + cache_other + ) + + linear_indices = LinearIndices(size(mesh_other)) + + if other_orientation == 1 + cells = axes(mesh_other, 2) + else # other_orientation == 2 + cells = axes(mesh_other, 1) end - else - if iseven(direction) # u_inner is "left" of boundary, u_boundary is "right" of boundary - flux = surface_flux_function(u_inner, u_boundary, orientation, equations) - else # u_boundary is "left" of boundary, u_inner is "right" of boundary - flux = surface_flux_function(u_boundary, u_inner, orientation, equations) + + # Copy solution data to the coupled boundary using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + node_index_range = eachnode(solver_other) + i_node_start, i_node_step = index_to_start_step_2d(indices[1], node_index_range) + j_node_start, j_node_step = index_to_start_step_2d(indices[2], node_index_range) + + i_cell_start, i_cell_step = index_to_start_step_2d(indices[1], axes(mesh_other, 1)) + j_cell_start, j_cell_step = index_to_start_step_2d(indices[2], axes(mesh_other, 2)) + + # We need indices starting at 1 for the handling of `i_cell` etc. + Base.require_one_based_indexing(cells) + + @threaded for i in eachindex(cells) + cell = cells[i] + i_cell = i_cell_start + (i - 1) * i_cell_step + j_cell = j_cell_start + (i - 1) * j_cell_step + + i_node = i_node_start + j_node = j_node_start + element_id = linear_indices[i_cell, j_cell] + + for element_id in eachnode(solver_other) + x_other = get_node_coords( + node_coordinates_other, equations_other, + solver_other, + i_node, j_node, linear_indices[i_cell, j_cell] + ) + u_node_other = get_node_vars( + u_other, equations_other, solver_other, i_node, + j_node, linear_indices[i_cell, j_cell] + ) + u_node_converted = coupling_converter( + x_other, u_node_other, + equations_other, + equations_own + ) + + for i in eachindex(u_node_converted) + u_boundary[i, element_id, cell] = u_node_converted[i] + end + + i_node += i_node_step + j_node += j_node_step + end end end - return flux -end - -function allocate_coupled_boundary_conditions(semi::AbstractSemidiscretization) - n_boundaries = 2 * ndims(semi) - mesh, equations, solver, _ = mesh_equations_solver_cache(semi) - - for direction in 1:n_boundaries - boundary_condition = semi.boundary_conditions[direction] - - allocate_coupled_boundary_condition(boundary_condition, direction, mesh, - equations, - solver) - end -end - -# Don't do anything for other BCs than BoundaryConditionCoupled -function allocate_coupled_boundary_condition(boundary_condition, direction, mesh, - equations, - solver) - return nothing -end - -# In 2D -function allocate_coupled_boundary_condition(boundary_condition::BoundaryConditionCoupled{2}, - direction, mesh, equations, dg::DGSEM) - if direction in (1, 2) - cell_size = size(mesh, 2) - else - cell_size = size(mesh, 1) - end - - uEltype = eltype(boundary_condition) - boundary_condition.u_boundary = Array{uEltype, 3}(undef, nvariables(equations), - nnodes(dg), - cell_size) -end - -# Don't do anything for other BCs than BoundaryConditionCoupled -function copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi) - return nothing -end - -function copy_to_coupled_boundary!(u_ode, semi_coupled, semi, i, n_boundaries, - boundary_condition, boundary_conditions...) - copy_to_coupled_boundary!(boundary_condition, u_ode, semi_coupled, semi) - if i < n_boundaries - copy_to_coupled_boundary!(u_ode, semi_coupled, semi, i + 1, n_boundaries, - boundary_conditions...) - end -end - -function copy_to_coupled_boundary!(boundary_conditions::Union{Tuple, NamedTuple}, u_ode, - semi_coupled, semi) - copy_to_coupled_boundary!(u_ode, semi_coupled, semi, 1, length(boundary_conditions), - boundary_conditions...) -end - -# In 2D -function copy_to_coupled_boundary!(boundary_condition::BoundaryConditionCoupled{2, - other_semi_index}, - u_ode, semi_coupled, semi) where {other_semi_index} - @unpack u_indices = semi_coupled - @unpack other_orientation, indices = boundary_condition - @unpack coupling_converter, u_boundary = boundary_condition - - mesh_own, equations_own, solver_own, cache_own = mesh_equations_solver_cache(semi) - other_semi = semi_coupled.semis[other_semi_index] - mesh_other, equations_other, solver_other, cache_other = mesh_equations_solver_cache(other_semi) - - node_coordinates_other = cache_other.elements.node_coordinates - u_ode_other = get_system_u_ode(u_ode, other_semi_index, semi_coupled) - u_other = wrap_array(u_ode_other, mesh_other, equations_other, solver_other, - cache_other) - - linear_indices = LinearIndices(size(mesh_other)) - - if other_orientation == 1 - cells = axes(mesh_other, 2) - else # other_orientation == 2 - cells = axes(mesh_other, 1) - end - - # Copy solution data to the coupled boundary using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - node_index_range = eachnode(solver_other) - i_node_start, i_node_step = index_to_start_step_2d(indices[1], node_index_range) - j_node_start, j_node_step = index_to_start_step_2d(indices[2], node_index_range) - - i_cell_start, i_cell_step = index_to_start_step_2d(indices[1], axes(mesh_other, 1)) - j_cell_start, j_cell_step = index_to_start_step_2d(indices[2], axes(mesh_other, 2)) - - # We need indices starting at 1 for the handling of `i_cell` etc. - Base.require_one_based_indexing(cells) - - @threaded for i in eachindex(cells) - cell = cells[i] - i_cell = i_cell_start + (i - 1) * i_cell_step - j_cell = j_cell_start + (i - 1) * j_cell_step - - i_node = i_node_start - j_node = j_node_start - element_id = linear_indices[i_cell, j_cell] - - for element_id in eachnode(solver_other) - x_other = get_node_coords(node_coordinates_other, equations_other, - solver_other, - i_node, j_node, linear_indices[i_cell, j_cell]) - u_node_other = get_node_vars(u_other, equations_other, solver_other, i_node, - j_node, linear_indices[i_cell, j_cell]) - u_node_converted = coupling_converter(x_other, u_node_other, - equations_other, - equations_own) - - for i in eachindex(u_node_converted) - u_boundary[i, element_id, cell] = u_node_converted[i] - end + ################################################################################ + ### DGSEM/structured + ################################################################################ + + @inline function calc_boundary_flux_by_direction!( + surface_flux_values, u, t, + orientation, + boundary_condition::BoundaryConditionCoupled, + mesh::Union{ + StructuredMesh, + StructuredMeshView, + }, + equations, + surface_integral, dg::DG, cache, + direction, node_indices, + surface_node_indices, element + ) + @unpack node_coordinates, contravariant_vectors, inverse_jacobian = cache.elements + @unpack surface_flux = surface_integral + + cell_indices = get_boundary_indices(element, orientation, mesh) + + u_inner = get_node_vars(u, equations, dg, node_indices..., element) + + # If the mapping is orientation-reversing, the contravariant vectors' orientation + # is reversed as well. The normal vector must be oriented in the direction + # from `left_element` to `right_element`, or the numerical flux will be computed + # incorrectly (downwind direction). + sign_jacobian = sign(inverse_jacobian[node_indices..., element]) + + # Contravariant vector Ja^i is the normal vector + normal = sign_jacobian * + get_contravariant_vector( + orientation, contravariant_vectors, + node_indices..., element + ) + + # If the mapping is orientation-reversing, the normal vector will be reversed (see above). + # However, the flux now has the wrong sign, since we need the physical flux in normal direction. + flux = sign_jacobian * boundary_condition( + u_inner, normal, direction, cell_indices, + surface_node_indices, surface_flux, equations + ) + + for v in eachvariable(equations) + surface_flux_values[v, surface_node_indices..., direction, element] = flux[v] + end + end - i_node += i_node_step - j_node += j_node_step + function get_boundary_indices( + element, orientation, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}} + ) + cartesian_indices = CartesianIndices(size(mesh)) + if orientation == 1 + # Get index of element in y-direction + cell_indices = (cartesian_indices[element][2],) + else # orientation == 2 + # Get index of element in x-direction + cell_indices = (cartesian_indices[element][1],) end + + return cell_indices + end + + ################################################################################ + ### Special elixirs + ################################################################################ + + # Analyze convergence for SemidiscretizationCoupled + function analyze_convergence( + errors_coupled, iterations, + semi_coupled::SemidiscretizationCoupled + ) + # Extract errors: the errors are currently stored as + # | iter 1 sys 1 var 1...n | iter 1 sys 2 var 1...n | ... | iter 2 sys 1 var 1...n | ... + # but for calling `analyze_convergence` below, we need the following layout + # sys n: | iter 1 var 1...n | iter 1 var 1...n | ... | iter 2 var 1...n | ... + # That is, we need to extract and join the data for a single system + errors = Dict{Symbol, Vector{Float64}}[] + for i in eachsystem(semi_coupled) + push!(errors, Dict(:l2 => Float64[], :linf => Float64[])) + end + offset = 0 + for iter in 1:iterations, i in eachsystem(semi_coupled) + # Extract information on current semi + semi = semi_coupled.semis[i] + _, equations, _, _ = mesh_equations_solver_cache(semi) + variablenames = varnames(cons2cons, equations) + + # Compute offset + first = offset + 1 + last = offset + length(variablenames) + offset += length(variablenames) + + # Append errors to appropriate storage + append!(errors[i][:l2], errors_coupled[:l2][first:last]) + append!(errors[i][:linf], errors_coupled[:linf][first:last]) + end + + eoc_mean_values = Vector{Dict{Symbol, Any}}(undef, nsystems(semi_coupled)) + for i in eachsystem(semi_coupled) + # Use visual cues to separate output from multiple systems + println() + println("="^100) + println("# System $i") + println("="^100) + + # Extract information on current semi + semi = semi_coupled.semis[i] + _, equations, _, _ = mesh_equations_solver_cache(semi) + variablenames = varnames(cons2cons, equations) + + eoc_mean_values[i] = analyze_convergence(errors[i], iterations, variablenames) + end + + return eoc_mean_values end -end - -################################################################################ -### DGSEM/structured -################################################################################ - -@inline function calc_boundary_flux_by_direction!(surface_flux_values, u, t, - orientation, - boundary_condition::BoundaryConditionCoupled, - mesh::Union{StructuredMesh, - StructuredMeshView}, - equations, - surface_integral, dg::DG, cache, - direction, node_indices, - surface_node_indices, element) - @unpack node_coordinates, contravariant_vectors, inverse_jacobian = cache.elements - @unpack surface_flux = surface_integral - - cell_indices = get_boundary_indices(element, orientation, mesh) - - u_inner = get_node_vars(u, equations, dg, node_indices..., element) - - # If the mapping is orientation-reversing, the contravariant vectors' orientation - # is reversed as well. The normal vector must be oriented in the direction - # from `left_element` to `right_element`, or the numerical flux will be computed - # incorrectly (downwind direction). - sign_jacobian = sign(inverse_jacobian[node_indices..., element]) - - # Contravariant vector Ja^i is the normal vector - normal = sign_jacobian * - get_contravariant_vector(orientation, contravariant_vectors, - node_indices..., element) - - # If the mapping is orientation-reversing, the normal vector will be reversed (see above). - # However, the flux now has the wrong sign, since we need the physical flux in normal direction. - flux = sign_jacobian * boundary_condition(u_inner, normal, direction, cell_indices, - surface_node_indices, surface_flux, equations) - - for v in eachvariable(equations) - surface_flux_values[v, surface_node_indices..., direction, element] = flux[v] - end -end - -function get_boundary_indices(element, orientation, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}) - cartesian_indices = CartesianIndices(size(mesh)) - if orientation == 1 - # Get index of element in y-direction - cell_indices = (cartesian_indices[element][2],) - else # orientation == 2 - # Get index of element in x-direction - cell_indices = (cartesian_indices[element][1],) - end - - return cell_indices -end - -################################################################################ -### Special elixirs -################################################################################ - -# Analyze convergence for SemidiscretizationCoupled -function analyze_convergence(errors_coupled, iterations, - semi_coupled::SemidiscretizationCoupled) - # Extract errors: the errors are currently stored as - # | iter 1 sys 1 var 1...n | iter 1 sys 2 var 1...n | ... | iter 2 sys 1 var 1...n | ... - # but for calling `analyze_convergence` below, we need the following layout - # sys n: | iter 1 var 1...n | iter 1 var 1...n | ... | iter 2 var 1...n | ... - # That is, we need to extract and join the data for a single system - errors = Dict{Symbol, Vector{Float64}}[] - for i in eachsystem(semi_coupled) - push!(errors, Dict(:l2 => Float64[], :linf => Float64[])) - end - offset = 0 - for iter in 1:iterations, i in eachsystem(semi_coupled) - # Extract information on current semi - semi = semi_coupled.semis[i] - _, equations, _, _ = mesh_equations_solver_cache(semi) - variablenames = varnames(cons2cons, equations) - - # Compute offset - first = offset + 1 - last = offset + length(variablenames) - offset += length(variablenames) - - # Append errors to appropriate storage - append!(errors[i][:l2], errors_coupled[:l2][first:last]) - append!(errors[i][:linf], errors_coupled[:linf][first:last]) - end - - eoc_mean_values = Vector{Dict{Symbol, Any}}(undef, nsystems(semi_coupled)) - for i in eachsystem(semi_coupled) - # Use visual cues to separate output from multiple systems - println() - println("="^100) - println("# System $i") - println("="^100) - - # Extract information on current semi - semi = semi_coupled.semis[i] - _, equations, _, _ = mesh_equations_solver_cache(semi) - variablenames = varnames(cons2cons, equations) - - eoc_mean_values[i] = analyze_convergence(errors[i], iterations, variablenames) - end - - return eoc_mean_values -end end # @muladd diff --git a/src/semidiscretization/semidiscretization_euler_acoustics.jl b/src/semidiscretization/semidiscretization_euler_acoustics.jl index 286315fb96..82eece1138 100644 --- a/src/semidiscretization/semidiscretization_euler_acoustics.jl +++ b/src/semidiscretization/semidiscretization_euler_acoustics.jl @@ -3,211 +3,253 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - SemidiscretizationEulerAcoustics(semi_acoustics::SemiAcoustics, semi_euler::SemiEuler; - source_region=x->true, weights=x->1.0) - -!!! warning "Experimental code" - This semidiscretization is experimental and may change in any future release. - -Construct a semidiscretization of the acoustic perturbation equations that is coupled with -the compressible Euler equations via source terms for the perturbed velocity. Both -semidiscretizations have to use the same mesh and solvers with a shared basis. The coupling region -is described by a function `source_region` that maps the coordinates of a single node to `true` or -`false` depending on whether the point lies within the coupling region or not. A weighting function -`weights` that maps coordinates to weights is applied to the acoustic source terms. -Note that this semidiscretization should be used in conjunction with -[`EulerAcousticsCouplingCallback`](@ref) and only works in two dimensions. -""" -struct SemidiscretizationEulerAcoustics{SemiAcoustics, SemiEuler, Cache} <: - AbstractSemidiscretization - semi_acoustics::SemiAcoustics - semi_euler::SemiEuler - performance_counter::PerformanceCounter - cache::Cache - - function SemidiscretizationEulerAcoustics{SemiAcoustics, SemiEuler, Cache}(semi_acoustics, - semi_euler, - cache) where { - SemiAcoustics, - SemiEuler, - Cache - } - - # Currently both semidiscretizations need to use a shared mesh - @assert semi_acoustics.mesh == semi_euler.mesh - - # Check if both solvers use the same polynomial basis - @assert semi_acoustics.solver.basis == semi_euler.solver.basis - - performance_counter = PerformanceCounter() - new(semi_acoustics, semi_euler, performance_counter, cache) - end -end - -function SemidiscretizationEulerAcoustics(semi_acoustics::SemiAcoustics, - semi_euler::SemiEuler; - source_region = x -> true, - weights = x -> 1.0) where - {Mesh, - SemiAcoustics <: - SemidiscretizationHyperbolic{Mesh, <:AbstractAcousticPerturbationEquations}, - SemiEuler <: - SemidiscretizationHyperbolic{Mesh, <:AbstractCompressibleEulerEquations}} - cache = create_cache(SemidiscretizationEulerAcoustics, source_region, weights, - mesh_equations_solver_cache(semi_acoustics)...) - - return SemidiscretizationEulerAcoustics{typeof(semi_acoustics), typeof(semi_euler), - typeof(cache)}(semi_acoustics, semi_euler, - cache) -end - -function create_cache(::Type{SemidiscretizationEulerAcoustics}, source_region, weights, - mesh, equations::AcousticPerturbationEquations2D, dg::DGSEM, - cache) - coupled_element_ids = get_coupled_element_ids(source_region, equations, dg, cache) - - acoustic_source_terms = zeros(eltype(cache.elements), - (ndims(equations), nnodes(dg), nnodes(dg), - length(coupled_element_ids))) - - acoustic_source_weights = precompute_weights(source_region, weights, - coupled_element_ids, - equations, dg, cache) - - return (; acoustic_source_terms, acoustic_source_weights, coupled_element_ids) -end - -function get_coupled_element_ids(source_region, equations, dg::DGSEM, cache) - coupled_element_ids = Vector{Int}(undef, 0) - - for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - x = get_node_coords(cache.elements.node_coordinates, equations, dg, i, j, - element) - if source_region(x) - push!(coupled_element_ids, element) - break - end + #! format: noindent + + """ + SemidiscretizationEulerAcoustics(semi_acoustics::SemiAcoustics, semi_euler::SemiEuler; + source_region=x->true, weights=x->1.0) + + !!! warning "Experimental code" + This semidiscretization is experimental and may change in any future release. + + Construct a semidiscretization of the acoustic perturbation equations that is coupled with + the compressible Euler equations via source terms for the perturbed velocity. Both + semidiscretizations have to use the same mesh and solvers with a shared basis. The coupling region + is described by a function `source_region` that maps the coordinates of a single node to `true` or + `false` depending on whether the point lies within the coupling region or not. A weighting function + `weights` that maps coordinates to weights is applied to the acoustic source terms. + Note that this semidiscretization should be used in conjunction with + [`EulerAcousticsCouplingCallback`](@ref) and only works in two dimensions. + """ + struct SemidiscretizationEulerAcoustics{SemiAcoustics, SemiEuler, Cache} <: + AbstractSemidiscretization + semi_acoustics::SemiAcoustics + semi_euler::SemiEuler + performance_counter::PerformanceCounter + cache::Cache + + function SemidiscretizationEulerAcoustics{SemiAcoustics, SemiEuler, Cache}( + semi_acoustics, + semi_euler, + cache + ) where { + SemiAcoustics, + SemiEuler, + Cache, + } + + # Currently both semidiscretizations need to use a shared mesh + @assert semi_acoustics.mesh == semi_euler.mesh + + # Check if both solvers use the same polynomial basis + @assert semi_acoustics.solver.basis == semi_euler.solver.basis + + performance_counter = PerformanceCounter() + new(semi_acoustics, semi_euler, performance_counter, cache) end end - return coupled_element_ids -end - -function precompute_weights(source_region, weights, coupled_element_ids, equations, - dg::DGSEM, cache) - acoustic_source_weights = zeros(eltype(cache.elements), - (nnodes(dg), nnodes(dg), - length(coupled_element_ids))) - - @threaded for k in eachindex(coupled_element_ids) - element = coupled_element_ids[k] - for j in eachnode(dg), i in eachnode(dg) - x = get_node_coords(cache.elements.node_coordinates, equations, dg, i, j, - element) - acoustic_source_weights[i, j, k] = source_region(x) ? weights(x) : - zero(weights(x)) - end + function SemidiscretizationEulerAcoustics( + semi_acoustics::SemiAcoustics, + semi_euler::SemiEuler; + source_region = x -> true, + weights = x -> 1.0 + ) where + { + Mesh, + SemiAcoustics <: + SemidiscretizationHyperbolic{Mesh, <:AbstractAcousticPerturbationEquations}, + SemiEuler <: + SemidiscretizationHyperbolic{Mesh, <:AbstractCompressibleEulerEquations}, + } + cache = create_cache( + SemidiscretizationEulerAcoustics, source_region, weights, + mesh_equations_solver_cache(semi_acoustics)... + ) + + return SemidiscretizationEulerAcoustics{ + typeof(semi_acoustics), typeof(semi_euler), + typeof(cache), + }( + semi_acoustics, semi_euler, + cache + ) end - return acoustic_source_weights -end + function create_cache( + ::Type{SemidiscretizationEulerAcoustics}, source_region, weights, + mesh, equations::AcousticPerturbationEquations2D, dg::DGSEM, + cache + ) + coupled_element_ids = get_coupled_element_ids(source_region, equations, dg, cache) + + acoustic_source_terms = zeros( + eltype(cache.elements), + ( + ndims(equations), nnodes(dg), nnodes(dg), + length(coupled_element_ids), + ) + ) + + acoustic_source_weights = precompute_weights( + source_region, weights, + coupled_element_ids, + equations, dg, cache + ) + + return (; acoustic_source_terms, acoustic_source_weights, coupled_element_ids) + end -function Base.show(io::IO, semi::SemidiscretizationEulerAcoustics) - @nospecialize semi # reduce precompilation time + function get_coupled_element_ids(source_region, equations, dg::DGSEM, cache) + coupled_element_ids = Vector{Int}(undef, 0) + + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + x = get_node_coords( + cache.elements.node_coordinates, equations, dg, i, j, + element + ) + if source_region(x) + push!(coupled_element_ids, element) + break + end + end + end - print(io, "SemidiscretizationEulerAcoustics(") - print(io, semi.semi_acoustics) - print(io, ", ", semi.semi_euler) - print(io, ", cache(") - for (idx, key) in enumerate(keys(semi.cache)) - idx > 1 && print(io, " ") - print(io, key) + return coupled_element_ids end - print(io, "))") -end - -function Base.show(io::IO, mime::MIME"text/plain", - semi::SemidiscretizationEulerAcoustics) - @nospecialize semi # reduce precompilation time - - if get(io, :compact, false) - show(io, semi) - else - summary_header(io, "SemidiscretizationEulerAcoustics") - summary_line(io, "semidiscretization Euler", - semi.semi_euler |> typeof |> nameof) - show(increment_indent(io), mime, semi.semi_euler) - summary_line(io, "semidiscretization acoustics", - semi.semi_acoustics |> typeof |> nameof) - show(increment_indent(io), mime, semi.semi_acoustics) - summary_footer(io) - end -end -# The acoustics semidiscretization is the main semidiscretization. -@inline function mesh_equations_solver_cache(semi::SemidiscretizationEulerAcoustics) - return mesh_equations_solver_cache(semi.semi_acoustics) -end + function precompute_weights( + source_region, weights, coupled_element_ids, equations, + dg::DGSEM, cache + ) + acoustic_source_weights = zeros( + eltype(cache.elements), + ( + nnodes(dg), nnodes(dg), + length(coupled_element_ids), + ) + ) + + @threaded for k in eachindex(coupled_element_ids) + element = coupled_element_ids[k] + for j in eachnode(dg), i in eachnode(dg) + x = get_node_coords( + cache.elements.node_coordinates, equations, dg, i, j, + element + ) + acoustic_source_weights[i, j, k] = source_region(x) ? weights(x) : + zero(weights(x)) + end + end -@inline Base.ndims(semi::SemidiscretizationEulerAcoustics) = ndims(semi.semi_acoustics) -@inline Base.real(semi::SemidiscretizationEulerAcoustics) = real(semi.semi_acoustics) + return acoustic_source_weights + end -# Computes the coefficients of the initial condition -@inline function compute_coefficients(t, semi::SemidiscretizationEulerAcoustics) - compute_coefficients(t, semi.semi_acoustics) -end + function Base.show(io::IO, semi::SemidiscretizationEulerAcoustics) + @nospecialize semi # reduce precompilation time -@inline function compute_coefficients!(u_ode, t, semi::SemidiscretizationEulerAcoustics) - compute_coefficients!(u_ode, t, semi.semi_acoustics) -end + print(io, "SemidiscretizationEulerAcoustics(") + print(io, semi.semi_acoustics) + print(io, ", ", semi.semi_euler) + print(io, ", cache(") + for (idx, key) in enumerate(keys(semi.cache)) + idx > 1 && print(io, " ") + print(io, key) + end + print(io, "))") + end -@inline function calc_error_norms(func, u, t, analyzer, - semi::SemidiscretizationEulerAcoustics, - cache_analysis) - calc_error_norms(func, u, t, analyzer, semi.semi_acoustics, cache_analysis) -end + function Base.show( + io::IO, mime::MIME"text/plain", + semi::SemidiscretizationEulerAcoustics + ) + @nospecialize semi # reduce precompilation time + + if get(io, :compact, false) + show(io, semi) + else + summary_header(io, "SemidiscretizationEulerAcoustics") + summary_line( + io, "semidiscretization Euler", + semi.semi_euler |> typeof |> nameof + ) + show(increment_indent(io), mime, semi.semi_euler) + summary_line( + io, "semidiscretization acoustics", + semi.semi_acoustics |> typeof |> nameof + ) + show(increment_indent(io), mime, semi.semi_acoustics) + summary_footer(io) + end + end -function rhs!(du_ode, u_ode, semi::SemidiscretizationEulerAcoustics, t) - @unpack semi_acoustics, cache = semi - @unpack acoustic_source_terms, acoustic_source_weights, coupled_element_ids = cache + # The acoustics semidiscretization is the main semidiscretization. + @inline function mesh_equations_solver_cache(semi::SemidiscretizationEulerAcoustics) + return mesh_equations_solver_cache(semi.semi_acoustics) + end - du_acoustics = wrap_array(du_ode, semi_acoustics) + @inline Base.ndims(semi::SemidiscretizationEulerAcoustics) = ndims(semi.semi_acoustics) + @inline Base.real(semi::SemidiscretizationEulerAcoustics) = real(semi.semi_acoustics) - time_start = time_ns() + # Computes the coefficients of the initial condition + @inline function compute_coefficients(t, semi::SemidiscretizationEulerAcoustics) + compute_coefficients(t, semi.semi_acoustics) + end - @trixi_timeit timer() "acoustics rhs!" rhs!(du_ode, u_ode, semi_acoustics, t) + @inline function compute_coefficients!(u_ode, t, semi::SemidiscretizationEulerAcoustics) + compute_coefficients!(u_ode, t, semi.semi_acoustics) + end - @trixi_timeit timer() "add acoustic source terms" begin - add_acoustic_source_terms!(du_acoustics, acoustic_source_terms, - acoustic_source_weights, coupled_element_ids, - mesh_equations_solver_cache(semi_acoustics)...) + @inline function calc_error_norms( + func, u, t, analyzer, + semi::SemidiscretizationEulerAcoustics, + cache_analysis + ) + calc_error_norms(func, u, t, analyzer, semi.semi_acoustics, cache_analysis) end - runtime = time_ns() - time_start - put!(semi.performance_counter, runtime) + function rhs!(du_ode, u_ode, semi::SemidiscretizationEulerAcoustics, t) + @unpack semi_acoustics, cache = semi + @unpack acoustic_source_terms, acoustic_source_weights, coupled_element_ids = cache + + du_acoustics = wrap_array(du_ode, semi_acoustics) - return nothing -end + time_start = time_ns() -function add_acoustic_source_terms!(du_acoustics, acoustic_source_terms, source_weights, - coupled_element_ids, mesh::TreeMesh{2}, equations, - dg::DGSEM, - cache) - @threaded for k in eachindex(coupled_element_ids) - element = coupled_element_ids[k] + @trixi_timeit timer() "acoustics rhs!" rhs!(du_ode, u_ode, semi_acoustics, t) - for j in eachnode(dg), i in eachnode(dg) - du_acoustics[1, i, j, element] += source_weights[i, j, k] * - acoustic_source_terms[1, i, j, k] - du_acoustics[2, i, j, element] += source_weights[i, j, k] * - acoustic_source_terms[2, i, j, k] + @trixi_timeit timer() "add acoustic source terms" begin + add_acoustic_source_terms!( + du_acoustics, acoustic_source_terms, + acoustic_source_weights, coupled_element_ids, + mesh_equations_solver_cache(semi_acoustics)... + ) end + + runtime = time_ns() - time_start + put!(semi.performance_counter, runtime) + + return nothing end - return nothing -end + function add_acoustic_source_terms!( + du_acoustics, acoustic_source_terms, source_weights, + coupled_element_ids, mesh::TreeMesh{2}, equations, + dg::DGSEM, + cache + ) + @threaded for k in eachindex(coupled_element_ids) + element = coupled_element_ids[k] + + for j in eachnode(dg), i in eachnode(dg) + du_acoustics[1, i, j, element] += source_weights[i, j, k] * + acoustic_source_terms[1, i, j, k] + du_acoustics[2, i, j, element] += source_weights[i, j, k] * + acoustic_source_terms[2, i, j, k] + end + end + + return nothing + end end # @muladd diff --git a/src/semidiscretization/semidiscretization_euler_gravity.jl b/src/semidiscretization/semidiscretization_euler_gravity.jl index 4201344df8..7af5a48239 100644 --- a/src/semidiscretization/semidiscretization_euler_gravity.jl +++ b/src/semidiscretization/semidiscretization_euler_gravity.jl @@ -3,513 +3,627 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - ParametersEulerGravity(; background_density=0.0, - gravitational_constant=1.0, - cfl=1.0, - resid_tol=1.0e-4, - n_iterations_max=10^4, - timestep_gravity=timestep_gravity_erk52_3Sstar!) - -Set up parameters for the gravitational part of a [`SemidiscretizationEulerGravity`](@ref). -""" -struct ParametersEulerGravity{RealT <: Real, TimestepGravity} - background_density :: RealT # aka rho0 - gravitational_constant :: RealT # aka G - cfl :: RealT - resid_tol :: RealT - n_iterations_max :: Int - timestep_gravity :: TimestepGravity -end - -function ParametersEulerGravity(; background_density = 0.0, - gravitational_constant = 1.0, - cfl = 1.0, - resid_tol = 1.0e-4, - n_iterations_max = 10^4, - timestep_gravity = timestep_gravity_erk52_3Sstar!) - background_density, gravitational_constant, cfl, resid_tol = promote(background_density, - gravitational_constant, - cfl, resid_tol) - ParametersEulerGravity(background_density, gravitational_constant, cfl, resid_tol, - n_iterations_max, timestep_gravity) -end - -function Base.show(io::IO, parameters::ParametersEulerGravity) - @nospecialize parameters # reduce precompilation time - - print(io, "ParametersEulerGravity(") - print(io, "background_density=", parameters.background_density) - print(io, ", gravitational_constant=", parameters.gravitational_constant) - print(io, ", cfl=", parameters.cfl) - print(io, ", n_iterations_max=", parameters.n_iterations_max) - print(io, ", timestep_gravity=", parameters.timestep_gravity) - print(io, ")") -end -function Base.show(io::IO, ::MIME"text/plain", parameters::ParametersEulerGravity) - @nospecialize parameters # reduce precompilation time - - if get(io, :compact, false) - show(io, parameters) - else - setup = [ - "background density (ρ₀)" => parameters.background_density, - "gravitational constant (G)" => parameters.gravitational_constant, - "CFL (gravity)" => parameters.cfl, - "max. #iterations" => parameters.n_iterations_max, - "time integrator" => parameters.timestep_gravity, - ] - summary_box(io, "ParametersEulerGravity", setup) + #! format: noindent + + """ + ParametersEulerGravity(; background_density=0.0, + gravitational_constant=1.0, + cfl=1.0, + resid_tol=1.0e-4, + n_iterations_max=10^4, + timestep_gravity=timestep_gravity_erk52_3Sstar!) + + Set up parameters for the gravitational part of a [`SemidiscretizationEulerGravity`](@ref). + """ + struct ParametersEulerGravity{RealT <: Real, TimestepGravity} + background_density::RealT # aka rho0 + gravitational_constant::RealT # aka G + cfl::RealT + resid_tol::RealT + n_iterations_max::Int + timestep_gravity::TimestepGravity end -end - -""" - SemidiscretizationEulerGravity - -A struct containing everything needed to describe a spatial semidiscretization -of a the compressible Euler equations with self-gravity, reformulating the -Poisson equation for the gravitational potential as steady-state problem of -the hyperblic diffusion equations. -- Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) - "A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics" - [arXiv: 2008.10593](https://arXiv.org/abs/2008.10593) -""" -struct SemidiscretizationEulerGravity{SemiEuler, SemiGravity, - Parameters <: ParametersEulerGravity, Cache} <: - AbstractSemidiscretization - semi_euler :: SemiEuler - semi_gravity :: SemiGravity - parameters :: Parameters - performance_counter :: PerformanceCounter - gravity_counter :: PerformanceCounter - cache :: Cache - - function SemidiscretizationEulerGravity{SemiEuler, SemiGravity, Parameters, Cache}(semi_euler::SemiEuler, - semi_gravity::SemiGravity, - parameters::Parameters, - cache::Cache) where { - SemiEuler, - SemiGravity, - Parameters <: - ParametersEulerGravity, - Cache - } - @assert ndims(semi_euler) == ndims(semi_gravity) - @assert typeof(semi_euler.mesh) == typeof(semi_gravity.mesh) - @assert polydeg(semi_euler.solver) == polydeg(semi_gravity.solver) - - performance_counter = PerformanceCounter() - gravity_counter = PerformanceCounter() - - new(semi_euler, semi_gravity, parameters, performance_counter, gravity_counter, - cache) + + function ParametersEulerGravity(; + background_density = 0.0, + gravitational_constant = 1.0, + cfl = 1.0, + resid_tol = 1.0e-4, + n_iterations_max = 10^4, + timestep_gravity = timestep_gravity_erk52_3Sstar! + ) + background_density, gravitational_constant, cfl, resid_tol = promote( + background_density, + gravitational_constant, + cfl, resid_tol + ) + ParametersEulerGravity( + background_density, gravitational_constant, cfl, resid_tol, + n_iterations_max, timestep_gravity + ) end -end - -""" - SemidiscretizationEulerGravity(semi_euler::SemiEuler, semi_gravity::SemiGravity, parameters) - -Construct a semidiscretization of the compressible Euler equations with self-gravity. -`parameters` should be given as [`ParametersEulerGravity`](@ref). -""" -function SemidiscretizationEulerGravity(semi_euler::SemiEuler, - semi_gravity::SemiGravity, - parameters) where - {Mesh, - SemiEuler <: - SemidiscretizationHyperbolic{Mesh, <:AbstractCompressibleEulerEquations}, - SemiGravity <: - SemidiscretizationHyperbolic{Mesh, <:AbstractHyperbolicDiffusionEquations}} - u_ode = compute_coefficients(zero(real(semi_gravity)), semi_gravity) - du_ode = similar(u_ode) - u_tmp1_ode = similar(u_ode) - u_tmp2_ode = similar(u_ode) - cache = (; u_ode, du_ode, u_tmp1_ode, u_tmp2_ode) - - SemidiscretizationEulerGravity{typeof(semi_euler), typeof(semi_gravity), - typeof(parameters), typeof(cache)}(semi_euler, - semi_gravity, - parameters, cache) -end - -# TODO: AD, add appropriate method for remake - -function Base.show(io::IO, semi::SemidiscretizationEulerGravity) - @nospecialize semi # reduce precompilation time - - print(io, "SemidiscretizationEulerGravity using") - print(io, semi.semi_euler) - print(io, ", ", semi.semi_gravity) - print(io, ", ", semi.parameters) - print(io, ", cache(") - for (idx, key) in enumerate(keys(semi.cache)) - idx > 1 && print(io, " ") - print(io, key) + + function Base.show(io::IO, parameters::ParametersEulerGravity) + @nospecialize parameters # reduce precompilation time + + print(io, "ParametersEulerGravity(") + print(io, "background_density=", parameters.background_density) + print(io, ", gravitational_constant=", parameters.gravitational_constant) + print(io, ", cfl=", parameters.cfl) + print(io, ", n_iterations_max=", parameters.n_iterations_max) + print(io, ", timestep_gravity=", parameters.timestep_gravity) + print(io, ")") end - print(io, "))") -end - -function Base.show(io::IO, mime::MIME"text/plain", semi::SemidiscretizationEulerGravity) - @nospecialize semi # reduce precompilation time - - if get(io, :compact, false) - show(io, semi) - else - summary_header(io, "SemidiscretizationEulerGravity") - summary_line(io, "semidiscretization Euler", - semi.semi_euler |> typeof |> nameof) - show(increment_indent(io), mime, semi.semi_euler) - summary_line(io, "semidiscretization gravity", - semi.semi_gravity |> typeof |> nameof) - show(increment_indent(io), mime, semi.semi_gravity) - summary_line(io, "parameters", semi.parameters |> typeof |> nameof) - show(increment_indent(io), mime, semi.parameters) - summary_footer(io) + function Base.show(io::IO, ::MIME"text/plain", parameters::ParametersEulerGravity) + @nospecialize parameters # reduce precompilation time + + if get(io, :compact, false) + show(io, parameters) + else + setup = [ + "background density (ρ₀)" => parameters.background_density, + "gravitational constant (G)" => parameters.gravitational_constant, + "CFL (gravity)" => parameters.cfl, + "max. #iterations" => parameters.n_iterations_max, + "time integrator" => parameters.timestep_gravity, + ] + summary_box(io, "ParametersEulerGravity", setup) + end + end + + """ + SemidiscretizationEulerGravity + + A struct containing everything needed to describe a spatial semidiscretization + of a the compressible Euler equations with self-gravity, reformulating the + Poisson equation for the gravitational potential as steady-state problem of + the hyperblic diffusion equations. + - Michael Schlottke-Lakemper, Andrew R. Winters, Hendrik Ranocha, Gregor J. Gassner (2020) + "A purely hyperbolic discontinuous Galerkin approach for self-gravitating gas dynamics" + [arXiv: 2008.10593](https://arXiv.org/abs/2008.10593) + """ + struct SemidiscretizationEulerGravity{ + SemiEuler, SemiGravity, + Parameters <: ParametersEulerGravity, Cache, + } <: + AbstractSemidiscretization + semi_euler::SemiEuler + semi_gravity::SemiGravity + parameters::Parameters + performance_counter::PerformanceCounter + gravity_counter::PerformanceCounter + cache::Cache + + function SemidiscretizationEulerGravity{SemiEuler, SemiGravity, Parameters, Cache}( + semi_euler::SemiEuler, + semi_gravity::SemiGravity, + parameters::Parameters, + cache::Cache + ) where { + SemiEuler, + SemiGravity, + Parameters <: + ParametersEulerGravity, + Cache, + } + @assert ndims(semi_euler) == ndims(semi_gravity) + @assert typeof(semi_euler.mesh) == typeof(semi_gravity.mesh) + @assert polydeg(semi_euler.solver) == polydeg(semi_gravity.solver) + + performance_counter = PerformanceCounter() + gravity_counter = PerformanceCounter() + + new( + semi_euler, semi_gravity, parameters, performance_counter, gravity_counter, + cache + ) + end end -end - -# The compressible Euler semidiscretization is considered to be the main semidiscretization. -# The hyperbolic diffusion equations part is only used internally to update the gravitational -# potential during an rhs! evaluation of the flow solver. -@inline function mesh_equations_solver_cache(semi::SemidiscretizationEulerGravity) - mesh_equations_solver_cache(semi.semi_euler) -end - -@inline Base.ndims(semi::SemidiscretizationEulerGravity) = ndims(semi.semi_euler) - -@inline Base.real(semi::SemidiscretizationEulerGravity) = real(semi.semi_euler) - -# computes the coefficients of the initial condition -@inline function compute_coefficients(t, semi::SemidiscretizationEulerGravity) - compute_coefficients!(semi.cache.u_ode, t, semi.semi_gravity) - compute_coefficients(t, semi.semi_euler) -end - -# computes the coefficients of the initial condition and stores the Euler part in `u_ode` -@inline function compute_coefficients!(u_ode, t, semi::SemidiscretizationEulerGravity) - compute_coefficients!(semi.cache.u_ode, t, semi.semi_gravity) - compute_coefficients!(u_ode, t, semi.semi_euler) -end - -@inline function calc_error_norms(func, u, t, analyzer, - semi::SemidiscretizationEulerGravity, cache_analysis) - calc_error_norms(func, u, t, analyzer, semi.semi_euler, cache_analysis) -end - -function rhs!(du_ode, u_ode, semi::SemidiscretizationEulerGravity, t) - @unpack semi_euler, semi_gravity, cache = semi - - u_euler = wrap_array(u_ode, semi_euler) - du_euler = wrap_array(du_ode, semi_euler) - u_gravity = wrap_array(cache.u_ode, semi_gravity) - - time_start = time_ns() - - # standard semidiscretization of the compressible Euler equations - @trixi_timeit timer() "Euler solver" rhs!(du_ode, u_ode, semi_euler, t) - - # compute gravitational potential and forces - @trixi_timeit timer() "gravity solver" update_gravity!(semi, u_ode) - - # add gravitational source source_terms to the Euler part - if ndims(semi_euler) == 1 - @views @. du_euler[2, .., :] -= u_euler[1, .., :] * u_gravity[2, .., :] - @views @. du_euler[3, .., :] -= u_euler[2, .., :] * u_gravity[2, .., :] - elseif ndims(semi_euler) == 2 - @views @. du_euler[2, .., :] -= u_euler[1, .., :] * u_gravity[2, .., :] - @views @. du_euler[3, .., :] -= u_euler[1, .., :] * u_gravity[3, .., :] - @views @. du_euler[4, .., :] -= (u_euler[2, .., :] * u_gravity[2, .., :] + - u_euler[3, .., :] * u_gravity[3, .., :]) - elseif ndims(semi_euler) == 3 - @views @. du_euler[2, .., :] -= u_euler[1, .., :] * u_gravity[2, .., :] - @views @. du_euler[3, .., :] -= u_euler[1, .., :] * u_gravity[3, .., :] - @views @. du_euler[4, .., :] -= u_euler[1, .., :] * u_gravity[4, .., :] - @views @. du_euler[5, .., :] -= (u_euler[2, .., :] * u_gravity[2, .., :] + - u_euler[3, .., :] * u_gravity[3, .., :] + - u_euler[4, .., :] * u_gravity[4, .., :]) - else - error("Number of dimensions $(ndims(semi_euler)) not supported.") + + """ + SemidiscretizationEulerGravity(semi_euler::SemiEuler, semi_gravity::SemiGravity, parameters) + + Construct a semidiscretization of the compressible Euler equations with self-gravity. + `parameters` should be given as [`ParametersEulerGravity`](@ref). + """ + function SemidiscretizationEulerGravity( + semi_euler::SemiEuler, + semi_gravity::SemiGravity, + parameters + ) where + { + Mesh, + SemiEuler <: + SemidiscretizationHyperbolic{Mesh, <:AbstractCompressibleEulerEquations}, + SemiGravity <: + SemidiscretizationHyperbolic{Mesh, <:AbstractHyperbolicDiffusionEquations}, + } + u_ode = compute_coefficients(zero(real(semi_gravity)), semi_gravity) + du_ode = similar(u_ode) + u_tmp1_ode = similar(u_ode) + u_tmp2_ode = similar(u_ode) + cache = (; u_ode, du_ode, u_tmp1_ode, u_tmp2_ode) + + SemidiscretizationEulerGravity{ + typeof(semi_euler), typeof(semi_gravity), + typeof(parameters), typeof(cache), + }( + semi_euler, + semi_gravity, + parameters, cache + ) + end + + # TODO: AD, add appropriate method for remake + + function Base.show(io::IO, semi::SemidiscretizationEulerGravity) + @nospecialize semi # reduce precompilation time + + print(io, "SemidiscretizationEulerGravity using") + print(io, semi.semi_euler) + print(io, ", ", semi.semi_gravity) + print(io, ", ", semi.parameters) + print(io, ", cache(") + for (idx, key) in enumerate(keys(semi.cache)) + idx > 1 && print(io, " ") + print(io, key) + end + print(io, "))") end - runtime = time_ns() - time_start - put!(semi.performance_counter, runtime) - - return nothing -end - -# TODO: Taal refactor, add some callbacks or so within the gravity update to allow investigating/optimizing it -function update_gravity!(semi::SemidiscretizationEulerGravity, u_ode) - @unpack semi_euler, semi_gravity, parameters, gravity_counter, cache = semi - - # Can be changed by AMR - resize!(cache.du_ode, length(cache.u_ode)) - resize!(cache.u_tmp1_ode, length(cache.u_ode)) - resize!(cache.u_tmp2_ode, length(cache.u_ode)) - - u_euler = wrap_array(u_ode, semi_euler) - u_gravity = wrap_array(cache.u_ode, semi_gravity) - du_gravity = wrap_array(cache.du_ode, semi_gravity) - - # set up main loop - finalstep = false - @unpack n_iterations_max, cfl, resid_tol, timestep_gravity = parameters - iter = 0 - t = zero(real(semi_gravity.solver)) - - # iterate gravity solver until convergence or maximum number of iterations are reached - @unpack equations = semi_gravity - while !finalstep - dt = @trixi_timeit timer() "calculate dt" begin - cfl * max_dt(u_gravity, t, semi_gravity.mesh, - have_constant_speed(equations), equations, - semi_gravity.solver, semi_gravity.cache) + function Base.show(io::IO, mime::MIME"text/plain", semi::SemidiscretizationEulerGravity) + @nospecialize semi # reduce precompilation time + + if get(io, :compact, false) + show(io, semi) + else + summary_header(io, "SemidiscretizationEulerGravity") + summary_line( + io, "semidiscretization Euler", + semi.semi_euler |> typeof |> nameof + ) + show(increment_indent(io), mime, semi.semi_euler) + summary_line( + io, "semidiscretization gravity", + semi.semi_gravity |> typeof |> nameof + ) + show(increment_indent(io), mime, semi.semi_gravity) + summary_line(io, "parameters", semi.parameters |> typeof |> nameof) + show(increment_indent(io), mime, semi.parameters) + summary_footer(io) end + end + + # The compressible Euler semidiscretization is considered to be the main semidiscretization. + # The hyperbolic diffusion equations part is only used internally to update the gravitational + # potential during an rhs! evaluation of the flow solver. + @inline function mesh_equations_solver_cache(semi::SemidiscretizationEulerGravity) + mesh_equations_solver_cache(semi.semi_euler) + end + + @inline Base.ndims(semi::SemidiscretizationEulerGravity) = ndims(semi.semi_euler) + + @inline Base.real(semi::SemidiscretizationEulerGravity) = real(semi.semi_euler) + + # computes the coefficients of the initial condition + @inline function compute_coefficients(t, semi::SemidiscretizationEulerGravity) + compute_coefficients!(semi.cache.u_ode, t, semi.semi_gravity) + compute_coefficients(t, semi.semi_euler) + end + + # computes the coefficients of the initial condition and stores the Euler part in `u_ode` + @inline function compute_coefficients!(u_ode, t, semi::SemidiscretizationEulerGravity) + compute_coefficients!(semi.cache.u_ode, t, semi.semi_gravity) + compute_coefficients!(u_ode, t, semi.semi_euler) + end + + @inline function calc_error_norms( + func, u, t, analyzer, + semi::SemidiscretizationEulerGravity, cache_analysis + ) + calc_error_norms(func, u, t, analyzer, semi.semi_euler, cache_analysis) + end + + function rhs!(du_ode, u_ode, semi::SemidiscretizationEulerGravity, t) + @unpack semi_euler, semi_gravity, cache = semi + + u_euler = wrap_array(u_ode, semi_euler) + du_euler = wrap_array(du_ode, semi_euler) + u_gravity = wrap_array(cache.u_ode, semi_gravity) - # evolve solution by one pseudo-time step time_start = time_ns() - timestep_gravity(cache, u_euler, t, dt, parameters, semi_gravity) - runtime = time_ns() - time_start - put!(gravity_counter, runtime) - - # update iteration counter - iter += 1 - t += dt - - # check if we reached the maximum number of iterations - if n_iterations_max > 0 && iter >= n_iterations_max - @warn "Max iterations reached: Gravity solver failed to converge!" residual=maximum(abs, - @views du_gravity[1, - .., - :]) t=t dt=dt - finalstep = true + + # standard semidiscretization of the compressible Euler equations + @trixi_timeit timer() "Euler solver" rhs!(du_ode, u_ode, semi_euler, t) + + # compute gravitational potential and forces + @trixi_timeit timer() "gravity solver" update_gravity!(semi, u_ode) + + # add gravitational source source_terms to the Euler part + if ndims(semi_euler) == 1 + @views @. du_euler[2, .., :] -= u_euler[1, .., :] * u_gravity[2, .., :] + @views @. du_euler[3, .., :] -= u_euler[2, .., :] * u_gravity[2, .., :] + elseif ndims(semi_euler) == 2 + @views @. du_euler[2, .., :] -= u_euler[1, .., :] * u_gravity[2, .., :] + @views @. du_euler[3, .., :] -= u_euler[1, .., :] * u_gravity[3, .., :] + @views @. du_euler[4, .., :] -= ( + u_euler[2, .., :] * u_gravity[2, .., :] + + u_euler[3, .., :] * u_gravity[3, .., :] + ) + elseif ndims(semi_euler) == 3 + @views @. du_euler[2, .., :] -= u_euler[1, .., :] * u_gravity[2, .., :] + @views @. du_euler[3, .., :] -= u_euler[1, .., :] * u_gravity[3, .., :] + @views @. du_euler[4, .., :] -= u_euler[1, .., :] * u_gravity[4, .., :] + @views @. du_euler[5, .., :] -= ( + u_euler[2, .., :] * u_gravity[2, .., :] + + u_euler[3, .., :] * u_gravity[3, .., :] + + u_euler[4, .., :] * u_gravity[4, .., :] + ) + else + error("Number of dimensions $(ndims(semi_euler)) not supported.") end - # this is an absolute tolerance check - if maximum(abs, @views du_gravity[1, .., :]) <= resid_tol - finalstep = true + runtime = time_ns() - time_start + put!(semi.performance_counter, runtime) + + return nothing + end + + # TODO: Taal refactor, add some callbacks or so within the gravity update to allow investigating/optimizing it + function update_gravity!(semi::SemidiscretizationEulerGravity, u_ode) + @unpack semi_euler, semi_gravity, parameters, gravity_counter, cache = semi + + # Can be changed by AMR + resize!(cache.du_ode, length(cache.u_ode)) + resize!(cache.u_tmp1_ode, length(cache.u_ode)) + resize!(cache.u_tmp2_ode, length(cache.u_ode)) + + u_euler = wrap_array(u_ode, semi_euler) + u_gravity = wrap_array(cache.u_ode, semi_gravity) + du_gravity = wrap_array(cache.du_ode, semi_gravity) + + # set up main loop + finalstep = false + @unpack n_iterations_max, cfl, resid_tol, timestep_gravity = parameters + iter = 0 + t = zero(real(semi_gravity.solver)) + + # iterate gravity solver until convergence or maximum number of iterations are reached + @unpack equations = semi_gravity + while !finalstep + dt = @trixi_timeit timer() "calculate dt" begin + cfl * max_dt( + u_gravity, t, semi_gravity.mesh, + have_constant_speed(equations), equations, + semi_gravity.solver, semi_gravity.cache + ) + end + + # evolve solution by one pseudo-time step + time_start = time_ns() + timestep_gravity(cache, u_euler, t, dt, parameters, semi_gravity) + runtime = time_ns() - time_start + put!(gravity_counter, runtime) + + # update iteration counter + iter += 1 + t += dt + + # check if we reached the maximum number of iterations + if n_iterations_max > 0 && iter >= n_iterations_max + @warn "Max iterations reached: Gravity solver failed to converge!" residual = maximum( + abs, + @views du_gravity[ + 1, + .., + :, + ] + ) t = t dt = dt + finalstep = true + end + + # this is an absolute tolerance check + if maximum(abs, @views du_gravity[1, .., :]) <= resid_tol + finalstep = true + end end + + return nothing end - return nothing -end - -# Integrate gravity solver for 2N-type low-storage schemes -function timestep_gravity_2N!(cache, u_euler, t, dt, gravity_parameters, semi_gravity, - a, b, c) - G = gravity_parameters.gravitational_constant - rho0 = gravity_parameters.background_density - grav_scale = -4.0 * pi * G - - @unpack u_ode, du_ode, u_tmp1_ode = cache - u_tmp1_ode .= zero(eltype(u_tmp1_ode)) - du_gravity = wrap_array(du_ode, semi_gravity) - for stage in eachindex(c) - t_stage = t + dt * c[stage] - - # rhs! has the source term for the harmonic problem - # We don't need a `@trixi_timeit timer() "rhs!"` here since that's already - # included in the `rhs!` call. - rhs!(du_ode, u_ode, semi_gravity, t_stage) - - # Source term: Jeans instability OR coupling convergence test OR blast wave - # put in gravity source term proportional to Euler density - # OBS! subtract off the background density ρ_0 (spatial mean value) - @views @. du_gravity[1, .., :] += grav_scale * (u_euler[1, .., :] - rho0) - - a_stage = a[stage] - b_stage_dt = b[stage] * dt - @trixi_timeit timer() "Runge-Kutta step" begin - @threaded for idx in eachindex(u_ode) - u_tmp1_ode[idx] = du_ode[idx] - u_tmp1_ode[idx] * a_stage - u_ode[idx] += u_tmp1_ode[idx] * b_stage_dt + # Integrate gravity solver for 2N-type low-storage schemes + function timestep_gravity_2N!( + cache, u_euler, t, dt, gravity_parameters, semi_gravity, + a, b, c + ) + G = gravity_parameters.gravitational_constant + rho0 = gravity_parameters.background_density + grav_scale = -4.0 * pi * G + + @unpack u_ode, du_ode, u_tmp1_ode = cache + u_tmp1_ode .= zero(eltype(u_tmp1_ode)) + du_gravity = wrap_array(du_ode, semi_gravity) + for stage in eachindex(c) + t_stage = t + dt * c[stage] + + # rhs! has the source term for the harmonic problem + # We don't need a `@trixi_timeit timer() "rhs!"` here since that's already + # included in the `rhs!` call. + rhs!(du_ode, u_ode, semi_gravity, t_stage) + + # Source term: Jeans instability OR coupling convergence test OR blast wave + # put in gravity source term proportional to Euler density + # OBS! subtract off the background density ρ_0 (spatial mean value) + @views @. du_gravity[1, .., :] += grav_scale * (u_euler[1, .., :] - rho0) + + a_stage = a[stage] + b_stage_dt = b[stage] * dt + @trixi_timeit timer() "Runge-Kutta step" begin + @threaded for idx in eachindex(u_ode) + u_tmp1_ode[idx] = du_ode[idx] - u_tmp1_ode[idx] * a_stage + u_ode[idx] += u_tmp1_ode[idx] * b_stage_dt + end end end + + return nothing end - return nothing -end - -function timestep_gravity_carpenter_kennedy_erk54_2N!(cache, u_euler, t, dt, - gravity_parameters, semi_gravity) - # Coefficients for Carpenter's 5-stage 4th-order low-storage Runge-Kutta method - a = SVector(0.0, 567301805773.0 / 1357537059087.0, - 2404267990393.0 / 2016746695238.0, - 3550918686646.0 / 2091501179385.0, 1275806237668.0 / 842570457699.0) - b = SVector(1432997174477.0 / 9575080441755.0, 5161836677717.0 / 13612068292357.0, - 1720146321549.0 / 2090206949498.0, 3134564353537.0 / 4481467310338.0, - 2277821191437.0 / 14882151754819.0) - c = SVector(0.0, 1432997174477.0 / 9575080441755.0, - 2526269341429.0 / 6820363962896.0, - 2006345519317.0 / 3224310063776.0, 2802321613138.0 / 2924317926251.0) - - timestep_gravity_2N!(cache, u_euler, t, dt, gravity_parameters, semi_gravity, a, b, - c) -end - -# Integrate gravity solver for 3S*-type low-storage schemes -function timestep_gravity_3Sstar!(cache, u_euler, t, dt, gravity_parameters, - semi_gravity, - gamma1, gamma2, gamma3, beta, delta, c) - G = gravity_parameters.gravitational_constant - rho0 = gravity_parameters.background_density - grav_scale = -4 * G * pi - - @unpack u_ode, du_ode, u_tmp1_ode, u_tmp2_ode = cache - u_tmp1_ode .= zero(eltype(u_tmp1_ode)) - u_tmp2_ode .= u_ode - du_gravity = wrap_array(du_ode, semi_gravity) - for stage in eachindex(c) - t_stage = t + dt * c[stage] - - # rhs! has the source term for the harmonic problem - # We don't need a `@trixi_timeit timer() "rhs!"` here since that's already - # included in the `rhs!` call. - rhs!(du_ode, u_ode, semi_gravity, t_stage) - - # Source term: Jeans instability OR coupling convergence test OR blast wave - # put in gravity source term proportional to Euler density - # OBS! subtract off the background density ρ_0 around which the Jeans instability is perturbed - @views @. du_gravity[1, .., :] += grav_scale * (u_euler[1, .., :] - rho0) - - delta_stage = delta[stage] - gamma1_stage = gamma1[stage] - gamma2_stage = gamma2[stage] - gamma3_stage = gamma3[stage] - beta_stage_dt = beta[stage] * dt - @trixi_timeit timer() "Runge-Kutta step" begin - @threaded for idx in eachindex(u_ode) - u_tmp1_ode[idx] += delta_stage * u_ode[idx] - u_ode[idx] = (gamma1_stage * u_ode[idx] + - gamma2_stage * u_tmp1_ode[idx] + - gamma3_stage * u_tmp2_ode[idx] + - beta_stage_dt * du_ode[idx]) + function timestep_gravity_carpenter_kennedy_erk54_2N!( + cache, u_euler, t, dt, + gravity_parameters, semi_gravity + ) + # Coefficients for Carpenter's 5-stage 4th-order low-storage Runge-Kutta method + a = SVector( + 0.0, 567301805773.0 / 1357537059087.0, + 2404267990393.0 / 2016746695238.0, + 3550918686646.0 / 2091501179385.0, 1275806237668.0 / 842570457699.0 + ) + b = SVector( + 1432997174477.0 / 9575080441755.0, 5161836677717.0 / 13612068292357.0, + 1720146321549.0 / 2090206949498.0, 3134564353537.0 / 4481467310338.0, + 2277821191437.0 / 14882151754819.0 + ) + c = SVector( + 0.0, 1432997174477.0 / 9575080441755.0, + 2526269341429.0 / 6820363962896.0, + 2006345519317.0 / 3224310063776.0, 2802321613138.0 / 2924317926251.0 + ) + + timestep_gravity_2N!( + cache, u_euler, t, dt, gravity_parameters, semi_gravity, a, b, + c + ) + end + + # Integrate gravity solver for 3S*-type low-storage schemes + function timestep_gravity_3Sstar!( + cache, u_euler, t, dt, gravity_parameters, + semi_gravity, + gamma1, gamma2, gamma3, beta, delta, c + ) + G = gravity_parameters.gravitational_constant + rho0 = gravity_parameters.background_density + grav_scale = -4 * G * pi + + @unpack u_ode, du_ode, u_tmp1_ode, u_tmp2_ode = cache + u_tmp1_ode .= zero(eltype(u_tmp1_ode)) + u_tmp2_ode .= u_ode + du_gravity = wrap_array(du_ode, semi_gravity) + for stage in eachindex(c) + t_stage = t + dt * c[stage] + + # rhs! has the source term for the harmonic problem + # We don't need a `@trixi_timeit timer() "rhs!"` here since that's already + # included in the `rhs!` call. + rhs!(du_ode, u_ode, semi_gravity, t_stage) + + # Source term: Jeans instability OR coupling convergence test OR blast wave + # put in gravity source term proportional to Euler density + # OBS! subtract off the background density ρ_0 around which the Jeans instability is perturbed + @views @. du_gravity[1, .., :] += grav_scale * (u_euler[1, .., :] - rho0) + + delta_stage = delta[stage] + gamma1_stage = gamma1[stage] + gamma2_stage = gamma2[stage] + gamma3_stage = gamma3[stage] + beta_stage_dt = beta[stage] * dt + @trixi_timeit timer() "Runge-Kutta step" begin + @threaded for idx in eachindex(u_ode) + u_tmp1_ode[idx] += delta_stage * u_ode[idx] + u_ode[idx] = ( + gamma1_stage * u_ode[idx] + + gamma2_stage * u_tmp1_ode[idx] + + gamma3_stage * u_tmp2_ode[idx] + + beta_stage_dt * du_ode[idx] + ) + end end end + + return nothing + end + + function timestep_gravity_erk51_3Sstar!( + cache, u_euler, t, dt, gravity_parameters, + semi_gravity + ) + # New 3Sstar coefficients optimized for polynomials of degree polydeg=3 + # and examples/parameters_hypdiff_lax_friedrichs.toml + # 5 stages, order 1 + gamma1 = SVector( + 0.0e+0, 5.2910412316555866e-1, + 2.8433964362349406e-1, -1.4467571130907027e+0, + 7.5592215948661057e-2 + ) + gamma2 = SVector( + 1.0e+0, 2.6366970460864109e-1, + 3.7423646095836322e-1, 7.8786901832431289e-1, + 3.7754129043053775e-1 + ) + gamma3 = SVector( + 0.0e+0, 0.0e+0, + 0.0e+0, 8.0043329115077388e-1, + 1.3550099149374278e-1 + ) + beta = SVector( + 1.9189497208340553e-1, 5.4506406707700059e-2, + 1.2103893164085415e-1, 6.8582252490550921e-1, + 8.7914657211972225e-1 + ) + delta = SVector( + 1.0e+0, 7.8593091509463076e-1, + 1.263903871745484e-1, 1.7726945920209813e-1, + 0.0e+0 + ) + c = SVector( + 0.0e+0, 1.9189497208340553e-1, 1.9580448818599061e-1, + 2.4241635859769023e-1, 5.0728347557552977e-1 + ) + + timestep_gravity_3Sstar!( + cache, u_euler, t, dt, gravity_parameters, semi_gravity, + gamma1, gamma2, gamma3, beta, delta, c + ) + end + + function timestep_gravity_erk52_3Sstar!( + cache, u_euler, t, dt, gravity_parameters, + semi_gravity + ) + # New 3Sstar coefficients optimized for polynomials of degree polydeg=3 + # and examples/parameters_hypdiff_lax_friedrichs.toml + # 5 stages, order 2 + gamma1 = SVector( + 0.0e+0, 5.2656474556752575e-1, + 1.0385212774098265e+0, 3.6859755007388034e-1, + -6.3350615190506088e-1 + ) + gamma2 = SVector( + 1.0e+0, 4.1892580153419307e-1, + -2.7595818152587825e-2, 9.1271323651988631e-2, + 6.8495995159465062e-1 + ) + gamma3 = SVector( + 0.0e+0, 0.0e+0, + 0.0e+0, 4.1301005663300466e-1, + -5.4537881202277507e-3 + ) + beta = SVector( + 4.5158640252832094e-1, 7.5974836561844006e-1, + 3.7561630338850771e-1, 2.9356700007428856e-2, + 2.5205285143494666e-1 + ) + delta = SVector( + 1.0e+0, 1.3011720142005145e-1, + 2.6579275844515687e-1, 9.9687218193685878e-1, + 0.0e+0 + ) + c = SVector( + 0.0e+0, 4.5158640252832094e-1, 1.0221535725056414e+0, + 1.4280257701954349e+0, 7.1581334196229851e-1 + ) + + timestep_gravity_3Sstar!( + cache, u_euler, t, dt, gravity_parameters, semi_gravity, + gamma1, gamma2, gamma3, beta, delta, c + ) + end + + function timestep_gravity_erk53_3Sstar!( + cache, u_euler, t, dt, gravity_parameters, + semi_gravity + ) + # New 3Sstar coefficients optimized for polynomials of degree polydeg=3 + # and examples/parameters_hypdiff_lax_friedrichs.toml + # 5 stages, order 3 + gamma1 = SVector( + 0.0e+0, 6.936220805401121e-1, + 9.1364483229179472e-1, 1.3129305757628569e+0, + -1.4615811339132949e+0 + ) + gamma2 = SVector( + 1.0e+0, 1.3224582239681788e+0, + 2.4213162353103135e-1, -3.8532017293685838e-1, + 1.5603355704723714e+0 + ) + gamma3 = SVector( + 0.0e+0, 0.0e+0, + 0.0e+0, 3.8306787039991996e-1, + -3.568312120171101e-1 + ) + beta = SVector( + 8.4476964977404881e-2, 3.0834660698015803e-1, + 3.2131664733089232e-1, 2.8783574345390539e-1, + 8.2199204703236073e-1 + ) + delta = SVector( + 1.0e+0, -7.6832695815481578e-1, + 1.2497251501714818e-1, 1.4496404749796306e+0, + 0.0e+0 + ) + c = SVector( + 0.0e+0, 8.4476964977404881e-2, 2.8110631488732202e-1, + 5.7093842145029405e-1, 7.2999896418559662e-1 + ) + + timestep_gravity_3Sstar!( + cache, u_euler, t, dt, gravity_parameters, semi_gravity, + gamma1, gamma2, gamma3, beta, delta, c + ) end - return nothing -end - -function timestep_gravity_erk51_3Sstar!(cache, u_euler, t, dt, gravity_parameters, - semi_gravity) - # New 3Sstar coefficients optimized for polynomials of degree polydeg=3 - # and examples/parameters_hypdiff_lax_friedrichs.toml - # 5 stages, order 1 - gamma1 = SVector(0.0000000000000000E+00, 5.2910412316555866E-01, - 2.8433964362349406E-01, -1.4467571130907027E+00, - 7.5592215948661057E-02) - gamma2 = SVector(1.0000000000000000E+00, 2.6366970460864109E-01, - 3.7423646095836322E-01, 7.8786901832431289E-01, - 3.7754129043053775E-01) - gamma3 = SVector(0.0000000000000000E+00, 0.0000000000000000E+00, - 0.0000000000000000E+00, 8.0043329115077388E-01, - 1.3550099149374278E-01) - beta = SVector(1.9189497208340553E-01, 5.4506406707700059E-02, - 1.2103893164085415E-01, 6.8582252490550921E-01, - 8.7914657211972225E-01) - delta = SVector(1.0000000000000000E+00, 7.8593091509463076E-01, - 1.2639038717454840E-01, 1.7726945920209813E-01, - 0.0000000000000000E+00) - c = SVector(0.0000000000000000E+00, 1.9189497208340553E-01, 1.9580448818599061E-01, - 2.4241635859769023E-01, 5.0728347557552977E-01) - - timestep_gravity_3Sstar!(cache, u_euler, t, dt, gravity_parameters, semi_gravity, - gamma1, gamma2, gamma3, beta, delta, c) -end - -function timestep_gravity_erk52_3Sstar!(cache, u_euler, t, dt, gravity_parameters, - semi_gravity) - # New 3Sstar coefficients optimized for polynomials of degree polydeg=3 - # and examples/parameters_hypdiff_lax_friedrichs.toml - # 5 stages, order 2 - gamma1 = SVector(0.0000000000000000E+00, 5.2656474556752575E-01, - 1.0385212774098265E+00, 3.6859755007388034E-01, - -6.3350615190506088E-01) - gamma2 = SVector(1.0000000000000000E+00, 4.1892580153419307E-01, - -2.7595818152587825E-02, 9.1271323651988631E-02, - 6.8495995159465062E-01) - gamma3 = SVector(0.0000000000000000E+00, 0.0000000000000000E+00, - 0.0000000000000000E+00, 4.1301005663300466E-01, - -5.4537881202277507E-03) - beta = SVector(4.5158640252832094E-01, 7.5974836561844006E-01, - 3.7561630338850771E-01, 2.9356700007428856E-02, - 2.5205285143494666E-01) - delta = SVector(1.0000000000000000E+00, 1.3011720142005145E-01, - 2.6579275844515687E-01, 9.9687218193685878E-01, - 0.0000000000000000E+00) - c = SVector(0.0000000000000000E+00, 4.5158640252832094E-01, 1.0221535725056414E+00, - 1.4280257701954349E+00, 7.1581334196229851E-01) - - timestep_gravity_3Sstar!(cache, u_euler, t, dt, gravity_parameters, semi_gravity, - gamma1, gamma2, gamma3, beta, delta, c) -end - -function timestep_gravity_erk53_3Sstar!(cache, u_euler, t, dt, gravity_parameters, - semi_gravity) - # New 3Sstar coefficients optimized for polynomials of degree polydeg=3 - # and examples/parameters_hypdiff_lax_friedrichs.toml - # 5 stages, order 3 - gamma1 = SVector(0.0000000000000000E+00, 6.9362208054011210E-01, - 9.1364483229179472E-01, 1.3129305757628569E+00, - -1.4615811339132949E+00) - gamma2 = SVector(1.0000000000000000E+00, 1.3224582239681788E+00, - 2.4213162353103135E-01, -3.8532017293685838E-01, - 1.5603355704723714E+00) - gamma3 = SVector(0.0000000000000000E+00, 0.0000000000000000E+00, - 0.0000000000000000E+00, 3.8306787039991996E-01, - -3.5683121201711010E-01) - beta = SVector(8.4476964977404881E-02, 3.0834660698015803E-01, - 3.2131664733089232E-01, 2.8783574345390539E-01, - 8.2199204703236073E-01) - delta = SVector(1.0000000000000000E+00, -7.6832695815481578E-01, - 1.2497251501714818E-01, 1.4496404749796306E+00, - 0.0000000000000000E+00) - c = SVector(0.0000000000000000E+00, 8.4476964977404881E-02, 2.8110631488732202E-01, - 5.7093842145029405E-01, 7.2999896418559662E-01) - - timestep_gravity_3Sstar!(cache, u_euler, t, dt, gravity_parameters, semi_gravity, - gamma1, gamma2, gamma3, beta, delta, c) -end - -# TODO: Taal decide, where should specific parts like these be? -@inline function save_solution_file(u_ode, t, dt, iter, - semi::SemidiscretizationEulerGravity, - solution_callback, - element_variables = Dict{Symbol, Any}(); - system = "") - # If this is called already as part of a multi-system setup (i.e., system is non-empty), - # we build a combined system name - if !isempty(system) - system_euler = system * "_euler" - system_gravity = system * "_gravity" - else - system_euler = "euler" - system_gravity = "gravity" + # TODO: Taal decide, where should specific parts like these be? + @inline function save_solution_file( + u_ode, t, dt, iter, + semi::SemidiscretizationEulerGravity, + solution_callback, + element_variables = Dict{Symbol, Any}(); + system = "" + ) + # If this is called already as part of a multi-system setup (i.e., system is non-empty), + # we build a combined system name + if !isempty(system) + system_euler = system * "_euler" + system_gravity = system * "_gravity" + else + system_euler = "euler" + system_gravity = "gravity" + end + + u_euler = wrap_array_native(u_ode, semi.semi_euler) + filename_euler = save_solution_file( + u_euler, t, dt, iter, + mesh_equations_solver_cache(semi.semi_euler)..., + solution_callback, element_variables, + system = system_euler + ) + + u_gravity = wrap_array_native(semi.cache.u_ode, semi.semi_gravity) + filename_gravity = save_solution_file( + u_gravity, t, dt, iter, + mesh_equations_solver_cache(semi.semi_gravity)..., + solution_callback, element_variables, + system = system_gravity + ) + + return filename_euler, filename_gravity end - u_euler = wrap_array_native(u_ode, semi.semi_euler) - filename_euler = save_solution_file(u_euler, t, dt, iter, - mesh_equations_solver_cache(semi.semi_euler)..., - solution_callback, element_variables, - system = system_euler) - - u_gravity = wrap_array_native(semi.cache.u_ode, semi.semi_gravity) - filename_gravity = save_solution_file(u_gravity, t, dt, iter, - mesh_equations_solver_cache(semi.semi_gravity)..., - solution_callback, element_variables, - system = system_gravity) - - return filename_euler, filename_gravity -end - -@inline function (amr_callback::AMRCallback)(u_ode, - semi::SemidiscretizationEulerGravity, - t, iter; kwargs...) - passive_args = ((semi.cache.u_ode, - mesh_equations_solver_cache(semi.semi_gravity)...),) - amr_callback(u_ode, mesh_equations_solver_cache(semi.semi_euler)..., semi, t, iter; - kwargs..., passive_args = passive_args) -end + @inline function (amr_callback::AMRCallback)( + u_ode, + semi::SemidiscretizationEulerGravity, + t, iter; kwargs... + ) + passive_args = ( + ( + semi.cache.u_ode, + mesh_equations_solver_cache(semi.semi_gravity)..., + ), + ) + amr_callback( + u_ode, mesh_equations_solver_cache(semi.semi_euler)..., semi, t, iter; + kwargs..., passive_args = passive_args + ) + end end # @muladd diff --git a/src/semidiscretization/semidiscretization_hyperbolic.jl b/src/semidiscretization/semidiscretization_hyperbolic.jl index e35c0e2ea9..5287aedc06 100644 --- a/src/semidiscretization/semidiscretization_hyperbolic.jl +++ b/src/semidiscretization/semidiscretization_hyperbolic.jl @@ -3,325 +3,429 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - SemidiscretizationHyperbolic - -A struct containing everything needed to describe a spatial semidiscretization -of a hyperbolic conservation law. -""" -struct SemidiscretizationHyperbolic{Mesh, Equations, InitialCondition, - BoundaryConditions, - SourceTerms, Solver, Cache} <: - AbstractSemidiscretization - mesh::Mesh - equations::Equations - - # This guy is a bit messy since we abuse it as some kind of "exact solution" - # although this doesn't really exist... - initial_condition::InitialCondition - - boundary_conditions::BoundaryConditions - source_terms::SourceTerms - solver::Solver - cache::Cache - performance_counter::PerformanceCounter - - function SemidiscretizationHyperbolic{Mesh, Equations, InitialCondition, - BoundaryConditions, SourceTerms, Solver, - Cache}(mesh::Mesh, equations::Equations, - initial_condition::InitialCondition, - boundary_conditions::BoundaryConditions, - source_terms::SourceTerms, - solver::Solver, - cache::Cache) where {Mesh, Equations, - InitialCondition, - BoundaryConditions, - SourceTerms, - Solver, - Cache} - performance_counter = PerformanceCounter() - - new(mesh, equations, initial_condition, boundary_conditions, source_terms, - solver, cache, performance_counter) - end -end - -""" - SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; - source_terms=nothing, - boundary_conditions=boundary_condition_periodic, - RealT=real(solver), - uEltype=RealT, - initial_cache=NamedTuple()) - -Construct a semidiscretization of a hyperbolic PDE. -""" -function SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; - source_terms = nothing, - boundary_conditions = boundary_condition_periodic, - # `RealT` is used as real type for node locations etc. - # while `uEltype` is used as element type of solutions etc. - RealT = real(solver), uEltype = RealT, - initial_cache = NamedTuple()) - @assert ndims(mesh) == ndims(equations) - - cache = (; create_cache(mesh, equations, solver, RealT, uEltype)..., - initial_cache...) - _boundary_conditions = digest_boundary_conditions(boundary_conditions, mesh, solver, - cache) - - check_periodicity_mesh_boundary_conditions(mesh, _boundary_conditions) - - SemidiscretizationHyperbolic{typeof(mesh), typeof(equations), - typeof(initial_condition), - typeof(_boundary_conditions), typeof(source_terms), - typeof(solver), typeof(cache)}(mesh, equations, - initial_condition, - _boundary_conditions, - source_terms, solver, - cache) -end - -# Create a new semidiscretization but change some parameters compared to the input. -# `Base.similar` follows a related concept but would require us to `copy` the `mesh`, -# which would impact the performance. Instead, `SciMLBase.remake` has exactly the -# semantics we want to use here. In particular, it allows us to re-use mutable parts, -# e.g. `remake(semi).mesh === semi.mesh`. -function remake(semi::SemidiscretizationHyperbolic; uEltype = real(semi.solver), - mesh = semi.mesh, - equations = semi.equations, - initial_condition = semi.initial_condition, - solver = semi.solver, - source_terms = semi.source_terms, - boundary_conditions = semi.boundary_conditions) - # TODO: Which parts do we want to `remake`? At least the solver needs some - # special care if shock-capturing volume integrals are used (because of - # the indicators and their own caches...). - SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; - source_terms, boundary_conditions, uEltype) -end - -# general fallback -function digest_boundary_conditions(boundary_conditions, mesh, solver, cache) - boundary_conditions -end - -# general fallback -function digest_boundary_conditions(boundary_conditions::BoundaryConditionPeriodic, - mesh, solver, cache) - boundary_conditions -end - -# resolve ambiguities with definitions below -function digest_boundary_conditions(boundary_conditions::BoundaryConditionPeriodic, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, solver, - cache) - boundary_conditions -end - -function digest_boundary_conditions(boundary_conditions::BoundaryConditionPeriodic, - mesh::Union{TreeMesh{2}, StructuredMesh{2}}, solver, - cache) - boundary_conditions -end - -function digest_boundary_conditions(boundary_conditions::BoundaryConditionPeriodic, - mesh::Union{TreeMesh{3}, StructuredMesh{3}}, solver, - cache) - boundary_conditions -end - -# allow passing a single BC that get converted into a tuple of BCs -# on (mapped) hypercube domains -function digest_boundary_conditions(boundary_conditions, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, solver, - cache) - (; x_neg = boundary_conditions, x_pos = boundary_conditions) -end - -function digest_boundary_conditions(boundary_conditions, - mesh::Union{TreeMesh{2}, StructuredMesh{2}}, solver, - cache) - (; x_neg = boundary_conditions, x_pos = boundary_conditions, - y_neg = boundary_conditions, y_pos = boundary_conditions) -end - -function digest_boundary_conditions(boundary_conditions, - mesh::Union{TreeMesh{3}, StructuredMesh{3}}, solver, - cache) - (; x_neg = boundary_conditions, x_pos = boundary_conditions, - y_neg = boundary_conditions, y_pos = boundary_conditions, - z_neg = boundary_conditions, z_pos = boundary_conditions) -end - -# allow passing a tuple of BCs that get converted into a named tuple to make it -# self-documenting on (mapped) hypercube domains -function digest_boundary_conditions(boundary_conditions::NTuple{2, Any}, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, solver, - cache) - (; x_neg = boundary_conditions[1], x_pos = boundary_conditions[2]) -end - -function digest_boundary_conditions(boundary_conditions::NTuple{4, Any}, - mesh::Union{TreeMesh{2}, StructuredMesh{2}}, solver, - cache) - (; x_neg = boundary_conditions[1], x_pos = boundary_conditions[2], - y_neg = boundary_conditions[3], y_pos = boundary_conditions[4]) -end - -function digest_boundary_conditions(boundary_conditions::NTuple{6, Any}, - mesh::Union{TreeMesh{3}, StructuredMesh{3}}, solver, - cache) - (; x_neg = boundary_conditions[1], x_pos = boundary_conditions[2], - y_neg = boundary_conditions[3], y_pos = boundary_conditions[4], - z_neg = boundary_conditions[5], z_pos = boundary_conditions[6]) -end - -# allow passing named tuples of BCs constructed in an arbitrary order -# on (mapped) hypercube domains -function digest_boundary_conditions(boundary_conditions::NamedTuple{Keys, ValueTypes}, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, solver, - cache) where {Keys, ValueTypes <: NTuple{2, Any}} - @unpack x_neg, x_pos = boundary_conditions - (; x_neg, x_pos) -end - -function digest_boundary_conditions(boundary_conditions::NamedTuple{Keys, ValueTypes}, - mesh::Union{TreeMesh{2}, StructuredMesh{2}}, solver, - cache) where {Keys, ValueTypes <: NTuple{4, Any}} - @unpack x_neg, x_pos, y_neg, y_pos = boundary_conditions - (; x_neg, x_pos, y_neg, y_pos) -end - -function digest_boundary_conditions(boundary_conditions::NamedTuple{Keys, ValueTypes}, - mesh::Union{TreeMesh{3}, StructuredMesh{3}}, solver, - cache) where {Keys, ValueTypes <: NTuple{6, Any}} - @unpack x_neg, x_pos, y_neg, y_pos, z_neg, z_pos = boundary_conditions - (; x_neg, x_pos, y_neg, y_pos, z_neg, z_pos) -end - -# sort the boundary conditions from a dictionary and into tuples -function digest_boundary_conditions(boundary_conditions::Dict, mesh, solver, cache) - UnstructuredSortedBoundaryTypes(boundary_conditions, cache) -end - -function digest_boundary_conditions(boundary_conditions::AbstractArray, mesh, solver, - cache) - throw(ArgumentError("Please use a (named) tuple instead of an (abstract) array to supply multiple boundary conditions (to improve performance).")) -end - -# No checks for these meshes yet available -function check_periodicity_mesh_boundary_conditions(mesh::Union{P4estMesh, - UnstructuredMesh2D, - T8codeMesh, - DGMultiMesh}, - boundary_conditions) -end - -# No actions needed for periodic boundary conditions -function check_periodicity_mesh_boundary_conditions(mesh::Union{TreeMesh, - StructuredMesh}, - boundary_conditions::BoundaryConditionPeriodic) -end - -function check_periodicity_mesh_boundary_conditions_x(mesh, x_neg, x_pos) - if isperiodic(mesh, 1) && - (x_neg != BoundaryConditionPeriodic() || - x_pos != BoundaryConditionPeriodic()) - @error "For periodic mesh non-periodic boundary conditions in x-direction are supplied." - end -end - -function check_periodicity_mesh_boundary_conditions_y(mesh, y_neg, y_pos) - if isperiodic(mesh, 2) && - (y_neg != BoundaryConditionPeriodic() || - y_pos != BoundaryConditionPeriodic()) - @error "For periodic mesh non-periodic boundary conditions in y-direction are supplied." - end -end - -function check_periodicity_mesh_boundary_conditions_z(mesh, z_neg, z_pos) - if isperiodic(mesh, 3) && - (z_neg != BoundaryConditionPeriodic() || - z_pos != BoundaryConditionPeriodic()) - @error "For periodic mesh non-periodic boundary conditions in z-direction are supplied." - end -end - -function check_periodicity_mesh_boundary_conditions(mesh::Union{TreeMesh{1}, - StructuredMesh{1}}, - boundary_conditions::Union{NamedTuple, - Tuple}) - check_periodicity_mesh_boundary_conditions_x(mesh, boundary_conditions[1], - boundary_conditions[2]) -end - -function check_periodicity_mesh_boundary_conditions(mesh::Union{TreeMesh{2}, - StructuredMesh{2}, - StructuredMeshView{2}}, - boundary_conditions::Union{NamedTuple, - Tuple}) - check_periodicity_mesh_boundary_conditions_x(mesh, boundary_conditions[1], - boundary_conditions[2]) - check_periodicity_mesh_boundary_conditions_y(mesh, boundary_conditions[3], - boundary_conditions[4]) -end - -function check_periodicity_mesh_boundary_conditions(mesh::Union{TreeMesh{3}, - StructuredMesh{3}}, - boundary_conditions::Union{NamedTuple, - Tuple}) - check_periodicity_mesh_boundary_conditions_x(mesh, boundary_conditions[1], - boundary_conditions[2]) - check_periodicity_mesh_boundary_conditions_y(mesh, boundary_conditions[3], - boundary_conditions[4]) - check_periodicity_mesh_boundary_conditions_z(mesh, boundary_conditions[5], - boundary_conditions[6]) -end - -function Base.show(io::IO, semi::SemidiscretizationHyperbolic) - @nospecialize semi # reduce precompilation time - - print(io, "SemidiscretizationHyperbolic(") - print(io, semi.mesh) - print(io, ", ", semi.equations) - print(io, ", ", semi.initial_condition) - print(io, ", ", semi.boundary_conditions) - print(io, ", ", semi.source_terms) - print(io, ", ", semi.solver) - print(io, ", cache(") - for (idx, key) in enumerate(keys(semi.cache)) - idx > 1 && print(io, " ") - print(io, key) - end - print(io, "))") -end - -function Base.show(io::IO, ::MIME"text/plain", semi::SemidiscretizationHyperbolic) - @nospecialize semi # reduce precompilation time - - if get(io, :compact, false) - show(io, semi) - else - summary_header(io, "SemidiscretizationHyperbolic") - summary_line(io, "#spatial dimensions", ndims(semi.equations)) - summary_line(io, "mesh", semi.mesh) - summary_line(io, "equations", semi.equations |> typeof |> nameof) - summary_line(io, "initial condition", semi.initial_condition) - - print_boundary_conditions(io, semi) - - summary_line(io, "source terms", semi.source_terms) - summary_line(io, "solver", semi.solver |> typeof |> nameof) - summary_line(io, "total #DOFs per field", ndofsglobal(semi)) - summary_footer(io) - end -end - -# type alias for dispatch in printing of boundary conditions -#! format: off + #! format: noindent + + """ + SemidiscretizationHyperbolic + + A struct containing everything needed to describe a spatial semidiscretization + of a hyperbolic conservation law. + """ + struct SemidiscretizationHyperbolic{ + Mesh, Equations, InitialCondition, + BoundaryConditions, + SourceTerms, Solver, Cache, + } <: + AbstractSemidiscretization + mesh::Mesh + equations::Equations + + # This guy is a bit messy since we abuse it as some kind of "exact solution" + # although this doesn't really exist... + initial_condition::InitialCondition + + boundary_conditions::BoundaryConditions + source_terms::SourceTerms + solver::Solver + cache::Cache + performance_counter::PerformanceCounter + + function SemidiscretizationHyperbolic{ + Mesh, Equations, InitialCondition, + BoundaryConditions, SourceTerms, Solver, + Cache, + }( + mesh::Mesh, equations::Equations, + initial_condition::InitialCondition, + boundary_conditions::BoundaryConditions, + source_terms::SourceTerms, + solver::Solver, + cache::Cache + ) where { + Mesh, Equations, + InitialCondition, + BoundaryConditions, + SourceTerms, + Solver, + Cache, + } + performance_counter = PerformanceCounter() + + new( + mesh, equations, initial_condition, boundary_conditions, source_terms, + solver, cache, performance_counter + ) + end + end + + """ + SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver; + source_terms=nothing, + boundary_conditions=boundary_condition_periodic, + RealT=real(solver), + uEltype=RealT, + initial_cache=NamedTuple()) + + Construct a semidiscretization of a hyperbolic PDE. + """ + function SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver; + source_terms = nothing, + boundary_conditions = boundary_condition_periodic, + # `RealT` is used as real type for node locations etc. + # while `uEltype` is used as element type of solutions etc. + RealT = real(solver), uEltype = RealT, + initial_cache = NamedTuple() + ) + @assert ndims(mesh) == ndims(equations) + + cache = (; + create_cache(mesh, equations, solver, RealT, uEltype)..., + initial_cache..., + ) + _boundary_conditions = digest_boundary_conditions( + boundary_conditions, mesh, solver, + cache + ) + + check_periodicity_mesh_boundary_conditions(mesh, _boundary_conditions) + + SemidiscretizationHyperbolic{ + typeof(mesh), typeof(equations), + typeof(initial_condition), + typeof(_boundary_conditions), typeof(source_terms), + typeof(solver), typeof(cache), + }( + mesh, equations, + initial_condition, + _boundary_conditions, + source_terms, solver, + cache + ) + end + + # Create a new semidiscretization but change some parameters compared to the input. + # `Base.similar` follows a related concept but would require us to `copy` the `mesh`, + # which would impact the performance. Instead, `SciMLBase.remake` has exactly the + # semantics we want to use here. In particular, it allows us to re-use mutable parts, + # e.g. `remake(semi).mesh === semi.mesh`. + function remake( + semi::SemidiscretizationHyperbolic; uEltype = real(semi.solver), + mesh = semi.mesh, + equations = semi.equations, + initial_condition = semi.initial_condition, + solver = semi.solver, + source_terms = semi.source_terms, + boundary_conditions = semi.boundary_conditions + ) + # TODO: Which parts do we want to `remake`? At least the solver needs some + # special care if shock-capturing volume integrals are used (because of + # the indicators and their own caches...). + SemidiscretizationHyperbolic( + mesh, equations, initial_condition, solver; + source_terms, boundary_conditions, uEltype + ) + end + + # general fallback + function digest_boundary_conditions(boundary_conditions, mesh, solver, cache) + boundary_conditions + end + + # general fallback + function digest_boundary_conditions( + boundary_conditions::BoundaryConditionPeriodic, + mesh, solver, cache + ) + boundary_conditions + end + + # resolve ambiguities with definitions below + function digest_boundary_conditions( + boundary_conditions::BoundaryConditionPeriodic, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, solver, + cache + ) + boundary_conditions + end + + function digest_boundary_conditions( + boundary_conditions::BoundaryConditionPeriodic, + mesh::Union{TreeMesh{2}, StructuredMesh{2}}, solver, + cache + ) + boundary_conditions + end + + function digest_boundary_conditions( + boundary_conditions::BoundaryConditionPeriodic, + mesh::Union{TreeMesh{3}, StructuredMesh{3}}, solver, + cache + ) + boundary_conditions + end + + # allow passing a single BC that get converted into a tuple of BCs + # on (mapped) hypercube domains + function digest_boundary_conditions( + boundary_conditions, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, solver, + cache + ) + (; x_neg = boundary_conditions, x_pos = boundary_conditions) + end + + function digest_boundary_conditions( + boundary_conditions, + mesh::Union{TreeMesh{2}, StructuredMesh{2}}, solver, + cache + ) + (; + x_neg = boundary_conditions, x_pos = boundary_conditions, + y_neg = boundary_conditions, y_pos = boundary_conditions, + ) + end + + function digest_boundary_conditions( + boundary_conditions, + mesh::Union{TreeMesh{3}, StructuredMesh{3}}, solver, + cache + ) + (; + x_neg = boundary_conditions, x_pos = boundary_conditions, + y_neg = boundary_conditions, y_pos = boundary_conditions, + z_neg = boundary_conditions, z_pos = boundary_conditions, + ) + end + + # allow passing a tuple of BCs that get converted into a named tuple to make it + # self-documenting on (mapped) hypercube domains + function digest_boundary_conditions( + boundary_conditions::NTuple{2, Any}, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, solver, + cache + ) + (; x_neg = boundary_conditions[1], x_pos = boundary_conditions[2]) + end + + function digest_boundary_conditions( + boundary_conditions::NTuple{4, Any}, + mesh::Union{TreeMesh{2}, StructuredMesh{2}}, solver, + cache + ) + (; + x_neg = boundary_conditions[1], x_pos = boundary_conditions[2], + y_neg = boundary_conditions[3], y_pos = boundary_conditions[4], + ) + end + + function digest_boundary_conditions( + boundary_conditions::NTuple{6, Any}, + mesh::Union{TreeMesh{3}, StructuredMesh{3}}, solver, + cache + ) + (; + x_neg = boundary_conditions[1], x_pos = boundary_conditions[2], + y_neg = boundary_conditions[3], y_pos = boundary_conditions[4], + z_neg = boundary_conditions[5], z_pos = boundary_conditions[6], + ) + end + + # allow passing named tuples of BCs constructed in an arbitrary order + # on (mapped) hypercube domains + function digest_boundary_conditions( + boundary_conditions::NamedTuple{Keys, ValueTypes}, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, solver, + cache + ) where {Keys, ValueTypes <: NTuple{2, Any}} + @unpack x_neg, x_pos = boundary_conditions + (; x_neg, x_pos) + end + + function digest_boundary_conditions( + boundary_conditions::NamedTuple{Keys, ValueTypes}, + mesh::Union{TreeMesh{2}, StructuredMesh{2}}, solver, + cache + ) where {Keys, ValueTypes <: NTuple{4, Any}} + @unpack x_neg, x_pos, y_neg, y_pos = boundary_conditions + (; x_neg, x_pos, y_neg, y_pos) + end + + function digest_boundary_conditions( + boundary_conditions::NamedTuple{Keys, ValueTypes}, + mesh::Union{TreeMesh{3}, StructuredMesh{3}}, solver, + cache + ) where {Keys, ValueTypes <: NTuple{6, Any}} + @unpack x_neg, x_pos, y_neg, y_pos, z_neg, z_pos = boundary_conditions + (; x_neg, x_pos, y_neg, y_pos, z_neg, z_pos) + end + + # sort the boundary conditions from a dictionary and into tuples + function digest_boundary_conditions(boundary_conditions::Dict, mesh, solver, cache) + UnstructuredSortedBoundaryTypes(boundary_conditions, cache) + end + + function digest_boundary_conditions( + boundary_conditions::AbstractArray, mesh, solver, + cache + ) + throw(ArgumentError("Please use a (named) tuple instead of an (abstract) array to supply multiple boundary conditions (to improve performance).")) + end + + # No checks for these meshes yet available + function check_periodicity_mesh_boundary_conditions( + mesh::Union{ + P4estMesh, + UnstructuredMesh2D, + T8codeMesh, + DGMultiMesh, + }, + boundary_conditions + ) + end + + # No actions needed for periodic boundary conditions + function check_periodicity_mesh_boundary_conditions( + mesh::Union{ + TreeMesh, + StructuredMesh, + }, + boundary_conditions::BoundaryConditionPeriodic + ) + end + + function check_periodicity_mesh_boundary_conditions_x(mesh, x_neg, x_pos) + if isperiodic(mesh, 1) && + ( + x_neg != BoundaryConditionPeriodic() || + x_pos != BoundaryConditionPeriodic() + ) + @error "For periodic mesh non-periodic boundary conditions in x-direction are supplied." + end + end + + function check_periodicity_mesh_boundary_conditions_y(mesh, y_neg, y_pos) + if isperiodic(mesh, 2) && + ( + y_neg != BoundaryConditionPeriodic() || + y_pos != BoundaryConditionPeriodic() + ) + @error "For periodic mesh non-periodic boundary conditions in y-direction are supplied." + end + end + + function check_periodicity_mesh_boundary_conditions_z(mesh, z_neg, z_pos) + if isperiodic(mesh, 3) && + ( + z_neg != BoundaryConditionPeriodic() || + z_pos != BoundaryConditionPeriodic() + ) + @error "For periodic mesh non-periodic boundary conditions in z-direction are supplied." + end + end + + function check_periodicity_mesh_boundary_conditions( + mesh::Union{ + TreeMesh{1}, + StructuredMesh{1}, + }, + boundary_conditions::Union{ + NamedTuple, + Tuple, + } + ) + check_periodicity_mesh_boundary_conditions_x( + mesh, boundary_conditions[1], + boundary_conditions[2] + ) + end + + function check_periodicity_mesh_boundary_conditions( + mesh::Union{ + TreeMesh{2}, + StructuredMesh{2}, + StructuredMeshView{2}, + }, + boundary_conditions::Union{ + NamedTuple, + Tuple, + } + ) + check_periodicity_mesh_boundary_conditions_x( + mesh, boundary_conditions[1], + boundary_conditions[2] + ) + check_periodicity_mesh_boundary_conditions_y( + mesh, boundary_conditions[3], + boundary_conditions[4] + ) + end + + function check_periodicity_mesh_boundary_conditions( + mesh::Union{ + TreeMesh{3}, + StructuredMesh{3}, + }, + boundary_conditions::Union{ + NamedTuple, + Tuple, + } + ) + check_periodicity_mesh_boundary_conditions_x( + mesh, boundary_conditions[1], + boundary_conditions[2] + ) + check_periodicity_mesh_boundary_conditions_y( + mesh, boundary_conditions[3], + boundary_conditions[4] + ) + check_periodicity_mesh_boundary_conditions_z( + mesh, boundary_conditions[5], + boundary_conditions[6] + ) + end + + function Base.show(io::IO, semi::SemidiscretizationHyperbolic) + @nospecialize semi # reduce precompilation time + + print(io, "SemidiscretizationHyperbolic(") + print(io, semi.mesh) + print(io, ", ", semi.equations) + print(io, ", ", semi.initial_condition) + print(io, ", ", semi.boundary_conditions) + print(io, ", ", semi.source_terms) + print(io, ", ", semi.solver) + print(io, ", cache(") + for (idx, key) in enumerate(keys(semi.cache)) + idx > 1 && print(io, " ") + print(io, key) + end + print(io, "))") + end + + function Base.show(io::IO, ::MIME"text/plain", semi::SemidiscretizationHyperbolic) + @nospecialize semi # reduce precompilation time + + if get(io, :compact, false) + show(io, semi) + else + summary_header(io, "SemidiscretizationHyperbolic") + summary_line(io, "#spatial dimensions", ndims(semi.equations)) + summary_line(io, "mesh", semi.mesh) + summary_line(io, "equations", semi.equations |> typeof |> nameof) + summary_line(io, "initial condition", semi.initial_condition) + + print_boundary_conditions(io, semi) + + summary_line(io, "source terms", semi.source_terms) + summary_line(io, "solver", semi.solver |> typeof |> nameof) + summary_line(io, "total #DOFs per field", ndofsglobal(semi)) + summary_footer(io) + end + end + + # type alias for dispatch in printing of boundary conditions + #! format: off const SemiHypMeshBCSolver{Mesh, BoundaryConditions, Solver} = SemidiscretizationHyperbolic{Mesh, Equations, @@ -331,96 +435,116 @@ const SemiHypMeshBCSolver{Mesh, BoundaryConditions, Solver} = Solver} where {Equations, InitialCondition, SourceTerms} -#! format: on - -# generic fallback: print the type of semi.boundary_condition. -function print_boundary_conditions(io, semi::SemiHypMeshBCSolver) - summary_line(io, "boundary conditions", typeof(semi.boundary_conditions)) -end - -function print_boundary_conditions(io, - semi::SemiHypMeshBCSolver{<:Any, - <:UnstructuredSortedBoundaryTypes}) - @unpack boundary_conditions = semi - @unpack boundary_dictionary = boundary_conditions - summary_line(io, "boundary conditions", length(boundary_dictionary)) - for (boundary_name, boundary_condition) in boundary_dictionary - summary_line(increment_indent(io), boundary_name, typeof(boundary_condition)) - end -end - -function print_boundary_conditions(io, semi::SemiHypMeshBCSolver{<:Any, <:NamedTuple}) - @unpack boundary_conditions = semi - summary_line(io, "boundary conditions", length(boundary_conditions)) - bc_names = keys(boundary_conditions) - for (i, bc_name) in enumerate(bc_names) - summary_line(increment_indent(io), String(bc_name), - typeof(boundary_conditions[i])) - end -end - -function print_boundary_conditions(io, - semi::SemiHypMeshBCSolver{<:Union{TreeMesh, - StructuredMesh}, - <:Union{Tuple, NamedTuple, - AbstractArray}}) - summary_line(io, "boundary conditions", 2 * ndims(semi)) - bcs = semi.boundary_conditions - - summary_line(increment_indent(io), "negative x", bcs[1]) - summary_line(increment_indent(io), "positive x", bcs[2]) - if ndims(semi) > 1 - summary_line(increment_indent(io), "negative y", bcs[3]) - summary_line(increment_indent(io), "positive y", bcs[4]) - end - if ndims(semi) > 2 - summary_line(increment_indent(io), "negative z", bcs[5]) - summary_line(increment_indent(io), "positive z", bcs[6]) - end -end - -@inline Base.ndims(semi::SemidiscretizationHyperbolic) = ndims(semi.mesh) - -@inline nvariables(semi::SemidiscretizationHyperbolic) = nvariables(semi.equations) - -@inline Base.real(semi::SemidiscretizationHyperbolic) = real(semi.solver) - -@inline function mesh_equations_solver_cache(semi::SemidiscretizationHyperbolic) - @unpack mesh, equations, solver, cache = semi - return mesh, equations, solver, cache -end - -function calc_error_norms(func, u_ode, t, analyzer, semi::SemidiscretizationHyperbolic, - cache_analysis) - @unpack mesh, equations, initial_condition, solver, cache = semi - u = wrap_array(u_ode, mesh, equations, solver, cache) - - calc_error_norms(func, u, t, analyzer, mesh, equations, initial_condition, solver, - cache, cache_analysis) -end - -function compute_coefficients(t, semi::SemidiscretizationHyperbolic) - # Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl` - compute_coefficients(semi.initial_condition, t, semi) -end - -function compute_coefficients!(u_ode, t, semi::SemidiscretizationHyperbolic) - compute_coefficients!(u_ode, semi.initial_condition, t, semi) -end - -function rhs!(du_ode, u_ode, semi::SemidiscretizationHyperbolic, t) - @unpack mesh, equations, initial_condition, boundary_conditions, source_terms, solver, cache = semi - - u = wrap_array(u_ode, mesh, equations, solver, cache) - du = wrap_array(du_ode, mesh, equations, solver, cache) - - # TODO: Taal decide, do we need to pass the mesh? - time_start = time_ns() - @trixi_timeit timer() "rhs!" rhs!(du, u, t, mesh, equations, initial_condition, - boundary_conditions, source_terms, solver, cache) - runtime = time_ns() - time_start - put!(semi.performance_counter, runtime) - - return nothing -end + #! format: on + + # generic fallback: print the type of semi.boundary_condition. + function print_boundary_conditions(io, semi::SemiHypMeshBCSolver) + summary_line(io, "boundary conditions", typeof(semi.boundary_conditions)) + end + + function print_boundary_conditions( + io, + semi::SemiHypMeshBCSolver{ + <:Any, + <:UnstructuredSortedBoundaryTypes, + } + ) + @unpack boundary_conditions = semi + @unpack boundary_dictionary = boundary_conditions + summary_line(io, "boundary conditions", length(boundary_dictionary)) + for (boundary_name, boundary_condition) in boundary_dictionary + summary_line(increment_indent(io), boundary_name, typeof(boundary_condition)) + end + end + + function print_boundary_conditions(io, semi::SemiHypMeshBCSolver{<:Any, <:NamedTuple}) + @unpack boundary_conditions = semi + summary_line(io, "boundary conditions", length(boundary_conditions)) + bc_names = keys(boundary_conditions) + for (i, bc_name) in enumerate(bc_names) + summary_line( + increment_indent(io), String(bc_name), + typeof(boundary_conditions[i]) + ) + end + end + + function print_boundary_conditions( + io, + semi::SemiHypMeshBCSolver{ + <:Union{ + TreeMesh, + StructuredMesh, + }, + <:Union{ + Tuple, NamedTuple, + AbstractArray, + }, + } + ) + summary_line(io, "boundary conditions", 2 * ndims(semi)) + bcs = semi.boundary_conditions + + summary_line(increment_indent(io), "negative x", bcs[1]) + summary_line(increment_indent(io), "positive x", bcs[2]) + if ndims(semi) > 1 + summary_line(increment_indent(io), "negative y", bcs[3]) + summary_line(increment_indent(io), "positive y", bcs[4]) + end + if ndims(semi) > 2 + summary_line(increment_indent(io), "negative z", bcs[5]) + summary_line(increment_indent(io), "positive z", bcs[6]) + end + end + + @inline Base.ndims(semi::SemidiscretizationHyperbolic) = ndims(semi.mesh) + + @inline nvariables(semi::SemidiscretizationHyperbolic) = nvariables(semi.equations) + + @inline Base.real(semi::SemidiscretizationHyperbolic) = real(semi.solver) + + @inline function mesh_equations_solver_cache(semi::SemidiscretizationHyperbolic) + @unpack mesh, equations, solver, cache = semi + return mesh, equations, solver, cache + end + + function calc_error_norms( + func, u_ode, t, analyzer, semi::SemidiscretizationHyperbolic, + cache_analysis + ) + @unpack mesh, equations, initial_condition, solver, cache = semi + u = wrap_array(u_ode, mesh, equations, solver, cache) + + calc_error_norms( + func, u, t, analyzer, mesh, equations, initial_condition, solver, + cache, cache_analysis + ) + end + + function compute_coefficients(t, semi::SemidiscretizationHyperbolic) + # Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl` + compute_coefficients(semi.initial_condition, t, semi) + end + + function compute_coefficients!(u_ode, t, semi::SemidiscretizationHyperbolic) + compute_coefficients!(u_ode, semi.initial_condition, t, semi) + end + + function rhs!(du_ode, u_ode, semi::SemidiscretizationHyperbolic, t) + @unpack mesh, equations, initial_condition, boundary_conditions, source_terms, solver, cache = semi + + u = wrap_array(u_ode, mesh, equations, solver, cache) + du = wrap_array(du_ode, mesh, equations, solver, cache) + + # TODO: Taal decide, do we need to pass the mesh? + time_start = time_ns() + @trixi_timeit timer() "rhs!" rhs!( + du, u, t, mesh, equations, initial_condition, + boundary_conditions, source_terms, solver, cache + ) + runtime = time_ns() - time_start + put!(semi.performance_counter, runtime) + + return nothing + end end # @muladd diff --git a/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl b/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl index 16f8da21c1..42240c293e 100644 --- a/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl +++ b/src/semidiscretization/semidiscretization_hyperbolic_parabolic.jl @@ -3,347 +3,398 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - SemidiscretizationHyperbolicParabolic - -A struct containing everything needed to describe a spatial semidiscretization -of a mixed hyperbolic-parabolic conservation law. -""" -struct SemidiscretizationHyperbolicParabolic{Mesh, Equations, EquationsParabolic, - InitialCondition, - BoundaryConditions, - BoundaryConditionsParabolic, - SourceTerms, Solver, SolverParabolic, - Cache, CacheParabolic} <: - AbstractSemidiscretization - mesh::Mesh - - equations::Equations - equations_parabolic::EquationsParabolic - - # This guy is a bit messy since we abuse it as some kind of "exact solution" - # although this doesn't really exist... - initial_condition::InitialCondition - - boundary_conditions::BoundaryConditions - boundary_conditions_parabolic::BoundaryConditionsParabolic - - source_terms::SourceTerms - - solver::Solver - solver_parabolic::SolverParabolic - - cache::Cache - cache_parabolic::CacheParabolic - - performance_counter::PerformanceCounterList{2} - - function SemidiscretizationHyperbolicParabolic{Mesh, Equations, EquationsParabolic, - InitialCondition, BoundaryConditions, - BoundaryConditionsParabolic, - SourceTerms, Solver, - SolverParabolic, Cache, - CacheParabolic}(mesh::Mesh, - equations::Equations, - equations_parabolic::EquationsParabolic, - initial_condition::InitialCondition, - boundary_conditions::BoundaryConditions, - boundary_conditions_parabolic::BoundaryConditionsParabolic, - source_terms::SourceTerms, - solver::Solver, - solver_parabolic::SolverParabolic, - cache::Cache, - cache_parabolic::CacheParabolic) where { - Mesh, - Equations, - EquationsParabolic, - InitialCondition, - BoundaryConditions, - BoundaryConditionsParabolic, - SourceTerms, - Solver, - SolverParabolic, - Cache, - CacheParabolic - } - @assert ndims(mesh) == ndims(equations) - - # Todo: assert nvariables(equations)==nvariables(equations_parabolic) - - performance_counter = PerformanceCounterList{2}(false) - - new(mesh, equations, equations_parabolic, initial_condition, - boundary_conditions, boundary_conditions_parabolic, - source_terms, solver, solver_parabolic, cache, cache_parabolic, - performance_counter) + #! format: noindent + + """ + SemidiscretizationHyperbolicParabolic + + A struct containing everything needed to describe a spatial semidiscretization + of a mixed hyperbolic-parabolic conservation law. + """ + struct SemidiscretizationHyperbolicParabolic{ + Mesh, Equations, EquationsParabolic, + InitialCondition, + BoundaryConditions, + BoundaryConditionsParabolic, + SourceTerms, Solver, SolverParabolic, + Cache, CacheParabolic, + } <: + AbstractSemidiscretization + mesh::Mesh + + equations::Equations + equations_parabolic::EquationsParabolic + + # This guy is a bit messy since we abuse it as some kind of "exact solution" + # although this doesn't really exist... + initial_condition::InitialCondition + + boundary_conditions::BoundaryConditions + boundary_conditions_parabolic::BoundaryConditionsParabolic + + source_terms::SourceTerms + + solver::Solver + solver_parabolic::SolverParabolic + + cache::Cache + cache_parabolic::CacheParabolic + + performance_counter::PerformanceCounterList{2} + + function SemidiscretizationHyperbolicParabolic{ + Mesh, Equations, EquationsParabolic, + InitialCondition, BoundaryConditions, + BoundaryConditionsParabolic, + SourceTerms, Solver, + SolverParabolic, Cache, + CacheParabolic, + }( + mesh::Mesh, + equations::Equations, + equations_parabolic::EquationsParabolic, + initial_condition::InitialCondition, + boundary_conditions::BoundaryConditions, + boundary_conditions_parabolic::BoundaryConditionsParabolic, + source_terms::SourceTerms, + solver::Solver, + solver_parabolic::SolverParabolic, + cache::Cache, + cache_parabolic::CacheParabolic + ) where { + Mesh, + Equations, + EquationsParabolic, + InitialCondition, + BoundaryConditions, + BoundaryConditionsParabolic, + SourceTerms, + Solver, + SolverParabolic, + Cache, + CacheParabolic, + } + @assert ndims(mesh) == ndims(equations) + + # Todo: assert nvariables(equations)==nvariables(equations_parabolic) + + performance_counter = PerformanceCounterList{2}(false) + + new( + mesh, equations, equations_parabolic, initial_condition, + boundary_conditions, boundary_conditions_parabolic, + source_terms, solver, solver_parabolic, cache, cache_parabolic, + performance_counter + ) + end end -end - -""" - SemidiscretizationHyperbolicParabolic(mesh, both_equations, initial_condition, solver; - solver_parabolic=default_parabolic_solver(), - source_terms=nothing, - both_boundary_conditions=(boundary_condition_periodic, boundary_condition_periodic), - RealT=real(solver), - uEltype=RealT, - both_initial_caches=(NamedTuple(), NamedTuple())) - -Construct a semidiscretization of a hyperbolic-parabolic PDE. -""" -function SemidiscretizationHyperbolicParabolic(mesh, equations::Tuple, - initial_condition, solver; - solver_parabolic = default_parabolic_solver(), - source_terms = nothing, - boundary_conditions = (boundary_condition_periodic, - boundary_condition_periodic), - # `RealT` is used as real type for node locations etc. - # while `uEltype` is used as element type of solutions etc. - RealT = real(solver), uEltype = RealT, - initial_caches = (NamedTuple(), - NamedTuple())) - equations_hyperbolic, equations_parabolic = equations - boundary_conditions_hyperbolic, boundary_conditions_parabolic = boundary_conditions - initial_hyperbolic_cache, initial_cache_parabolic = initial_caches - - return SemidiscretizationHyperbolicParabolic(mesh, equations_hyperbolic, - equations_parabolic, - initial_condition, solver; - solver_parabolic, source_terms, - boundary_conditions = boundary_conditions_hyperbolic, - boundary_conditions_parabolic = boundary_conditions_parabolic, - RealT, uEltype, - initial_cache = initial_hyperbolic_cache, - initial_cache_parabolic = initial_cache_parabolic) -end - -function SemidiscretizationHyperbolicParabolic(mesh, equations, equations_parabolic, - initial_condition, solver; - solver_parabolic = default_parabolic_solver(), - source_terms = nothing, - boundary_conditions = boundary_condition_periodic, - boundary_conditions_parabolic = boundary_condition_periodic, - # `RealT` is used as real type for node locations etc. - # while `uEltype` is used as element type of solutions etc. - RealT = real(solver), uEltype = RealT, - initial_cache = NamedTuple(), - initial_cache_parabolic = NamedTuple()) - cache = (; create_cache(mesh, equations, solver, RealT, uEltype)..., - initial_cache...) - _boundary_conditions = digest_boundary_conditions(boundary_conditions, mesh, solver, - cache) - _boundary_conditions_parabolic = digest_boundary_conditions(boundary_conditions_parabolic, - mesh, solver, cache) - - check_periodicity_mesh_boundary_conditions(mesh, _boundary_conditions) - - cache_parabolic = (; - create_cache_parabolic(mesh, equations, equations_parabolic, - solver, solver_parabolic, RealT, - uEltype)..., - initial_cache_parabolic...) - - SemidiscretizationHyperbolicParabolic{typeof(mesh), typeof(equations), - typeof(equations_parabolic), - typeof(initial_condition), - typeof(_boundary_conditions), - typeof(_boundary_conditions_parabolic), - typeof(source_terms), typeof(solver), - typeof(solver_parabolic), typeof(cache), - typeof(cache_parabolic)}(mesh, equations, - equations_parabolic, - initial_condition, - _boundary_conditions, - _boundary_conditions_parabolic, - source_terms, - solver, - solver_parabolic, - cache, - cache_parabolic) -end - -# Create a new semidiscretization but change some parameters compared to the input. -# `Base.similar` follows a related concept but would require us to `copy` the `mesh`, -# which would impact the performance. Instead, `SciMLBase.remake` has exactly the -# semantics we want to use here. In particular, it allows us to re-use mutable parts, -# e.g. `remake(semi).mesh === semi.mesh`. -function remake(semi::SemidiscretizationHyperbolicParabolic; - uEltype = real(semi.solver), - mesh = semi.mesh, - equations = semi.equations, - equations_parabolic = semi.equations_parabolic, - initial_condition = semi.initial_condition, - solver = semi.solver, - solver_parabolic = semi.solver_parabolic, - source_terms = semi.source_terms, - boundary_conditions = semi.boundary_conditions, - boundary_conditions_parabolic = semi.boundary_conditions_parabolic) - # TODO: Which parts do we want to `remake`? At least the solver needs some - # special care if shock-capturing volume integrals are used (because of - # the indicators and their own caches...). - SemidiscretizationHyperbolicParabolic(mesh, equations, equations_parabolic, - initial_condition, solver; solver_parabolic, - source_terms, boundary_conditions, - boundary_conditions_parabolic, uEltype) -end - -function Base.show(io::IO, semi::SemidiscretizationHyperbolicParabolic) - @nospecialize semi # reduce precompilation time - - print(io, "SemidiscretizationHyperbolicParabolic(") - print(io, semi.mesh) - print(io, ", ", semi.equations) - print(io, ", ", semi.equations_parabolic) - print(io, ", ", semi.initial_condition) - print(io, ", ", semi.boundary_conditions) - print(io, ", ", semi.boundary_conditions_parabolic) - print(io, ", ", semi.source_terms) - print(io, ", ", semi.solver) - print(io, ", ", semi.solver_parabolic) - print(io, ", cache(") - for (idx, key) in enumerate(keys(semi.cache)) - idx > 1 && print(io, " ") - print(io, key) + + """ + SemidiscretizationHyperbolicParabolic(mesh, both_equations, initial_condition, solver; + solver_parabolic=default_parabolic_solver(), + source_terms=nothing, + both_boundary_conditions=(boundary_condition_periodic, boundary_condition_periodic), + RealT=real(solver), + uEltype=RealT, + both_initial_caches=(NamedTuple(), NamedTuple())) + + Construct a semidiscretization of a hyperbolic-parabolic PDE. + """ + function SemidiscretizationHyperbolicParabolic( + mesh, equations::Tuple, + initial_condition, solver; + solver_parabolic = default_parabolic_solver(), + source_terms = nothing, + boundary_conditions = ( + boundary_condition_periodic, + boundary_condition_periodic, + ), + # `RealT` is used as real type for node locations etc. + # while `uEltype` is used as element type of solutions etc. + RealT = real(solver), uEltype = RealT, + initial_caches = ( + NamedTuple(), + NamedTuple(), + ) + ) + equations_hyperbolic, equations_parabolic = equations + boundary_conditions_hyperbolic, boundary_conditions_parabolic = boundary_conditions + initial_hyperbolic_cache, initial_cache_parabolic = initial_caches + + return SemidiscretizationHyperbolicParabolic( + mesh, equations_hyperbolic, + equations_parabolic, + initial_condition, solver; + solver_parabolic, source_terms, + boundary_conditions = boundary_conditions_hyperbolic, + boundary_conditions_parabolic = boundary_conditions_parabolic, + RealT, uEltype, + initial_cache = initial_hyperbolic_cache, + initial_cache_parabolic = initial_cache_parabolic + ) + end + + function SemidiscretizationHyperbolicParabolic( + mesh, equations, equations_parabolic, + initial_condition, solver; + solver_parabolic = default_parabolic_solver(), + source_terms = nothing, + boundary_conditions = boundary_condition_periodic, + boundary_conditions_parabolic = boundary_condition_periodic, + # `RealT` is used as real type for node locations etc. + # while `uEltype` is used as element type of solutions etc. + RealT = real(solver), uEltype = RealT, + initial_cache = NamedTuple(), + initial_cache_parabolic = NamedTuple() + ) + cache = (; + create_cache(mesh, equations, solver, RealT, uEltype)..., + initial_cache..., + ) + _boundary_conditions = digest_boundary_conditions( + boundary_conditions, mesh, solver, + cache + ) + _boundary_conditions_parabolic = digest_boundary_conditions( + boundary_conditions_parabolic, + mesh, solver, cache + ) + + check_periodicity_mesh_boundary_conditions(mesh, _boundary_conditions) + + cache_parabolic = (; + create_cache_parabolic( + mesh, equations, equations_parabolic, + solver, solver_parabolic, RealT, + uEltype + )..., + initial_cache_parabolic..., + ) + + SemidiscretizationHyperbolicParabolic{ + typeof(mesh), typeof(equations), + typeof(equations_parabolic), + typeof(initial_condition), + typeof(_boundary_conditions), + typeof(_boundary_conditions_parabolic), + typeof(source_terms), typeof(solver), + typeof(solver_parabolic), typeof(cache), + typeof(cache_parabolic), + }( + mesh, equations, + equations_parabolic, + initial_condition, + _boundary_conditions, + _boundary_conditions_parabolic, + source_terms, + solver, + solver_parabolic, + cache, + cache_parabolic + ) + end + + # Create a new semidiscretization but change some parameters compared to the input. + # `Base.similar` follows a related concept but would require us to `copy` the `mesh`, + # which would impact the performance. Instead, `SciMLBase.remake` has exactly the + # semantics we want to use here. In particular, it allows us to re-use mutable parts, + # e.g. `remake(semi).mesh === semi.mesh`. + function remake( + semi::SemidiscretizationHyperbolicParabolic; + uEltype = real(semi.solver), + mesh = semi.mesh, + equations = semi.equations, + equations_parabolic = semi.equations_parabolic, + initial_condition = semi.initial_condition, + solver = semi.solver, + solver_parabolic = semi.solver_parabolic, + source_terms = semi.source_terms, + boundary_conditions = semi.boundary_conditions, + boundary_conditions_parabolic = semi.boundary_conditions_parabolic + ) + # TODO: Which parts do we want to `remake`? At least the solver needs some + # special care if shock-capturing volume integrals are used (because of + # the indicators and their own caches...). + SemidiscretizationHyperbolicParabolic( + mesh, equations, equations_parabolic, + initial_condition, solver; solver_parabolic, + source_terms, boundary_conditions, + boundary_conditions_parabolic, uEltype + ) + end + + function Base.show(io::IO, semi::SemidiscretizationHyperbolicParabolic) + @nospecialize semi # reduce precompilation time + + print(io, "SemidiscretizationHyperbolicParabolic(") + print(io, semi.mesh) + print(io, ", ", semi.equations) + print(io, ", ", semi.equations_parabolic) + print(io, ", ", semi.initial_condition) + print(io, ", ", semi.boundary_conditions) + print(io, ", ", semi.boundary_conditions_parabolic) + print(io, ", ", semi.source_terms) + print(io, ", ", semi.solver) + print(io, ", ", semi.solver_parabolic) + print(io, ", cache(") + for (idx, key) in enumerate(keys(semi.cache)) + idx > 1 && print(io, " ") + print(io, key) + end + print(io, "))") end - print(io, "))") -end - -function Base.show(io::IO, ::MIME"text/plain", - semi::SemidiscretizationHyperbolicParabolic) - @nospecialize semi # reduce precompilation time - - if get(io, :compact, false) - show(io, semi) - else - summary_header(io, "SemidiscretizationHyperbolicParabolic") - summary_line(io, "#spatial dimensions", ndims(semi.equations)) - summary_line(io, "mesh", semi.mesh) - summary_line(io, "hyperbolic equations", semi.equations |> typeof |> nameof) - summary_line(io, "parabolic equations", - semi.equations_parabolic |> typeof |> nameof) - summary_line(io, "initial condition", semi.initial_condition) - - # print_boundary_conditions(io, semi) - - summary_line(io, "source terms", semi.source_terms) - summary_line(io, "solver", semi.solver |> typeof |> nameof) - summary_line(io, "parabolic solver", semi.solver_parabolic |> typeof |> nameof) - summary_line(io, "total #DOFs per field", ndofsglobal(semi)) - summary_footer(io) + + function Base.show( + io::IO, ::MIME"text/plain", + semi::SemidiscretizationHyperbolicParabolic + ) + @nospecialize semi # reduce precompilation time + + if get(io, :compact, false) + show(io, semi) + else + summary_header(io, "SemidiscretizationHyperbolicParabolic") + summary_line(io, "#spatial dimensions", ndims(semi.equations)) + summary_line(io, "mesh", semi.mesh) + summary_line(io, "hyperbolic equations", semi.equations |> typeof |> nameof) + summary_line( + io, "parabolic equations", + semi.equations_parabolic |> typeof |> nameof + ) + summary_line(io, "initial condition", semi.initial_condition) + + # print_boundary_conditions(io, semi) + + summary_line(io, "source terms", semi.source_terms) + summary_line(io, "solver", semi.solver |> typeof |> nameof) + summary_line(io, "parabolic solver", semi.solver_parabolic |> typeof |> nameof) + summary_line(io, "total #DOFs per field", ndofsglobal(semi)) + summary_footer(io) + end end -end - -@inline Base.ndims(semi::SemidiscretizationHyperbolicParabolic) = ndims(semi.mesh) - -@inline function nvariables(semi::SemidiscretizationHyperbolicParabolic) - nvariables(semi.equations) -end - -@inline Base.real(semi::SemidiscretizationHyperbolicParabolic) = real(semi.solver) - -# retain dispatch on hyperbolic equations only -@inline function mesh_equations_solver_cache(semi::SemidiscretizationHyperbolicParabolic) - @unpack mesh, equations, solver, cache = semi - return mesh, equations, solver, cache -end - -function calc_error_norms(func, u_ode, t, analyzer, - semi::SemidiscretizationHyperbolicParabolic, cache_analysis) - @unpack mesh, equations, initial_condition, solver, cache = semi - u = wrap_array(u_ode, mesh, equations, solver, cache) - - calc_error_norms(func, u, t, analyzer, mesh, equations, initial_condition, solver, - cache, cache_analysis) -end - -function compute_coefficients(t, semi::SemidiscretizationHyperbolicParabolic) - # Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl` - compute_coefficients(semi.initial_condition, t, semi) -end - -function compute_coefficients!(u_ode, t, semi::SemidiscretizationHyperbolicParabolic) - compute_coefficients!(u_ode, semi.initial_condition, t, semi) -end - -""" - semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan) - -Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan` -that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/). -The parabolic right-hand side is the first function of the split ODE problem and -will be used by default by the implicit part of IMEX methods from the -SciML ecosystem. -""" -function semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan; - reset_threads = true) - # Optionally reset Polyester.jl threads. See - # https://github.com/trixi-framework/Trixi.jl/issues/1583 - # https://github.com/JuliaSIMD/Polyester.jl/issues/30 - if reset_threads - Polyester.reset_threads!() + + @inline Base.ndims(semi::SemidiscretizationHyperbolicParabolic) = ndims(semi.mesh) + + @inline function nvariables(semi::SemidiscretizationHyperbolicParabolic) + nvariables(semi.equations) end - u0_ode = compute_coefficients(first(tspan), semi) - # TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using - # mpi_isparallel() && MPI.Barrier(mpi_comm()) - # See https://github.com/trixi-framework/Trixi.jl/issues/328 - iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs! - # Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the - # first function implicitly and the second one explicitly. Thus, we pass the - # stiffer parabolic function first. - return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi) -end - -function rhs!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t) - @unpack mesh, equations, initial_condition, boundary_conditions, source_terms, solver, cache = semi - - u = wrap_array(u_ode, mesh, equations, solver, cache) - du = wrap_array(du_ode, mesh, equations, solver, cache) - - # TODO: Taal decide, do we need to pass the mesh? - time_start = time_ns() - @trixi_timeit timer() "rhs!" rhs!(du, u, t, mesh, equations, initial_condition, - boundary_conditions, source_terms, solver, cache) - runtime = time_ns() - time_start - put!(semi.performance_counter.counters[1], runtime) - - return nothing -end - -function rhs_parabolic!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t) - @unpack mesh, equations_parabolic, initial_condition, boundary_conditions_parabolic, source_terms, solver, solver_parabolic, cache, cache_parabolic = semi - - u = wrap_array(u_ode, mesh, equations_parabolic, solver, cache_parabolic) - du = wrap_array(du_ode, mesh, equations_parabolic, solver, cache_parabolic) - - # TODO: Taal decide, do we need to pass the mesh? - time_start = time_ns() - @trixi_timeit timer() "parabolic rhs!" rhs_parabolic!(du, u, t, mesh, - equations_parabolic, - initial_condition, - boundary_conditions_parabolic, - source_terms, - solver, solver_parabolic, - cache, cache_parabolic) - runtime = time_ns() - time_start - put!(semi.performance_counter.counters[2], runtime) - - return nothing -end - -function _jacobian_ad_forward(semi::SemidiscretizationHyperbolicParabolic, t0, u0_ode, - du_ode, config) - new_semi = remake(semi, uEltype = eltype(config)) - - du_ode_hyp = Vector{eltype(config)}(undef, length(du_ode)) - J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode - # Implementation of split ODE problem in OrdinaryDiffEq - rhs!(du_ode_hyp, u_ode, new_semi, t0) - rhs_parabolic!(du_ode, u_ode, new_semi, t0) - du_ode .+= du_ode_hyp + @inline Base.real(semi::SemidiscretizationHyperbolicParabolic) = real(semi.solver) + + # retain dispatch on hyperbolic equations only + @inline function mesh_equations_solver_cache(semi::SemidiscretizationHyperbolicParabolic) + @unpack mesh, equations, solver, cache = semi + return mesh, equations, solver, cache end - return J -end + function calc_error_norms( + func, u_ode, t, analyzer, + semi::SemidiscretizationHyperbolicParabolic, cache_analysis + ) + @unpack mesh, equations, initial_condition, solver, cache = semi + u = wrap_array(u_ode, mesh, equations, solver, cache) + + calc_error_norms( + func, u, t, analyzer, mesh, equations, initial_condition, solver, + cache, cache_analysis + ) + end + + function compute_coefficients(t, semi::SemidiscretizationHyperbolicParabolic) + # Call `compute_coefficients` in `src/semidiscretization/semidiscretization.jl` + compute_coefficients(semi.initial_condition, t, semi) + end + + function compute_coefficients!(u_ode, t, semi::SemidiscretizationHyperbolicParabolic) + compute_coefficients!(u_ode, semi.initial_condition, t, semi) + end + + """ + semidiscretize(semi::SemidiscretizationHyperbolicParabolic, tspan) + + Wrap the semidiscretization `semi` as a split ODE problem in the time interval `tspan` + that can be passed to `solve` from the [SciML ecosystem](https://diffeq.sciml.ai/latest/). + The parabolic right-hand side is the first function of the split ODE problem and + will be used by default by the implicit part of IMEX methods from the + SciML ecosystem. + """ + function semidiscretize( + semi::SemidiscretizationHyperbolicParabolic, tspan; + reset_threads = true + ) + # Optionally reset Polyester.jl threads. See + # https://github.com/trixi-framework/Trixi.jl/issues/1583 + # https://github.com/JuliaSIMD/Polyester.jl/issues/30 + if reset_threads + Polyester.reset_threads!() + end + + u0_ode = compute_coefficients(first(tspan), semi) + # TODO: MPI, do we want to synchronize loading and print debug statements, e.g. using + # mpi_isparallel() && MPI.Barrier(mpi_comm()) + # See https://github.com/trixi-framework/Trixi.jl/issues/328 + iip = true # is-inplace, i.e., we modify a vector when calling rhs_parabolic!, rhs! + # Note that the IMEX time integration methods of OrdinaryDiffEq.jl treat the + # first function implicitly and the second one explicitly. Thus, we pass the + # stiffer parabolic function first. + return SplitODEProblem{iip}(rhs_parabolic!, rhs!, u0_ode, tspan, semi) + end + + function rhs!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t) + @unpack mesh, equations, initial_condition, boundary_conditions, source_terms, solver, cache = semi + + u = wrap_array(u_ode, mesh, equations, solver, cache) + du = wrap_array(du_ode, mesh, equations, solver, cache) + + # TODO: Taal decide, do we need to pass the mesh? + time_start = time_ns() + @trixi_timeit timer() "rhs!" rhs!( + du, u, t, mesh, equations, initial_condition, + boundary_conditions, source_terms, solver, cache + ) + runtime = time_ns() - time_start + put!(semi.performance_counter.counters[1], runtime) + + return nothing + end + + function rhs_parabolic!(du_ode, u_ode, semi::SemidiscretizationHyperbolicParabolic, t) + @unpack mesh, equations_parabolic, initial_condition, boundary_conditions_parabolic, source_terms, solver, solver_parabolic, cache, cache_parabolic = semi + + u = wrap_array(u_ode, mesh, equations_parabolic, solver, cache_parabolic) + du = wrap_array(du_ode, mesh, equations_parabolic, solver, cache_parabolic) + + # TODO: Taal decide, do we need to pass the mesh? + time_start = time_ns() + @trixi_timeit timer() "parabolic rhs!" rhs_parabolic!( + du, u, t, mesh, + equations_parabolic, + initial_condition, + boundary_conditions_parabolic, + source_terms, + solver, solver_parabolic, + cache, cache_parabolic + ) + runtime = time_ns() - time_start + put!(semi.performance_counter.counters[2], runtime) + + return nothing + end + + function _jacobian_ad_forward( + semi::SemidiscretizationHyperbolicParabolic, t0, u0_ode, + du_ode, config + ) + new_semi = remake(semi, uEltype = eltype(config)) + + du_ode_hyp = Vector{eltype(config)}(undef, length(du_ode)) + J = ForwardDiff.jacobian(du_ode, u0_ode, config) do du_ode, u_ode + # Implementation of split ODE problem in OrdinaryDiffEq + rhs!(du_ode_hyp, u_ode, new_semi, t0) + rhs_parabolic!(du_ode, u_ode, new_semi, t0) + du_ode .+= du_ode_hyp + end + + return J + end end # @muladd diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 628e39e6a8..cb124465a8 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -3,757 +3,835 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -abstract type AbstractVolumeIntegral end - -function get_element_variables!(element_variables, u, mesh, equations, - volume_integral::AbstractVolumeIntegral, dg, cache) - nothing -end - -function get_node_variables!(node_variables, mesh, equations, - volume_integral::AbstractVolumeIntegral, dg, cache) - nothing -end - -""" - VolumeIntegralStrongForm() - -The classical strong form volume integral type for FD/DG methods. -""" -struct VolumeIntegralStrongForm <: AbstractVolumeIntegral end - -""" - VolumeIntegralWeakForm() - -The classical weak form volume integral type for DG methods as explained in -standard textbooks. - -## References - -- Kopriva (2009) - Implementing Spectral Methods for Partial Differential Equations: - Algorithms for Scientists and Engineers - [doi: 10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) -- Hesthaven, Warburton (2007) - Nodal Discontinuous Galerkin Methods: Algorithms, Analysis, and - Applications - [doi: 10.1007/978-0-387-72067-8](https://doi.org/10.1007/978-0-387-72067-8) - -`VolumeIntegralWeakForm()` is only implemented for conserved terms as -non-conservative terms should always be discretized in conjunction with a flux-splitting scheme, -see [`VolumeIntegralFluxDifferencing`](@ref). -This treatment is required to achieve, e.g., entropy-stability or well-balancedness. -""" -struct VolumeIntegralWeakForm <: AbstractVolumeIntegral end - -create_cache(mesh, equations, ::VolumeIntegralWeakForm, dg, uEltype) = NamedTuple() - -""" - VolumeIntegralFluxDifferencing(volume_flux) - -Volume integral type for DG methods based on SBP operators and flux differencing -using a symmetric two-point `volume_flux`. This `volume_flux` needs to satisfy -the interface of numerical fluxes in Trixi.jl. - -## References - -- LeFloch, Mercier, Rohde (2002) - Fully Discrete, Entropy Conservative Schemes of Arbitrary Order - [doi: 10.1137/S003614290240069X](https://doi.org/10.1137/S003614290240069X) -- Fisher, Carpenter (2013) - High-order entropy stable finite difference schemes for nonlinear - conservation laws: Finite domains - [doi: 10.1016/j.jcp.2013.06.014](https://doi.org/10.1016/j.jcp.2013.06.014) -- Hendrik Ranocha (2017) - Comparison of Some Entropy Conservative Numerical Fluxes for the Euler Equations - [arXiv: 1701.02264](https://arxiv.org/abs/1701.02264) - [doi: 10.1007/s10915-017-0618-1](https://doi.org/10.1007/s10915-017-0618-1) -- Chen, Shu (2017) - Entropy stable high order discontinuous Galerkin methods with suitable - quadrature rules for hyperbolic conservation laws - [doi: 10.1016/j.jcp.2017.05.025](https://doi.org/10.1016/j.jcp.2017.05.025) -""" -struct VolumeIntegralFluxDifferencing{VolumeFlux} <: AbstractVolumeIntegral - volume_flux::VolumeFlux -end - -function Base.show(io::IO, ::MIME"text/plain", integral::VolumeIntegralFluxDifferencing) - @nospecialize integral # reduce precompilation time - - if get(io, :compact, false) - show(io, integral) - else - setup = [ - "volume flux" => integral.volume_flux, - ] - summary_box(io, "VolumeIntegralFluxDifferencing", setup) - end -end - -""" - VolumeIntegralShockCapturingHG(indicator; volume_flux_dg=flux_central, - volume_flux_fv=flux_lax_friedrichs) - -Shock-capturing volume integral type for DG methods using a convex blending of -the finite volume method with numerical flux `volume_flux_fv` and the -[`VolumeIntegralFluxDifferencing`](@ref) with volume flux `volume_flux_dg`. -The amount of blending is determined by the `indicator`, e.g., -[`IndicatorHennemannGassner`](@ref). - -## References - -- Hennemann, Gassner (2020) - "A provably entropy stable subcell shock capturing approach for high order split form DG" - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -struct VolumeIntegralShockCapturingHG{VolumeFluxDG, VolumeFluxFV, Indicator} <: - AbstractVolumeIntegral - volume_flux_dg::VolumeFluxDG # symmetric, e.g. split-form or entropy-conservative - volume_flux_fv::VolumeFluxFV # non-symmetric in general, e.g. entropy-dissipative - indicator::Indicator -end - -function VolumeIntegralShockCapturingHG(indicator; volume_flux_dg = flux_central, - volume_flux_fv = flux_lax_friedrichs) - VolumeIntegralShockCapturingHG{typeof(volume_flux_dg), typeof(volume_flux_fv), - typeof(indicator)}(volume_flux_dg, volume_flux_fv, - indicator) -end - -function Base.show(io::IO, mime::MIME"text/plain", - integral::VolumeIntegralShockCapturingHG) - @nospecialize integral # reduce precompilation time - - if get(io, :compact, false) - show(io, integral) - else - summary_header(io, "VolumeIntegralShockCapturingHG") - summary_line(io, "volume flux DG", integral.volume_flux_dg) - summary_line(io, "volume flux FV", integral.volume_flux_fv) - summary_line(io, "indicator", integral.indicator |> typeof |> nameof) - show(increment_indent(io), mime, integral.indicator) - summary_footer(io) - end -end - -function get_element_variables!(element_variables, u, mesh, equations, - volume_integral::VolumeIntegralShockCapturingHG, dg, - cache) - # call the indicator to get up-to-date values for IO - volume_integral.indicator(u, mesh, equations, dg, cache) - get_element_variables!(element_variables, volume_integral.indicator, - volume_integral) -end - -""" - VolumeIntegralPureLGLFiniteVolume(volume_flux_fv) - -A volume integral that only uses the subcell finite volume schemes of the -[`VolumeIntegralShockCapturingHG`](@ref). - -This gives a formally O(1)-accurate finite volume scheme on an LGL-type subcell -mesh (LGL = Legendre-Gauss-Lobatto). - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. - -## References - -- Hennemann, Gassner (2020) - "A provably entropy stable subcell shock capturing approach for high order split form DG" - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -struct VolumeIntegralPureLGLFiniteVolume{VolumeFluxFV} <: AbstractVolumeIntegral - volume_flux_fv::VolumeFluxFV # non-symmetric in general, e.g. entropy-dissipative -end -# TODO: Figure out if this can also be used for Gauss nodes, not just LGL, and adjust the name accordingly - -function Base.show(io::IO, ::MIME"text/plain", - integral::VolumeIntegralPureLGLFiniteVolume) - @nospecialize integral # reduce precompilation time - - if get(io, :compact, false) - show(io, integral) - else - setup = [ - "FV flux" => integral.volume_flux_fv, - ] - summary_box(io, "VolumeIntegralPureLGLFiniteVolume", setup) - end -end - -""" - VolumeIntegralSubcellLimiting(limiter; - volume_flux_dg, volume_flux_fv) - -A subcell limiting volume integral type for DG methods based on subcell blending approaches -with a low-order FV method. Used with limiter [`SubcellLimiterIDP`](@ref). - -!!! note - Subcell limiting methods are not fully functional on non-conforming meshes. This is - mainly because the implementation assumes that low- and high-order schemes have the same - surface terms, which is not guaranteed for non-conforming meshes. The low-order scheme - with a high-order mortar is not invariant domain preserving. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -struct VolumeIntegralSubcellLimiting{VolumeFluxDG, VolumeFluxFV, Limiter} <: - AbstractVolumeIntegral - volume_flux_dg::VolumeFluxDG - volume_flux_fv::VolumeFluxFV - limiter::Limiter -end - -function VolumeIntegralSubcellLimiting(limiter; volume_flux_dg, - volume_flux_fv) - VolumeIntegralSubcellLimiting{typeof(volume_flux_dg), typeof(volume_flux_fv), - typeof(limiter)}(volume_flux_dg, volume_flux_fv, - limiter) -end - -function Base.show(io::IO, mime::MIME"text/plain", - integral::VolumeIntegralSubcellLimiting) - @nospecialize integral # reduce precompilation time - - if get(io, :compact, false) - show(io, integral) - else - summary_header(io, "VolumeIntegralSubcellLimiting") - summary_line(io, "volume flux DG", integral.volume_flux_dg) - summary_line(io, "volume flux FV", integral.volume_flux_fv) - summary_line(io, "limiter", integral.limiter |> typeof |> nameof) - show(increment_indent(io), mime, integral.limiter) - summary_footer(io) - end -end - -function get_node_variables!(node_variables, mesh, equations, - volume_integral::VolumeIntegralSubcellLimiting, dg, cache) - # While for the element-wise limiting with `VolumeIntegralShockCapturingHG` the indicator is - # called here to get up-to-date values for IO, this is not easily possible in this case - # because the calculation is very integrated into the method. - # See also https://github.com/trixi-framework/Trixi.jl/pull/1611#discussion_r1334553206. - # Therefore, the coefficients at `t=t^{n-1}` are saved. Thus, the coefficients of the first - # stored solution (initial condition) are not yet defined and were manually set to `NaN`. - get_node_variables!(node_variables, volume_integral.limiter, volume_integral, - equations) -end - -# TODO: FD. Should this definition live in a different file because it is -# not strictly a DG method? -""" - VolumeIntegralUpwind(splitting) - -Specialized volume integral for finite difference summation-by-parts (FDSBP) -solvers. Can be used together with the upwind SBP operators of Mattsson (2017) -implemented in SummationByPartsOperators.jl. The `splitting` controls the -discretization. - -See also [`splitting_steger_warming`](@ref), [`splitting_lax_friedrichs`](@ref), -[`splitting_vanleer_haenel`](@ref). - -## References - -- Mattsson (2017) - Diagonal-norm upwind SBP operators - [doi: 10.1016/j.jcp.2017.01.042](https://doi.org/10.1016/j.jcp.2017.01.042) - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. -""" -struct VolumeIntegralUpwind{FluxSplitting} <: AbstractVolumeIntegral - splitting::FluxSplitting -end - -function Base.show(io::IO, ::MIME"text/plain", integral::VolumeIntegralUpwind) - @nospecialize integral # reduce precompilation time - - if get(io, :compact, false) - show(io, integral) - else - setup = [ - "flux splitting" => integral.splitting, - ] - summary_box(io, "VolumeIntegralUpwind", setup) - end -end - -abstract type AbstractSurfaceIntegral end - -""" - SurfaceIntegralWeakForm(surface_flux=flux_central) - -The classical weak form surface integral type for DG methods as explained in standard -textbooks. - -See also [`VolumeIntegralWeakForm`](@ref). - -## References - -- Kopriva (2009) - Implementing Spectral Methods for Partial Differential Equations: - Algorithms for Scientists and Engineers - [doi: 10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) -- Hesthaven, Warburton (2007) - Nodal Discontinuous Galerkin Methods: Algorithms, Analysis, and - Applications - [doi: 10.1007/978-0-387-72067-8](https://doi.org/10.1007/978-0-387-72067-8) -""" -struct SurfaceIntegralWeakForm{SurfaceFlux} <: AbstractSurfaceIntegral - surface_flux::SurfaceFlux -end - -SurfaceIntegralWeakForm() = SurfaceIntegralWeakForm(flux_central) - -function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralWeakForm) - @nospecialize integral # reduce precompilation time - - if get(io, :compact, false) - show(io, integral) - else - setup = [ - "surface flux" => integral.surface_flux, - ] - summary_box(io, "SurfaceIntegralWeakForm", setup) - end -end - -""" - SurfaceIntegralStrongForm(surface_flux=flux_central) - -The classical strong form surface integral type for FD/DG methods. - -See also [`VolumeIntegralStrongForm`](@ref). -""" -struct SurfaceIntegralStrongForm{SurfaceFlux} <: AbstractSurfaceIntegral - surface_flux::SurfaceFlux -end - -SurfaceIntegralStrongForm() = SurfaceIntegralStrongForm(flux_central) - -function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralStrongForm) - @nospecialize integral # reduce precompilation time - - if get(io, :compact, false) - show(io, integral) - else - setup = [ - "surface flux" => integral.surface_flux, - ] - summary_box(io, "SurfaceIntegralStrongForm", setup) - end -end - -# TODO: FD. Should this definition live in a different file because it is -# not strictly a DG method? -""" - SurfaceIntegralUpwind(splitting) - -Couple elements with upwind simultaneous approximation terms (SATs) -that use a particular flux `splitting`, e.g., -[`splitting_steger_warming`](@ref). - -See also [`VolumeIntegralUpwind`](@ref). - -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. -""" -struct SurfaceIntegralUpwind{FluxSplitting} <: AbstractSurfaceIntegral - splitting::FluxSplitting -end - -function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralUpwind) - @nospecialize integral # reduce precompilation time - - if get(io, :compact, false) - show(io, integral) - else - setup = [ - "flux splitting" => integral.splitting, - ] - summary_box(io, "SurfaceIntegralUpwind", setup) - end -end - -""" - DG(; basis, mortar, surface_integral, volume_integral) - -Create a discontinuous Galerkin method. -If [`basis isa LobattoLegendreBasis`](@ref LobattoLegendreBasis), -this creates a [`DGSEM`](@ref). -""" -struct DG{Basis, Mortar, SurfaceIntegral, VolumeIntegral} - basis::Basis - mortar::Mortar - surface_integral::SurfaceIntegral - volume_integral::VolumeIntegral -end - -function Base.show(io::IO, dg::DG) - @nospecialize dg # reduce precompilation time - - print(io, "DG{", real(dg), "}(") - print(io, dg.basis) - print(io, ", ", dg.mortar) - print(io, ", ", dg.surface_integral) - print(io, ", ", dg.volume_integral) - print(io, ")") -end - -function Base.show(io::IO, mime::MIME"text/plain", dg::DG) - @nospecialize dg # reduce precompilation time - - if get(io, :compact, false) - show(io, dg) - else - summary_header(io, "DG{" * string(real(dg)) * "}") - summary_line(io, "basis", dg.basis) - summary_line(io, "mortar", dg.mortar) - summary_line(io, "surface integral", dg.surface_integral |> typeof |> nameof) - show(increment_indent(io), mime, dg.surface_integral) - summary_line(io, "volume integral", dg.volume_integral |> typeof |> nameof) - if !(dg.volume_integral isa VolumeIntegralWeakForm) && - !(dg.volume_integral isa VolumeIntegralStrongForm) - show(increment_indent(io), mime, dg.volume_integral) + #! format: noindent + + abstract type AbstractVolumeIntegral end + + function get_element_variables!( + element_variables, u, mesh, equations, + volume_integral::AbstractVolumeIntegral, dg, cache + ) + nothing + end + + function get_node_variables!( + node_variables, mesh, equations, + volume_integral::AbstractVolumeIntegral, dg, cache + ) + nothing + end + + """ + VolumeIntegralStrongForm() + + The classical strong form volume integral type for FD/DG methods. + """ + struct VolumeIntegralStrongForm <: AbstractVolumeIntegral end + + """ + VolumeIntegralWeakForm() + + The classical weak form volume integral type for DG methods as explained in + standard textbooks. + + ## References + + - Kopriva (2009) + Implementing Spectral Methods for Partial Differential Equations: + Algorithms for Scientists and Engineers + [doi: 10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) + - Hesthaven, Warburton (2007) + Nodal Discontinuous Galerkin Methods: Algorithms, Analysis, and + Applications + [doi: 10.1007/978-0-387-72067-8](https://doi.org/10.1007/978-0-387-72067-8) + + `VolumeIntegralWeakForm()` is only implemented for conserved terms as + non-conservative terms should always be discretized in conjunction with a flux-splitting scheme, + see [`VolumeIntegralFluxDifferencing`](@ref). + This treatment is required to achieve, e.g., entropy-stability or well-balancedness. + """ + struct VolumeIntegralWeakForm <: AbstractVolumeIntegral end + + create_cache(mesh, equations, ::VolumeIntegralWeakForm, dg, uEltype) = NamedTuple() + + """ + VolumeIntegralFluxDifferencing(volume_flux) + + Volume integral type for DG methods based on SBP operators and flux differencing + using a symmetric two-point `volume_flux`. This `volume_flux` needs to satisfy + the interface of numerical fluxes in Trixi.jl. + + ## References + + - LeFloch, Mercier, Rohde (2002) + Fully Discrete, Entropy Conservative Schemes of Arbitrary Order + [doi: 10.1137/S003614290240069X](https://doi.org/10.1137/S003614290240069X) + - Fisher, Carpenter (2013) + High-order entropy stable finite difference schemes for nonlinear + conservation laws: Finite domains + [doi: 10.1016/j.jcp.2013.06.014](https://doi.org/10.1016/j.jcp.2013.06.014) + - Hendrik Ranocha (2017) + Comparison of Some Entropy Conservative Numerical Fluxes for the Euler Equations + [arXiv: 1701.02264](https://arxiv.org/abs/1701.02264) + [doi: 10.1007/s10915-017-0618-1](https://doi.org/10.1007/s10915-017-0618-1) + - Chen, Shu (2017) + Entropy stable high order discontinuous Galerkin methods with suitable + quadrature rules for hyperbolic conservation laws + [doi: 10.1016/j.jcp.2017.05.025](https://doi.org/10.1016/j.jcp.2017.05.025) + """ + struct VolumeIntegralFluxDifferencing{VolumeFlux} <: AbstractVolumeIntegral + volume_flux::VolumeFlux + end + + function Base.show(io::IO, ::MIME"text/plain", integral::VolumeIntegralFluxDifferencing) + @nospecialize integral # reduce precompilation time + + if get(io, :compact, false) + show(io, integral) + else + setup = [ + "volume flux" => integral.volume_flux, + ] + summary_box(io, "VolumeIntegralFluxDifferencing", setup) end - summary_footer(io) - end -end - -Base.summary(io::IO, dg::DG) = print(io, "DG(" * summary(dg.basis) * ")") - -@inline Base.real(dg::DG) = real(dg.basis) - -function get_element_variables!(element_variables, u, mesh, equations, dg::DG, cache) - get_element_variables!(element_variables, u, mesh, equations, dg.volume_integral, - dg, cache) -end - -function get_node_variables!(node_variables, mesh, equations, dg::DG, cache) - get_node_variables!(node_variables, mesh, equations, dg.volume_integral, dg, cache) -end - -const MeshesDGSEM = Union{TreeMesh, StructuredMesh, StructuredMeshView, - UnstructuredMesh2D, - P4estMesh, T8codeMesh} - -@inline function ndofs(mesh::MeshesDGSEM, dg::DG, cache) - nelements(cache.elements) * nnodes(dg)^ndims(mesh) -end - -# TODO: Taal performance, 1:nnodes(dg) vs. Base.OneTo(nnodes(dg)) vs. SOneTo(nnodes(dg)) for DGSEM -""" - eachnode(dg::DG) - -Return an iterator over the indices that specify the location in relevant data structures -for the nodes in `dg`. -In particular, not the nodes themselves are returned. -""" -@inline eachnode(dg::DG) = Base.OneTo(nnodes(dg)) -@inline nnodes(dg::DG) = nnodes(dg.basis) - -# This is used in some more general analysis code and needs to dispatch on the -# `mesh` for some combinations of mesh/solver. -@inline nelements(mesh, dg::DG, cache) = nelements(dg, cache) -@inline function ndofsglobal(mesh, dg::DG, cache) - nelementsglobal(mesh, dg, cache) * nnodes(dg)^ndims(mesh) -end - -""" - eachelement(dg::DG, cache) - -Return an iterator over the indices that specify the location in relevant data structures -for the elements in `cache`. -In particular, not the elements themselves are returned. -""" -@inline eachelement(dg::DG, cache) = Base.OneTo(nelements(dg, cache)) - -""" - eachinterface(dg::DG, cache) - -Return an iterator over the indices that specify the location in relevant data structures -for the interfaces in `cache`. -In particular, not the interfaces themselves are returned. -""" -@inline eachinterface(dg::DG, cache) = Base.OneTo(ninterfaces(dg, cache)) - -""" - eachboundary(dg::DG, cache) - -Return an iterator over the indices that specify the location in relevant data structures -for the boundaries in `cache`. -In particular, not the boundaries themselves are returned. -""" -@inline eachboundary(dg::DG, cache) = Base.OneTo(nboundaries(dg, cache)) - -""" - eachmortar(dg::DG, cache) - -Return an iterator over the indices that specify the location in relevant data structures -for the mortars in `cache`. -In particular, not the mortars themselves are returned. -""" -@inline eachmortar(dg::DG, cache) = Base.OneTo(nmortars(dg, cache)) - -""" - eachmpiinterface(dg::DG, cache) - -Return an iterator over the indices that specify the location in relevant data structures -for the MPI interfaces in `cache`. -In particular, not the interfaces themselves are returned. -""" -@inline eachmpiinterface(dg::DG, cache) = Base.OneTo(nmpiinterfaces(dg, cache)) - -""" - eachmpimortar(dg::DG, cache) - -Return an iterator over the indices that specify the location in relevant data structures -for the MPI mortars in `cache`. -In particular, not the mortars themselves are returned. -""" -@inline eachmpimortar(dg::DG, cache) = Base.OneTo(nmpimortars(dg, cache)) - -@inline nelements(dg::DG, cache) = nelements(cache.elements) -@inline function nelementsglobal(mesh, dg::DG, cache) - mpi_isparallel() ? cache.mpi_cache.n_elements_global : nelements(dg, cache) -end -@inline ninterfaces(dg::DG, cache) = ninterfaces(cache.interfaces) -@inline nboundaries(dg::DG, cache) = nboundaries(cache.boundaries) -@inline nmortars(dg::DG, cache) = nmortars(cache.mortars) -@inline nmpiinterfaces(dg::DG, cache) = nmpiinterfaces(cache.mpi_interfaces) -@inline nmpimortars(dg::DG, cache) = nmpimortars(cache.mpi_mortars) - -# The following functions assume an array-of-structs memory layout -# We would like to experiment with different memory layout choices -# in the future, see -# - https://github.com/trixi-framework/Trixi.jl/issues/88 -# - https://github.com/trixi-framework/Trixi.jl/issues/87 -# - https://github.com/trixi-framework/Trixi.jl/issues/86 -@inline function get_node_coords(x, equations, solver::DG, indices...) - SVector(ntuple(@inline(idx->x[idx, indices...]), Val(ndims(equations)))) -end - -@inline function get_node_vars(u, equations, solver::DG, indices...) - # There is a cut-off at `n == 10` inside of the method - # `ntuple(f::F, n::Integer) where F` in Base at ntuple.jl:17 - # in Julia `v1.5`, leading to type instabilities if - # more than ten variables are used. That's why we use - # `Val(...)` below. - # We use `@inline` to make sure that the `getindex` calls are - # really inlined, which might be the default choice of the Julia - # compiler for standard `Array`s but not necessarily for more - # advanced array types such as `PtrArray`s, cf. - # https://github.com/JuliaSIMD/VectorizationBase.jl/issues/55 - SVector(ntuple(@inline(v->u[v, indices...]), Val(nvariables(equations)))) -end - -@inline function get_surface_node_vars(u, equations, solver::DG, indices...) - # There is a cut-off at `n == 10` inside of the method - # `ntuple(f::F, n::Integer) where F` in Base at ntuple.jl:17 - # in Julia `v1.5`, leading to type instabilities if - # more than ten variables are used. That's why we use - # `Val(...)` below. - u_ll = SVector(ntuple(@inline(v->u[1, v, indices...]), Val(nvariables(equations)))) - u_rr = SVector(ntuple(@inline(v->u[2, v, indices...]), Val(nvariables(equations)))) - return u_ll, u_rr -end - -@inline function set_node_vars!(u, u_node, equations, solver::DG, indices...) - for v in eachvariable(equations) - u[v, indices...] = u_node[v] - end - return nothing -end - -@inline function add_to_node_vars!(u, u_node, equations, solver::DG, indices...) - for v in eachvariable(equations) - u[v, indices...] += u_node[v] - end - return nothing -end - -# Use this function instead of `add_to_node_vars` to speed up -# multiply-and-add-to-node-vars operations -# See https://github.com/trixi-framework/Trixi.jl/pull/643 -@inline function multiply_add_to_node_vars!(u, factor, u_node, equations, solver::DG, - indices...) - for v in eachvariable(equations) - u[v, indices...] = u[v, indices...] + factor * u_node[v] - end - return nothing -end - -# Used for analyze_solution -SolutionAnalyzer(dg::DG; kwargs...) = SolutionAnalyzer(dg.basis; kwargs...) - -AdaptorAMR(mesh, dg::DG) = AdaptorL2(dg.basis) - -# General structs for discretizations based on the basic principle of -# DGSEM (discontinuous Galerkin spectral element method) -include("dgsem/dgsem.jl") - -# Finite difference methods using summation by parts (SBP) operators -# These methods are very similar to DG methods since they also impose interface -# and boundary conditions weakly. Thus, these methods can re-use a lot of -# functionality implemented for DGSEM. -include("fdsbp_tree/fdsbp.jl") -include("fdsbp_unstructured/fdsbp.jl") - -function allocate_coefficients(mesh::AbstractMesh, equations, dg::DG, cache) - # We must allocate a `Vector` in order to be able to `resize!` it (AMR). - # cf. wrap_array - zeros(eltype(cache.elements), - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache)) -end - -@inline function wrap_array(u_ode::AbstractVector, mesh::AbstractMesh, equations, - dg::DGSEM, cache) - @boundscheck begin - @assert length(u_ode) == - nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) end - # We would like to use - # reshape(u_ode, (nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., nelements(dg, cache))) - # but that results in - # ERROR: LoadError: cannot resize array with shared data - # when we resize! `u_ode` during AMR. - # - # !!! danger "Segfaults" - # Remember to `GC.@preserve` temporaries such as copies of `u_ode` - # and other stuff that is only used indirectly via `wrap_array` afterwards! - - # Currently, there are problems when AD is used with `PtrArray`s in broadcasts - # since LoopVectorization does not support `ForwardDiff.Dual`s. Hence, we use - # optimized `PtrArray`s whenever possible and fall back to plain `Array`s - # otherwise. - if _PREFERENCE_POLYESTER && LoopVectorization.check_args(u_ode) - # This version using `PtrArray`s from StrideArrays.jl is very fast and - # does not result in allocations. + + """ + VolumeIntegralShockCapturingHG(indicator; volume_flux_dg=flux_central, + volume_flux_fv=flux_lax_friedrichs) + + Shock-capturing volume integral type for DG methods using a convex blending of + the finite volume method with numerical flux `volume_flux_fv` and the + [`VolumeIntegralFluxDifferencing`](@ref) with volume flux `volume_flux_dg`. + The amount of blending is determined by the `indicator`, e.g., + [`IndicatorHennemannGassner`](@ref). + + ## References + + - Hennemann, Gassner (2020) + "A provably entropy stable subcell shock capturing approach for high order split form DG" + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + struct VolumeIntegralShockCapturingHG{VolumeFluxDG, VolumeFluxFV, Indicator} <: + AbstractVolumeIntegral + volume_flux_dg::VolumeFluxDG # symmetric, e.g. split-form or entropy-conservative + volume_flux_fv::VolumeFluxFV # non-symmetric in general, e.g. entropy-dissipative + indicator::Indicator + end + + function VolumeIntegralShockCapturingHG( + indicator; volume_flux_dg = flux_central, + volume_flux_fv = flux_lax_friedrichs + ) + VolumeIntegralShockCapturingHG{ + typeof(volume_flux_dg), typeof(volume_flux_fv), + typeof(indicator), + }( + volume_flux_dg, volume_flux_fv, + indicator + ) + end + + function Base.show( + io::IO, mime::MIME"text/plain", + integral::VolumeIntegralShockCapturingHG + ) + @nospecialize integral # reduce precompilation time + + if get(io, :compact, false) + show(io, integral) + else + summary_header(io, "VolumeIntegralShockCapturingHG") + summary_line(io, "volume flux DG", integral.volume_flux_dg) + summary_line(io, "volume flux FV", integral.volume_flux_fv) + summary_line(io, "indicator", integral.indicator |> typeof |> nameof) + show(increment_indent(io), mime, integral.indicator) + summary_footer(io) + end + end + + function get_element_variables!( + element_variables, u, mesh, equations, + volume_integral::VolumeIntegralShockCapturingHG, dg, + cache + ) + # call the indicator to get up-to-date values for IO + volume_integral.indicator(u, mesh, equations, dg, cache) + get_element_variables!( + element_variables, volume_integral.indicator, + volume_integral + ) + end + + """ + VolumeIntegralPureLGLFiniteVolume(volume_flux_fv) + + A volume integral that only uses the subcell finite volume schemes of the + [`VolumeIntegralShockCapturingHG`](@ref). + + This gives a formally O(1)-accurate finite volume scheme on an LGL-type subcell + mesh (LGL = Legendre-Gauss-Lobatto). + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + + ## References + + - Hennemann, Gassner (2020) + "A provably entropy stable subcell shock capturing approach for high order split form DG" + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + struct VolumeIntegralPureLGLFiniteVolume{VolumeFluxFV} <: AbstractVolumeIntegral + volume_flux_fv::VolumeFluxFV # non-symmetric in general, e.g. entropy-dissipative + end + # TODO: Figure out if this can also be used for Gauss nodes, not just LGL, and adjust the name accordingly + + function Base.show( + io::IO, ::MIME"text/plain", + integral::VolumeIntegralPureLGLFiniteVolume + ) + @nospecialize integral # reduce precompilation time + + if get(io, :compact, false) + show(io, integral) + else + setup = [ + "FV flux" => integral.volume_flux_fv, + ] + summary_box(io, "VolumeIntegralPureLGLFiniteVolume", setup) + end + end + + """ + VolumeIntegralSubcellLimiting(limiter; + volume_flux_dg, volume_flux_fv) + + A subcell limiting volume integral type for DG methods based on subcell blending approaches + with a low-order FV method. Used with limiter [`SubcellLimiterIDP`](@ref). + + !!! note + Subcell limiting methods are not fully functional on non-conforming meshes. This is + mainly because the implementation assumes that low- and high-order schemes have the same + surface terms, which is not guaranteed for non-conforming meshes. The low-order scheme + with a high-order mortar is not invariant domain preserving. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + struct VolumeIntegralSubcellLimiting{VolumeFluxDG, VolumeFluxFV, Limiter} <: + AbstractVolumeIntegral + volume_flux_dg::VolumeFluxDG + volume_flux_fv::VolumeFluxFV + limiter::Limiter + end + + function VolumeIntegralSubcellLimiting( + limiter; volume_flux_dg, + volume_flux_fv + ) + VolumeIntegralSubcellLimiting{ + typeof(volume_flux_dg), typeof(volume_flux_fv), + typeof(limiter), + }( + volume_flux_dg, volume_flux_fv, + limiter + ) + end + + function Base.show( + io::IO, mime::MIME"text/plain", + integral::VolumeIntegralSubcellLimiting + ) + @nospecialize integral # reduce precompilation time + + if get(io, :compact, false) + show(io, integral) + else + summary_header(io, "VolumeIntegralSubcellLimiting") + summary_line(io, "volume flux DG", integral.volume_flux_dg) + summary_line(io, "volume flux FV", integral.volume_flux_fv) + summary_line(io, "limiter", integral.limiter |> typeof |> nameof) + show(increment_indent(io), mime, integral.limiter) + summary_footer(io) + end + end + + function get_node_variables!( + node_variables, mesh, equations, + volume_integral::VolumeIntegralSubcellLimiting, dg, cache + ) + # While for the element-wise limiting with `VolumeIntegralShockCapturingHG` the indicator is + # called here to get up-to-date values for IO, this is not easily possible in this case + # because the calculation is very integrated into the method. + # See also https://github.com/trixi-framework/Trixi.jl/pull/1611#discussion_r1334553206. + # Therefore, the coefficients at `t=t^{n-1}` are saved. Thus, the coefficients of the first + # stored solution (initial condition) are not yet defined and were manually set to `NaN`. + get_node_variables!( + node_variables, volume_integral.limiter, volume_integral, + equations + ) + end + + # TODO: FD. Should this definition live in a different file because it is + # not strictly a DG method? + """ + VolumeIntegralUpwind(splitting) + + Specialized volume integral for finite difference summation-by-parts (FDSBP) + solvers. Can be used together with the upwind SBP operators of Mattsson (2017) + implemented in SummationByPartsOperators.jl. The `splitting` controls the + discretization. + + See also [`splitting_steger_warming`](@ref), [`splitting_lax_friedrichs`](@ref), + [`splitting_vanleer_haenel`](@ref). + + ## References + + - Mattsson (2017) + Diagonal-norm upwind SBP operators + [doi: 10.1016/j.jcp.2017.01.042](https://doi.org/10.1016/j.jcp.2017.01.042) + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + """ + struct VolumeIntegralUpwind{FluxSplitting} <: AbstractVolumeIntegral + splitting::FluxSplitting + end + + function Base.show(io::IO, ::MIME"text/plain", integral::VolumeIntegralUpwind) + @nospecialize integral # reduce precompilation time + + if get(io, :compact, false) + show(io, integral) + else + setup = [ + "flux splitting" => integral.splitting, + ] + summary_box(io, "VolumeIntegralUpwind", setup) + end + end + + abstract type AbstractSurfaceIntegral end + + """ + SurfaceIntegralWeakForm(surface_flux=flux_central) + + The classical weak form surface integral type for DG methods as explained in standard + textbooks. + + See also [`VolumeIntegralWeakForm`](@ref). + + ## References + + - Kopriva (2009) + Implementing Spectral Methods for Partial Differential Equations: + Algorithms for Scientists and Engineers + [doi: 10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) + - Hesthaven, Warburton (2007) + Nodal Discontinuous Galerkin Methods: Algorithms, Analysis, and + Applications + [doi: 10.1007/978-0-387-72067-8](https://doi.org/10.1007/978-0-387-72067-8) + """ + struct SurfaceIntegralWeakForm{SurfaceFlux} <: AbstractSurfaceIntegral + surface_flux::SurfaceFlux + end + + SurfaceIntegralWeakForm() = SurfaceIntegralWeakForm(flux_central) + + function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralWeakForm) + @nospecialize integral # reduce precompilation time + + if get(io, :compact, false) + show(io, integral) + else + setup = [ + "surface flux" => integral.surface_flux, + ] + summary_box(io, "SurfaceIntegralWeakForm", setup) + end + end + + """ + SurfaceIntegralStrongForm(surface_flux=flux_central) + + The classical strong form surface integral type for FD/DG methods. + + See also [`VolumeIntegralStrongForm`](@ref). + """ + struct SurfaceIntegralStrongForm{SurfaceFlux} <: AbstractSurfaceIntegral + surface_flux::SurfaceFlux + end + + SurfaceIntegralStrongForm() = SurfaceIntegralStrongForm(flux_central) + + function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralStrongForm) + @nospecialize integral # reduce precompilation time + + if get(io, :compact, false) + show(io, integral) + else + setup = [ + "surface flux" => integral.surface_flux, + ] + summary_box(io, "SurfaceIntegralStrongForm", setup) + end + end + + # TODO: FD. Should this definition live in a different file because it is + # not strictly a DG method? + """ + SurfaceIntegralUpwind(splitting) + + Couple elements with upwind simultaneous approximation terms (SATs) + that use a particular flux `splitting`, e.g., + [`splitting_steger_warming`](@ref). + + See also [`VolumeIntegralUpwind`](@ref). + + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + """ + struct SurfaceIntegralUpwind{FluxSplitting} <: AbstractSurfaceIntegral + splitting::FluxSplitting + end + + function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralUpwind) + @nospecialize integral # reduce precompilation time + + if get(io, :compact, false) + show(io, integral) + else + setup = [ + "flux splitting" => integral.splitting, + ] + summary_box(io, "SurfaceIntegralUpwind", setup) + end + end + + """ + DG(; basis, mortar, surface_integral, volume_integral) + + Create a discontinuous Galerkin method. + If [`basis isa LobattoLegendreBasis`](@ref LobattoLegendreBasis), + this creates a [`DGSEM`](@ref). + """ + struct DG{Basis, Mortar, SurfaceIntegral, VolumeIntegral} + basis::Basis + mortar::Mortar + surface_integral::SurfaceIntegral + volume_integral::VolumeIntegral + end + + function Base.show(io::IO, dg::DG) + @nospecialize dg # reduce precompilation time + + print(io, "DG{", real(dg), "}(") + print(io, dg.basis) + print(io, ", ", dg.mortar) + print(io, ", ", dg.surface_integral) + print(io, ", ", dg.volume_integral) + print(io, ")") + end + + function Base.show(io::IO, mime::MIME"text/plain", dg::DG) + @nospecialize dg # reduce precompilation time + + if get(io, :compact, false) + show(io, dg) + else + summary_header(io, "DG{" * string(real(dg)) * "}") + summary_line(io, "basis", dg.basis) + summary_line(io, "mortar", dg.mortar) + summary_line(io, "surface integral", dg.surface_integral |> typeof |> nameof) + show(increment_indent(io), mime, dg.surface_integral) + summary_line(io, "volume integral", dg.volume_integral |> typeof |> nameof) + if !(dg.volume_integral isa VolumeIntegralWeakForm) && + !(dg.volume_integral isa VolumeIntegralStrongForm) + show(increment_indent(io), mime, dg.volume_integral) + end + summary_footer(io) + end + end + + Base.summary(io::IO, dg::DG) = print(io, "DG(" * summary(dg.basis) * ")") + + @inline Base.real(dg::DG) = real(dg.basis) + + function get_element_variables!(element_variables, u, mesh, equations, dg::DG, cache) + get_element_variables!( + element_variables, u, mesh, equations, dg.volume_integral, + dg, cache + ) + end + + function get_node_variables!(node_variables, mesh, equations, dg::DG, cache) + get_node_variables!(node_variables, mesh, equations, dg.volume_integral, dg, cache) + end + + const MeshesDGSEM = Union{ + TreeMesh, StructuredMesh, StructuredMeshView, + UnstructuredMesh2D, + P4estMesh, T8codeMesh, + } + + @inline function ndofs(mesh::MeshesDGSEM, dg::DG, cache) + nelements(cache.elements) * nnodes(dg)^ndims(mesh) + end + + # TODO: Taal performance, 1:nnodes(dg) vs. Base.OneTo(nnodes(dg)) vs. SOneTo(nnodes(dg)) for DGSEM + """ + eachnode(dg::DG) + + Return an iterator over the indices that specify the location in relevant data structures + for the nodes in `dg`. + In particular, not the nodes themselves are returned. + """ + @inline eachnode(dg::DG) = Base.OneTo(nnodes(dg)) + @inline nnodes(dg::DG) = nnodes(dg.basis) + + # This is used in some more general analysis code and needs to dispatch on the + # `mesh` for some combinations of mesh/solver. + @inline nelements(mesh, dg::DG, cache) = nelements(dg, cache) + @inline function ndofsglobal(mesh, dg::DG, cache) + nelementsglobal(mesh, dg, cache) * nnodes(dg)^ndims(mesh) + end + + """ + eachelement(dg::DG, cache) + + Return an iterator over the indices that specify the location in relevant data structures + for the elements in `cache`. + In particular, not the elements themselves are returned. + """ + @inline eachelement(dg::DG, cache) = Base.OneTo(nelements(dg, cache)) + + """ + eachinterface(dg::DG, cache) + + Return an iterator over the indices that specify the location in relevant data structures + for the interfaces in `cache`. + In particular, not the interfaces themselves are returned. + """ + @inline eachinterface(dg::DG, cache) = Base.OneTo(ninterfaces(dg, cache)) + + """ + eachboundary(dg::DG, cache) + + Return an iterator over the indices that specify the location in relevant data structures + for the boundaries in `cache`. + In particular, not the boundaries themselves are returned. + """ + @inline eachboundary(dg::DG, cache) = Base.OneTo(nboundaries(dg, cache)) + + """ + eachmortar(dg::DG, cache) + + Return an iterator over the indices that specify the location in relevant data structures + for the mortars in `cache`. + In particular, not the mortars themselves are returned. + """ + @inline eachmortar(dg::DG, cache) = Base.OneTo(nmortars(dg, cache)) + + """ + eachmpiinterface(dg::DG, cache) + + Return an iterator over the indices that specify the location in relevant data structures + for the MPI interfaces in `cache`. + In particular, not the interfaces themselves are returned. + """ + @inline eachmpiinterface(dg::DG, cache) = Base.OneTo(nmpiinterfaces(dg, cache)) + + """ + eachmpimortar(dg::DG, cache) + + Return an iterator over the indices that specify the location in relevant data structures + for the MPI mortars in `cache`. + In particular, not the mortars themselves are returned. + """ + @inline eachmpimortar(dg::DG, cache) = Base.OneTo(nmpimortars(dg, cache)) + + @inline nelements(dg::DG, cache) = nelements(cache.elements) + @inline function nelementsglobal(mesh, dg::DG, cache) + mpi_isparallel() ? cache.mpi_cache.n_elements_global : nelements(dg, cache) + end + @inline ninterfaces(dg::DG, cache) = ninterfaces(cache.interfaces) + @inline nboundaries(dg::DG, cache) = nboundaries(cache.boundaries) + @inline nmortars(dg::DG, cache) = nmortars(cache.mortars) + @inline nmpiinterfaces(dg::DG, cache) = nmpiinterfaces(cache.mpi_interfaces) + @inline nmpimortars(dg::DG, cache) = nmpimortars(cache.mpi_mortars) + + # The following functions assume an array-of-structs memory layout + # We would like to experiment with different memory layout choices + # in the future, see + # - https://github.com/trixi-framework/Trixi.jl/issues/88 + # - https://github.com/trixi-framework/Trixi.jl/issues/87 + # - https://github.com/trixi-framework/Trixi.jl/issues/86 + @inline function get_node_coords(x, equations, solver::DG, indices...) + SVector(ntuple(@inline(idx -> x[idx, indices...]), Val(ndims(equations)))) + end + + @inline function get_node_vars(u, equations, solver::DG, indices...) + # There is a cut-off at `n == 10` inside of the method + # `ntuple(f::F, n::Integer) where F` in Base at ntuple.jl:17 + # in Julia `v1.5`, leading to type instabilities if + # more than ten variables are used. That's why we use + # `Val(...)` below. + # We use `@inline` to make sure that the `getindex` calls are + # really inlined, which might be the default choice of the Julia + # compiler for standard `Array`s but not necessarily for more + # advanced array types such as `PtrArray`s, cf. + # https://github.com/JuliaSIMD/VectorizationBase.jl/issues/55 + SVector(ntuple(@inline(v -> u[v, indices...]), Val(nvariables(equations)))) + end + + @inline function get_surface_node_vars(u, equations, solver::DG, indices...) + # There is a cut-off at `n == 10` inside of the method + # `ntuple(f::F, n::Integer) where F` in Base at ntuple.jl:17 + # in Julia `v1.5`, leading to type instabilities if + # more than ten variables are used. That's why we use + # `Val(...)` below. + u_ll = SVector(ntuple(@inline(v -> u[1, v, indices...]), Val(nvariables(equations)))) + u_rr = SVector(ntuple(@inline(v -> u[2, v, indices...]), Val(nvariables(equations)))) + return u_ll, u_rr + end + + @inline function set_node_vars!(u, u_node, equations, solver::DG, indices...) + for v in eachvariable(equations) + u[v, indices...] = u_node[v] + end + return nothing + end + + @inline function add_to_node_vars!(u, u_node, equations, solver::DG, indices...) + for v in eachvariable(equations) + u[v, indices...] += u_node[v] + end + return nothing + end + + # Use this function instead of `add_to_node_vars` to speed up + # multiply-and-add-to-node-vars operations + # See https://github.com/trixi-framework/Trixi.jl/pull/643 + @inline function multiply_add_to_node_vars!( + u, factor, u_node, equations, solver::DG, + indices... + ) + for v in eachvariable(equations) + u[v, indices...] = u[v, indices...] + factor * u_node[v] + end + return nothing + end + + # Used for analyze_solution + SolutionAnalyzer(dg::DG; kwargs...) = SolutionAnalyzer(dg.basis; kwargs...) + + AdaptorAMR(mesh, dg::DG) = AdaptorL2(dg.basis) + + # General structs for discretizations based on the basic principle of + # DGSEM (discontinuous Galerkin spectral element method) + include("dgsem/dgsem.jl") + + # Finite difference methods using summation by parts (SBP) operators + # These methods are very similar to DG methods since they also impose interface + # and boundary conditions weakly. Thus, these methods can re-use a lot of + # functionality implemented for DGSEM. + include("fdsbp_tree/fdsbp.jl") + include("fdsbp_unstructured/fdsbp.jl") + + function allocate_coefficients(mesh::AbstractMesh, equations, dg::DG, cache) + # We must allocate a `Vector` in order to be able to `resize!` it (AMR). + # cf. wrap_array + zeros( + eltype(cache.elements), + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + ) + end + + @inline function wrap_array( + u_ode::AbstractVector, mesh::AbstractMesh, equations, + dg::DGSEM, cache + ) + @boundscheck begin + @assert length(u_ode) == + nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + end + # We would like to use + # reshape(u_ode, (nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., nelements(dg, cache))) + # but that results in + # ERROR: LoadError: cannot resize array with shared data + # when we resize! `u_ode` during AMR. # - # !!! danger "Heisenbug" - # Do not use this code when `@threaded` uses `Threads.@threads`. There is - # a very strange Heisenbug that makes some parts very slow *sometimes*. - # In fact, everything can be fast and fine for many cases but some parts - # of the RHS evaluation can take *exactly* (!) five seconds randomly... - # Hence, this version should only be used when `@threaded` is based on - # `@batch` from Polyester.jl or something similar. Using Polyester.jl - # is probably the best option since everything will be handed over to - # Chris Elrod, one of the best performance software engineers for Julia. - PtrArray(pointer(u_ode), - (StaticInt(nvariables(equations)), - ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., - nelements(dg, cache))) - # (nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., nelements(dg, cache))) - else - # The following version is reasonably fast and allows us to `resize!(u_ode, ...)`. - unsafe_wrap(Array{eltype(u_ode), ndims(mesh) + 2}, pointer(u_ode), - (nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., - nelements(dg, cache))) - end -end - -# Finite difference summation by parts (FDSBP) methods -@inline function wrap_array(u_ode::AbstractVector, mesh::AbstractMesh, equations, - dg::FDSBP, cache) - @boundscheck begin - @assert length(u_ode) == + # !!! danger "Segfaults" + # Remember to `GC.@preserve` temporaries such as copies of `u_ode` + # and other stuff that is only used indirectly via `wrap_array` afterwards! + + # Currently, there are problems when AD is used with `PtrArray`s in broadcasts + # since LoopVectorization does not support `ForwardDiff.Dual`s. Hence, we use + # optimized `PtrArray`s whenever possible and fall back to plain `Array`s + # otherwise. + if _PREFERENCE_POLYESTER && LoopVectorization.check_args(u_ode) + # This version using `PtrArray`s from StrideArrays.jl is very fast and + # does not result in allocations. + # + # !!! danger "Heisenbug" + # Do not use this code when `@threaded` uses `Threads.@threads`. There is + # a very strange Heisenbug that makes some parts very slow *sometimes*. + # In fact, everything can be fast and fine for many cases but some parts + # of the RHS evaluation can take *exactly* (!) five seconds randomly... + # Hence, this version should only be used when `@threaded` is based on + # `@batch` from Polyester.jl or something similar. Using Polyester.jl + # is probably the best option since everything will be handed over to + # Chris Elrod, one of the best performance software engineers for Julia. + PtrArray( + pointer(u_ode), + ( + StaticInt(nvariables(equations)), + ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., + nelements(dg, cache), + ) + ) + # (nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., nelements(dg, cache))) + else + # The following version is reasonably fast and allows us to `resize!(u_ode, ...)`. + unsafe_wrap( + Array{eltype(u_ode), ndims(mesh) + 2}, pointer(u_ode), + ( + nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., + nelements(dg, cache), + ) + ) + end + end + + # Finite difference summation by parts (FDSBP) methods + @inline function wrap_array( + u_ode::AbstractVector, mesh::AbstractMesh, equations, + dg::FDSBP, cache + ) + @boundscheck begin + @assert length(u_ode) == nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + end + # See comments on the DGSEM version above + if LoopVectorization.check_args(u_ode) + # Here, we do not specialize on the number of nodes using `StaticInt` since + # - it will not be type stable (SBP operators just store it as a runtime value) + # - FD methods tend to use high node counts + PtrArray( + pointer(u_ode), + ( + StaticInt(nvariables(equations)), + ntuple(_ -> nnodes(dg), ndims(mesh))..., nelements(dg, cache), + ) + ) + else + # The following version is reasonably fast and allows us to `resize!(u_ode, ...)`. + unsafe_wrap( + Array{eltype(u_ode), ndims(mesh) + 2}, pointer(u_ode), + ( + nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., + nelements(dg, cache), + ) + ) + end end - # See comments on the DGSEM version above - if LoopVectorization.check_args(u_ode) - # Here, we do not specialize on the number of nodes using `StaticInt` since - # - it will not be type stable (SBP operators just store it as a runtime value) - # - FD methods tend to use high node counts - PtrArray(pointer(u_ode), - (StaticInt(nvariables(equations)), - ntuple(_ -> nnodes(dg), ndims(mesh))..., nelements(dg, cache))) - else - # The following version is reasonably fast and allows us to `resize!(u_ode, ...)`. - unsafe_wrap(Array{eltype(u_ode), ndims(mesh) + 2}, pointer(u_ode), - (nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., - nelements(dg, cache))) - end -end - -# General fallback -@inline function wrap_array(u_ode::AbstractVector, mesh::AbstractMesh, equations, - dg::DG, cache) - wrap_array_native(u_ode, mesh, equations, dg, cache) -end - -# Like `wrap_array`, but guarantees to return a plain `Array`, which can be better -# for interfacing with external C libraries (MPI, HDF5, visualization), -# writing solution files etc. -@inline function wrap_array_native(u_ode::AbstractVector, mesh::AbstractMesh, equations, - dg::DG, cache) - @boundscheck begin - @assert length(u_ode) == + + # General fallback + @inline function wrap_array( + u_ode::AbstractVector, mesh::AbstractMesh, equations, + dg::DG, cache + ) + wrap_array_native(u_ode, mesh, equations, dg, cache) + end + + # Like `wrap_array`, but guarantees to return a plain `Array`, which can be better + # for interfacing with external C libraries (MPI, HDF5, visualization), + # writing solution files etc. + @inline function wrap_array_native( + u_ode::AbstractVector, mesh::AbstractMesh, equations, + dg::DG, cache + ) + @boundscheck begin + @assert length(u_ode) == nvariables(equations) * nnodes(dg)^ndims(mesh) * nelements(dg, cache) + end + unsafe_wrap( + Array{eltype(u_ode), ndims(mesh) + 2}, pointer(u_ode), + ( + nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., + nelements(dg, cache), + ) + ) end - unsafe_wrap(Array{eltype(u_ode), ndims(mesh) + 2}, pointer(u_ode), - (nvariables(equations), ntuple(_ -> nnodes(dg), ndims(mesh))..., - nelements(dg, cache))) -end - -function compute_coefficients!(u, func, t, mesh::AbstractMesh{1}, equations, dg::DG, - cache) - @threaded for element in eachelement(dg, cache) - for i in eachnode(dg) - x_node = get_node_coords(cache.elements.node_coordinates, equations, dg, i, - element) - # Changing the node positions passed to the initial condition by the minimum - # amount possible with the current type of floating point numbers allows setting - # discontinuous initial data in a simple way. In particular, a check like `if x < x_jump` - # works if the jump location `x_jump` is at the position of an interface. - if i == 1 - x_node = SVector(nextfloat(x_node[1])) - elseif i == nnodes(dg) - x_node = SVector(prevfloat(x_node[1])) + + function compute_coefficients!( + u, func, t, mesh::AbstractMesh{1}, equations, dg::DG, + cache + ) + @threaded for element in eachelement(dg, cache) + for i in eachnode(dg) + x_node = get_node_coords( + cache.elements.node_coordinates, equations, dg, i, + element + ) + # Changing the node positions passed to the initial condition by the minimum + # amount possible with the current type of floating point numbers allows setting + # discontinuous initial data in a simple way. In particular, a check like `if x < x_jump` + # works if the jump location `x_jump` is at the position of an interface. + if i == 1 + x_node = SVector(nextfloat(x_node[1])) + elseif i == nnodes(dg) + x_node = SVector(prevfloat(x_node[1])) + end + u_node = func(x_node, t, equations) + set_node_vars!(u, u_node, equations, dg, i, element) end - u_node = func(x_node, t, equations) - set_node_vars!(u, u_node, equations, dg, i, element) end end -end -function compute_coefficients!(u, func, t, mesh::AbstractMesh{2}, equations, dg::DG, - cache) - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - x_node = get_node_coords(cache.elements.node_coordinates, equations, dg, i, - j, element) - u_node = func(x_node, t, equations) - set_node_vars!(u, u_node, equations, dg, i, j, element) + function compute_coefficients!( + u, func, t, mesh::AbstractMesh{2}, equations, dg::DG, + cache + ) + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + x_node = get_node_coords( + cache.elements.node_coordinates, equations, dg, i, + j, element + ) + u_node = func(x_node, t, equations) + set_node_vars!(u, u_node, equations, dg, i, j, element) + end end end -end -function compute_coefficients!(u, func, t, mesh::AbstractMesh{3}, equations, dg::DG, - cache) - @threaded for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - x_node = get_node_coords(cache.elements.node_coordinates, equations, dg, i, - j, k, element) - u_node = func(x_node, t, equations) - set_node_vars!(u, u_node, equations, dg, i, j, k, element) + function compute_coefficients!( + u, func, t, mesh::AbstractMesh{3}, equations, dg::DG, + cache + ) + @threaded for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + x_node = get_node_coords( + cache.elements.node_coordinates, equations, dg, i, + j, k, element + ) + u_node = func(x_node, t, equations) + set_node_vars!(u, u_node, equations, dg, i, j, k, element) + end end end -end - -# Discretizations specific to each mesh type of Trixi.jl -# If some functionality is shared by multiple combinations of meshes/solvers, -# it is defined in the directory of the most basic mesh and solver type. -# The most basic solver type in Trixi.jl is DGSEM (historic reasons and background -# of the main contributors). -# We consider the `TreeMesh` to be the most basic mesh type since it is Cartesian -# and was the first mesh in Trixi.jl. The order of the other mesh types is the same -# as the include order below. -include("dgsem_tree/dg.jl") -include("dgsem_structured/dg.jl") -include("dgsem_unstructured/dg.jl") -include("dgsem_p4est/dg.jl") -include("dgsem_t8code/dg.jl") + + # Discretizations specific to each mesh type of Trixi.jl + # If some functionality is shared by multiple combinations of meshes/solvers, + # it is defined in the directory of the most basic mesh and solver type. + # The most basic solver type in Trixi.jl is DGSEM (historic reasons and background + # of the main contributors). + # We consider the `TreeMesh` to be the most basic mesh type since it is Cartesian + # and was the first mesh in Trixi.jl. The order of the other mesh types is the same + # as the include order below. + include("dgsem_tree/dg.jl") + include("dgsem_structured/dg.jl") + include("dgsem_unstructured/dg.jl") + include("dgsem_p4est/dg.jl") + include("dgsem_t8code/dg.jl") end # @muladd diff --git a/src/solvers/dgmulti/dg.jl b/src/solvers/dgmulti/dg.jl index 695260f4b9..4492407c10 100644 --- a/src/solvers/dgmulti/dg.jl +++ b/src/solvers/dgmulti/dg.jl @@ -3,679 +3,770 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# out <- A*x -mul_by!(A) = @inline (out, x) -> matmul!(out, A, x) -mul_by!(A::T) where {T <: SimpleKronecker} = @inline (out, x) -> mul!(out, A, x) -mul_by!(A::AbstractSparseMatrix) = @inline (out, x) -> mul!(out, A, x) -function mul_by!(A::LinearAlgebra.AdjOrTrans{T, S}) where {T, S <: AbstractSparseMatrix} - @inline (out, x) -> mul!(out, A, x) -end - -# out <- out + α * A * x -mul_by_accum!(A, α) = @inline (out, x) -> matmul!(out, A, x, α, One()) -function mul_by_accum!(A::AbstractSparseMatrix, α) - @inline (out, x) -> mul!(out, A, x, α, One()) -end - -# out <- out + A * x -mul_by_accum!(A) = mul_by_accum!(A, One()) - -# specialize for SBP operators since `matmul!` doesn't work for `UniformScaling` types. -struct MulByUniformScaling end -struct MulByAccumUniformScaling end -mul_by!(A::UniformScaling) = MulByUniformScaling() -mul_by_accum!(A::UniformScaling) = MulByAccumUniformScaling() - -# StructArray fallback -@inline function apply_to_each_field(f::F, args::Vararg{Any, N}) where {F, N} - StructArrays.foreachfield(f, args...) -end - -# specialize for UniformScaling types: works for either StructArray{SVector} or Matrix{SVector} -# solution storage formats. -@inline apply_to_each_field(f::MulByUniformScaling, out, x, args...) = copy!(out, x) -@inline function apply_to_each_field(f::MulByAccumUniformScaling, out, x, args...) - @threaded for i in eachindex(x) - out[i] = out[i] + x[i] - end -end - -""" - eachdim(mesh) - -Return an iterator over the indices that specify the location in relevant data structures -for the dimensions in `AbstractTree`. -In particular, not the dimensions themselves are returned. -""" -@inline eachdim(mesh) = Base.OneTo(ndims(mesh)) - -# iteration over all elements in a mesh -@inline function ndofs(mesh::DGMultiMesh, dg::DGMulti, other_args...) - dg.basis.Np * mesh.md.num_elements -end -""" - eachelement(mesh::DGMultiMesh, dg::DGMulti, other_args...) - -Return an iterator over the indices that specify the location in relevant data structures -for the elements in `mesh`. -In particular, not the elements themselves are returned. -""" -@inline function eachelement(mesh::DGMultiMesh, dg::DGMulti, other_args...) - Base.OneTo(mesh.md.num_elements) -end - -# iteration over quantities in a single element -@inline nnodes(basis::RefElemData) = basis.Np - -""" - each_face_node(mesh::DGMultiMesh, dg::DGMulti, other_args...) - -Return an iterator over the indices that specify the location in relevant data structures -for the face nodes in `dg`. -In particular, not the face_nodes themselves are returned. -""" -@inline function each_face_node(mesh::DGMultiMesh, dg::DGMulti, other_args...) - Base.OneTo(dg.basis.Nfq) -end - -""" - each_quad_node(mesh::DGMultiMesh, dg::DGMulti, other_args...) - -Return an iterator over the indices that specify the location in relevant data structures -for the quadrature nodes in `dg`. -In particular, not the quadrature nodes themselves are returned. -""" -@inline function each_quad_node(mesh::DGMultiMesh, dg::DGMulti, other_args...) - Base.OneTo(dg.basis.Nq) -end - -# iteration over quantities over the entire mesh (dofs, quad nodes, face nodes). -""" - each_dof_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) - -Return an iterator over the indices that specify the location in relevant data structures -for the degrees of freedom (DOF) in `dg`. -In particular, not the DOFs themselves are returned. -""" -@inline function each_dof_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) - Base.OneTo(ndofs(mesh, dg, other_args...)) -end - -""" - each_quad_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) - -Return an iterator over the indices that specify the location in relevant data structures -for the global quadrature nodes in `mesh`. -In particular, not the quadrature nodes themselves are returned. -""" -@inline function each_quad_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) - Base.OneTo(dg.basis.Nq * mesh.md.num_elements) -end - -""" - each_face_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) - -Return an iterator over the indices that specify the location in relevant data structures -for the face nodes in `mesh`. -In particular, not the face nodes themselves are returned. -""" -@inline function each_face_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) - Base.OneTo(dg.basis.Nfq * mesh.md.num_elements) -end - -# interface with semidiscretization_hyperbolic -wrap_array(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode -wrap_array_native(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode -function digest_boundary_conditions(boundary_conditions::NamedTuple{Keys, ValueTypes}, - mesh::DGMultiMesh, - dg::DGMulti, - cache) where {Keys, ValueTypes <: NTuple{N, Any} - } where {N} - return boundary_conditions -end - -# Allocate nested array type for DGMulti solution storage. -function allocate_nested_array(uEltype, nvars, array_dimensions, dg) - # store components as separate arrays, combine via StructArrays - return StructArray{SVector{nvars, uEltype}}(ntuple(_ -> zeros(uEltype, - array_dimensions...), - nvars)) -end - -function reset_du!(du, dg::DGMulti, other_args...) - @threaded for i in eachindex(du) - du[i] = zero(eltype(du)) - end - - return du -end - -# Constructs cache variables for both affine and non-affine (curved) DGMultiMeshes -function create_cache(mesh::DGMultiMesh{NDIMS}, equations, dg::DGMultiWeakForm, RealT, - uEltype) where {NDIMS} - rd = dg.basis - md = mesh.md - - # volume quadrature weights, volume interpolation matrix, mass matrix, differentiation matrices - @unpack wq, Vq, M, Drst = rd - - # ∫f(u) * dv/dx_i = ∑_j (Vq*Drst[i])'*diagm(wq)*(rstxyzJ[i,j].*f(Vq*u)) - weak_differentiation_matrices = map(D -> -M \ ((Vq * D)' * Diagonal(wq)), Drst) - - nvars = nvariables(equations) - - # storage for volume quadrature values, face quadrature values, flux values - u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg) - u_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg) - flux_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg) - if typeof(rd.approximation_type) <: - Union{SBP, AbstractNonperiodicDerivativeOperator} - lift_scalings = rd.wf ./ rd.wq[rd.Fmask] # lift scalings for diag-norm SBP operators - else - lift_scalings = nothing - end - - # local storage for volume integral and source computations - local_values_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) - for _ in 1:Threads.nthreads()] - - # For curved meshes, we interpolate geometric terms from nodal points to quadrature points. - # For affine meshes, we just access one element of this interpolated data. - dxidxhatj = map(x -> rd.Vq * x, md.rstxyzJ) - - # interpolate J to quadrature points for weight-adjusted DG (WADG) - invJ = inv.(rd.Vq * md.J) - - # for scaling by curved geometric terms (not used by affine DGMultiMesh) - flux_threaded = [[allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) - for _ in 1:NDIMS] for _ in 1:Threads.nthreads()] - rotated_flux_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) - for _ in 1:Threads.nthreads()] - - return (; md, weak_differentiation_matrices, lift_scalings, invJ, dxidxhatj, - u_values, u_face_values, flux_face_values, - local_values_threaded, flux_threaded, rotated_flux_threaded) -end - -function allocate_coefficients(mesh::DGMultiMesh, equations, dg::DGMulti, cache) - return allocate_nested_array(real(dg), nvariables(equations), size(mesh.md.x), dg) -end - -function compute_coefficients!(u, initial_condition, t, - mesh::DGMultiMesh, equations, dg::DGMulti, cache) - md = mesh.md - rd = dg.basis - @unpack u_values = cache - - # evaluate the initial condition at quadrature points - @threaded for i in each_quad_node_global(mesh, dg, cache) - u_values[i] = initial_condition(SVector(getindex.(md.xyzq, i)), - t, equations) - end - - # multiplying by Pq computes the L2 projection - apply_to_each_field(mul_by!(rd.Pq), u, u_values) -end - -# estimates the timestep based on polynomial degree and mesh. Does not account for physics (e.g., -# computes an estimate of `dt` based on the advection equation with constant unit advection speed). -function estimate_dt(mesh::DGMultiMesh, dg::DGMulti) - rd = dg.basis # RefElemData - return StartUpDG.estimate_h(rd, mesh.md) / StartUpDG.inverse_trace_constant(rd) -end - -dt_polydeg_scaling(dg::DGMulti) = inv(dg.basis.N + 1) -function dt_polydeg_scaling(dg::DGMulti{3, <:Wedge, <:TensorProductWedge}) - inv(maximum(dg.basis.N) + 1) -end - -# for the stepsize callback -function max_dt(u, t, mesh::DGMultiMesh, - constant_speed::False, equations, dg::DGMulti{NDIMS}, - cache) where {NDIMS} - @unpack md = mesh - rd = dg.basis - - dt_min = Inf - for e in eachelement(mesh, dg, cache) - h_e = StartUpDG.estimate_h(e, rd, md) - max_speeds = ntuple(_ -> nextfloat(zero(t)), NDIMS) - for i in Base.OneTo(rd.Np) # loop over nodes - lambda_i = max_abs_speeds(u[i, e], equations) - max_speeds = max.(max_speeds, lambda_i) + #! format: noindent + + # out <- A*x + mul_by!(A) = @inline (out, x) -> matmul!(out, A, x) + mul_by!(A::T) where {T <: SimpleKronecker} = @inline (out, x) -> mul!(out, A, x) + mul_by!(A::AbstractSparseMatrix) = @inline (out, x) -> mul!(out, A, x) + function mul_by!(A::LinearAlgebra.AdjOrTrans{T, S}) where {T, S <: AbstractSparseMatrix} + @inline (out, x) -> mul!(out, A, x) + end + + # out <- out + α * A * x + mul_by_accum!(A, α) = @inline (out, x) -> matmul!(out, A, x, α, One()) + function mul_by_accum!(A::AbstractSparseMatrix, α) + @inline (out, x) -> mul!(out, A, x, α, One()) + end + + # out <- out + A * x + mul_by_accum!(A) = mul_by_accum!(A, One()) + + # specialize for SBP operators since `matmul!` doesn't work for `UniformScaling` types. + struct MulByUniformScaling end + struct MulByAccumUniformScaling end + mul_by!(A::UniformScaling) = MulByUniformScaling() + mul_by_accum!(A::UniformScaling) = MulByAccumUniformScaling() + + # StructArray fallback + @inline function apply_to_each_field(f::F, args::Vararg{Any, N}) where {F, N} + StructArrays.foreachfield(f, args...) + end + + # specialize for UniformScaling types: works for either StructArray{SVector} or Matrix{SVector} + # solution storage formats. + @inline apply_to_each_field(f::MulByUniformScaling, out, x, args...) = copy!(out, x) + @inline function apply_to_each_field(f::MulByAccumUniformScaling, out, x, args...) + @threaded for i in eachindex(x) + out[i] = out[i] + x[i] end - dt_min = min(dt_min, h_e / sum(max_speeds)) - end - # This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by - # `polydeg+1`. This is because `nnodes(dg)` returns the total number of - # multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns - # the number of 1D nodes for `DGSEM` solvers. - return 2 * dt_min * dt_polydeg_scaling(dg) -end - -function max_dt(u, t, mesh::DGMultiMesh, - constant_speed::True, equations, dg::DGMulti{NDIMS}, - cache) where {NDIMS} - @unpack md = mesh - rd = dg.basis - - dt_min = Inf - for e in eachelement(mesh, dg, cache) - h_e = StartUpDG.estimate_h(e, rd, md) - max_speeds = ntuple(_ -> nextfloat(zero(t)), NDIMS) - for i in Base.OneTo(rd.Np) # loop over nodes - max_speeds = max.(max_abs_speeds(equations), max_speeds) + end + + """ + eachdim(mesh) + + Return an iterator over the indices that specify the location in relevant data structures + for the dimensions in `AbstractTree`. + In particular, not the dimensions themselves are returned. + """ + @inline eachdim(mesh) = Base.OneTo(ndims(mesh)) + + # iteration over all elements in a mesh + @inline function ndofs(mesh::DGMultiMesh, dg::DGMulti, other_args...) + dg.basis.Np * mesh.md.num_elements + end + """ + eachelement(mesh::DGMultiMesh, dg::DGMulti, other_args...) + + Return an iterator over the indices that specify the location in relevant data structures + for the elements in `mesh`. + In particular, not the elements themselves are returned. + """ + @inline function eachelement(mesh::DGMultiMesh, dg::DGMulti, other_args...) + Base.OneTo(mesh.md.num_elements) + end + + # iteration over quantities in a single element + @inline nnodes(basis::RefElemData) = basis.Np + + """ + each_face_node(mesh::DGMultiMesh, dg::DGMulti, other_args...) + + Return an iterator over the indices that specify the location in relevant data structures + for the face nodes in `dg`. + In particular, not the face_nodes themselves are returned. + """ + @inline function each_face_node(mesh::DGMultiMesh, dg::DGMulti, other_args...) + Base.OneTo(dg.basis.Nfq) + end + + """ + each_quad_node(mesh::DGMultiMesh, dg::DGMulti, other_args...) + + Return an iterator over the indices that specify the location in relevant data structures + for the quadrature nodes in `dg`. + In particular, not the quadrature nodes themselves are returned. + """ + @inline function each_quad_node(mesh::DGMultiMesh, dg::DGMulti, other_args...) + Base.OneTo(dg.basis.Nq) + end + + # iteration over quantities over the entire mesh (dofs, quad nodes, face nodes). + """ + each_dof_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) + + Return an iterator over the indices that specify the location in relevant data structures + for the degrees of freedom (DOF) in `dg`. + In particular, not the DOFs themselves are returned. + """ + @inline function each_dof_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) + Base.OneTo(ndofs(mesh, dg, other_args...)) + end + + """ + each_quad_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) + + Return an iterator over the indices that specify the location in relevant data structures + for the global quadrature nodes in `mesh`. + In particular, not the quadrature nodes themselves are returned. + """ + @inline function each_quad_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) + Base.OneTo(dg.basis.Nq * mesh.md.num_elements) + end + + """ + each_face_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) + + Return an iterator over the indices that specify the location in relevant data structures + for the face nodes in `mesh`. + In particular, not the face nodes themselves are returned. + """ + @inline function each_face_node_global(mesh::DGMultiMesh, dg::DGMulti, other_args...) + Base.OneTo(dg.basis.Nfq * mesh.md.num_elements) + end + + # interface with semidiscretization_hyperbolic + wrap_array(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode + wrap_array_native(u_ode, mesh::DGMultiMesh, equations, dg::DGMulti, cache) = u_ode + function digest_boundary_conditions( + boundary_conditions::NamedTuple{Keys, ValueTypes}, + mesh::DGMultiMesh, + dg::DGMulti, + cache + ) where { + Keys, ValueTypes <: NTuple{N, Any}, + } where {N} + return boundary_conditions + end + + # Allocate nested array type for DGMulti solution storage. + function allocate_nested_array(uEltype, nvars, array_dimensions, dg) + # store components as separate arrays, combine via StructArrays + return StructArray{SVector{nvars, uEltype}}( + ntuple( + _ -> zeros( + uEltype, + array_dimensions... + ), + nvars + ) + ) + end + + function reset_du!(du, dg::DGMulti, other_args...) + @threaded for i in eachindex(du) + du[i] = zero(eltype(du)) end - dt_min = min(dt_min, h_e / sum(max_speeds)) - end - # This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by - # `polydeg+1`. This is because `nnodes(dg)` returns the total number of - # multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns - # the number of 1D nodes for `DGSEM` solvers. - return 2 * dt_min * dt_polydeg_scaling(dg) -end - -# interpolates from solution coefficients to face quadrature points -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache, u, mesh::DGMultiMesh, equations, - surface_integral, dg::DGMulti) - rd = dg.basis - @unpack u_face_values = cache - apply_to_each_field(mul_by!(rd.Vf), u_face_values, u) -end - -# version for affine meshes -function calc_volume_integral!(du, u, mesh::DGMultiMesh, - have_nonconservative_terms::False, equations, - volume_integral::VolumeIntegralWeakForm, dg::DGMulti, - cache) - rd = dg.basis - md = mesh.md - @unpack weak_differentiation_matrices, dxidxhatj, u_values, local_values_threaded = cache - @unpack rstxyzJ = md # geometric terms - - # interpolate to quadrature points - apply_to_each_field(mul_by!(rd.Vq), u_values, u) - - @threaded for e in eachelement(mesh, dg, cache) - flux_values = local_values_threaded[Threads.threadid()] - for i in eachdim(mesh) - # Here, the broadcasting operation does allocate - #flux_values .= flux.(view(u_values, :, e), i, equations) - # Use loop instead - for j in eachindex(flux_values) - flux_values[j] = flux(u_values[j, e], i, equations) - end - for j in eachdim(mesh) - apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[j], - dxidxhatj[i, j][1, e]), - view(du, :, e), flux_values) - end + + return du + end + + # Constructs cache variables for both affine and non-affine (curved) DGMultiMeshes + function create_cache( + mesh::DGMultiMesh{NDIMS}, equations, dg::DGMultiWeakForm, RealT, + uEltype + ) where {NDIMS} + rd = dg.basis + md = mesh.md + + # volume quadrature weights, volume interpolation matrix, mass matrix, differentiation matrices + @unpack wq, Vq, M, Drst = rd + + # ∫f(u) * dv/dx_i = ∑_j (Vq*Drst[i])'*diagm(wq)*(rstxyzJ[i,j].*f(Vq*u)) + weak_differentiation_matrices = map(D -> -M \ ((Vq * D)' * Diagonal(wq)), Drst) + + nvars = nvariables(equations) + + # storage for volume quadrature values, face quadrature values, flux values + u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg) + u_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg) + flux_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg) + if typeof(rd.approximation_type) <: + Union{SBP, AbstractNonperiodicDerivativeOperator} + lift_scalings = rd.wf ./ rd.wq[rd.Fmask] # lift scalings for diag-norm SBP operators + else + lift_scalings = nothing end + + # local storage for volume integral and source computations + local_values_threaded = [ + allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) + for _ in 1:Threads.nthreads() + ] + + # For curved meshes, we interpolate geometric terms from nodal points to quadrature points. + # For affine meshes, we just access one element of this interpolated data. + dxidxhatj = map(x -> rd.Vq * x, md.rstxyzJ) + + # interpolate J to quadrature points for weight-adjusted DG (WADG) + invJ = inv.(rd.Vq * md.J) + + # for scaling by curved geometric terms (not used by affine DGMultiMesh) + flux_threaded = [ + [ + allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) + for _ in 1:NDIMS + ] for _ in 1:Threads.nthreads() + ] + rotated_flux_threaded = [ + allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) + for _ in 1:Threads.nthreads() + ] + + return (; + md, weak_differentiation_matrices, lift_scalings, invJ, dxidxhatj, + u_values, u_face_values, flux_face_values, + local_values_threaded, flux_threaded, rotated_flux_threaded, + ) end -end - -# version for curved meshes -function calc_volume_integral!(du, u, mesh::DGMultiMesh{NDIMS, <:NonAffine}, - have_nonconservative_terms::False, equations, - volume_integral::VolumeIntegralWeakForm, dg::DGMulti, - cache) where {NDIMS} - rd = dg.basis - (; weak_differentiation_matrices, u_values) = cache - (; dxidxhatj) = cache - - # interpolate to quadrature points - apply_to_each_field(mul_by!(rd.Vq), u_values, u) - - @threaded for e in eachelement(mesh, dg, cache) - flux_values = cache.flux_threaded[Threads.threadid()] - for i in eachdim(mesh) - # Here, the broadcasting operation does not allocate - flux_values[i] .= flux.(view(u_values, :, e), i, equations) + + function allocate_coefficients(mesh::DGMultiMesh, equations, dg::DGMulti, cache) + return allocate_nested_array(real(dg), nvariables(equations), size(mesh.md.x), dg) + end + + function compute_coefficients!( + u, initial_condition, t, + mesh::DGMultiMesh, equations, dg::DGMulti, cache + ) + md = mesh.md + rd = dg.basis + @unpack u_values = cache + + # evaluate the initial condition at quadrature points + @threaded for i in each_quad_node_global(mesh, dg, cache) + u_values[i] = initial_condition( + SVector(getindex.(md.xyzq, i)), + t, equations + ) end - # rotate flux with df_i/dx_i = sum_j d(x_i)/d(x̂_j) * d(f_i)/d(x̂_j). - # Example: df_x/dx + df_y/dy = dr/dx * df_x/dr + ds/dx * df_x/ds - # + dr/dy * df_y/dr + ds/dy * df_y/ds - # = Dr * (dr/dx * fx + dr/dy * fy) + Ds * (...) - # = Dr * (f_r) + Ds * (f_s) + # multiplying by Pq computes the L2 projection + apply_to_each_field(mul_by!(rd.Pq), u, u_values) + end - rotated_flux_values = cache.rotated_flux_threaded[Threads.threadid()] - for j in eachdim(mesh) - fill!(rotated_flux_values, zero(eltype(rotated_flux_values))) + # estimates the timestep based on polynomial degree and mesh. Does not account for physics (e.g., + # computes an estimate of `dt` based on the advection equation with constant unit advection speed). + function estimate_dt(mesh::DGMultiMesh, dg::DGMulti) + rd = dg.basis # RefElemData + return StartUpDG.estimate_h(rd, mesh.md) / StartUpDG.inverse_trace_constant(rd) + end + + dt_polydeg_scaling(dg::DGMulti) = inv(dg.basis.N + 1) + function dt_polydeg_scaling(dg::DGMulti{3, <:Wedge, <:TensorProductWedge}) + inv(maximum(dg.basis.N) + 1) + end - # compute rotated fluxes + # for the stepsize callback + function max_dt( + u, t, mesh::DGMultiMesh, + constant_speed::False, equations, dg::DGMulti{NDIMS}, + cache + ) where {NDIMS} + @unpack md = mesh + rd = dg.basis + + dt_min = Inf + for e in eachelement(mesh, dg, cache) + h_e = StartUpDG.estimate_h(e, rd, md) + max_speeds = ntuple(_ -> nextfloat(zero(t)), NDIMS) + for i in Base.OneTo(rd.Np) # loop over nodes + lambda_i = max_abs_speeds(u[i, e], equations) + max_speeds = max.(max_speeds, lambda_i) + end + dt_min = min(dt_min, h_e / sum(max_speeds)) + end + # This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by + # `polydeg+1`. This is because `nnodes(dg)` returns the total number of + # multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns + # the number of 1D nodes for `DGSEM` solvers. + return 2 * dt_min * dt_polydeg_scaling(dg) + end + + function max_dt( + u, t, mesh::DGMultiMesh, + constant_speed::True, equations, dg::DGMulti{NDIMS}, + cache + ) where {NDIMS} + @unpack md = mesh + rd = dg.basis + + dt_min = Inf + for e in eachelement(mesh, dg, cache) + h_e = StartUpDG.estimate_h(e, rd, md) + max_speeds = ntuple(_ -> nextfloat(zero(t)), NDIMS) + for i in Base.OneTo(rd.Np) # loop over nodes + max_speeds = max.(max_abs_speeds(equations), max_speeds) + end + dt_min = min(dt_min, h_e / sum(max_speeds)) + end + # This mimics `max_dt` for `TreeMesh`, except that `nnodes(dg)` is replaced by + # `polydeg+1`. This is because `nnodes(dg)` returns the total number of + # multi-dimensional nodes for DGMulti solver types, while `nnodes(dg)` returns + # the number of 1D nodes for `DGSEM` solvers. + return 2 * dt_min * dt_polydeg_scaling(dg) + end + + # interpolates from solution coefficients to face quadrature points + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache, u, mesh::DGMultiMesh, equations, + surface_integral, dg::DGMulti + ) + rd = dg.basis + @unpack u_face_values = cache + apply_to_each_field(mul_by!(rd.Vf), u_face_values, u) + end + + # version for affine meshes + function calc_volume_integral!( + du, u, mesh::DGMultiMesh, + have_nonconservative_terms::False, equations, + volume_integral::VolumeIntegralWeakForm, dg::DGMulti, + cache + ) + rd = dg.basis + md = mesh.md + @unpack weak_differentiation_matrices, dxidxhatj, u_values, local_values_threaded = cache + @unpack rstxyzJ = md # geometric terms + + # interpolate to quadrature points + apply_to_each_field(mul_by!(rd.Vq), u_values, u) + + @threaded for e in eachelement(mesh, dg, cache) + flux_values = local_values_threaded[Threads.threadid()] for i in eachdim(mesh) - for ii in eachindex(rotated_flux_values) - flux_i_node = flux_values[i][ii] - dxidxhatj_node = dxidxhatj[i, j][ii, e] - rotated_flux_values[ii] = rotated_flux_values[ii] + - dxidxhatj_node * flux_i_node + # Here, the broadcasting operation does allocate + #flux_values .= flux.(view(u_values, :, e), i, equations) + # Use loop instead + for j in eachindex(flux_values) + flux_values[j] = flux(u_values[j, e], i, equations) + end + for j in eachdim(mesh) + apply_to_each_field( + mul_by_accum!( + weak_differentiation_matrices[j], + dxidxhatj[i, j][1, e] + ), + view(du, :, e), flux_values + ) end end + end + end + + # version for curved meshes + function calc_volume_integral!( + du, u, mesh::DGMultiMesh{NDIMS, <:NonAffine}, + have_nonconservative_terms::False, equations, + volume_integral::VolumeIntegralWeakForm, dg::DGMulti, + cache + ) where {NDIMS} + rd = dg.basis + (; weak_differentiation_matrices, u_values) = cache + (; dxidxhatj) = cache + + # interpolate to quadrature points + apply_to_each_field(mul_by!(rd.Vq), u_values, u) + + @threaded for e in eachelement(mesh, dg, cache) + flux_values = cache.flux_threaded[Threads.threadid()] + for i in eachdim(mesh) + # Here, the broadcasting operation does not allocate + flux_values[i] .= flux.(view(u_values, :, e), i, equations) + end + + # rotate flux with df_i/dx_i = sum_j d(x_i)/d(x̂_j) * d(f_i)/d(x̂_j). + # Example: df_x/dx + df_y/dy = dr/dx * df_x/dr + ds/dx * df_x/ds + # + dr/dy * df_y/dr + ds/dy * df_y/ds + # = Dr * (dr/dx * fx + dr/dy * fy) + Ds * (...) + # = Dr * (f_r) + Ds * (f_s) - # apply weak differentiation matrices to rotated fluxes - apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[j]), - view(du, :, e), rotated_flux_values) + rotated_flux_values = cache.rotated_flux_threaded[Threads.threadid()] + for j in eachdim(mesh) + fill!(rotated_flux_values, zero(eltype(rotated_flux_values))) + + # compute rotated fluxes + for i in eachdim(mesh) + for ii in eachindex(rotated_flux_values) + flux_i_node = flux_values[i][ii] + dxidxhatj_node = dxidxhatj[i, j][ii, e] + rotated_flux_values[ii] = rotated_flux_values[ii] + + dxidxhatj_node * flux_i_node + end + end + + # apply weak differentiation matrices to rotated fluxes + apply_to_each_field( + mul_by_accum!(weak_differentiation_matrices[j]), + view(du, :, e), rotated_flux_values + ) + end end end -end - -function calc_interface_flux!(cache, surface_integral::SurfaceIntegralWeakForm, - mesh::DGMultiMesh, - have_nonconservative_terms::False, equations, - dg::DGMulti{NDIMS}) where {NDIMS} - @unpack surface_flux = surface_integral - md = mesh.md - @unpack mapM, mapP, nxyzJ, Jf = md - @unpack u_face_values, flux_face_values = cache - - @threaded for face_node_index in each_face_node_global(mesh, dg, cache) - - # inner (idM -> minus) and outer (idP -> plus) indices - idM, idP = mapM[face_node_index], mapP[face_node_index] - uM = u_face_values[idM] - uP = u_face_values[idP] - normal = SVector{NDIMS}(getindex.(nxyzJ, idM)) / Jf[idM] - flux_face_values[idM] = surface_flux(uM, uP, normal, equations) * Jf[idM] - end -end - -function calc_interface_flux!(cache, surface_integral::SurfaceIntegralWeakForm, - mesh::DGMultiMesh, - have_nonconservative_terms::True, equations, - dg::DGMulti{NDIMS}) where {NDIMS} - flux_conservative, flux_nonconservative = surface_integral.surface_flux - md = mesh.md - @unpack mapM, mapP, nxyzJ, Jf = md - @unpack u_face_values, flux_face_values = cache - - @threaded for face_node_index in each_face_node_global(mesh, dg, cache) - - # inner (idM -> minus) and outer (idP -> plus) indices - idM, idP = mapM[face_node_index], mapP[face_node_index] - uM = u_face_values[idM] - - # compute flux if node is not a boundary node - if idM != idP + + function calc_interface_flux!( + cache, surface_integral::SurfaceIntegralWeakForm, + mesh::DGMultiMesh, + have_nonconservative_terms::False, equations, + dg::DGMulti{NDIMS} + ) where {NDIMS} + @unpack surface_flux = surface_integral + md = mesh.md + @unpack mapM, mapP, nxyzJ, Jf = md + @unpack u_face_values, flux_face_values = cache + + @threaded for face_node_index in each_face_node_global(mesh, dg, cache) + + # inner (idM -> minus) and outer (idP -> plus) indices + idM, idP = mapM[face_node_index], mapP[face_node_index] + uM = u_face_values[idM] uP = u_face_values[idP] normal = SVector{NDIMS}(getindex.(nxyzJ, idM)) / Jf[idM] - conservative_part = flux_conservative(uM, uP, normal, equations) - - # Two notes on the use of `flux_nonconservative`: - # 1. In contrast to other mesh types, only one nonconservative part needs to be - # computed since we loop over the elements, not the unique interfaces. - # 2. In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. However, - # both are the same at watertight interfaces, so we pass `normal` twice. - nonconservative_part = flux_nonconservative(uM, uP, normal, normal, - equations) - # The factor 0.5 is necessary for the nonconservative fluxes based on the - # interpretation of global SBP operators. - flux_face_values[idM] = (conservative_part + 0.5 * nonconservative_part) * - Jf[idM] + flux_face_values[idM] = surface_flux(uM, uP, normal, equations) * Jf[idM] end end -end - -# assumes cache.flux_face_values is computed and filled with -# for polyomial discretizations, use dense LIFT matrix for surface contributions. -function calc_surface_integral!(du, u, mesh::DGMultiMesh, equations, - surface_integral::SurfaceIntegralWeakForm, - dg::DGMulti, cache) - rd = dg.basis - apply_to_each_field(mul_by_accum!(rd.LIFT), du, cache.flux_face_values) -end - -# Specialize for nodal SBP discretizations. Uses that Vf*u = u[Fmask,:] -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache, u, mesh::DGMultiMesh, equations, surface_integral, - dg::DGMultiSBP) - rd = dg.basis - @unpack Fmask = rd - @unpack u_face_values = cache - @threaded for e in eachelement(mesh, dg, cache) - for (i, fid) in enumerate(Fmask) - u_face_values[i, e] = u[fid, e] + + function calc_interface_flux!( + cache, surface_integral::SurfaceIntegralWeakForm, + mesh::DGMultiMesh, + have_nonconservative_terms::True, equations, + dg::DGMulti{NDIMS} + ) where {NDIMS} + flux_conservative, flux_nonconservative = surface_integral.surface_flux + md = mesh.md + @unpack mapM, mapP, nxyzJ, Jf = md + @unpack u_face_values, flux_face_values = cache + + @threaded for face_node_index in each_face_node_global(mesh, dg, cache) + + # inner (idM -> minus) and outer (idP -> plus) indices + idM, idP = mapM[face_node_index], mapP[face_node_index] + uM = u_face_values[idM] + + # compute flux if node is not a boundary node + if idM != idP + uP = u_face_values[idP] + normal = SVector{NDIMS}(getindex.(nxyzJ, idM)) / Jf[idM] + conservative_part = flux_conservative(uM, uP, normal, equations) + + # Two notes on the use of `flux_nonconservative`: + # 1. In contrast to other mesh types, only one nonconservative part needs to be + # computed since we loop over the elements, not the unique interfaces. + # 2. In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. However, + # both are the same at watertight interfaces, so we pass `normal` twice. + nonconservative_part = flux_nonconservative( + uM, uP, normal, normal, + equations + ) + # The factor 0.5 is necessary for the nonconservative fluxes based on the + # interpretation of global SBP operators. + flux_face_values[idM] = (conservative_part + 0.5 * nonconservative_part) * + Jf[idM] + end end end -end -# Specialize for nodal SBP discretizations. Uses that du = LIFT*u is equivalent to -# du[Fmask,:] .= u ./ rd.wq[rd.Fmask] -function calc_surface_integral!(du, u, mesh::DGMultiMesh, equations, - surface_integral::SurfaceIntegralWeakForm, - dg::DGMultiSBP, cache) - rd = dg.basis - @unpack flux_face_values, lift_scalings = cache - - @threaded for e in eachelement(mesh, dg, cache) - for i in each_face_node(mesh, dg, cache) - fid = rd.Fmask[i] - du[fid, e] = du[fid, e] + flux_face_values[i, e] * lift_scalings[i] - end + # assumes cache.flux_face_values is computed and filled with + # for polyomial discretizations, use dense LIFT matrix for surface contributions. + function calc_surface_integral!( + du, u, mesh::DGMultiMesh, equations, + surface_integral::SurfaceIntegralWeakForm, + dg::DGMulti, cache + ) + rd = dg.basis + apply_to_each_field(mul_by_accum!(rd.LIFT), du, cache.flux_face_values) end -end - -# do nothing for periodic (default) boundary conditions -function calc_boundary_flux!(cache, t, boundary_conditions::BoundaryConditionPeriodic, - mesh, have_nonconservative_terms, equations, dg::DGMulti) - nothing -end - -function calc_boundary_flux!(cache, t, boundary_conditions, mesh, - have_nonconservative_terms, equations, dg::DGMulti) - for (key, value) in zip(keys(boundary_conditions), boundary_conditions) - calc_single_boundary_flux!(cache, t, value, - key, - mesh, have_nonconservative_terms, equations, dg) - end -end - -function calc_single_boundary_flux!(cache, t, boundary_condition, boundary_key, mesh, - have_nonconservative_terms::False, equations, - dg::DGMulti{NDIMS}) where {NDIMS} - rd = dg.basis - md = mesh.md - @unpack u_face_values, flux_face_values = cache - @unpack xyzf, nxyzJ, Jf = md - @unpack surface_flux = dg.surface_integral - - # reshape face/normal arrays to have size = (num_points_on_face, num_faces_total). - # mesh.boundary_faces indexes into the columns of these face-reshaped arrays. - num_faces = StartUpDG.num_faces(rd.element_type) - num_pts_per_face = rd.Nfq ÷ num_faces - num_faces_total = num_faces * md.num_elements - - # This function was originally defined as - # `reshape_by_face(u) = reshape(view(u, :), num_pts_per_face, num_faces_total)`. - # This results in allocations due to https://github.com/JuliaLang/julia/issues/36313. - # To avoid allocations, we use Tim Holy's suggestion: - # https://github.com/JuliaLang/julia/issues/36313#issuecomment-782336300. - reshape_by_face(u) = Base.ReshapedArray(u, (num_pts_per_face, num_faces_total), ()) - - u_face_values = reshape_by_face(u_face_values) - flux_face_values = reshape_by_face(flux_face_values) - Jf = reshape_by_face(Jf) - nxyzJ, xyzf = reshape_by_face.(nxyzJ), reshape_by_face.(xyzf) # broadcast over nxyzJ::NTuple{NDIMS,Matrix} - - # loop through boundary faces, which correspond to columns of reshaped u_face_values, ... - for f in mesh.boundary_faces[boundary_key] - for i in Base.OneTo(num_pts_per_face) - face_normal = SVector{NDIMS}(getindex.(nxyzJ, i, f)) / Jf[i, f] - face_coordinates = SVector{NDIMS}(getindex.(xyzf, i, f)) - flux_face_values[i, f] = boundary_condition(u_face_values[i, f], - face_normal, face_coordinates, - t, - surface_flux, equations) * - Jf[i, f] + + # Specialize for nodal SBP discretizations. Uses that Vf*u = u[Fmask,:] + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache, u, mesh::DGMultiMesh, equations, surface_integral, + dg::DGMultiSBP + ) + rd = dg.basis + @unpack Fmask = rd + @unpack u_face_values = cache + @threaded for e in eachelement(mesh, dg, cache) + for (i, fid) in enumerate(Fmask) + u_face_values[i, e] = u[fid, e] + end end end - # Note: modifying the values of the reshaped array modifies the values of cache.flux_face_values. - # However, we don't have to re-reshape, since cache.flux_face_values still retains its original shape. -end - -function calc_single_boundary_flux!(cache, t, boundary_condition, boundary_key, mesh, - have_nonconservative_terms::True, equations, - dg::DGMulti{NDIMS}) where {NDIMS} - rd = dg.basis - md = mesh.md - surface_flux, nonconservative_flux = dg.surface_integral.surface_flux - - # reshape face/normal arrays to have size = (num_points_on_face, num_faces_total). - # mesh.boundary_faces indexes into the columns of these face-reshaped arrays. - num_pts_per_face = rd.Nfq ÷ StartUpDG.num_faces(rd.element_type) - num_faces_total = StartUpDG.num_faces(rd.element_type) * md.num_elements - - # This function was originally defined as - # `reshape_by_face(u) = reshape(view(u, :), num_pts_per_face, num_faces_total)`. - # This results in allocations due to https://github.com/JuliaLang/julia/issues/36313. - # To avoid allocations, we use Tim Holy's suggestion: - # https://github.com/JuliaLang/julia/issues/36313#issuecomment-782336300. - reshape_by_face(u) = Base.ReshapedArray(u, (num_pts_per_face, num_faces_total), ()) - - u_face_values = reshape_by_face(cache.u_face_values) - flux_face_values = reshape_by_face(cache.flux_face_values) - Jf = reshape_by_face(md.Jf) - nxyzJ, xyzf = reshape_by_face.(md.nxyzJ), reshape_by_face.(md.xyzf) # broadcast over nxyzJ::NTuple{NDIMS,Matrix} - - # loop through boundary faces, which correspond to columns of reshaped u_face_values, ... - for f in mesh.boundary_faces[boundary_key] - for i in Base.OneTo(num_pts_per_face) - face_normal = SVector{NDIMS}(getindex.(nxyzJ, i, f)) / Jf[i, f] - face_coordinates = SVector{NDIMS}(getindex.(xyzf, i, f)) - - # Compute conservative and non-conservative fluxes separately. - # This imposes boundary conditions on the conservative part of the flux. - cons_flux_at_face_node = boundary_condition(u_face_values[i, f], - face_normal, face_coordinates, - t, - surface_flux, equations) - - # Compute pointwise nonconservative numerical flux at the boundary. - # In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. - # However, there is only one `face_normal` at boundaries, which we pass in twice. - # Note: This does not set any type of boundary condition for the nonconservative term - noncons_flux_at_face_node = nonconservative_flux(u_face_values[i, f], - u_face_values[i, f], - face_normal, face_normal, - equations) - - flux_face_values[i, f] = (cons_flux_at_face_node + - 0.5 * noncons_flux_at_face_node) * Jf[i, f] + # Specialize for nodal SBP discretizations. Uses that du = LIFT*u is equivalent to + # du[Fmask,:] .= u ./ rd.wq[rd.Fmask] + function calc_surface_integral!( + du, u, mesh::DGMultiMesh, equations, + surface_integral::SurfaceIntegralWeakForm, + dg::DGMultiSBP, cache + ) + rd = dg.basis + @unpack flux_face_values, lift_scalings = cache + + @threaded for e in eachelement(mesh, dg, cache) + for i in each_face_node(mesh, dg, cache) + fid = rd.Fmask[i] + du[fid, e] = du[fid, e] + flux_face_values[i, e] * lift_scalings[i] + end end end - # Note: modifying the values of the reshaped array modifies the values of cache.flux_face_values. - # However, we don't have to re-reshape, since cache.flux_face_values still retains its original shape. -end + # do nothing for periodic (default) boundary conditions + function calc_boundary_flux!( + cache, t, boundary_conditions::BoundaryConditionPeriodic, + mesh, have_nonconservative_terms, equations, dg::DGMulti + ) + nothing + end -# inverts Jacobian and scales by -1.0 -function invert_jacobian!(du, mesh::DGMultiMesh, equations, dg::DGMulti, cache; - scaling = -1) - @threaded for e in eachelement(mesh, dg, cache) - invJ = cache.invJ[1, e] - for i in axes(du, 1) - du[i, e] *= scaling * invJ + function calc_boundary_flux!( + cache, t, boundary_conditions, mesh, + have_nonconservative_terms, equations, dg::DGMulti + ) + for (key, value) in zip(keys(boundary_conditions), boundary_conditions) + calc_single_boundary_flux!( + cache, t, value, + key, + mesh, have_nonconservative_terms, equations, dg + ) end end -end -# inverts Jacobian using weight-adjusted DG, and scales by -1.0. -# - Chan, Jesse, Russell J. Hewett, and Timothy Warburton. -# "Weight-adjusted discontinuous Galerkin methods: curvilinear meshes." -# https://doi.org/10.1137/16M1089198 -function invert_jacobian!(du, mesh::DGMultiMesh{NDIMS, <:NonAffine}, equations, - dg::DGMulti, cache; scaling = -1) where {NDIMS} - # Vq = interpolation matrix to quadrature points, Pq = quadrature-based L2 projection matrix - (; Pq, Vq) = dg.basis - (; local_values_threaded, invJ) = cache - - @threaded for e in eachelement(mesh, dg, cache) - du_at_quad_points = local_values_threaded[Threads.threadid()] + function calc_single_boundary_flux!( + cache, t, boundary_condition, boundary_key, mesh, + have_nonconservative_terms::False, equations, + dg::DGMulti{NDIMS} + ) where {NDIMS} + rd = dg.basis + md = mesh.md + @unpack u_face_values, flux_face_values = cache + @unpack xyzf, nxyzJ, Jf = md + @unpack surface_flux = dg.surface_integral + + # reshape face/normal arrays to have size = (num_points_on_face, num_faces_total). + # mesh.boundary_faces indexes into the columns of these face-reshaped arrays. + num_faces = StartUpDG.num_faces(rd.element_type) + num_pts_per_face = rd.Nfq ÷ num_faces + num_faces_total = num_faces * md.num_elements + + # This function was originally defined as + # `reshape_by_face(u) = reshape(view(u, :), num_pts_per_face, num_faces_total)`. + # This results in allocations due to https://github.com/JuliaLang/julia/issues/36313. + # To avoid allocations, we use Tim Holy's suggestion: + # https://github.com/JuliaLang/julia/issues/36313#issuecomment-782336300. + reshape_by_face(u) = Base.ReshapedArray(u, (num_pts_per_face, num_faces_total), ()) + + u_face_values = reshape_by_face(u_face_values) + flux_face_values = reshape_by_face(flux_face_values) + Jf = reshape_by_face(Jf) + nxyzJ, xyzf = reshape_by_face.(nxyzJ), reshape_by_face.(xyzf) # broadcast over nxyzJ::NTuple{NDIMS,Matrix} + + # loop through boundary faces, which correspond to columns of reshaped u_face_values, ... + for f in mesh.boundary_faces[boundary_key] + for i in Base.OneTo(num_pts_per_face) + face_normal = SVector{NDIMS}(getindex.(nxyzJ, i, f)) / Jf[i, f] + face_coordinates = SVector{NDIMS}(getindex.(xyzf, i, f)) + flux_face_values[i, f] = boundary_condition( + u_face_values[i, f], + face_normal, face_coordinates, + t, + surface_flux, equations + ) * + Jf[i, f] + end + end - # interpolate solution to quadrature - apply_to_each_field(mul_by!(Vq), du_at_quad_points, view(du, :, e)) + # Note: modifying the values of the reshaped array modifies the values of cache.flux_face_values. + # However, we don't have to re-reshape, since cache.flux_face_values still retains its original shape. + end - # scale by quadrature points - for i in eachindex(du_at_quad_points) - du_at_quad_points[i] *= scaling * invJ[i, e] + function calc_single_boundary_flux!( + cache, t, boundary_condition, boundary_key, mesh, + have_nonconservative_terms::True, equations, + dg::DGMulti{NDIMS} + ) where {NDIMS} + rd = dg.basis + md = mesh.md + surface_flux, nonconservative_flux = dg.surface_integral.surface_flux + + # reshape face/normal arrays to have size = (num_points_on_face, num_faces_total). + # mesh.boundary_faces indexes into the columns of these face-reshaped arrays. + num_pts_per_face = rd.Nfq ÷ StartUpDG.num_faces(rd.element_type) + num_faces_total = StartUpDG.num_faces(rd.element_type) * md.num_elements + + # This function was originally defined as + # `reshape_by_face(u) = reshape(view(u, :), num_pts_per_face, num_faces_total)`. + # This results in allocations due to https://github.com/JuliaLang/julia/issues/36313. + # To avoid allocations, we use Tim Holy's suggestion: + # https://github.com/JuliaLang/julia/issues/36313#issuecomment-782336300. + reshape_by_face(u) = Base.ReshapedArray(u, (num_pts_per_face, num_faces_total), ()) + + u_face_values = reshape_by_face(cache.u_face_values) + flux_face_values = reshape_by_face(cache.flux_face_values) + Jf = reshape_by_face(md.Jf) + nxyzJ, xyzf = reshape_by_face.(md.nxyzJ), reshape_by_face.(md.xyzf) # broadcast over nxyzJ::NTuple{NDIMS,Matrix} + + # loop through boundary faces, which correspond to columns of reshaped u_face_values, ... + for f in mesh.boundary_faces[boundary_key] + for i in Base.OneTo(num_pts_per_face) + face_normal = SVector{NDIMS}(getindex.(nxyzJ, i, f)) / Jf[i, f] + face_coordinates = SVector{NDIMS}(getindex.(xyzf, i, f)) + + # Compute conservative and non-conservative fluxes separately. + # This imposes boundary conditions on the conservative part of the flux. + cons_flux_at_face_node = boundary_condition( + u_face_values[i, f], + face_normal, face_coordinates, + t, + surface_flux, equations + ) + + # Compute pointwise nonconservative numerical flux at the boundary. + # In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. + # However, there is only one `face_normal` at boundaries, which we pass in twice. + # Note: This does not set any type of boundary condition for the nonconservative term + noncons_flux_at_face_node = nonconservative_flux( + u_face_values[i, f], + u_face_values[i, f], + face_normal, face_normal, + equations + ) + + flux_face_values[i, f] = ( + cons_flux_at_face_node + + 0.5 * noncons_flux_at_face_node + ) * Jf[i, f] + end end - # project back to polynomials - apply_to_each_field(mul_by!(Pq), view(du, :, e), du_at_quad_points) - end -end - -# Multiple calc_sources! to resolve method ambiguities -function calc_sources!(du, u, t, source_terms::Nothing, - mesh, equations, dg::DGMulti, cache) - nothing -end -function calc_sources!(du, u, t, source_terms::Nothing, - mesh, equations, dg::DGMultiFluxDiffSBP, cache) - nothing -end - -# uses quadrature + projection to compute source terms. -function calc_sources!(du, u, t, source_terms, - mesh, equations, dg::DGMulti, cache) - rd = dg.basis - md = mesh.md - @unpack Pq = rd - @unpack u_values, local_values_threaded = cache - @threaded for e in eachelement(mesh, dg, cache) - source_values = local_values_threaded[Threads.threadid()] - - u_e = view(u_values, :, e) # u_values should already be computed from volume integral - - for i in each_quad_node(mesh, dg, cache) - source_values[i] = source_terms(u_e[i], SVector(getindex.(md.xyzq, i, e)), - t, equations) + # Note: modifying the values of the reshaped array modifies the values of cache.flux_face_values. + # However, we don't have to re-reshape, since cache.flux_face_values still retains its original shape. + end + + # inverts Jacobian and scales by -1.0 + function invert_jacobian!( + du, mesh::DGMultiMesh, equations, dg::DGMulti, cache; + scaling = -1 + ) + @threaded for e in eachelement(mesh, dg, cache) + invJ = cache.invJ[1, e] + for i in axes(du, 1) + du[i, e] *= scaling * invJ + end end - apply_to_each_field(mul_by_accum!(Pq), view(du, :, e), source_values) end -end -function rhs!(du, u, t, mesh, equations, - initial_condition, boundary_conditions::BC, source_terms::Source, - dg::DGMulti, cache) where {BC, Source} - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + # inverts Jacobian using weight-adjusted DG, and scales by -1.0. + # - Chan, Jesse, Russell J. Hewett, and Timothy Warburton. + # "Weight-adjusted discontinuous Galerkin methods: curvilinear meshes." + # https://doi.org/10.1137/16M1089198 + function invert_jacobian!( + du, mesh::DGMultiMesh{NDIMS, <:NonAffine}, equations, + dg::DGMulti, cache; scaling = -1 + ) where {NDIMS} + # Vq = interpolation matrix to quadrature points, Pq = quadrature-based L2 projection matrix + (; Pq, Vq) = dg.basis + (; local_values_threaded, invJ) = cache + + @threaded for e in eachelement(mesh, dg, cache) + du_at_quad_points = local_values_threaded[Threads.threadid()] + + # interpolate solution to quadrature + apply_to_each_field(mul_by!(Vq), du_at_quad_points, view(du, :, e)) + + # scale by quadrature points + for i in eachindex(du_at_quad_points) + du_at_quad_points[i] *= scaling * invJ[i, e] + end - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) + # project back to polynomials + apply_to_each_field(mul_by!(Pq), view(du, :, e), du_at_quad_points) + end end - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache, u, mesh, equations, dg.surface_integral, dg) + # Multiple calc_sources! to resolve method ambiguities + function calc_sources!( + du, u, t, source_terms::Nothing, + mesh, equations, dg::DGMulti, cache + ) + nothing end - - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache, dg.surface_integral, mesh, - have_nonconservative_terms(equations), equations, dg) + function calc_sources!( + du, u, t, source_terms::Nothing, + mesh, equations, dg::DGMultiFluxDiffSBP, cache + ) + nothing end - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, t, boundary_conditions, mesh, - have_nonconservative_terms(equations), equations, dg) + # uses quadrature + projection to compute source terms. + function calc_sources!( + du, u, t, source_terms, + mesh, equations, dg::DGMulti, cache + ) + rd = dg.basis + md = mesh.md + @unpack Pq = rd + @unpack u_values, local_values_threaded = cache + @threaded for e in eachelement(mesh, dg, cache) + source_values = local_values_threaded[Threads.threadid()] + + u_e = view(u_values, :, e) # u_values should already be computed from volume integral + + for i in each_quad_node(mesh, dg, cache) + source_values[i] = source_terms( + u_e[i], SVector(getindex.(md.xyzq, i, e)), + t, equations + ) + end + apply_to_each_field(mul_by_accum!(Pq), view(du, :, e), source_values) + end end - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, dg.surface_integral, dg, cache) - end + function rhs!( + du, u, t, mesh, equations, + initial_condition, boundary_conditions::BC, source_terms::Source, + dg::DGMulti, cache + ) where {BC, Source} + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - @trixi_timeit timer() "Jacobian" invert_jacobian!(du, mesh, equations, dg, cache) + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!(cache, u, mesh, equations, dg.surface_integral, dg) + end - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, mesh, equations, dg, cache) - end + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache, dg.surface_integral, mesh, + have_nonconservative_terms(equations), equations, dg + ) + end + + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, t, boundary_conditions, mesh, + have_nonconservative_terms(equations), equations, dg + ) + end - return nothing -end + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!(du, u, mesh, equations, dg.surface_integral, dg, cache) + end + + @trixi_timeit timer() "Jacobian" invert_jacobian!(du, mesh, equations, dg, cache) + + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, mesh, equations, dg, cache) + end + + return nothing + end end # @muladd diff --git a/src/solvers/dgmulti/dg_parabolic.jl b/src/solvers/dgmulti/dg_parabolic.jl index 7dfe443024..6ea836447b 100644 --- a/src/solvers/dgmulti/dg_parabolic.jl +++ b/src/solvers/dgmulti/dg_parabolic.jl @@ -1,8 +1,10 @@ # version for standard (e.g., non-entropy stable or flux differencing) schemes -function create_cache_parabolic(mesh::DGMultiMesh, - equations_hyperbolic::AbstractEquations, - equations_parabolic::AbstractEquationsParabolic, - dg::DGMulti, parabolic_scheme, RealT, uEltype) +function create_cache_parabolic( + mesh::DGMultiMesh, + equations_hyperbolic::AbstractEquations, + equations_parabolic::AbstractEquationsParabolic, + dg::DGMulti, parabolic_scheme, RealT, uEltype + ) # default to taking derivatives of all hyperbolic variables # TODO: parabolic; utilize the parabolic variables in `equations_parabolic` to reduce memory usage in the parabolic cache @@ -27,41 +29,65 @@ function create_cache_parabolic(mesh::DGMultiMesh, # u_transformed stores "transformed" variables for computing the gradient u_transformed = allocate_nested_array(uEltype, nvars, size(md.x), dg) - gradients = SVector{ndims(mesh)}(ntuple(_ -> similar(u_transformed, - (dg.basis.Nq, - mesh.md.num_elements)), - ndims(mesh))) + gradients = SVector{ndims(mesh)}( + ntuple( + _ -> similar( + u_transformed, + ( + dg.basis.Nq, + mesh.md.num_elements, + ) + ), + ndims(mesh) + ) + ) flux_viscous = similar.(gradients) u_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg) scalar_flux_face_values = similar(u_face_values) gradients_face_values = ntuple(_ -> similar(u_face_values), ndims(mesh)) - local_u_values_threaded = [similar(u_transformed, dg.basis.Nq) - for _ in 1:Threads.nthreads()] - local_flux_viscous_threaded = [SVector{ndims(mesh)}(ntuple(_ -> similar(u_transformed, - dg.basis.Nq), - ndims(mesh))) - for _ in 1:Threads.nthreads()] - local_flux_face_values_threaded = [similar(scalar_flux_face_values[:, 1]) - for _ in 1:Threads.nthreads()] - - return (; u_transformed, gradients, flux_viscous, - weak_differentiation_matrices, strong_differentiation_matrices, - gradient_lift_matrix, projection_face_interpolation_matrix, - divergence_lift_matrix, - dxidxhatj, J, invJ, # geometric terms - u_face_values, gradients_face_values, scalar_flux_face_values, - local_u_values_threaded, local_flux_viscous_threaded, - local_flux_face_values_threaded) + local_u_values_threaded = [ + similar(u_transformed, dg.basis.Nq) + for _ in 1:Threads.nthreads() + ] + local_flux_viscous_threaded = [ + SVector{ndims(mesh)}( + ntuple( + _ -> similar( + u_transformed, + dg.basis.Nq + ), + ndims(mesh) + ) + ) + for _ in 1:Threads.nthreads() + ] + local_flux_face_values_threaded = [ + similar(scalar_flux_face_values[:, 1]) + for _ in 1:Threads.nthreads() + ] + + return (; + u_transformed, gradients, flux_viscous, + weak_differentiation_matrices, strong_differentiation_matrices, + gradient_lift_matrix, projection_face_interpolation_matrix, + divergence_lift_matrix, + dxidxhatj, J, invJ, # geometric terms + u_face_values, gradients_face_values, scalar_flux_face_values, + local_u_values_threaded, local_flux_viscous_threaded, + local_flux_face_values_threaded, + ) end # Transform solution variables prior to taking the gradient # (e.g., conservative to primitive variables). Defaults to doing nothing. # TODO: can we avoid copying data? -function transform_variables!(u_transformed, u, mesh, - equations_parabolic::AbstractEquationsParabolic, - dg::DGMulti, parabolic_scheme, cache, cache_parabolic) +function transform_variables!( + u_transformed, u, mesh, + equations_parabolic::AbstractEquationsParabolic, + dg::DGMulti, parabolic_scheme, cache, cache_parabolic + ) transformation = gradient_variable_transformation(equations_parabolic) @threaded for i in eachindex(u) @@ -70,9 +96,11 @@ function transform_variables!(u_transformed, u, mesh, end # TODO: reuse entropy projection computations for DGMultiFluxDiff{<:Polynomial} (including `GaussSBP` solvers) -function calc_gradient_surface_integral!(gradients, u, scalar_flux_face_values, - mesh, equations::AbstractEquationsParabolic, - dg::DGMulti, cache, cache_parabolic) +function calc_gradient_surface_integral!( + gradients, u, scalar_flux_face_values, + mesh, equations::AbstractEquationsParabolic, + dg::DGMulti, cache, cache_parabolic + ) (; gradient_lift_matrix, local_flux_face_values_threaded) = cache_parabolic @threaded for e in eachelement(mesh, dg) local_flux_values = local_flux_face_values_threaded[Threads.threadid()] @@ -80,17 +108,21 @@ function calc_gradient_surface_integral!(gradients, u, scalar_flux_face_values, for i in eachindex(local_flux_values) # compute flux * (nx, ny, nz) local_flux_values[i] = scalar_flux_face_values[i, e] * - mesh.md.nxyzJ[dim][i, e] + mesh.md.nxyzJ[dim][i, e] end - apply_to_each_field(mul_by_accum!(gradient_lift_matrix), - view(gradients[dim], :, e), local_flux_values) + apply_to_each_field( + mul_by_accum!(gradient_lift_matrix), + view(gradients[dim], :, e), local_flux_values + ) end end end -function calc_gradient_volume_integral!(gradients, u, mesh::DGMultiMesh, - equations::AbstractEquationsParabolic, - dg::DGMulti, cache, cache_parabolic) +function calc_gradient_volume_integral!( + gradients, u, mesh::DGMultiMesh, + equations::AbstractEquationsParabolic, + dg::DGMulti, cache, cache_parabolic + ) (; strong_differentiation_matrices) = cache_parabolic # compute volume contributions to gradients @@ -100,16 +132,22 @@ function calc_gradient_volume_integral!(gradients, u, mesh::DGMultiMesh, # We assume each element is affine (e.g., constant geometric terms) here. dxidxhatj = mesh.md.rstxyzJ[i, j][1, e] - apply_to_each_field(mul_by_accum!(strong_differentiation_matrices[j], - dxidxhatj), - view(gradients[i], :, e), view(u, :, e)) + apply_to_each_field( + mul_by_accum!( + strong_differentiation_matrices[j], + dxidxhatj + ), + view(gradients[i], :, e), view(u, :, e) + ) end end end -function calc_gradient_volume_integral!(gradients, u, mesh::DGMultiMesh{NDIMS, <:NonAffine}, - equations::AbstractEquationsParabolic, - dg::DGMulti, cache, cache_parabolic) where {NDIMS} +function calc_gradient_volume_integral!( + gradients, u, mesh::DGMultiMesh{NDIMS, <:NonAffine}, + equations::AbstractEquationsParabolic, + dg::DGMulti, cache, cache_parabolic + ) where {NDIMS} (; strong_differentiation_matrices, dxidxhatj, local_flux_viscous_threaded) = cache_parabolic # compute volume contributions to gradients @@ -118,30 +156,36 @@ function calc_gradient_volume_integral!(gradients, u, mesh::DGMultiMesh{NDIMS, < # compute gradients with respect to reference coordinates local_reference_gradients = local_flux_viscous_threaded[Threads.threadid()] for i in eachdim(mesh) - apply_to_each_field(mul_by!(strong_differentiation_matrices[i]), - local_reference_gradients[i], view(u, :, e)) + apply_to_each_field( + mul_by!(strong_differentiation_matrices[i]), + local_reference_gradients[i], view(u, :, e) + ) end # rotate to physical frame on each element for i in eachdim(mesh), j in eachdim(mesh) for node in eachindex(local_reference_gradients[j]) gradients[i][node, e] = gradients[i][node, e] + - dxidxhatj[i, j][node, e] * - local_reference_gradients[j][node] + dxidxhatj[i, j][node, e] * + local_reference_gradients[j][node] end end end end -function calc_gradient!(gradients, u::StructArray, t, mesh::DGMultiMesh, - equations::AbstractEquationsParabolic, - boundary_conditions, dg::DGMulti, cache, cache_parabolic) +function calc_gradient!( + gradients, u::StructArray, t, mesh::DGMultiMesh, + equations::AbstractEquationsParabolic, + boundary_conditions, dg::DGMulti, cache, cache_parabolic + ) for dim in eachindex(gradients) reset_du!(gradients[dim], dg) end - calc_gradient_volume_integral!(gradients, u, mesh, equations, dg, cache, - cache_parabolic) + calc_gradient_volume_integral!( + gradients, u, mesh, equations, dg, cache, + cache_parabolic + ) (; u_face_values) = cache_parabolic apply_to_each_field(mul_by!(dg.basis.Vf), u_face_values, u) @@ -158,20 +202,26 @@ function calc_gradient!(gradients, u::StructArray, t, mesh::DGMultiMesh, scalar_flux_face_values[idM] = 0.5 * (uP - uM) end - calc_boundary_flux!(scalar_flux_face_values, u_face_values, t, Gradient(), - boundary_conditions, - mesh, equations, dg, cache, cache_parabolic) + calc_boundary_flux!( + scalar_flux_face_values, u_face_values, t, Gradient(), + boundary_conditions, + mesh, equations, dg, cache, cache_parabolic + ) # compute surface contributions - calc_gradient_surface_integral!(gradients, u, scalar_flux_face_values, - mesh, equations, dg, cache, cache_parabolic) + calc_gradient_surface_integral!( + gradients, u, scalar_flux_face_values, + mesh, equations, dg, cache, cache_parabolic + ) invert_jacobian_gradient!(gradients, mesh, equations, dg, cache, cache_parabolic) end # affine mesh - constant Jacobian version -function invert_jacobian_gradient!(gradients, mesh::DGMultiMesh, equations, dg::DGMulti, - cache, cache_parabolic) +function invert_jacobian_gradient!( + gradients, mesh::DGMultiMesh, equations, dg::DGMulti, + cache, cache_parabolic + ) @threaded for e in eachelement(mesh, dg) # Here, we exploit the fact that J is constant on affine elements, @@ -187,9 +237,11 @@ function invert_jacobian_gradient!(gradients, mesh::DGMultiMesh, equations, dg:: end # non-affine mesh - variable Jacobian version -function invert_jacobian_gradient!(gradients, mesh::DGMultiMesh{NDIMS, <:NonAffine}, - equations, - dg::DGMulti, cache, cache_parabolic) where {NDIMS} +function invert_jacobian_gradient!( + gradients, mesh::DGMultiMesh{NDIMS, <:NonAffine}, + equations, + dg::DGMulti, cache, cache_parabolic + ) where {NDIMS} (; invJ) = cache_parabolic @threaded for e in eachelement(mesh, dg) for dim in eachdim(mesh) @@ -201,37 +253,49 @@ function invert_jacobian_gradient!(gradients, mesh::DGMultiMesh{NDIMS, <:NonAffi end # do nothing for periodic domains -function calc_boundary_flux!(flux, u, t, operator_type, ::BoundaryConditionPeriodic, - mesh, equations::AbstractEquationsParabolic, dg::DGMulti, - cache, cache_parabolic) +function calc_boundary_flux!( + flux, u, t, operator_type, ::BoundaryConditionPeriodic, + mesh, equations::AbstractEquationsParabolic, dg::DGMulti, + cache, cache_parabolic + ) return nothing end # "lispy tuple programming" instead of for loop for type stability -function calc_boundary_flux!(flux, u, t, operator_type, boundary_conditions, - mesh, equations, dg::DGMulti, cache, cache_parabolic) +function calc_boundary_flux!( + flux, u, t, operator_type, boundary_conditions, + mesh, equations, dg::DGMulti, cache, cache_parabolic + ) # peel off first boundary condition - calc_single_boundary_flux!(flux, u, t, operator_type, first(boundary_conditions), - first(keys(boundary_conditions)), - mesh, equations, dg, cache, cache_parabolic) + calc_single_boundary_flux!( + flux, u, t, operator_type, first(boundary_conditions), + first(keys(boundary_conditions)), + mesh, equations, dg, cache, cache_parabolic + ) # recurse on the remainder of the boundary conditions - calc_boundary_flux!(flux, u, t, operator_type, Base.tail(boundary_conditions), - mesh, equations, dg, cache, cache_parabolic) + calc_boundary_flux!( + flux, u, t, operator_type, Base.tail(boundary_conditions), + mesh, equations, dg, cache, cache_parabolic + ) end # terminate recursion -function calc_boundary_flux!(flux, u, t, operator_type, - boundary_conditions::NamedTuple{(), Tuple{}}, - mesh, equations, dg::DGMulti, cache, cache_parabolic) +function calc_boundary_flux!( + flux, u, t, operator_type, + boundary_conditions::NamedTuple{(), Tuple{}}, + mesh, equations, dg::DGMulti, cache, cache_parabolic + ) nothing end -function calc_single_boundary_flux!(flux_face_values, u_face_values, t, - operator_type, boundary_condition, boundary_key, - mesh, equations, dg::DGMulti{NDIMS}, cache, - cache_parabolic) where {NDIMS} +function calc_single_boundary_flux!( + flux_face_values, u_face_values, t, + operator_type, boundary_condition, boundary_key, + mesh, equations, dg::DGMulti{NDIMS}, cache, + cache_parabolic + ) where {NDIMS} rd = dg.basis md = mesh.md @@ -250,10 +314,12 @@ function calc_single_boundary_flux!(flux_face_values, u_face_values, t, # for both the gradient and the divergence, the boundary flux is scalar valued. # for the gradient, it is the solution; for divergence, it is the normal flux. - flux_face_values[fid, e] = boundary_condition(flux_face_values[fid, e], - u_face_values[fid, e], - face_normal, face_coordinates, t, - operator_type, equations) + flux_face_values[fid, e] = boundary_condition( + flux_face_values[fid, e], + u_face_values[fid, e], + face_normal, face_coordinates, t, + operator_type, equations + ) # Here, we use the "strong form" for the Gradient (and the "weak form" for Divergence). # `flux_face_values` should contain the boundary values for `u`, and we @@ -267,9 +333,11 @@ function calc_single_boundary_flux!(flux_face_values, u_face_values, t, return nothing end -function calc_viscous_fluxes!(flux_viscous, u, gradients, mesh::DGMultiMesh, - equations::AbstractEquationsParabolic, - dg::DGMulti, cache, cache_parabolic) +function calc_viscous_fluxes!( + flux_viscous, u, gradients, mesh::DGMultiMesh, + equations::AbstractEquationsParabolic, + dg::DGMulti, cache, cache_parabolic + ) for dim in eachdim(mesh) reset_du!(flux_viscous[dim], dg) end @@ -297,18 +365,22 @@ function calc_viscous_fluxes!(flux_viscous, u, gradients, mesh::DGMultiMesh, end # no penalization for a BR1 parabolic solver -function calc_viscous_penalty!(scalar_flux_face_values, u_face_values, t, - boundary_conditions, - mesh, equations::AbstractEquationsParabolic, dg::DGMulti, - parabolic_scheme::ViscousFormulationBassiRebay1, cache, - cache_parabolic) +function calc_viscous_penalty!( + scalar_flux_face_values, u_face_values, t, + boundary_conditions, + mesh, equations::AbstractEquationsParabolic, dg::DGMulti, + parabolic_scheme::ViscousFormulationBassiRebay1, cache, + cache_parabolic + ) return nothing end -function calc_viscous_penalty!(scalar_flux_face_values, u_face_values, t, - boundary_conditions, - mesh, equations::AbstractEquationsParabolic, dg::DGMulti, - parabolic_scheme, cache, cache_parabolic) +function calc_viscous_penalty!( + scalar_flux_face_values, u_face_values, t, + boundary_conditions, + mesh, equations::AbstractEquationsParabolic, dg::DGMulti, + parabolic_scheme, cache, cache_parabolic + ) # compute fluxes at interfaces (; scalar_flux_face_values) = cache_parabolic (; mapM, mapP) = mesh.md @@ -316,30 +388,36 @@ function calc_viscous_penalty!(scalar_flux_face_values, u_face_values, t, idM, idP = mapM[face_node_index], mapP[face_node_index] uM, uP = u_face_values[idM], u_face_values[idP] scalar_flux_face_values[idM] = scalar_flux_face_values[idM] + - penalty(uP, uM, equations, parabolic_scheme) + penalty(uP, uM, equations, parabolic_scheme) end return nothing end -function calc_divergence_volume_integral!(du, u, flux_viscous, mesh::DGMultiMesh, - equations::AbstractEquationsParabolic, - dg::DGMulti, cache, cache_parabolic) +function calc_divergence_volume_integral!( + du, u, flux_viscous, mesh::DGMultiMesh, + equations::AbstractEquationsParabolic, + dg::DGMulti, cache, cache_parabolic + ) (; weak_differentiation_matrices) = cache_parabolic # compute volume contributions to divergence @threaded for e in eachelement(mesh, dg) for i in eachdim(mesh), j in eachdim(mesh) dxidxhatj = mesh.md.rstxyzJ[i, j][1, e] # assumes mesh is affine - apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[j], dxidxhatj), - view(du, :, e), view(flux_viscous[i], :, e)) + apply_to_each_field( + mul_by_accum!(weak_differentiation_matrices[j], dxidxhatj), + view(du, :, e), view(flux_viscous[i], :, e) + ) end end end -function calc_divergence_volume_integral!(du, u, flux_viscous, - mesh::DGMultiMesh{NDIMS, <:NonAffine}, - equations::AbstractEquationsParabolic, - dg::DGMulti, cache, cache_parabolic) where {NDIMS} +function calc_divergence_volume_integral!( + du, u, flux_viscous, + mesh::DGMultiMesh{NDIMS, <:NonAffine}, + equations::AbstractEquationsParabolic, + dg::DGMulti, cache, cache_parabolic + ) where {NDIMS} (; weak_differentiation_matrices, dxidxhatj, local_flux_viscous_threaded) = cache_parabolic # compute volume contributions to divergence @@ -351,33 +429,41 @@ function calc_divergence_volume_integral!(du, u, flux_viscous, for j in eachdim(mesh) for node in eachindex(local_viscous_flux) local_viscous_flux[node] = local_viscous_flux[node] + - dxidxhatj[j, i][node, e] * - flux_viscous[j][node, e] + dxidxhatj[j, i][node, e] * + flux_viscous[j][node, e] end end # differentiate with respect to reference coordinates - apply_to_each_field(mul_by_accum!(weak_differentiation_matrices[i]), - view(du, :, e), local_viscous_flux) + apply_to_each_field( + mul_by_accum!(weak_differentiation_matrices[i]), + view(du, :, e), local_viscous_flux + ) end end end -function calc_divergence!(du, u::StructArray, t, flux_viscous, mesh::DGMultiMesh, - equations::AbstractEquationsParabolic, - boundary_conditions, dg::DGMulti, parabolic_scheme, cache, - cache_parabolic) +function calc_divergence!( + du, u::StructArray, t, flux_viscous, mesh::DGMultiMesh, + equations::AbstractEquationsParabolic, + boundary_conditions, dg::DGMulti, parabolic_scheme, cache, + cache_parabolic + ) reset_du!(du, dg) - calc_divergence_volume_integral!(du, u, flux_viscous, mesh, equations, dg, cache, - cache_parabolic) + calc_divergence_volume_integral!( + du, u, flux_viscous, mesh, equations, dg, cache, + cache_parabolic + ) # interpolates from solution coefficients to face quadrature points (; projection_face_interpolation_matrix) = cache_parabolic flux_viscous_face_values = cache_parabolic.gradients_face_values # reuse storage for dim in eachdim(mesh) - apply_to_each_field(mul_by!(projection_face_interpolation_matrix), - flux_viscous_face_values[dim], flux_viscous[dim]) + apply_to_each_field( + mul_by!(projection_face_interpolation_matrix), + flux_viscous_face_values[dim], flux_viscous[dim] + ) end # compute fluxes at interfaces @@ -394,22 +480,28 @@ function calc_divergence!(du, u::StructArray, t, flux_viscous, mesh::DGMultiMesh fP = flux_viscous_face_values[dim][idP] # Here, we use the "weak" formulation to compute the divergence (to ensure stability on curved meshes). flux_face_value = flux_face_value + - 0.5 * (fP + fM) * nxyzJ[dim][face_node_index] + 0.5 * (fP + fM) * nxyzJ[dim][face_node_index] end scalar_flux_face_values[idM] = flux_face_value end - calc_boundary_flux!(scalar_flux_face_values, cache_parabolic.u_face_values, t, - Divergence(), - boundary_conditions, mesh, equations, dg, cache, cache_parabolic) + calc_boundary_flux!( + scalar_flux_face_values, cache_parabolic.u_face_values, t, + Divergence(), + boundary_conditions, mesh, equations, dg, cache, cache_parabolic + ) - calc_viscous_penalty!(scalar_flux_face_values, cache_parabolic.u_face_values, t, - boundary_conditions, mesh, equations, dg, parabolic_scheme, - cache, cache_parabolic) + calc_viscous_penalty!( + scalar_flux_face_values, cache_parabolic.u_face_values, t, + boundary_conditions, mesh, equations, dg, parabolic_scheme, + cache, cache_parabolic + ) # surface contributions - apply_to_each_field(mul_by_accum!(cache_parabolic.divergence_lift_matrix), du, - scalar_flux_face_values) + apply_to_each_field( + mul_by_accum!(cache_parabolic.divergence_lift_matrix), du, + scalar_flux_face_values + ) # Note: we do not flip the sign of the geometric Jacobian here. # This is because the parabolic fluxes are assumed to be of the form @@ -424,31 +516,41 @@ end # 2. compute f(u, grad(u)) # 3. compute div(u) # boundary conditions will be applied to both grad(u) and div(u). -function rhs_parabolic!(du, u, t, mesh::DGMultiMesh, - equations_parabolic::AbstractEquationsParabolic, - initial_condition, boundary_conditions, source_terms, - dg::DGMulti, parabolic_scheme, cache, cache_parabolic) +function rhs_parabolic!( + du, u, t, mesh::DGMultiMesh, + equations_parabolic::AbstractEquationsParabolic, + initial_condition, boundary_conditions, source_terms, + dg::DGMulti, parabolic_scheme, cache, cache_parabolic + ) reset_du!(du, dg) @trixi_timeit timer() "transform variables" begin (; u_transformed, gradients, flux_viscous) = cache_parabolic - transform_variables!(u_transformed, u, mesh, equations_parabolic, - dg, parabolic_scheme, cache, cache_parabolic) + transform_variables!( + u_transformed, u, mesh, equations_parabolic, + dg, parabolic_scheme, cache, cache_parabolic + ) end @trixi_timeit timer() "calc gradient" begin - calc_gradient!(gradients, u_transformed, t, mesh, equations_parabolic, - boundary_conditions, dg, cache, cache_parabolic) + calc_gradient!( + gradients, u_transformed, t, mesh, equations_parabolic, + boundary_conditions, dg, cache, cache_parabolic + ) end @trixi_timeit timer() "calc viscous fluxes" begin - calc_viscous_fluxes!(flux_viscous, u_transformed, gradients, - mesh, equations_parabolic, dg, cache, cache_parabolic) + calc_viscous_fluxes!( + flux_viscous, u_transformed, gradients, + mesh, equations_parabolic, dg, cache, cache_parabolic + ) end @trixi_timeit timer() "calc divergence" begin - calc_divergence!(du, u_transformed, t, flux_viscous, mesh, equations_parabolic, - boundary_conditions, dg, parabolic_scheme, cache, cache_parabolic) + calc_divergence!( + du, u_transformed, t, flux_viscous, mesh, equations_parabolic, + boundary_conditions, dg, parabolic_scheme, cache, cache_parabolic + ) end return nothing end diff --git a/src/solvers/dgmulti/flux_differencing.jl b/src/solvers/dgmulti/flux_differencing.jl index 36aa50dff4..ecb4117e4c 100644 --- a/src/solvers/dgmulti/flux_differencing.jl +++ b/src/solvers/dgmulti/flux_differencing.jl @@ -3,705 +3,857 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# hadamard_sum!(du, A, -# flux_is_symmetric, volume_flux, -# orientation_or_normal_direction, u, equations) -# -# Computes the flux difference ∑_j A[i, j] * f(u_i, u_j) and accumulates the result into `du`. -# Called by `local_flux_differencing` to compute local contributions to flux differencing -# volume integrals. -# -# - `du`, `u` are vectors -# - `A` is the skew-symmetric flux differencing matrix -# - `flux_is_symmetric` is a `Val{<:Bool}` indicating if f(u_i, u_j) = f(u_j, u_i) -# -# The matrix `A` can be either dense or sparse. In the latter case, you should -# use the `adjoint` of a `SparseMatrixCSC` to mimic a `SparseMatrixCSR`, which -# is more efficient for matrix vector products. - -# Version for dense operators and symmetric fluxes -@inline function hadamard_sum!(du, A, - flux_is_symmetric::True, volume_flux, - orientation_or_normal_direction, u, equations) - row_ids, col_ids = axes(A) - - for i in row_ids - u_i = u[i] - du_i = du[i] - for j in col_ids - # This routine computes only the upper-triangular part of the hadamard sum (A .* F). - # We avoid computing the lower-triangular part, and instead accumulate those contributions - # while computing the upper-triangular part (using the fact that A is skew-symmetric and F - # is symmetric). - if j > i + #! format: noindent + + # hadamard_sum!(du, A, + # flux_is_symmetric, volume_flux, + # orientation_or_normal_direction, u, equations) + # + # Computes the flux difference ∑_j A[i, j] * f(u_i, u_j) and accumulates the result into `du`. + # Called by `local_flux_differencing` to compute local contributions to flux differencing + # volume integrals. + # + # - `du`, `u` are vectors + # - `A` is the skew-symmetric flux differencing matrix + # - `flux_is_symmetric` is a `Val{<:Bool}` indicating if f(u_i, u_j) = f(u_j, u_i) + # + # The matrix `A` can be either dense or sparse. In the latter case, you should + # use the `adjoint` of a `SparseMatrixCSC` to mimic a `SparseMatrixCSR`, which + # is more efficient for matrix vector products. + + # Version for dense operators and symmetric fluxes + @inline function hadamard_sum!( + du, A, + flux_is_symmetric::True, volume_flux, + orientation_or_normal_direction, u, equations + ) + row_ids, col_ids = axes(A) + + for i in row_ids + u_i = u[i] + du_i = du[i] + for j in col_ids + # This routine computes only the upper-triangular part of the hadamard sum (A .* F). + # We avoid computing the lower-triangular part, and instead accumulate those contributions + # while computing the upper-triangular part (using the fact that A is skew-symmetric and F + # is symmetric). + if j > i + u_j = u[j] + AF_ij = 2 * A[i, j] * + volume_flux( + u_i, u_j, orientation_or_normal_direction, + equations + ) + du_i = du_i + AF_ij + du[j] = du[j] - AF_ij + end + end + du[i] = du_i + end + end + + # Version for dense operators and non-symmetric fluxes + @inline function hadamard_sum!( + du, A, + flux_is_symmetric::False, volume_flux, + orientation::Integer, u, equations + ) + row_ids, col_ids = axes(A) + + for i in row_ids + u_i = u[i] + du_i = du[i] + for j in col_ids u_j = u[j] - AF_ij = 2 * A[i, j] * - volume_flux(u_i, u_j, orientation_or_normal_direction, - equations) - du_i = du_i + AF_ij - du[j] = du[j] - AF_ij + f_ij = volume_flux(u_i, u_j, orientation, equations) + du_i = du_i + 2 * A[i, j] * f_ij end + du[i] = du_i end - du[i] = du_i - end -end - -# Version for dense operators and non-symmetric fluxes -@inline function hadamard_sum!(du, A, - flux_is_symmetric::False, volume_flux, - orientation::Integer, u, equations) - row_ids, col_ids = axes(A) - - for i in row_ids - u_i = u[i] - du_i = du[i] - for j in col_ids - u_j = u[j] - f_ij = volume_flux(u_i, u_j, orientation, equations) - du_i = du_i + 2 * A[i, j] * f_ij + end + + @inline function hadamard_sum!( + du, A, + flux_is_symmetric::False, volume_flux, + normal_direction::AbstractVector, u, equations + ) + row_ids, col_ids = axes(A) + + for i in row_ids + u_i = u[i] + du_i = du[i] + for j in col_ids + u_j = u[j] + # The `normal_direction::AbstractVector` has to be passed in twice. + # This is because on curved meshes, nonconservative fluxes are + # evaluated using both the normal and its average at interfaces. + f_ij = volume_flux(u_i, u_j, normal_direction, normal_direction, equations) + du_i = du_i + 2 * A[i, j] * f_ij + end + du[i] = du_i end - du[i] = du_i - end -end - -@inline function hadamard_sum!(du, A, - flux_is_symmetric::False, volume_flux, - normal_direction::AbstractVector, u, equations) - row_ids, col_ids = axes(A) - - for i in row_ids - u_i = u[i] - du_i = du[i] - for j in col_ids - u_j = u[j] - # The `normal_direction::AbstractVector` has to be passed in twice. - # This is because on curved meshes, nonconservative fluxes are - # evaluated using both the normal and its average at interfaces. - f_ij = volume_flux(u_i, u_j, normal_direction, normal_direction, equations) - du_i = du_i + 2 * A[i, j] * f_ij + end + + # Version for sparse operators and symmetric fluxes + @inline function hadamard_sum!( + du, + A::LinearAlgebra.Adjoint{ + <:Any, + <:AbstractSparseMatrixCSC, + }, + flux_is_symmetric::True, volume_flux, + orientation_or_normal_direction, u, equations + ) + A_base = parent(A) # the adjoint of a SparseMatrixCSC is basically a SparseMatrixCSR + row_ids = axes(A, 2) + rows = rowvals(A_base) + vals = nonzeros(A_base) + + for i in row_ids + u_i = u[i] + du_i = du[i] + for id in nzrange(A_base, i) + j = rows[id] + # This routine computes only the upper-triangular part of the hadamard sum (A .* F). + # We avoid computing the lower-triangular part, and instead accumulate those contributions + # while computing the upper-triangular part (using the fact that A is skew-symmetric and F + # is symmetric). + if j > i + u_j = u[j] + A_ij = vals[id] + AF_ij = 2 * A_ij * + volume_flux( + u_i, u_j, orientation_or_normal_direction, + equations + ) + du_i = du_i + AF_ij + du[j] = du[j] - AF_ij + end + end + du[i] = du_i end - du[i] = du_i - end -end - -# Version for sparse operators and symmetric fluxes -@inline function hadamard_sum!(du, - A::LinearAlgebra.Adjoint{<:Any, - <:AbstractSparseMatrixCSC}, - flux_is_symmetric::True, volume_flux, - orientation_or_normal_direction, u, equations) - A_base = parent(A) # the adjoint of a SparseMatrixCSC is basically a SparseMatrixCSR - row_ids = axes(A, 2) - rows = rowvals(A_base) - vals = nonzeros(A_base) - - for i in row_ids - u_i = u[i] - du_i = du[i] - for id in nzrange(A_base, i) - j = rows[id] - # This routine computes only the upper-triangular part of the hadamard sum (A .* F). - # We avoid computing the lower-triangular part, and instead accumulate those contributions - # while computing the upper-triangular part (using the fact that A is skew-symmetric and F - # is symmetric). - if j > i - u_j = u[j] - A_ij = vals[id] - AF_ij = 2 * A_ij * - volume_flux(u_i, u_j, orientation_or_normal_direction, - equations) - du_i = du_i + AF_ij - du[j] = du[j] - AF_ij + end + + # Version for sparse operators and symmetric fluxes with curved meshes + @inline function hadamard_sum!( + du, + A::LinearAlgebra.Adjoint{ + <:Any, + <:AbstractSparseMatrixCSC, + }, + flux_is_symmetric::True, volume_flux, + normal_directions::AbstractVector{<:AbstractVector}, + u, equations + ) + A_base = parent(A) # the adjoint of a SparseMatrixCSC is basically a SparseMatrixCSR + row_ids = axes(A, 2) + rows = rowvals(A_base) + vals = nonzeros(A_base) + + for i in row_ids + u_i = u[i] + du_i = du[i] + for id in nzrange(A_base, i) + j = rows[id] + # This routine computes only the upper-triangular part of the hadamard sum (A .* F). + # We avoid computing the lower-triangular part, and instead accumulate those contributions + # while computing the upper-triangular part (using the fact that A is skew-symmetric and F + # is symmetric). + if j > i + u_j = u[j] + A_ij = vals[id] + + # provably entropy stable de-aliasing of geometric terms + normal_direction = 0.5 * ( + getindex.(normal_directions, i) + + getindex.(normal_directions, j) + ) + + AF_ij = 2 * A_ij * volume_flux(u_i, u_j, normal_direction, equations) + du_i = du_i + AF_ij + du[j] = du[j] - AF_ij + end end + du[i] = du_i end - du[i] = du_i - end -end - -# Version for sparse operators and symmetric fluxes with curved meshes -@inline function hadamard_sum!(du, - A::LinearAlgebra.Adjoint{<:Any, - <:AbstractSparseMatrixCSC}, - flux_is_symmetric::True, volume_flux, - normal_directions::AbstractVector{<:AbstractVector}, - u, equations) - A_base = parent(A) # the adjoint of a SparseMatrixCSC is basically a SparseMatrixCSR - row_ids = axes(A, 2) - rows = rowvals(A_base) - vals = nonzeros(A_base) - - for i in row_ids - u_i = u[i] - du_i = du[i] - for id in nzrange(A_base, i) - j = rows[id] - # This routine computes only the upper-triangular part of the hadamard sum (A .* F). - # We avoid computing the lower-triangular part, and instead accumulate those contributions - # while computing the upper-triangular part (using the fact that A is skew-symmetric and F - # is symmetric). - if j > i - u_j = u[j] + end + + # TODO: DGMulti. Fix for curved meshes. + # Version for sparse operators and non-symmetric fluxes + @inline function hadamard_sum!( + du, + A::LinearAlgebra.Adjoint{ + <:Any, + <:AbstractSparseMatrixCSC, + }, + flux_is_symmetric::False, volume_flux, + normal_direction::AbstractVector, u, equations + ) + A_base = parent(A) # the adjoint of a SparseMatrixCSC is basically a SparseMatrixCSR + row_ids = axes(A, 2) + rows = rowvals(A_base) + vals = nonzeros(A_base) + + for i in row_ids + u_i = u[i] + du_i = du[i] + for id in nzrange(A_base, i) A_ij = vals[id] + j = rows[id] + # The `normal_direction::AbstractVector` has to be passed in twice. + # This is because on curved meshes, nonconservative fluxes are + # evaluated using both the normal and its average at interfaces. + u_j = u[j] + f_ij = volume_flux(u_i, u_j, normal_direction, normal_direction, equations) + du_i = du_i + 2 * A_ij * f_ij + end + du[i] = du_i + end + end - # provably entropy stable de-aliasing of geometric terms - normal_direction = 0.5 * (getindex.(normal_directions, i) + - getindex.(normal_directions, j)) + # For DGMulti implementations, we construct "physical" differentiation operators by taking linear + # combinations of reference differentiation operators scaled by geometric change of variables terms. + # We use a lazy evaluation of physical differentiation operators, so that we can compute linear + # combinations of differentiation operators on-the-fly in an allocation-free manner. + @inline function build_lazy_physical_derivative( + element, orientation, + mesh::DGMultiMesh{1}, dg, cache, + operator_scaling = 1.0 + ) + @unpack Qrst_skew = cache + @unpack rxJ = mesh.md + # ignore orientation + return LazyMatrixLinearCombo(Qrst_skew, operator_scaling .* (rxJ[1, element],)) + end - AF_ij = 2 * A_ij * volume_flux(u_i, u_j, normal_direction, equations) - du_i = du_i + AF_ij - du[j] = du[j] - AF_ij - end + @inline function build_lazy_physical_derivative( + element, orientation, + mesh::DGMultiMesh{2}, dg, cache, + operator_scaling = 1.0 + ) + @unpack Qrst_skew = cache + @unpack rxJ, sxJ, ryJ, syJ = mesh.md + if orientation == 1 + return LazyMatrixLinearCombo( + Qrst_skew, + operator_scaling .* + (rxJ[1, element], sxJ[1, element]) + ) + else # if orientation == 2 + return LazyMatrixLinearCombo( + Qrst_skew, + operator_scaling .* + (ryJ[1, element], syJ[1, element]) + ) + end + end + + @inline function build_lazy_physical_derivative( + element, orientation, + mesh::DGMultiMesh{3}, dg, cache, + operator_scaling = 1.0 + ) + @unpack Qrst_skew = cache + @unpack rxJ, sxJ, txJ, ryJ, syJ, tyJ, rzJ, szJ, tzJ = mesh.md + if orientation == 1 + return LazyMatrixLinearCombo( + Qrst_skew, + operator_scaling .* + ( + rxJ[1, element], sxJ[1, element], + txJ[1, element], + ) + ) + elseif orientation == 2 + return LazyMatrixLinearCombo( + Qrst_skew, + operator_scaling .* + ( + ryJ[1, element], syJ[1, element], + tyJ[1, element], + ) + ) + else # if orientation == 3 + return LazyMatrixLinearCombo( + Qrst_skew, + operator_scaling .* + ( + rzJ[1, element], szJ[1, element], + tzJ[1, element], + ) + ) + end + end + + # Return the contravariant basis vector corresponding to the Cartesian + # coordinate diretion `orientation` in a given `element` of the `mesh`. + # The contravariant basis vectors have entries `dx_i / dxhat_j` where + # j ∈ {1, ..., NDIMS}. Here, `x_i` and `xhat_j` are the ith physical coordinate + # and jth reference coordinate, respectively. These are geometric terms which + # appear when using the chain rule to compute physical derivatives as a linear + # combination of reference derivatives. + @inline function get_contravariant_vector( + element, orientation, + mesh::DGMultiMesh{NDIMS}, cache + ) where {NDIMS} + # note that rstxyzJ = [rxJ, sxJ, txJ; ryJ syJ tyJ; rzJ szJ tzJ], so that this will return + # SVector{2}(rxJ[1, element], ryJ[1, element]) in 2D. + + # assumes geometric terms are constant on each element + dxidxhatj = mesh.md.rstxyzJ + return SVector{NDIMS}(getindex.(dxidxhatj[:, orientation], 1, element)) + end + + @inline function get_contravariant_vector( + element, orientation, + mesh::DGMultiMesh{NDIMS, NonAffine}, + cache + ) where {NDIMS} + # note that rstxyzJ = [rxJ, sxJ, txJ; ryJ syJ tyJ; rzJ szJ tzJ] + + # assumes geometric terms vary spatially over each element + (; dxidxhatj) = cache + return SVector{NDIMS}(view.(dxidxhatj[:, orientation], :, element)) + end + + # use hybridized SBP operators for general flux differencing schemes. + function compute_flux_differencing_SBP_matrices(dg::DGMulti) + compute_flux_differencing_SBP_matrices(dg, has_sparse_operators(dg)) + end + + function compute_flux_differencing_SBP_matrices(dg::DGMulti, sparse_operators) + rd = dg.basis + Qrst_hybridized, VhP, Ph = StartUpDG.hybridized_SBP_operators(rd) + Qrst_skew = map(A -> 0.5 * (A - A'), Qrst_hybridized) + if sparse_operators == true + Qrst_skew = map(Qi -> droptol!(sparse(Qi'), 100 * eps(eltype(Qi)))', Qrst_skew) end - du[i] = du_i - end -end - -# TODO: DGMulti. Fix for curved meshes. -# Version for sparse operators and non-symmetric fluxes -@inline function hadamard_sum!(du, - A::LinearAlgebra.Adjoint{<:Any, - <:AbstractSparseMatrixCSC}, - flux_is_symmetric::False, volume_flux, - normal_direction::AbstractVector, u, equations) - A_base = parent(A) # the adjoint of a SparseMatrixCSC is basically a SparseMatrixCSR - row_ids = axes(A, 2) - rows = rowvals(A_base) - vals = nonzeros(A_base) - - for i in row_ids - u_i = u[i] - du_i = du[i] - for id in nzrange(A_base, i) - A_ij = vals[id] - j = rows[id] - # The `normal_direction::AbstractVector` has to be passed in twice. - # This is because on curved meshes, nonconservative fluxes are - # evaluated using both the normal and its average at interfaces. - u_j = u[j] - f_ij = volume_flux(u_i, u_j, normal_direction, normal_direction, equations) - du_i = du_i + 2 * A_ij * f_ij + return Qrst_skew, VhP, Ph + end + + # use traditional multidimensional SBP operators for SBP approximation types. + function compute_flux_differencing_SBP_matrices( + dg::DGMultiFluxDiffSBP, + sparse_operators + ) + rd = dg.basis + @unpack M, Drst, Pq = rd + Qrst = map(D -> M * D, Drst) + Qrst_skew = map(A -> 0.5 * (A - A'), Qrst) + if sparse_operators == true + Qrst_skew = map(Qi -> droptol!(sparse(Qi'), 100 * eps(eltype(Qi)))', Qrst_skew) end - du[i] = du_i - end -end - -# For DGMulti implementations, we construct "physical" differentiation operators by taking linear -# combinations of reference differentiation operators scaled by geometric change of variables terms. -# We use a lazy evaluation of physical differentiation operators, so that we can compute linear -# combinations of differentiation operators on-the-fly in an allocation-free manner. -@inline function build_lazy_physical_derivative(element, orientation, - mesh::DGMultiMesh{1}, dg, cache, - operator_scaling = 1.0) - @unpack Qrst_skew = cache - @unpack rxJ = mesh.md - # ignore orientation - return LazyMatrixLinearCombo(Qrst_skew, operator_scaling .* (rxJ[1, element],)) -end - -@inline function build_lazy_physical_derivative(element, orientation, - mesh::DGMultiMesh{2}, dg, cache, - operator_scaling = 1.0) - @unpack Qrst_skew = cache - @unpack rxJ, sxJ, ryJ, syJ = mesh.md - if orientation == 1 - return LazyMatrixLinearCombo(Qrst_skew, - operator_scaling .* - (rxJ[1, element], sxJ[1, element])) - else # if orientation == 2 - return LazyMatrixLinearCombo(Qrst_skew, - operator_scaling .* - (ryJ[1, element], syJ[1, element])) - end -end - -@inline function build_lazy_physical_derivative(element, orientation, - mesh::DGMultiMesh{3}, dg, cache, - operator_scaling = 1.0) - @unpack Qrst_skew = cache - @unpack rxJ, sxJ, txJ, ryJ, syJ, tyJ, rzJ, szJ, tzJ = mesh.md - if orientation == 1 - return LazyMatrixLinearCombo(Qrst_skew, - operator_scaling .* - (rxJ[1, element], sxJ[1, element], - txJ[1, element])) - elseif orientation == 2 - return LazyMatrixLinearCombo(Qrst_skew, - operator_scaling .* - (ryJ[1, element], syJ[1, element], - tyJ[1, element])) - else # if orientation == 3 - return LazyMatrixLinearCombo(Qrst_skew, - operator_scaling .* - (rzJ[1, element], szJ[1, element], - tzJ[1, element])) - end -end - -# Return the contravariant basis vector corresponding to the Cartesian -# coordinate diretion `orientation` in a given `element` of the `mesh`. -# The contravariant basis vectors have entries `dx_i / dxhat_j` where -# j ∈ {1, ..., NDIMS}. Here, `x_i` and `xhat_j` are the ith physical coordinate -# and jth reference coordinate, respectively. These are geometric terms which -# appear when using the chain rule to compute physical derivatives as a linear -# combination of reference derivatives. -@inline function get_contravariant_vector(element, orientation, - mesh::DGMultiMesh{NDIMS}, cache) where {NDIMS} - # note that rstxyzJ = [rxJ, sxJ, txJ; ryJ syJ tyJ; rzJ szJ tzJ], so that this will return - # SVector{2}(rxJ[1, element], ryJ[1, element]) in 2D. - - # assumes geometric terms are constant on each element - dxidxhatj = mesh.md.rstxyzJ - return SVector{NDIMS}(getindex.(dxidxhatj[:, orientation], 1, element)) -end - -@inline function get_contravariant_vector(element, orientation, - mesh::DGMultiMesh{NDIMS, NonAffine}, - cache) where {NDIMS} - # note that rstxyzJ = [rxJ, sxJ, txJ; ryJ syJ tyJ; rzJ szJ tzJ] - - # assumes geometric terms vary spatially over each element - (; dxidxhatj) = cache - return SVector{NDIMS}(view.(dxidxhatj[:, orientation], :, element)) -end - -# use hybridized SBP operators for general flux differencing schemes. -function compute_flux_differencing_SBP_matrices(dg::DGMulti) - compute_flux_differencing_SBP_matrices(dg, has_sparse_operators(dg)) -end - -function compute_flux_differencing_SBP_matrices(dg::DGMulti, sparse_operators) - rd = dg.basis - Qrst_hybridized, VhP, Ph = StartUpDG.hybridized_SBP_operators(rd) - Qrst_skew = map(A -> 0.5 * (A - A'), Qrst_hybridized) - if sparse_operators == true - Qrst_skew = map(Qi -> droptol!(sparse(Qi'), 100 * eps(eltype(Qi)))', Qrst_skew) - end - return Qrst_skew, VhP, Ph -end - -# use traditional multidimensional SBP operators for SBP approximation types. -function compute_flux_differencing_SBP_matrices(dg::DGMultiFluxDiffSBP, - sparse_operators) - rd = dg.basis - @unpack M, Drst, Pq = rd - Qrst = map(D -> M * D, Drst) - Qrst_skew = map(A -> 0.5 * (A - A'), Qrst) - if sparse_operators == true - Qrst_skew = map(Qi -> droptol!(sparse(Qi'), 100 * eps(eltype(Qi)))', Qrst_skew) - end - return Qrst_skew -end - -# For flux differencing SBP-type approximations, store solutions in Matrix{SVector{nvars}}. -# This results in a slight speedup for `calc_volume_integral!`. -function allocate_nested_array(uEltype, nvars, array_dimensions, dg::DGMultiFluxDiffSBP) - return zeros(SVector{nvars, uEltype}, array_dimensions...) -end - -function create_cache(mesh::DGMultiMesh, equations, dg::DGMultiFluxDiffSBP, RealT, - uEltype) - rd = dg.basis - md = mesh.md - - # for use with flux differencing schemes - Qrst_skew = compute_flux_differencing_SBP_matrices(dg) - - # Todo: DGMulti. Factor common storage into a struct (MeshDataCache?) for reuse across solvers? - # storage for volume quadrature values, face quadrature values, flux values - nvars = nvariables(equations) - u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg) - u_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg) - flux_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg) - lift_scalings = rd.wf ./ rd.wq[rd.Fmask] # lift scalings for diag-norm SBP operators - - local_values_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) - for _ in 1:Threads.nthreads()] - - # Use an array of SVectors (chunks of `nvars` are contiguous in memory) to speed up flux differencing - fluxdiff_local_threaded = [zeros(SVector{nvars, uEltype}, rd.Nq) - for _ in 1:Threads.nthreads()] - - return (; md, Qrst_skew, dxidxhatj = md.rstxyzJ, + return Qrst_skew + end + + # For flux differencing SBP-type approximations, store solutions in Matrix{SVector{nvars}}. + # This results in a slight speedup for `calc_volume_integral!`. + function allocate_nested_array(uEltype, nvars, array_dimensions, dg::DGMultiFluxDiffSBP) + return zeros(SVector{nvars, uEltype}, array_dimensions...) + end + + function create_cache( + mesh::DGMultiMesh, equations, dg::DGMultiFluxDiffSBP, RealT, + uEltype + ) + rd = dg.basis + md = mesh.md + + # for use with flux differencing schemes + Qrst_skew = compute_flux_differencing_SBP_matrices(dg) + + # Todo: DGMulti. Factor common storage into a struct (MeshDataCache?) for reuse across solvers? + # storage for volume quadrature values, face quadrature values, flux values + nvars = nvariables(equations) + u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg) + u_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg) + flux_face_values = allocate_nested_array(uEltype, nvars, size(md.xf), dg) + lift_scalings = rd.wf ./ rd.wq[rd.Fmask] # lift scalings for diag-norm SBP operators + + local_values_threaded = [ + allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) + for _ in 1:Threads.nthreads() + ] + + # Use an array of SVectors (chunks of `nvars` are contiguous in memory) to speed up flux differencing + fluxdiff_local_threaded = [ + zeros(SVector{nvars, uEltype}, rd.Nq) + for _ in 1:Threads.nthreads() + ] + + return (; + md, Qrst_skew, dxidxhatj = md.rstxyzJ, invJ = inv.(md.J), lift_scalings, inv_wq = inv.(rd.wq), u_values, u_face_values, flux_face_values, - local_values_threaded, fluxdiff_local_threaded) -end - -# most general create_cache: works for `DGMultiFluxDiff{<:Polynomial}` -function create_cache(mesh::DGMultiMesh, equations, dg::DGMultiFluxDiff, RealT, uEltype) - rd = dg.basis - @unpack md = mesh - - Qrst_skew, VhP, Ph = compute_flux_differencing_SBP_matrices(dg) - - # temp storage for entropy variables at volume quad points - nvars = nvariables(equations) - entropy_var_values = allocate_nested_array(uEltype, nvars, (rd.Nq, md.num_elements), - dg) - - # storage for all quadrature points (concatenated volume / face quadrature points) - num_quad_points_total = rd.Nq + rd.Nfq - entropy_projected_u_values = allocate_nested_array(uEltype, nvars, - (num_quad_points_total, - md.num_elements), dg) - projected_entropy_var_values = allocate_nested_array(uEltype, nvars, - (num_quad_points_total, - md.num_elements), dg) - - # For this specific solver, `prolong2interfaces` will not be used anymore. - # Instead, this step is also performed in `entropy_projection!`. Thus, we set - # `u_face_values` as a `view` into `entropy_projected_u_values`. We do not do - # the same for `u_values` since we will use that with LoopVectorization, which - # cannot handle such views as of v0.12.66, the latest version at the time of writing. - u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg) - u_face_values = view(entropy_projected_u_values, (rd.Nq + 1):num_quad_points_total, - :) - flux_face_values = similar(u_face_values) - - # local storage for interface fluxes, rhs, and source - local_values_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) - for _ in 1:Threads.nthreads()] - - # Use an array of SVectors (chunks of `nvars` are contiguous in memory) to speed up flux differencing - # The result is then transferred to rhs_local_threaded::StructArray{<:SVector} before - # projecting it and storing it into `du`. - fluxdiff_local_threaded = [zeros(SVector{nvars, uEltype}, num_quad_points_total) - for _ in 1:Threads.nthreads()] - rhs_local_threaded = [allocate_nested_array(uEltype, nvars, - (num_quad_points_total,), dg) - for _ in 1:Threads.nthreads()] - - # interpolate geometric terms to both quadrature and face values for curved meshes - (; Vq, Vf) = dg.basis - interpolated_geometric_terms = map(x -> [Vq; Vf] * x, mesh.md.rstxyzJ) - J = rd.Vq * md.J - - return (; md, Qrst_skew, VhP, Ph, + local_values_threaded, fluxdiff_local_threaded, + ) + end + + # most general create_cache: works for `DGMultiFluxDiff{<:Polynomial}` + function create_cache(mesh::DGMultiMesh, equations, dg::DGMultiFluxDiff, RealT, uEltype) + rd = dg.basis + @unpack md = mesh + + Qrst_skew, VhP, Ph = compute_flux_differencing_SBP_matrices(dg) + + # temp storage for entropy variables at volume quad points + nvars = nvariables(equations) + entropy_var_values = allocate_nested_array( + uEltype, nvars, (rd.Nq, md.num_elements), + dg + ) + + # storage for all quadrature points (concatenated volume / face quadrature points) + num_quad_points_total = rd.Nq + rd.Nfq + entropy_projected_u_values = allocate_nested_array( + uEltype, nvars, + ( + num_quad_points_total, + md.num_elements, + ), dg + ) + projected_entropy_var_values = allocate_nested_array( + uEltype, nvars, + ( + num_quad_points_total, + md.num_elements, + ), dg + ) + + # For this specific solver, `prolong2interfaces` will not be used anymore. + # Instead, this step is also performed in `entropy_projection!`. Thus, we set + # `u_face_values` as a `view` into `entropy_projected_u_values`. We do not do + # the same for `u_values` since we will use that with LoopVectorization, which + # cannot handle such views as of v0.12.66, the latest version at the time of writing. + u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg) + u_face_values = view( + entropy_projected_u_values, (rd.Nq + 1):num_quad_points_total, + : + ) + flux_face_values = similar(u_face_values) + + # local storage for interface fluxes, rhs, and source + local_values_threaded = [ + allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) + for _ in 1:Threads.nthreads() + ] + + # Use an array of SVectors (chunks of `nvars` are contiguous in memory) to speed up flux differencing + # The result is then transferred to rhs_local_threaded::StructArray{<:SVector} before + # projecting it and storing it into `du`. + fluxdiff_local_threaded = [ + zeros(SVector{nvars, uEltype}, num_quad_points_total) + for _ in 1:Threads.nthreads() + ] + rhs_local_threaded = [ + allocate_nested_array( + uEltype, nvars, + (num_quad_points_total,), dg + ) + for _ in 1:Threads.nthreads() + ] + + # interpolate geometric terms to both quadrature and face values for curved meshes + (; Vq, Vf) = dg.basis + interpolated_geometric_terms = map(x -> [Vq; Vf] * x, mesh.md.rstxyzJ) + J = rd.Vq * md.J + + return (; + md, Qrst_skew, VhP, Ph, invJ = inv.(J), dxidxhatj = interpolated_geometric_terms, entropy_var_values, projected_entropy_var_values, entropy_projected_u_values, u_values, u_face_values, flux_face_values, - local_values_threaded, fluxdiff_local_threaded, rhs_local_threaded) -end - -# TODO: DGMulti. Address hard-coding of `entropy2cons!` and `cons2entropy!` for this function. -function entropy_projection!(cache, u, mesh::DGMultiMesh, equations, dg::DGMulti) - rd = dg.basis - @unpack Vq = rd - @unpack VhP, entropy_var_values, u_values = cache - @unpack projected_entropy_var_values, entropy_projected_u_values = cache - - apply_to_each_field(mul_by!(Vq), u_values, u) - - cons2entropy!(entropy_var_values, u_values, equations) - - # "VhP" fuses the projection "P" with interpolation to volume and face quadrature "Vh" - apply_to_each_field(mul_by!(VhP), projected_entropy_var_values, entropy_var_values) - - entropy2cons!(entropy_projected_u_values, projected_entropy_var_values, equations) - return nothing -end - -@inline function cons2entropy!(entropy_var_values::StructArray, - u_values::StructArray, - equations) - @threaded for i in eachindex(u_values) - entropy_var_values[i] = cons2entropy(u_values[i], equations) - end -end - -@inline function entropy2cons!(entropy_projected_u_values::StructArray, - projected_entropy_var_values::StructArray, - equations) - @threaded for i in eachindex(projected_entropy_var_values) - entropy_projected_u_values[i] = entropy2cons(projected_entropy_var_values[i], - equations) - end -end - -# Trait-like system to dispatch based on whether or not the SBP operators are sparse. -# Designed to be extendable to include specialized `approximation_types` too. -@inline function has_sparse_operators(dg::DGMultiFluxDiff) - rd = dg.basis - return has_sparse_operators(rd.element_type, rd.approximation_type) -end - -# General fallback for DGMulti solvers: -# Polynomial-based solvers use hybridized SBP operators, which have blocks scaled by outward -# normal components. This implies that operators for different coordinate directions have -# different sparsity patterns. We default to using sum factorization (which is faster when -# operators are sparse) for all `DGMulti` / `StartUpDG.jl` approximation types. -@inline has_sparse_operators(element_type, approx_type) = True() - -# For traditional SBP operators on triangles, the operators are fully dense. We avoid using -# sum factorization here, which is slower for fully dense matrices. -@inline function has_sparse_operators(::Union{Tri, Tet}, - approx_type::AT) where {AT <: SBP} - False() -end - -# SBP/GaussSBP operators on quads/hexes use tensor-product operators. Thus, sum factorization is -# more efficient and we use the sparsity structure. -@inline function has_sparse_operators(::Union{Quad, Hex}, - approx_type::AT) where {AT <: SBP} - True() -end -@inline has_sparse_operators(::Union{Quad, Hex}, approx_type::GaussSBP) = True() - -# FD SBP methods have sparse operators -@inline function has_sparse_operators(::Union{Line, Quad, Hex}, - approx_type::AbstractDerivativeOperator) - True() -end - -# Computes flux differencing contribution from each Cartesian direction over a single element. -# For dense operators, we do not use sum factorization. -@inline function local_flux_differencing!(fluxdiff_local, u_local, element_index, - has_nonconservative_terms::False, volume_flux, - has_sparse_operators::False, mesh, - equations, dg, cache) - for dim in eachdim(mesh) - Qi_skew = build_lazy_physical_derivative(element_index, dim, mesh, dg, cache) - # True() indicates the volume flux is symmetric - hadamard_sum!(fluxdiff_local, Qi_skew, - True(), volume_flux, - dim, u_local, equations) - end -end - -@inline function local_flux_differencing!(fluxdiff_local, u_local, element_index, - has_nonconservative_terms::True, volume_flux, - has_sparse_operators::False, mesh, - equations, dg, cache) - flux_conservative, flux_nonconservative = volume_flux - for dim in eachdim(mesh) - Qi_skew = build_lazy_physical_derivative(element_index, dim, mesh, dg, cache) - # True() indicates the flux is symmetric. - hadamard_sum!(fluxdiff_local, Qi_skew, - True(), flux_conservative, - dim, u_local, equations) - - # The final argument .5 scales the operator by 1/2 for the nonconservative terms. - half_Qi_skew = build_lazy_physical_derivative(element_index, dim, mesh, dg, - cache, 0.5) - # False() indicates the flux is non-symmetric. - hadamard_sum!(fluxdiff_local, half_Qi_skew, - False(), flux_nonconservative, - dim, u_local, equations) - end -end - -# When the operators are sparse, we use the sum-factorization approach to -# computing flux differencing. -@inline function local_flux_differencing!(fluxdiff_local, u_local, element_index, - has_nonconservative_terms::False, volume_flux, - has_sparse_operators::True, mesh, - equations, dg, cache) - @unpack Qrst_skew = cache - for dim in eachdim(mesh) - # There are two ways to write this flux differencing discretization on affine meshes. - # - # 1. Use numerical fluxes in Cartesian directions and sum up the discrete derivative - # operators per coordinate direction accordingly. - # 2. Use discrete derivative operators per coordinate direction and corresponding - # numerical fluxes in arbitrary (non-Cartesian) space directions. - # - # The first option makes it necessary to sum up the individual sparsity - # patterns of each reference coordinate direction. On tensor-product - # elements such as `Quad()` or `Hex()` elements, this increases the number of - # potentially expensive numerical flux evaluations by a factor of `ndims(mesh)`. - # Thus, we use the second option below (which basically corresponds to the - # well-known sum factorization on tensor product elements). - # Note that there is basically no difference for dense derivative operators. - normal_direction = get_contravariant_vector(element_index, dim, mesh, cache) - Q_skew = Qrst_skew[dim] - - # True() indicates the flux is symmetric - hadamard_sum!(fluxdiff_local, Q_skew, - True(), volume_flux, - normal_direction, u_local, equations) - end -end - -@inline function local_flux_differencing!(fluxdiff_local, u_local, element_index, - has_nonconservative_terms::True, volume_flux, - has_sparse_operators::True, mesh, - equations, dg, cache) - @unpack Qrst_skew = cache - flux_conservative, flux_nonconservative = volume_flux - for dim in eachdim(mesh) - normal_direction = get_contravariant_vector(element_index, dim, mesh, cache) - Q_skew = Qrst_skew[dim] - - # True() indicates the flux is symmetric - hadamard_sum!(fluxdiff_local, Q_skew, - True(), flux_conservative, - normal_direction, u_local, equations) - - # We scale the operator by 1/2 for the nonconservative terms. - half_Q_skew = LazyMatrixLinearCombo((Q_skew,), (0.5,)) - # False() indicates the flux is non-symmetric - hadamard_sum!(fluxdiff_local, half_Q_skew, - False(), flux_nonconservative, - normal_direction, u_local, equations) - end -end - -# calculates volume integral for <:Polynomial approximation types. We -# do not assume any additional structure (such as collocated volume or -# face nodes, tensor product structure, etc) in `DGMulti`. -function calc_volume_integral!(du, u, mesh::DGMultiMesh, - have_nonconservative_terms, equations, - volume_integral, dg::DGMultiFluxDiff, - cache) - @unpack entropy_projected_u_values, Ph = cache - @unpack fluxdiff_local_threaded, rhs_local_threaded = cache - - @threaded for e in eachelement(mesh, dg, cache) - fluxdiff_local = fluxdiff_local_threaded[Threads.threadid()] - fill!(fluxdiff_local, zero(eltype(fluxdiff_local))) - u_local = view(entropy_projected_u_values, :, e) - - local_flux_differencing!(fluxdiff_local, u_local, e, - have_nonconservative_terms, - volume_integral.volume_flux, - has_sparse_operators(dg), - mesh, equations, dg, cache) - - # convert fluxdiff_local::Vector{<:SVector} to StructArray{<:SVector} for faster - # apply_to_each_field performance. - rhs_local = rhs_local_threaded[Threads.threadid()] - for i in Base.OneTo(length(fluxdiff_local)) - rhs_local[i] = fluxdiff_local[i] - end - apply_to_each_field(mul_by_accum!(Ph), view(du, :, e), rhs_local) + local_values_threaded, fluxdiff_local_threaded, rhs_local_threaded, + ) end -end -function calc_volume_integral!(du, u, mesh::DGMultiMesh, - have_nonconservative_terms, equations, - volume_integral, dg::DGMultiFluxDiffSBP, - cache) - @unpack fluxdiff_local_threaded, inv_wq = cache + # TODO: DGMulti. Address hard-coding of `entropy2cons!` and `cons2entropy!` for this function. + function entropy_projection!(cache, u, mesh::DGMultiMesh, equations, dg::DGMulti) + rd = dg.basis + @unpack Vq = rd + @unpack VhP, entropy_var_values, u_values = cache + @unpack projected_entropy_var_values, entropy_projected_u_values = cache - @threaded for e in eachelement(mesh, dg, cache) - fluxdiff_local = fluxdiff_local_threaded[Threads.threadid()] - fill!(fluxdiff_local, zero(eltype(fluxdiff_local))) - u_local = view(u, :, e) + apply_to_each_field(mul_by!(Vq), u_values, u) - local_flux_differencing!(fluxdiff_local, u_local, e, - have_nonconservative_terms, - volume_integral.volume_flux, - has_sparse_operators(dg), - mesh, equations, dg, cache) + cons2entropy!(entropy_var_values, u_values, equations) - for i in each_quad_node(mesh, dg, cache) - du[i, e] = du[i, e] + fluxdiff_local[i] * inv_wq[i] - end + # "VhP" fuses the projection "P" with interpolation to volume and face quadrature "Vh" + apply_to_each_field(mul_by!(VhP), projected_entropy_var_values, entropy_var_values) + + entropy2cons!(entropy_projected_u_values, projected_entropy_var_values, equations) + return nothing end -end -# Specialize since `u_values` isn't computed for DGMultiFluxDiffSBP solvers. -function calc_sources!(du, u, t, source_terms, - mesh, equations, dg::DGMultiFluxDiffSBP, cache) - md = mesh.md + @inline function cons2entropy!( + entropy_var_values::StructArray, + u_values::StructArray, + equations + ) + @threaded for i in eachindex(u_values) + entropy_var_values[i] = cons2entropy(u_values[i], equations) + end + end - @threaded for e in eachelement(mesh, dg, cache) - for i in each_quad_node(mesh, dg, cache) - du[i, e] += source_terms(u[i, e], SVector(getindex.(md.xyzq, i, e)), t, - equations) + @inline function entropy2cons!( + entropy_projected_u_values::StructArray, + projected_entropy_var_values::StructArray, + equations + ) + @threaded for i in eachindex(projected_entropy_var_values) + entropy_projected_u_values[i] = entropy2cons( + projected_entropy_var_values[i], + equations + ) end end -end -# Specializes on Polynomial (e.g., modal) DG methods with a flux differencing volume integral, e.g., -# an entropy conservative/stable discretization. For modal DG schemes, an extra `entropy_projection!` -# is required (see https://doi.org/10.1016/j.jcp.2018.02.033, Section 4.3). -# Also called by DGMultiFluxDiff{<:GaussSBP} solvers. -function rhs!(du, u, t, mesh, equations, initial_condition, boundary_conditions::BC, - source_terms::Source, dg::DGMultiFluxDiff, cache) where {Source, BC} - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + # Trait-like system to dispatch based on whether or not the SBP operators are sparse. + # Designed to be extendable to include specialized `approximation_types` too. + @inline function has_sparse_operators(dg::DGMultiFluxDiff) + rd = dg.basis + return has_sparse_operators(rd.element_type, rd.approximation_type) + end - # this function evaluates the solution at volume and face quadrature points (which was previously - # done in `prolong2interfaces` and `calc_volume_integral`) - @trixi_timeit timer() "entropy_projection!" begin - entropy_projection!(cache, u, mesh, equations, dg) + # General fallback for DGMulti solvers: + # Polynomial-based solvers use hybridized SBP operators, which have blocks scaled by outward + # normal components. This implies that operators for different coordinate directions have + # different sparsity patterns. We default to using sum factorization (which is faster when + # operators are sparse) for all `DGMulti` / `StartUpDG.jl` approximation types. + @inline has_sparse_operators(element_type, approx_type) = True() + + # For traditional SBP operators on triangles, the operators are fully dense. We avoid using + # sum factorization here, which is slower for fully dense matrices. + @inline function has_sparse_operators( + ::Union{Tri, Tet}, + approx_type::AT + ) where {AT <: SBP} + False() end - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, have_nonconservative_terms(equations), - equations, - dg.volume_integral, dg, cache) + # SBP/GaussSBP operators on quads/hexes use tensor-product operators. Thus, sum factorization is + # more efficient and we use the sparsity structure. + @inline function has_sparse_operators( + ::Union{Quad, Hex}, + approx_type::AT + ) where {AT <: SBP} + True() + end + @inline has_sparse_operators(::Union{Quad, Hex}, approx_type::GaussSBP) = True() + + # FD SBP methods have sparse operators + @inline function has_sparse_operators( + ::Union{Line, Quad, Hex}, + approx_type::AbstractDerivativeOperator + ) + True() end - # the following functions are the same as in VolumeIntegralWeakForm, and can be reused from dg.jl - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache, dg.surface_integral, mesh, - have_nonconservative_terms(equations), equations, dg) + # Computes flux differencing contribution from each Cartesian direction over a single element. + # For dense operators, we do not use sum factorization. + @inline function local_flux_differencing!( + fluxdiff_local, u_local, element_index, + has_nonconservative_terms::False, volume_flux, + has_sparse_operators::False, mesh, + equations, dg, cache + ) + for dim in eachdim(mesh) + Qi_skew = build_lazy_physical_derivative(element_index, dim, mesh, dg, cache) + # True() indicates the volume flux is symmetric + hadamard_sum!( + fluxdiff_local, Qi_skew, + True(), volume_flux, + dim, u_local, equations + ) + end end - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, t, boundary_conditions, mesh, - have_nonconservative_terms(equations), equations, dg) + @inline function local_flux_differencing!( + fluxdiff_local, u_local, element_index, + has_nonconservative_terms::True, volume_flux, + has_sparse_operators::False, mesh, + equations, dg, cache + ) + flux_conservative, flux_nonconservative = volume_flux + for dim in eachdim(mesh) + Qi_skew = build_lazy_physical_derivative(element_index, dim, mesh, dg, cache) + # True() indicates the flux is symmetric. + hadamard_sum!( + fluxdiff_local, Qi_skew, + True(), flux_conservative, + dim, u_local, equations + ) + + # The final argument .5 scales the operator by 1/2 for the nonconservative terms. + half_Qi_skew = build_lazy_physical_derivative( + element_index, dim, mesh, dg, + cache, 0.5 + ) + # False() indicates the flux is non-symmetric. + hadamard_sum!( + fluxdiff_local, half_Qi_skew, + False(), flux_nonconservative, + dim, u_local, equations + ) + end end - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) + # When the operators are sparse, we use the sum-factorization approach to + # computing flux differencing. + @inline function local_flux_differencing!( + fluxdiff_local, u_local, element_index, + has_nonconservative_terms::False, volume_flux, + has_sparse_operators::True, mesh, + equations, dg, cache + ) + @unpack Qrst_skew = cache + for dim in eachdim(mesh) + # There are two ways to write this flux differencing discretization on affine meshes. + # + # 1. Use numerical fluxes in Cartesian directions and sum up the discrete derivative + # operators per coordinate direction accordingly. + # 2. Use discrete derivative operators per coordinate direction and corresponding + # numerical fluxes in arbitrary (non-Cartesian) space directions. + # + # The first option makes it necessary to sum up the individual sparsity + # patterns of each reference coordinate direction. On tensor-product + # elements such as `Quad()` or `Hex()` elements, this increases the number of + # potentially expensive numerical flux evaluations by a factor of `ndims(mesh)`. + # Thus, we use the second option below (which basically corresponds to the + # well-known sum factorization on tensor product elements). + # Note that there is basically no difference for dense derivative operators. + normal_direction = get_contravariant_vector(element_index, dim, mesh, cache) + Q_skew = Qrst_skew[dim] + + # True() indicates the flux is symmetric + hadamard_sum!( + fluxdiff_local, Q_skew, + True(), volume_flux, + normal_direction, u_local, equations + ) + end end - @trixi_timeit timer() "Jacobian" invert_jacobian!(du, mesh, equations, dg, cache) + @inline function local_flux_differencing!( + fluxdiff_local, u_local, element_index, + has_nonconservative_terms::True, volume_flux, + has_sparse_operators::True, mesh, + equations, dg, cache + ) + @unpack Qrst_skew = cache + flux_conservative, flux_nonconservative = volume_flux + for dim in eachdim(mesh) + normal_direction = get_contravariant_vector(element_index, dim, mesh, cache) + Q_skew = Qrst_skew[dim] + + # True() indicates the flux is symmetric + hadamard_sum!( + fluxdiff_local, Q_skew, + True(), flux_conservative, + normal_direction, u_local, equations + ) + + # We scale the operator by 1/2 for the nonconservative terms. + half_Q_skew = LazyMatrixLinearCombo((Q_skew,), (0.5,)) + # False() indicates the flux is non-symmetric + hadamard_sum!( + fluxdiff_local, half_Q_skew, + False(), flux_nonconservative, + normal_direction, u_local, equations + ) + end + end - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, mesh, equations, dg, cache) + # calculates volume integral for <:Polynomial approximation types. We + # do not assume any additional structure (such as collocated volume or + # face nodes, tensor product structure, etc) in `DGMulti`. + function calc_volume_integral!( + du, u, mesh::DGMultiMesh, + have_nonconservative_terms, equations, + volume_integral, dg::DGMultiFluxDiff, + cache + ) + @unpack entropy_projected_u_values, Ph = cache + @unpack fluxdiff_local_threaded, rhs_local_threaded = cache + + @threaded for e in eachelement(mesh, dg, cache) + fluxdiff_local = fluxdiff_local_threaded[Threads.threadid()] + fill!(fluxdiff_local, zero(eltype(fluxdiff_local))) + u_local = view(entropy_projected_u_values, :, e) + + local_flux_differencing!( + fluxdiff_local, u_local, e, + have_nonconservative_terms, + volume_integral.volume_flux, + has_sparse_operators(dg), + mesh, equations, dg, cache + ) + + # convert fluxdiff_local::Vector{<:SVector} to StructArray{<:SVector} for faster + # apply_to_each_field performance. + rhs_local = rhs_local_threaded[Threads.threadid()] + for i in Base.OneTo(length(fluxdiff_local)) + rhs_local[i] = fluxdiff_local[i] + end + apply_to_each_field(mul_by_accum!(Ph), view(du, :, e), rhs_local) + end end - return nothing -end + function calc_volume_integral!( + du, u, mesh::DGMultiMesh, + have_nonconservative_terms, equations, + volume_integral, dg::DGMultiFluxDiffSBP, + cache + ) + @unpack fluxdiff_local_threaded, inv_wq = cache + + @threaded for e in eachelement(mesh, dg, cache) + fluxdiff_local = fluxdiff_local_threaded[Threads.threadid()] + fill!(fluxdiff_local, zero(eltype(fluxdiff_local))) + u_local = view(u, :, e) + + local_flux_differencing!( + fluxdiff_local, u_local, e, + have_nonconservative_terms, + volume_integral.volume_flux, + has_sparse_operators(dg), + mesh, equations, dg, cache + ) + + for i in each_quad_node(mesh, dg, cache) + du[i, e] = du[i, e] + fluxdiff_local[i] * inv_wq[i] + end + end + end + + # Specialize since `u_values` isn't computed for DGMultiFluxDiffSBP solvers. + function calc_sources!( + du, u, t, source_terms, + mesh, equations, dg::DGMultiFluxDiffSBP, cache + ) + md = mesh.md + + @threaded for e in eachelement(mesh, dg, cache) + for i in each_quad_node(mesh, dg, cache) + du[i, e] += source_terms( + u[i, e], SVector(getindex.(md.xyzq, i, e)), t, + equations + ) + end + end + end -# Specializes on SBP (e.g., nodal/collocation) DG methods with a flux differencing volume -# integral, e.g., an entropy conservative/stable discretization. The implementation of `rhs!` -# for such schemes is very similar to the implementation of `rhs!` for standard DG methods, -# but specializes `calc_volume_integral`. -function rhs!(du, u, t, mesh, equations, - initial_condition, boundary_conditions::BC, source_terms::Source, - dg::DGMultiFluxDiffSBP, cache) where {BC, Source} - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + # Specializes on Polynomial (e.g., modal) DG methods with a flux differencing volume integral, e.g., + # an entropy conservative/stable discretization. For modal DG schemes, an extra `entropy_projection!` + # is required (see https://doi.org/10.1016/j.jcp.2018.02.033, Section 4.3). + # Also called by DGMultiFluxDiff{<:GaussSBP} solvers. + function rhs!( + du, u, t, mesh, equations, initial_condition, boundary_conditions::BC, + source_terms::Source, dg::DGMultiFluxDiff, cache + ) where {Source, BC} + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # this function evaluates the solution at volume and face quadrature points (which was previously + # done in `prolong2interfaces` and `calc_volume_integral`) + @trixi_timeit timer() "entropy_projection!" begin + entropy_projection!(cache, u, mesh, equations, dg) + end - @trixi_timeit timer() "volume integral" calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), - equations, - dg.volume_integral, - dg, cache) + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, have_nonconservative_terms(equations), + equations, + dg.volume_integral, dg, cache + ) + end - @trixi_timeit timer() "prolong2interfaces" prolong2interfaces!(cache, u, mesh, - equations, - dg.surface_integral, - dg) + # the following functions are the same as in VolumeIntegralWeakForm, and can be reused from dg.jl + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache, dg.surface_integral, mesh, + have_nonconservative_terms(equations), equations, dg + ) + end - @trixi_timeit timer() "interface flux" calc_interface_flux!(cache, - dg.surface_integral, - mesh, - have_nonconservative_terms(equations), - equations, dg) + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, t, boundary_conditions, mesh, + have_nonconservative_terms(equations), equations, dg + ) + end - @trixi_timeit timer() "boundary flux" calc_boundary_flux!(cache, t, - boundary_conditions, mesh, - have_nonconservative_terms(equations), - equations, dg) + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end - @trixi_timeit timer() "surface integral" calc_surface_integral!(du, u, mesh, - equations, - dg.surface_integral, - dg, cache) + @trixi_timeit timer() "Jacobian" invert_jacobian!(du, mesh, equations, dg, cache) - @trixi_timeit timer() "Jacobian" invert_jacobian!(du, mesh, equations, dg, cache) + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, mesh, equations, dg, cache) + end - @trixi_timeit timer() "source terms" calc_sources!(du, u, t, source_terms, mesh, - equations, dg, cache) + return nothing + end - return nothing -end + # Specializes on SBP (e.g., nodal/collocation) DG methods with a flux differencing volume + # integral, e.g., an entropy conservative/stable discretization. The implementation of `rhs!` + # for such schemes is very similar to the implementation of `rhs!` for standard DG methods, + # but specializes `calc_volume_integral`. + function rhs!( + du, u, t, mesh, equations, + initial_condition, boundary_conditions::BC, source_terms::Source, + dg::DGMultiFluxDiffSBP, cache + ) where {BC, Source} + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + @trixi_timeit timer() "volume integral" calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), + equations, + dg.volume_integral, + dg, cache + ) + + @trixi_timeit timer() "prolong2interfaces" prolong2interfaces!( + cache, u, mesh, + equations, + dg.surface_integral, + dg + ) + + @trixi_timeit timer() "interface flux" calc_interface_flux!( + cache, + dg.surface_integral, + mesh, + have_nonconservative_terms(equations), + equations, dg + ) + + @trixi_timeit timer() "boundary flux" calc_boundary_flux!( + cache, t, + boundary_conditions, mesh, + have_nonconservative_terms(equations), + equations, dg + ) + + @trixi_timeit timer() "surface integral" calc_surface_integral!( + du, u, mesh, + equations, + dg.surface_integral, + dg, cache + ) + + @trixi_timeit timer() "Jacobian" invert_jacobian!(du, mesh, equations, dg, cache) + + @trixi_timeit timer() "source terms" calc_sources!( + du, u, t, source_terms, mesh, + equations, dg, cache + ) + + return nothing + end end # @muladd diff --git a/src/solvers/dgmulti/flux_differencing_compressible_euler.jl b/src/solvers/dgmulti/flux_differencing_compressible_euler.jl index 70a29bc73f..59d75faa8e 100644 --- a/src/solvers/dgmulti/flux_differencing_compressible_euler.jl +++ b/src/solvers/dgmulti/flux_differencing_compressible_euler.jl @@ -3,179 +3,195 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# TODO: Upstream, LoopVectorization -# At the time of writing, LoopVectorization.jl cannot handle this kind of -# loop optimally when passing our custom functions `cons2entropy` and -# `entropy2cons`. Thus, we need to insert the physics directly here to -# get a significant runtime performance improvement. -function cons2entropy!(entropy_var_values::StructArray, - u_values::StructArray, - equations::CompressibleEulerEquations2D) - # The following is semantically equivalent to - # @threaded for i in eachindex(u_values) - # entropy_var_values[i] = cons2entropy(u_values[i], equations) - # end - # but much more efficient due to explicit optimization via `@turbo` from - # LoopVectorization.jl. - @unpack gamma, inv_gamma_minus_one = equations - - rho_values, rho_v1_values, rho_v2_values, rho_e_values = StructArrays.components(u_values) - w1_values, w2_values, w3_values, w4_values = StructArrays.components(entropy_var_values) - - @turbo thread=true for i in eachindex(rho_values, rho_v1_values, rho_v2_values, - rho_e_values, - w1_values, w2_values, w3_values, w4_values) - rho = rho_values[i] - rho_v1 = rho_v1_values[i] - rho_v2 = rho_v2_values[i] - rho_e = rho_e_values[i] - - # The following is basically the same code as in `cons2entropy` - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v_square = v1^2 + v2^2 - p = (gamma - 1) * (rho_e - 0.5 * rho * v_square) - s = log(p) - gamma * log(rho) - rho_p = rho / p - - w1_values[i] = (gamma - s) * inv_gamma_minus_one - 0.5 * rho_p * v_square - w2_values[i] = rho_p * v1 - w3_values[i] = rho_p * v2 - w4_values[i] = -rho_p + #! format: noindent + + # TODO: Upstream, LoopVectorization + # At the time of writing, LoopVectorization.jl cannot handle this kind of + # loop optimally when passing our custom functions `cons2entropy` and + # `entropy2cons`. Thus, we need to insert the physics directly here to + # get a significant runtime performance improvement. + function cons2entropy!( + entropy_var_values::StructArray, + u_values::StructArray, + equations::CompressibleEulerEquations2D + ) + # The following is semantically equivalent to + # @threaded for i in eachindex(u_values) + # entropy_var_values[i] = cons2entropy(u_values[i], equations) + # end + # but much more efficient due to explicit optimization via `@turbo` from + # LoopVectorization.jl. + @unpack gamma, inv_gamma_minus_one = equations + + rho_values, rho_v1_values, rho_v2_values, rho_e_values = StructArrays.components(u_values) + w1_values, w2_values, w3_values, w4_values = StructArrays.components(entropy_var_values) + + @turbo thread = true for i in eachindex( + rho_values, rho_v1_values, rho_v2_values, + rho_e_values, + w1_values, w2_values, w3_values, w4_values + ) + rho = rho_values[i] + rho_v1 = rho_v1_values[i] + rho_v2 = rho_v2_values[i] + rho_e = rho_e_values[i] + + # The following is basically the same code as in `cons2entropy` + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v_square = v1^2 + v2^2 + p = (gamma - 1) * (rho_e - 0.5 * rho * v_square) + s = log(p) - gamma * log(rho) + rho_p = rho / p + + w1_values[i] = (gamma - s) * inv_gamma_minus_one - 0.5 * rho_p * v_square + w2_values[i] = rho_p * v1 + w3_values[i] = rho_p * v2 + w4_values[i] = -rho_p + end end -end - -function entropy2cons!(entropy_projected_u_values::StructArray, - projected_entropy_var_values::StructArray, - equations::CompressibleEulerEquations2D) - # The following is semantically equivalent to - # @threaded for i in eachindex(projected_entropy_var_values) - # entropy_projected_u_values[i] = entropy2cons(projected_entropy_var_values[i], equations) - # end - # but much more efficient due to explicit optimization via `@turbo` from - # LoopVectorization.jl. - @unpack gamma, inv_gamma_minus_one = equations - gamma_minus_one = gamma - 1 - - rho_values, rho_v1_values, rho_v2_values, rho_e_values = StructArrays.components(entropy_projected_u_values) - w1_values, w2_values, w3_values, w4_values = StructArrays.components(projected_entropy_var_values) - - @turbo thread=true for i in eachindex(rho_values, rho_v1_values, rho_v2_values, - rho_e_values, - w1_values, w2_values, w3_values, w4_values) - - # The following is basically the same code as in `entropy2cons` - # Convert to entropy `-rho * s` used by - # - See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD - # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) - # instead of `-rho * s / (gamma - 1)` - w1 = gamma_minus_one * w1_values[i] - w2 = gamma_minus_one * w2_values[i] - w3 = gamma_minus_one * w3_values[i] - w4 = gamma_minus_one * w4_values[i] - - # s = specific entropy, eq. (53) - s = gamma - w1 + (w2^2 + w3^2) / (2 * w4) - - # eq. (52) - rho_iota = (gamma_minus_one / (-w4)^gamma)^(inv_gamma_minus_one) * - exp(-s * inv_gamma_minus_one) - - # eq. (51) - rho_values[i] = -rho_iota * w4 - rho_v1_values[i] = rho_iota * w2 - rho_v2_values[i] = rho_iota * w3 - rho_e_values[i] = rho_iota * (1 - (w2^2 + w3^2) / (2 * w4)) + + function entropy2cons!( + entropy_projected_u_values::StructArray, + projected_entropy_var_values::StructArray, + equations::CompressibleEulerEquations2D + ) + # The following is semantically equivalent to + # @threaded for i in eachindex(projected_entropy_var_values) + # entropy_projected_u_values[i] = entropy2cons(projected_entropy_var_values[i], equations) + # end + # but much more efficient due to explicit optimization via `@turbo` from + # LoopVectorization.jl. + @unpack gamma, inv_gamma_minus_one = equations + gamma_minus_one = gamma - 1 + + rho_values, rho_v1_values, rho_v2_values, rho_e_values = StructArrays.components(entropy_projected_u_values) + w1_values, w2_values, w3_values, w4_values = StructArrays.components(projected_entropy_var_values) + + @turbo thread = true for i in eachindex( + rho_values, rho_v1_values, rho_v2_values, + rho_e_values, + w1_values, w2_values, w3_values, w4_values + ) + + # The following is basically the same code as in `entropy2cons` + # Convert to entropy `-rho * s` used by + # - See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD + # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) + # instead of `-rho * s / (gamma - 1)` + w1 = gamma_minus_one * w1_values[i] + w2 = gamma_minus_one * w2_values[i] + w3 = gamma_minus_one * w3_values[i] + w4 = gamma_minus_one * w4_values[i] + + # s = specific entropy, eq. (53) + s = gamma - w1 + (w2^2 + w3^2) / (2 * w4) + + # eq. (52) + rho_iota = (gamma_minus_one / (-w4)^gamma)^(inv_gamma_minus_one) * + exp(-s * inv_gamma_minus_one) + + # eq. (51) + rho_values[i] = -rho_iota * w4 + rho_v1_values[i] = rho_iota * w2 + rho_v2_values[i] = rho_iota * w3 + rho_e_values[i] = rho_iota * (1 - (w2^2 + w3^2) / (2 * w4)) + end end -end - -function cons2entropy!(entropy_var_values::StructArray, - u_values::StructArray, - equations::CompressibleEulerEquations3D) - # The following is semantically equivalent to - # @threaded for i in eachindex(u_values) - # entropy_var_values[i] = cons2entropy(u_values[i], equations) - # end - # but much more efficient due to explicit optimization via `@turbo` from - # LoopVectorization.jl. - @unpack gamma, inv_gamma_minus_one = equations - - rho_values, rho_v1_values, rho_v2_values, rho_v3_values, rho_e_values = StructArrays.components(u_values) - w1_values, w2_values, w3_values, w4_values, w5_values = StructArrays.components(entropy_var_values) - - @turbo thread=true for i in eachindex(rho_values, rho_v1_values, rho_v2_values, - rho_v3_values, rho_e_values, - w1_values, w2_values, w3_values, w4_values, - w5_values) - rho = rho_values[i] - rho_v1 = rho_v1_values[i] - rho_v2 = rho_v2_values[i] - rho_v3 = rho_v3_values[i] - rho_e = rho_e_values[i] - - # The following is basically the same code as in `cons2entropy` - v1 = rho_v1 / rho - v2 = rho_v2 / rho - v3 = rho_v3 / rho - v_square = v1^2 + v2^2 + v3^2 - p = (gamma - 1) * (rho_e - 0.5 * rho * v_square) - s = log(p) - gamma * log(rho) - rho_p = rho / p - - w1_values[i] = (gamma - s) * inv_gamma_minus_one - 0.5 * rho_p * v_square - w2_values[i] = rho_p * v1 - w3_values[i] = rho_p * v2 - w4_values[i] = rho_p * v3 - w5_values[i] = -rho_p + + function cons2entropy!( + entropy_var_values::StructArray, + u_values::StructArray, + equations::CompressibleEulerEquations3D + ) + # The following is semantically equivalent to + # @threaded for i in eachindex(u_values) + # entropy_var_values[i] = cons2entropy(u_values[i], equations) + # end + # but much more efficient due to explicit optimization via `@turbo` from + # LoopVectorization.jl. + @unpack gamma, inv_gamma_minus_one = equations + + rho_values, rho_v1_values, rho_v2_values, rho_v3_values, rho_e_values = StructArrays.components(u_values) + w1_values, w2_values, w3_values, w4_values, w5_values = StructArrays.components(entropy_var_values) + + @turbo thread = true for i in eachindex( + rho_values, rho_v1_values, rho_v2_values, + rho_v3_values, rho_e_values, + w1_values, w2_values, w3_values, w4_values, + w5_values + ) + rho = rho_values[i] + rho_v1 = rho_v1_values[i] + rho_v2 = rho_v2_values[i] + rho_v3 = rho_v3_values[i] + rho_e = rho_e_values[i] + + # The following is basically the same code as in `cons2entropy` + v1 = rho_v1 / rho + v2 = rho_v2 / rho + v3 = rho_v3 / rho + v_square = v1^2 + v2^2 + v3^2 + p = (gamma - 1) * (rho_e - 0.5 * rho * v_square) + s = log(p) - gamma * log(rho) + rho_p = rho / p + + w1_values[i] = (gamma - s) * inv_gamma_minus_one - 0.5 * rho_p * v_square + w2_values[i] = rho_p * v1 + w3_values[i] = rho_p * v2 + w4_values[i] = rho_p * v3 + w5_values[i] = -rho_p + end end -end - -function entropy2cons!(entropy_projected_u_values::StructArray, - projected_entropy_var_values::StructArray, - equations::CompressibleEulerEquations3D) - # The following is semantically equivalent to - # @threaded for i in eachindex(projected_entropy_var_values) - # entropy_projected_u_values[i] = entropy2cons(projected_entropy_var_values[i], equations) - # end - # but much more efficient due to explicit optimization via `@turbo` from - # LoopVectorization.jl. - @unpack gamma, inv_gamma_minus_one = equations - gamma_minus_one = gamma - 1 - - rho_values, rho_v1_values, rho_v2_values, rho_v3_values, rho_e_values = StructArrays.components(entropy_projected_u_values) - w1_values, w2_values, w3_values, w4_values, w5_values = StructArrays.components(projected_entropy_var_values) - - @turbo thread=true for i in eachindex(rho_values, rho_v1_values, rho_v2_values, - rho_v3_values, rho_e_values, - w1_values, w2_values, w3_values, w4_values, - w5_values) - - # The following is basically the same code as in `entropy2cons` - # Convert to entropy `-rho * s` used by - # - See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD - # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) - # instead of `-rho * s / (gamma - 1)` - w1 = gamma_minus_one * w1_values[i] - w2 = gamma_minus_one * w2_values[i] - w3 = gamma_minus_one * w3_values[i] - w4 = gamma_minus_one * w4_values[i] - w5 = gamma_minus_one * w5_values[i] - - # s = specific entropy, eq. (53) - s = gamma - w1 + (w2^2 + w3^2 + w4^2) / (2 * w5) - - # eq. (52) - rho_iota = (gamma_minus_one / (-w5)^gamma)^(inv_gamma_minus_one) * - exp(-s * inv_gamma_minus_one) - - # eq. (51) - rho_values[i] = -rho_iota * w5 - rho_v1_values[i] = rho_iota * w2 - rho_v2_values[i] = rho_iota * w3 - rho_v3_values[i] = rho_iota * w4 - rho_e_values[i] = rho_iota * (1 - (w2^2 + w3^2 + w4^2) / (2 * w5)) + + function entropy2cons!( + entropy_projected_u_values::StructArray, + projected_entropy_var_values::StructArray, + equations::CompressibleEulerEquations3D + ) + # The following is semantically equivalent to + # @threaded for i in eachindex(projected_entropy_var_values) + # entropy_projected_u_values[i] = entropy2cons(projected_entropy_var_values[i], equations) + # end + # but much more efficient due to explicit optimization via `@turbo` from + # LoopVectorization.jl. + @unpack gamma, inv_gamma_minus_one = equations + gamma_minus_one = gamma - 1 + + rho_values, rho_v1_values, rho_v2_values, rho_v3_values, rho_e_values = StructArrays.components(entropy_projected_u_values) + w1_values, w2_values, w3_values, w4_values, w5_values = StructArrays.components(projected_entropy_var_values) + + @turbo thread = true for i in eachindex( + rho_values, rho_v1_values, rho_v2_values, + rho_v3_values, rho_e_values, + w1_values, w2_values, w3_values, w4_values, + w5_values + ) + + # The following is basically the same code as in `entropy2cons` + # Convert to entropy `-rho * s` used by + # - See Hughes, Franca, Mallet (1986) A new finite element formulation for CFD + # [DOI: 10.1016/0045-7825(86)90127-1](https://doi.org/10.1016/0045-7825(86)90127-1) + # instead of `-rho * s / (gamma - 1)` + w1 = gamma_minus_one * w1_values[i] + w2 = gamma_minus_one * w2_values[i] + w3 = gamma_minus_one * w3_values[i] + w4 = gamma_minus_one * w4_values[i] + w5 = gamma_minus_one * w5_values[i] + + # s = specific entropy, eq. (53) + s = gamma - w1 + (w2^2 + w3^2 + w4^2) / (2 * w5) + + # eq. (52) + rho_iota = (gamma_minus_one / (-w5)^gamma)^(inv_gamma_minus_one) * + exp(-s * inv_gamma_minus_one) + + # eq. (51) + rho_values[i] = -rho_iota * w5 + rho_v1_values[i] = rho_iota * w2 + rho_v2_values[i] = rho_iota * w3 + rho_v3_values[i] = rho_iota * w4 + rho_e_values[i] = rho_iota * (1 - (w2^2 + w3^2 + w4^2) / (2 * w5)) + end end -end end # @muladd diff --git a/src/solvers/dgmulti/flux_differencing_gauss_sbp.jl b/src/solvers/dgmulti/flux_differencing_gauss_sbp.jl index 63a37f6780..9ce5fa3827 100644 --- a/src/solvers/dgmulti/flux_differencing_gauss_sbp.jl +++ b/src/solvers/dgmulti/flux_differencing_gauss_sbp.jl @@ -1,4 +1,3 @@ - # ========= GaussSBP approximation types ============ # Note: we define type aliases outside of the @muladd block to avoid Revise breaking when code # inside the @muladd block is edited. See https://github.com/trixi-framework/Trixi.jl/issues/801 @@ -40,9 +39,11 @@ abstract type AbstractTensorProductGaussOperator end # TensorProductGaussFaceOperator{Tmat, Ti} # # Data for performing tensor product interpolation from volume nodes to face nodes. -struct TensorProductGaussFaceOperator{NDIMS, OperatorType <: AbstractGaussOperator, - Tmat, Tweights, Tfweights, Tindices} <: - AbstractTensorProductGaussOperator +struct TensorProductGaussFaceOperator{ + NDIMS, OperatorType <: AbstractGaussOperator, + Tmat, Tweights, Tfweights, Tindices, + } <: + AbstractTensorProductGaussOperator interp_matrix_gauss_to_face_1d::Tmat inv_volume_weights_1d::Tweights face_weights::Tfweights @@ -51,8 +52,10 @@ struct TensorProductGaussFaceOperator{NDIMS, OperatorType <: AbstractGaussOperat nfaces::Int end -function TensorProductGaussFaceOperator(operator::AbstractGaussOperator, - dg::DGMulti{1, Line, GaussSBP}) +function TensorProductGaussFaceOperator( + operator::AbstractGaussOperator, + dg::DGMulti{1, Line, GaussSBP} + ) rd = dg.basis rq1D, wq1D = StartUpDG.gauss_quad(0, 0, polydeg(dg)) @@ -68,15 +71,19 @@ function TensorProductGaussFaceOperator(operator::AbstractGaussOperator, Tw = typeof(inv.(wq1D)) Tf = typeof(rd.wf) Ti = typeof(face_indices_tensor_product) - return TensorProductGaussFaceOperator{1, T_op, Tm, Tw, Tf, Ti}(interp_matrix_gauss_to_face_1d, - inv.(wq1D), rd.wf, - face_indices_tensor_product, - nnodes_1d, num_faces) + return TensorProductGaussFaceOperator{1, T_op, Tm, Tw, Tf, Ti}( + interp_matrix_gauss_to_face_1d, + inv.(wq1D), rd.wf, + face_indices_tensor_product, + nnodes_1d, num_faces + ) end # constructor for a 2D operator -function TensorProductGaussFaceOperator(operator::AbstractGaussOperator, - dg::DGMulti{2, Quad, GaussSBP}) +function TensorProductGaussFaceOperator( + operator::AbstractGaussOperator, + dg::DGMulti{2, Quad, GaussSBP} + ) rd = dg.basis rq1D, wq1D = StartUpDG.gauss_quad(0, 0, polydeg(dg)) @@ -98,15 +105,19 @@ function TensorProductGaussFaceOperator(operator::AbstractGaussOperator, Tw = typeof(inv.(wq1D)) Tf = typeof(rd.wf) Ti = typeof(face_indices_tensor_product) - return TensorProductGaussFaceOperator{2, T_op, Tm, Tw, Tf, Ti}(interp_matrix_gauss_to_face_1d, - inv.(wq1D), rd.wf, - face_indices_tensor_product, - nnodes_1d, num_faces) + return TensorProductGaussFaceOperator{2, T_op, Tm, Tw, Tf, Ti}( + interp_matrix_gauss_to_face_1d, + inv.(wq1D), rd.wf, + face_indices_tensor_product, + nnodes_1d, num_faces + ) end # constructor for a 3D operator -function TensorProductGaussFaceOperator(operator::AbstractGaussOperator, - dg::DGMulti{3, Hex, GaussSBP}) +function TensorProductGaussFaceOperator( + operator::AbstractGaussOperator, + dg::DGMulti{3, Hex, GaussSBP} + ) rd = dg.basis rq1D, wq1D = StartUpDG.gauss_quad(0, 0, polydeg(dg)) @@ -117,8 +128,10 @@ function TensorProductGaussFaceOperator(operator::AbstractGaussOperator, # Permutation of indices in a tensor product form num_faces = StartUpDG.num_faces(rd.element_type) indices = reshape(1:length(rd.rf), nnodes_1d, nnodes_1d, num_faces) - face_indices_tensor_product = zeros(Int, 2, nnodes_1d, nnodes_1d, - ndims(rd.element_type)) + face_indices_tensor_product = zeros( + Int, 2, nnodes_1d, nnodes_1d, + ndims(rd.element_type) + ) for j in 1:nnodes_1d, i in 1:nnodes_1d # loop over nodes in one face face_indices_tensor_product[:, i, j, 1] .= indices[i, j, 1:2] face_indices_tensor_product[:, i, j, 2] .= indices[i, j, 3:4] @@ -130,10 +143,12 @@ function TensorProductGaussFaceOperator(operator::AbstractGaussOperator, Tw = typeof(inv.(wq1D)) Tf = typeof(rd.wf) Ti = typeof(face_indices_tensor_product) - return TensorProductGaussFaceOperator{3, T_op, Tm, Tw, Tf, Ti}(interp_matrix_gauss_to_face_1d, - inv.(wq1D), rd.wf, - face_indices_tensor_product, - nnodes_1d, num_faces) + return TensorProductGaussFaceOperator{3, T_op, Tm, Tw, Tf, Ti}( + interp_matrix_gauss_to_face_1d, + inv.(wq1D), rd.wf, + face_indices_tensor_product, + nnodes_1d, num_faces + ) end # specialize behavior of `mul_by!(A)` where `A isa TensorProductGaussFaceOperator)` @@ -141,25 +156,35 @@ end return (out, x) -> tensor_product_gauss_face_operator!(out, A, x) end -@inline function tensor_product_gauss_face_operator!(out::AbstractMatrix, - A::AbstractTensorProductGaussOperator, - x::AbstractMatrix) +@inline function tensor_product_gauss_face_operator!( + out::AbstractMatrix, + A::AbstractTensorProductGaussOperator, + x::AbstractMatrix + ) @threaded for col in Base.OneTo(size(out, 2)) tensor_product_gauss_face_operator!(view(out, :, col), A, view(x, :, col)) end end -@inline function tensor_product_gauss_face_operator!(out::AbstractVector, - A::TensorProductGaussFaceOperator{1, - Interpolation}, - x::AbstractVector) +@inline function tensor_product_gauss_face_operator!( + out::AbstractVector, + A::TensorProductGaussFaceOperator{ + 1, + Interpolation, + }, + x::AbstractVector + ) mul!(out, A.interp_matrix_gauss_to_face_1d, x) end -@inline function tensor_product_gauss_face_operator!(out::AbstractVector, - A::TensorProductGaussFaceOperator{1, - <:Projection}, - x::AbstractVector) +@inline function tensor_product_gauss_face_operator!( + out::AbstractVector, + A::TensorProductGaussFaceOperator{ + 1, + <:Projection, + }, + x::AbstractVector + ) mul!(out, A.interp_matrix_gauss_to_face_1d', x) @. out *= A.inv_volume_weights_1d end @@ -169,463 +194,527 @@ end # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -#! format: off -# Interpolates values from volume Gauss nodes to face nodes on one element. -@inline function tensor_product_gauss_face_operator!(out::AbstractVector, - A::TensorProductGaussFaceOperator{2, Interpolation}, - x_in::AbstractVector) -#! format: on - (; interp_matrix_gauss_to_face_1d, face_indices_tensor_product) = A - (; nnodes_1d) = A - - fill!(out, zero(eltype(out))) - - # for 2D GaussSBP nodes, the indexing is first in x, then in y - x = reshape(x_in, nnodes_1d, nnodes_1d) - - # interpolation in the x-direction - @turbo for i in Base.OneTo(nnodes_1d) # loop over nodes in a face - index_left = face_indices_tensor_product[1, i, 1] - index_right = face_indices_tensor_product[2, i, 1] - for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes - out[index_left] = out[index_left] + - interp_matrix_gauss_to_face_1d[1, jj] * x[jj, i] - out[index_right] = out[index_right] + - interp_matrix_gauss_to_face_1d[2, jj] * x[jj, i] + #! format: noindent + + #! format: off + # Interpolates values from volume Gauss nodes to face nodes on one element. + @inline function tensor_product_gauss_face_operator!( + out::AbstractVector, + A::TensorProductGaussFaceOperator{2, Interpolation}, + x_in::AbstractVector + ) + #! format: on + (; interp_matrix_gauss_to_face_1d, face_indices_tensor_product) = A + (; nnodes_1d) = A + + fill!(out, zero(eltype(out))) + + # for 2D GaussSBP nodes, the indexing is first in x, then in y + x = reshape(x_in, nnodes_1d, nnodes_1d) + + # interpolation in the x-direction + @turbo for i in Base.OneTo(nnodes_1d) # loop over nodes in a face + index_left = face_indices_tensor_product[1, i, 1] + index_right = face_indices_tensor_product[2, i, 1] + for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes + out[index_left] = out[index_left] + + interp_matrix_gauss_to_face_1d[1, jj] * x[jj, i] + out[index_right] = out[index_right] + + interp_matrix_gauss_to_face_1d[2, jj] * x[jj, i] + end end - end - # interpolation in the y-direction - @turbo for i in Base.OneTo(nnodes_1d) # loop over nodes in a face - index_left = face_indices_tensor_product[1, i, 2] - index_right = face_indices_tensor_product[2, i, 2] - for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes - out[index_left] = out[index_left] + - interp_matrix_gauss_to_face_1d[1, jj] * x[i, jj] - out[index_right] = out[index_right] + - interp_matrix_gauss_to_face_1d[2, jj] * x[i, jj] + # interpolation in the y-direction + @turbo for i in Base.OneTo(nnodes_1d) # loop over nodes in a face + index_left = face_indices_tensor_product[1, i, 2] + index_right = face_indices_tensor_product[2, i, 2] + for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes + out[index_left] = out[index_left] + + interp_matrix_gauss_to_face_1d[1, jj] * x[i, jj] + out[index_right] = out[index_right] + + interp_matrix_gauss_to_face_1d[2, jj] * x[i, jj] + end end end -end -# Interpolates values from volume Gauss nodes to face nodes on one element. -#! format: off -@inline function tensor_product_gauss_face_operator!(out::AbstractVector, - A::TensorProductGaussFaceOperator{3, Interpolation}, - x::AbstractVector) -#! format: on - (; interp_matrix_gauss_to_face_1d, face_indices_tensor_product) = A - (; nnodes_1d) = A - - fill!(out, zero(eltype(out))) - - # for 3D GaussSBP nodes, the indexing is first in y, then x, then z. - x = reshape(x, nnodes_1d, nnodes_1d, nnodes_1d) - - # interpolation in the y-direction - @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) # loop over nodes in a face - index_left = face_indices_tensor_product[1, i, j, 2] - index_right = face_indices_tensor_product[2, i, j, 2] - for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes - out[index_left] = out[index_left] + - interp_matrix_gauss_to_face_1d[1, jj] * x[jj, i, j] - out[index_right] = out[index_right] + - interp_matrix_gauss_to_face_1d[2, jj] * x[jj, i, j] + # Interpolates values from volume Gauss nodes to face nodes on one element. + #! format: off + @inline function tensor_product_gauss_face_operator!( + out::AbstractVector, + A::TensorProductGaussFaceOperator{3, Interpolation}, + x::AbstractVector + ) + #! format: on + (; interp_matrix_gauss_to_face_1d, face_indices_tensor_product) = A + (; nnodes_1d) = A + + fill!(out, zero(eltype(out))) + + # for 3D GaussSBP nodes, the indexing is first in y, then x, then z. + x = reshape(x, nnodes_1d, nnodes_1d, nnodes_1d) + + # interpolation in the y-direction + @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) # loop over nodes in a face + index_left = face_indices_tensor_product[1, i, j, 2] + index_right = face_indices_tensor_product[2, i, j, 2] + for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes + out[index_left] = out[index_left] + + interp_matrix_gauss_to_face_1d[1, jj] * x[jj, i, j] + out[index_right] = out[index_right] + + interp_matrix_gauss_to_face_1d[2, jj] * x[jj, i, j] + end end - end - # interpolation in the x-direction - @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) # loop over nodes in a face - index_left = face_indices_tensor_product[1, i, j, 1] - index_right = face_indices_tensor_product[2, i, j, 1] - for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes - out[index_left] = out[index_left] + - interp_matrix_gauss_to_face_1d[1, jj] * x[i, jj, j] - out[index_right] = out[index_right] + - interp_matrix_gauss_to_face_1d[2, jj] * x[i, jj, j] + # interpolation in the x-direction + @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) # loop over nodes in a face + index_left = face_indices_tensor_product[1, i, j, 1] + index_right = face_indices_tensor_product[2, i, j, 1] + for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes + out[index_left] = out[index_left] + + interp_matrix_gauss_to_face_1d[1, jj] * x[i, jj, j] + out[index_right] = out[index_right] + + interp_matrix_gauss_to_face_1d[2, jj] * x[i, jj, j] + end end - end - # interpolation in the z-direction - @turbo for i in Base.OneTo(nnodes_1d), j in Base.OneTo(nnodes_1d) # loop over nodes in a face - index_left = face_indices_tensor_product[1, i, j, 3] - index_right = face_indices_tensor_product[2, i, j, 3] - for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes - # The ordering (i,j) -> (j,i) needs to be reversed for this last face. - # This is due to way we define face nodes for Hex() types in StartUpDG.jl. - out[index_left] = out[index_left] + - interp_matrix_gauss_to_face_1d[1, jj] * x[j, i, jj] - out[index_right] = out[index_right] + - interp_matrix_gauss_to_face_1d[2, jj] * x[j, i, jj] + # interpolation in the z-direction + @turbo for i in Base.OneTo(nnodes_1d), j in Base.OneTo(nnodes_1d) # loop over nodes in a face + index_left = face_indices_tensor_product[1, i, j, 3] + index_right = face_indices_tensor_product[2, i, j, 3] + for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes + # The ordering (i,j) -> (j,i) needs to be reversed for this last face. + # This is due to way we define face nodes for Hex() types in StartUpDG.jl. + out[index_left] = out[index_left] + + interp_matrix_gauss_to_face_1d[1, jj] * x[j, i, jj] + out[index_right] = out[index_right] + + interp_matrix_gauss_to_face_1d[2, jj] * x[j, i, jj] + end end end -end -# Projects face node values to volume Gauss nodes on one element. -#! format: off -@inline function tensor_product_gauss_face_operator!(out_vec::AbstractVector, - A::TensorProductGaussFaceOperator{2, Projection{ApplyFaceWeights}}, - x::AbstractVector) where {ApplyFaceWeights} -#! format: on - (; interp_matrix_gauss_to_face_1d, face_indices_tensor_product) = A - (; inv_volume_weights_1d, nnodes_1d) = A - - fill!(out_vec, zero(eltype(out_vec))) - - # As of Julia 1.9, Base.ReshapedArray does not produce allocations when setting values. - # Thus, Base.ReshapedArray should be used if you are setting values in the array. - # `reshape` is fine if you are only accessing values. - # Note that, for 2D GaussSBP nodes, the indexing is first in x, then y - out = Base.ReshapedArray(out_vec, (nnodes_1d, nnodes_1d), ()) - - if ApplyFaceWeights == true - @turbo for i in eachindex(x) - x[i] = x[i] * A.face_weights[i] + # Projects face node values to volume Gauss nodes on one element. + #! format: off + @inline function tensor_product_gauss_face_operator!( + out_vec::AbstractVector, + A::TensorProductGaussFaceOperator{2, Projection{ApplyFaceWeights}}, + x::AbstractVector + ) where {ApplyFaceWeights} + #! format: on + (; interp_matrix_gauss_to_face_1d, face_indices_tensor_product) = A + (; inv_volume_weights_1d, nnodes_1d) = A + + fill!(out_vec, zero(eltype(out_vec))) + + # As of Julia 1.9, Base.ReshapedArray does not produce allocations when setting values. + # Thus, Base.ReshapedArray should be used if you are setting values in the array. + # `reshape` is fine if you are only accessing values. + # Note that, for 2D GaussSBP nodes, the indexing is first in x, then y + out = Base.ReshapedArray(out_vec, (nnodes_1d, nnodes_1d), ()) + + if ApplyFaceWeights == true + @turbo for i in eachindex(x) + x[i] = x[i] * A.face_weights[i] + end end - end - # interpolation in the x-direction - @turbo for i in Base.OneTo(nnodes_1d) # loop over face nodes - index_left = face_indices_tensor_product[1, i, 1] - index_right = face_indices_tensor_product[2, i, 1] - for jj in Base.OneTo(nnodes_1d) # loop over a line of volume nodes - out[jj, i] = out[jj, i] + - interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] - out[jj, i] = out[jj, i] + - interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + # interpolation in the x-direction + @turbo for i in Base.OneTo(nnodes_1d) # loop over face nodes + index_left = face_indices_tensor_product[1, i, 1] + index_right = face_indices_tensor_product[2, i, 1] + for jj in Base.OneTo(nnodes_1d) # loop over a line of volume nodes + out[jj, i] = out[jj, i] + + interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] + out[jj, i] = out[jj, i] + + interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + end end - end - # interpolation in the y-direction - @turbo for i in Base.OneTo(nnodes_1d) - index_left = face_indices_tensor_product[1, i, 2] - index_right = face_indices_tensor_product[2, i, 2] - # loop over a line of volume nodes - for jj in Base.OneTo(nnodes_1d) - out[i, jj] = out[i, jj] + - interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] - out[i, jj] = out[i, jj] + - interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + # interpolation in the y-direction + @turbo for i in Base.OneTo(nnodes_1d) + index_left = face_indices_tensor_product[1, i, 2] + index_right = face_indices_tensor_product[2, i, 2] + # loop over a line of volume nodes + for jj in Base.OneTo(nnodes_1d) + out[i, jj] = out[i, jj] + + interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] + out[i, jj] = out[i, jj] + + interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + end end - end - # apply inv(M) - @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) - out[i, j] = out[i, j] * inv_volume_weights_1d[i] * inv_volume_weights_1d[j] + # apply inv(M) + @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) + out[i, j] = out[i, j] * inv_volume_weights_1d[i] * inv_volume_weights_1d[j] + end end -end -# Interpolates values from volume Gauss nodes to face nodes on one element. -#! format: off -@inline function tensor_product_gauss_face_operator!(out_vec::AbstractVector, - A::TensorProductGaussFaceOperator{3, Projection{ApplyFaceWeights}}, - x::AbstractVector) where {ApplyFaceWeights} -#! format: on - @unpack interp_matrix_gauss_to_face_1d, face_indices_tensor_product = A - @unpack inv_volume_weights_1d, nnodes_1d, nfaces = A - - fill!(out_vec, zero(eltype(out_vec))) - - # As of Julia 1.9, Base.ReshapedArray does not produce allocations when setting values. - # Thus, Base.ReshapedArray should be used if you are setting values in the array. - # `reshape` is fine if you are only accessing values. - # Note that, for 3D GaussSBP nodes, the indexing is first in y, then x, then z. - out = Base.ReshapedArray(out_vec, (nnodes_1d, nnodes_1d, nnodes_1d), ()) - - if ApplyFaceWeights == true - @turbo for i in eachindex(x) - x[i] = x[i] * A.face_weights[i] + # Interpolates values from volume Gauss nodes to face nodes on one element. + #! format: off + @inline function tensor_product_gauss_face_operator!( + out_vec::AbstractVector, + A::TensorProductGaussFaceOperator{3, Projection{ApplyFaceWeights}}, + x::AbstractVector + ) where {ApplyFaceWeights} + #! format: on + @unpack interp_matrix_gauss_to_face_1d, face_indices_tensor_product = A + @unpack inv_volume_weights_1d, nnodes_1d, nfaces = A + + fill!(out_vec, zero(eltype(out_vec))) + + # As of Julia 1.9, Base.ReshapedArray does not produce allocations when setting values. + # Thus, Base.ReshapedArray should be used if you are setting values in the array. + # `reshape` is fine if you are only accessing values. + # Note that, for 3D GaussSBP nodes, the indexing is first in y, then x, then z. + out = Base.ReshapedArray(out_vec, (nnodes_1d, nnodes_1d, nnodes_1d), ()) + + if ApplyFaceWeights == true + @turbo for i in eachindex(x) + x[i] = x[i] * A.face_weights[i] + end end - end - # interpolation in the y-direction - @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) # loop over nodes in a face - index_left = face_indices_tensor_product[1, i, j, 2] - index_right = face_indices_tensor_product[2, i, j, 2] - for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes - out[jj, i, j] = out[jj, i, j] + - interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] - out[jj, i, j] = out[jj, i, j] + - interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + # interpolation in the y-direction + @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) # loop over nodes in a face + index_left = face_indices_tensor_product[1, i, j, 2] + index_right = face_indices_tensor_product[2, i, j, 2] + for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes + out[jj, i, j] = out[jj, i, j] + + interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] + out[jj, i, j] = out[jj, i, j] + + interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + end end - end - # interpolation in the x-direction - @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) # loop over nodes in a face - index_left = face_indices_tensor_product[1, i, j, 1] - index_right = face_indices_tensor_product[2, i, j, 1] - for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes - out[i, jj, j] = out[i, jj, j] + - interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] - out[i, jj, j] = out[i, jj, j] + - interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + # interpolation in the x-direction + @turbo for j in Base.OneTo(nnodes_1d), i in Base.OneTo(nnodes_1d) # loop over nodes in a face + index_left = face_indices_tensor_product[1, i, j, 1] + index_right = face_indices_tensor_product[2, i, j, 1] + for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes + out[i, jj, j] = out[i, jj, j] + + interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] + out[i, jj, j] = out[i, jj, j] + + interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + end end - end - # interpolation in the z-direction - @turbo for i in Base.OneTo(nnodes_1d), j in Base.OneTo(nnodes_1d) # loop over nodes in a face - index_left = face_indices_tensor_product[1, i, j, 3] - index_right = face_indices_tensor_product[2, i, j, 3] - for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes - # The ordering (i,j) -> (j,i) needs to be reversed for this last face. - # This is due to way we define face nodes for Hex() types in StartUpDG.jl. - out[j, i, jj] = out[j, i, jj] + - interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] - out[j, i, jj] = out[j, i, jj] + - interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + # interpolation in the z-direction + @turbo for i in Base.OneTo(nnodes_1d), j in Base.OneTo(nnodes_1d) # loop over nodes in a face + index_left = face_indices_tensor_product[1, i, j, 3] + index_right = face_indices_tensor_product[2, i, j, 3] + for jj in Base.OneTo(nnodes_1d) # loop over "line" of volume nodes + # The ordering (i,j) -> (j,i) needs to be reversed for this last face. + # This is due to way we define face nodes for Hex() types in StartUpDG.jl. + out[j, i, jj] = out[j, i, jj] + + interp_matrix_gauss_to_face_1d[1, jj] * x[index_left] + out[j, i, jj] = out[j, i, jj] + + interp_matrix_gauss_to_face_1d[2, jj] * x[index_right] + end end - end - # apply inv(M) - @turbo for k in Base.OneTo(nnodes_1d), j in Base.OneTo(nnodes_1d), - i in Base.OneTo(nnodes_1d) + # apply inv(M) + @turbo for k in Base.OneTo(nnodes_1d), j in Base.OneTo(nnodes_1d), + i in Base.OneTo(nnodes_1d) - out[i, j, k] = out[i, j, k] * inv_volume_weights_1d[i] * - inv_volume_weights_1d[j] * inv_volume_weights_1d[k] + out[i, j, k] = out[i, j, k] * inv_volume_weights_1d[i] * + inv_volume_weights_1d[j] * inv_volume_weights_1d[k] + end end -end - -# For now, this is mostly the same as `create_cache` for DGMultiFluxDiff{<:Polynomial}. -# In the future, we may modify it so that we can specialize additional parts of GaussSBP() solvers. -function create_cache(mesh::DGMultiMesh, equations, - dg::DGMultiFluxDiff{<:GaussSBP, <:Union{Line, Quad, Hex}}, RealT, - uEltype) - # call general Polynomial flux differencing constructor - cache = invoke(create_cache, - Tuple{typeof(mesh), typeof(equations), - DGMultiFluxDiff, typeof(RealT), typeof(uEltype)}, - mesh, equations, dg, RealT, uEltype) - - rd = dg.basis - @unpack md = mesh - - # for change of basis prior to the volume integral and entropy projection - r1D, _ = StartUpDG.gauss_lobatto_quad(0, 0, polydeg(dg)) - rq1D, _ = StartUpDG.gauss_quad(0, 0, polydeg(dg)) - interp_matrix_lobatto_to_gauss_1D = polynomial_interpolation_matrix(r1D, rq1D) - interp_matrix_gauss_to_lobatto_1D = polynomial_interpolation_matrix(rq1D, r1D) - NDIMS = ndims(rd.element_type) - interp_matrix_lobatto_to_gauss = SimpleKronecker(NDIMS, - interp_matrix_lobatto_to_gauss_1D, - uEltype) - interp_matrix_gauss_to_lobatto = SimpleKronecker(NDIMS, - interp_matrix_gauss_to_lobatto_1D, - uEltype) - inv_gauss_weights = inv.(rd.wq) - - # specialized operators to perform tensor product interpolation to faces for Gauss nodes - interp_matrix_gauss_to_face = TensorProductGaussFaceOperator(Interpolation(), dg) - projection_matrix_gauss_to_face = TensorProductGaussFaceOperator(Projection{Static.False()}(), - dg) - - # `LIFT` matrix for Gauss nodes - this is equivalent to `projection_matrix_gauss_to_face` scaled by `diagm(rd.wf)`, - # where `rd.wf` are Gauss node face quadrature weights. - gauss_LIFT = TensorProductGaussFaceOperator(Projection{Static.True()}(), dg) - - nvars = nvariables(equations) - rhs_volume_local_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) - for _ in 1:Threads.nthreads()] - gauss_volume_local_threaded = [allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) - for _ in 1:Threads.nthreads()] - - return (; cache..., projection_matrix_gauss_to_face, gauss_LIFT, inv_gauss_weights, + # For now, this is mostly the same as `create_cache` for DGMultiFluxDiff{<:Polynomial}. + # In the future, we may modify it so that we can specialize additional parts of GaussSBP() solvers. + function create_cache( + mesh::DGMultiMesh, equations, + dg::DGMultiFluxDiff{<:GaussSBP, <:Union{Line, Quad, Hex}}, RealT, + uEltype + ) + + # call general Polynomial flux differencing constructor + cache = invoke( + create_cache, + Tuple{ + typeof(mesh), typeof(equations), + DGMultiFluxDiff, typeof(RealT), typeof(uEltype), + }, + mesh, equations, dg, RealT, uEltype + ) + + rd = dg.basis + @unpack md = mesh + + # for change of basis prior to the volume integral and entropy projection + r1D, _ = StartUpDG.gauss_lobatto_quad(0, 0, polydeg(dg)) + rq1D, _ = StartUpDG.gauss_quad(0, 0, polydeg(dg)) + interp_matrix_lobatto_to_gauss_1D = polynomial_interpolation_matrix(r1D, rq1D) + interp_matrix_gauss_to_lobatto_1D = polynomial_interpolation_matrix(rq1D, r1D) + NDIMS = ndims(rd.element_type) + interp_matrix_lobatto_to_gauss = SimpleKronecker( + NDIMS, + interp_matrix_lobatto_to_gauss_1D, + uEltype + ) + interp_matrix_gauss_to_lobatto = SimpleKronecker( + NDIMS, + interp_matrix_gauss_to_lobatto_1D, + uEltype + ) + inv_gauss_weights = inv.(rd.wq) + + # specialized operators to perform tensor product interpolation to faces for Gauss nodes + interp_matrix_gauss_to_face = TensorProductGaussFaceOperator(Interpolation(), dg) + projection_matrix_gauss_to_face = TensorProductGaussFaceOperator( + Projection{Static.False()}(), + dg + ) + + # `LIFT` matrix for Gauss nodes - this is equivalent to `projection_matrix_gauss_to_face` scaled by `diagm(rd.wf)`, + # where `rd.wf` are Gauss node face quadrature weights. + gauss_LIFT = TensorProductGaussFaceOperator(Projection{Static.True()}(), dg) + + nvars = nvariables(equations) + rhs_volume_local_threaded = [ + allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) + for _ in 1:Threads.nthreads() + ] + gauss_volume_local_threaded = [ + allocate_nested_array(uEltype, nvars, (rd.Nq,), dg) + for _ in 1:Threads.nthreads() + ] + + return (; + cache..., projection_matrix_gauss_to_face, gauss_LIFT, inv_gauss_weights, rhs_volume_local_threaded, gauss_volume_local_threaded, interp_matrix_lobatto_to_gauss, interp_matrix_gauss_to_lobatto, interp_matrix_gauss_to_face, - create_cache(mesh, equations, dg.volume_integral, dg, RealT, uEltype)...) # add cache specialized on the volume integral -end - -# by default, return an empty tuple for volume integral caches -create_cache(mesh, equations, volume_integral, dg, RealT, uEltype) = NamedTuple() - -# TODO: DGMulti. Address hard-coding of `entropy2cons!` and `cons2entropy!` for this function. -function entropy_projection!(cache, u, mesh::DGMultiMesh, equations, - dg::DGMultiFluxDiff{<:GaussSBP}) - rd = dg.basis - @unpack Vq = rd - @unpack VhP, entropy_var_values, u_values = cache - @unpack projected_entropy_var_values, entropy_projected_u_values = cache - @unpack interp_matrix_lobatto_to_gauss, interp_matrix_gauss_to_face = cache - - @threaded for e in eachelement(mesh, dg, cache) - apply_to_each_field(mul_by!(interp_matrix_lobatto_to_gauss), - view(u_values, :, e), view(u, :, e)) + create_cache(mesh, equations, dg.volume_integral, dg, RealT, uEltype)..., + ) # add cache specialized on the volume integral end - # transform quadrature values to entropy variables - cons2entropy!(entropy_var_values, u_values, equations) + # by default, return an empty tuple for volume integral caches + create_cache(mesh, equations, volume_integral, dg, RealT, uEltype) = NamedTuple() + + # TODO: DGMulti. Address hard-coding of `entropy2cons!` and `cons2entropy!` for this function. + function entropy_projection!( + cache, u, mesh::DGMultiMesh, equations, + dg::DGMultiFluxDiff{<:GaussSBP} + ) + rd = dg.basis + @unpack Vq = rd + @unpack VhP, entropy_var_values, u_values = cache + @unpack projected_entropy_var_values, entropy_projected_u_values = cache + @unpack interp_matrix_lobatto_to_gauss, interp_matrix_gauss_to_face = cache + + @threaded for e in eachelement(mesh, dg, cache) + apply_to_each_field( + mul_by!(interp_matrix_lobatto_to_gauss), + view(u_values, :, e), view(u, :, e) + ) + end - volume_indices = Base.OneTo(rd.Nq) - face_indices = (rd.Nq + 1):(rd.Nq + rd.Nfq) + # transform quadrature values to entropy variables + cons2entropy!(entropy_var_values, u_values, equations) + + volume_indices = Base.OneTo(rd.Nq) + face_indices = (rd.Nq + 1):(rd.Nq + rd.Nfq) + + # Interpolate volume Gauss nodes to Gauss face nodes (note the layout of + # `projected_entropy_var_values = [vol pts; face pts]`). + entropy_var_face_values = view(projected_entropy_var_values, face_indices, :) + apply_to_each_field( + mul_by!(interp_matrix_gauss_to_face), entropy_var_face_values, + entropy_var_values + ) + + # directly copy over volume values (no entropy projection required) + entropy_projected_volume_values = view( + entropy_projected_u_values, volume_indices, + : + ) + @threaded for i in eachindex(u_values) + entropy_projected_volume_values[i] = u_values[i] + end - # Interpolate volume Gauss nodes to Gauss face nodes (note the layout of - # `projected_entropy_var_values = [vol pts; face pts]`). - entropy_var_face_values = view(projected_entropy_var_values, face_indices, :) - apply_to_each_field(mul_by!(interp_matrix_gauss_to_face), entropy_var_face_values, - entropy_var_values) + # transform entropy to conservative variables on face values + entropy_projected_face_values = view(entropy_projected_u_values, face_indices, :) + entropy2cons!(entropy_projected_face_values, entropy_var_face_values, equations) - # directly copy over volume values (no entropy projection required) - entropy_projected_volume_values = view(entropy_projected_u_values, volume_indices, - :) - @threaded for i in eachindex(u_values) - entropy_projected_volume_values[i] = u_values[i] + return nothing end - # transform entropy to conservative variables on face values - entropy_projected_face_values = view(entropy_projected_u_values, face_indices, :) - entropy2cons!(entropy_projected_face_values, entropy_var_face_values, equations) - - return nothing -end - -# Assumes cache.flux_face_values is already computed. -# Enables tensor product evaluation of `LIFT isa TensorProductGaussFaceOperator`. -function calc_surface_integral!(du, u, mesh::DGMultiMesh, equations, - surface_integral::SurfaceIntegralWeakForm, - dg::DGMultiFluxDiff{<:GaussSBP}, cache) - (; gauss_LIFT, gauss_volume_local_threaded) = cache - - @threaded for e in eachelement(mesh, dg, cache) - - # applies LIFT matrix, output is stored at Gauss nodes - gauss_volume_local = gauss_volume_local_threaded[Threads.threadid()] - apply_to_each_field(mul_by!(gauss_LIFT), gauss_volume_local, - view(cache.flux_face_values, :, e)) - - for i in eachindex(gauss_volume_local) - du[i, e] = du[i, e] + gauss_volume_local[i] + # Assumes cache.flux_face_values is already computed. + # Enables tensor product evaluation of `LIFT isa TensorProductGaussFaceOperator`. + function calc_surface_integral!( + du, u, mesh::DGMultiMesh, equations, + surface_integral::SurfaceIntegralWeakForm, + dg::DGMultiFluxDiff{<:GaussSBP}, cache + ) + (; gauss_LIFT, gauss_volume_local_threaded) = cache + + @threaded for e in eachelement(mesh, dg, cache) + + # applies LIFT matrix, output is stored at Gauss nodes + gauss_volume_local = gauss_volume_local_threaded[Threads.threadid()] + apply_to_each_field( + mul_by!(gauss_LIFT), gauss_volume_local, + view(cache.flux_face_values, :, e) + ) + + for i in eachindex(gauss_volume_local) + du[i, e] = du[i, e] + gauss_volume_local[i] + end end end -end - -@inline function flux_differencing_kernel!(du, u, element, mesh::DGMultiMesh, - have_nonconservative_terms, equations, - volume_flux, dg::DGMultiFluxDiff{<:GaussSBP}, - cache, alpha = true) - fluxdiff_local = cache.fluxdiff_local_threaded[Threads.threadid()] - fill!(fluxdiff_local, zero(eltype(fluxdiff_local))) - u_local = view(cache.entropy_projected_u_values, :, element) - - local_flux_differencing!(fluxdiff_local, u_local, element, - have_nonconservative_terms, - volume_flux, has_sparse_operators(dg), - mesh, equations, dg, cache) - - # convert `fluxdiff_local::Vector{<:SVector}` to `rhs_local::StructArray{<:SVector}` - # for faster performance when using `apply_to_each_field`. - rhs_local = cache.rhs_local_threaded[Threads.threadid()] - for i in Base.OneTo(length(fluxdiff_local)) - rhs_local[i] = fluxdiff_local[i] - end - - project_rhs_to_gauss_nodes!(du, rhs_local, element, mesh, dg, cache, alpha) -end -function project_rhs_to_gauss_nodes!(du, rhs_local, element, mesh::DGMultiMesh, - dg::DGMulti, cache, alpha = true) - - # Here, we exploit that under a Gauss nodal basis the structure of the projection - # matrix `Ph = [diagm(1 ./ wq), projection_matrix_gauss_to_face]` such that - # `Ph * [u; uf] = (u ./ wq) + projection_matrix_gauss_to_face * uf`. - volume_indices = Base.OneTo(dg.basis.Nq) - face_indices = (dg.basis.Nq + 1):(dg.basis.Nq + dg.basis.Nfq) - local_volume_flux = view(rhs_local, volume_indices) - local_face_flux = view(rhs_local, face_indices) - - # initialize rhs_volume_local = projection_matrix_gauss_to_face * local_face_flux - rhs_volume_local = cache.rhs_volume_local_threaded[Threads.threadid()] - apply_to_each_field(mul_by!(cache.projection_matrix_gauss_to_face), - rhs_volume_local, local_face_flux) - - # accumulate volume contributions at Gauss nodes - for i in eachindex(rhs_volume_local) - du_local = rhs_volume_local[i] + - local_volume_flux[i] * cache.inv_gauss_weights[i] - du[i, element] = du[i, element] + alpha * du_local - end -end + @inline function flux_differencing_kernel!( + du, u, element, mesh::DGMultiMesh, + have_nonconservative_terms, equations, + volume_flux, dg::DGMultiFluxDiff{<:GaussSBP}, + cache, alpha = true + ) + fluxdiff_local = cache.fluxdiff_local_threaded[Threads.threadid()] + fill!(fluxdiff_local, zero(eltype(fluxdiff_local))) + u_local = view(cache.entropy_projected_u_values, :, element) + + local_flux_differencing!( + fluxdiff_local, u_local, element, + have_nonconservative_terms, + volume_flux, has_sparse_operators(dg), + mesh, equations, dg, cache + ) + + # convert `fluxdiff_local::Vector{<:SVector}` to `rhs_local::StructArray{<:SVector}` + # for faster performance when using `apply_to_each_field`. + rhs_local = cache.rhs_local_threaded[Threads.threadid()] + for i in Base.OneTo(length(fluxdiff_local)) + rhs_local[i] = fluxdiff_local[i] + end -function calc_volume_integral!(du, u, mesh::DGMultiMesh, - have_nonconservative_terms, equations, - volume_integral::VolumeIntegralFluxDifferencing, - dg::DGMultiFluxDiff{<:GaussSBP}, cache) - @threaded for e in eachelement(mesh, dg, cache) - flux_differencing_kernel!(du, u, e, mesh, - have_nonconservative_terms, equations, - volume_integral.volume_flux, dg, cache) + project_rhs_to_gauss_nodes!(du, rhs_local, element, mesh, dg, cache, alpha) end -end - -# interpolate back to Lobatto nodes after applying the inverse Jacobian at Gauss points -function invert_jacobian_and_interpolate!(du, mesh::DGMultiMesh, equations, - dg::DGMultiFluxDiff{<:GaussSBP}, cache; - scaling = -1) - (; interp_matrix_gauss_to_lobatto, rhs_volume_local_threaded, invJ) = cache - @threaded for e in eachelement(mesh, dg, cache) - rhs_volume_local = rhs_volume_local_threaded[Threads.threadid()] - - # At this point, `rhs_volume_local` should still be stored at Gauss points. - # We scale it by the inverse Jacobian before transforming back to Lobatto. + function project_rhs_to_gauss_nodes!( + du, rhs_local, element, mesh::DGMultiMesh, + dg::DGMulti, cache, alpha = true + ) + + # Here, we exploit that under a Gauss nodal basis the structure of the projection + # matrix `Ph = [diagm(1 ./ wq), projection_matrix_gauss_to_face]` such that + # `Ph * [u; uf] = (u ./ wq) + projection_matrix_gauss_to_face * uf`. + volume_indices = Base.OneTo(dg.basis.Nq) + face_indices = (dg.basis.Nq + 1):(dg.basis.Nq + dg.basis.Nfq) + local_volume_flux = view(rhs_local, volume_indices) + local_face_flux = view(rhs_local, face_indices) + + # initialize rhs_volume_local = projection_matrix_gauss_to_face * local_face_flux + rhs_volume_local = cache.rhs_volume_local_threaded[Threads.threadid()] + apply_to_each_field( + mul_by!(cache.projection_matrix_gauss_to_face), + rhs_volume_local, local_face_flux + ) + + # accumulate volume contributions at Gauss nodes for i in eachindex(rhs_volume_local) - rhs_volume_local[i] = du[i, e] * invJ[i, e] * scaling + du_local = rhs_volume_local[i] + + local_volume_flux[i] * cache.inv_gauss_weights[i] + du[i, element] = du[i, element] + alpha * du_local end - - # Interpolate result back to Lobatto nodes for ease of analysis, visualization - apply_to_each_field(mul_by!(interp_matrix_gauss_to_lobatto), - view(du, :, e), rhs_volume_local) end -end -# Specialize RHS so that we can call `invert_jacobian_and_interpolate!` instead of just `invert_jacobian!`, -# since `invert_jacobian!` is also used in other places (e.g., parabolic terms). -function rhs!(du, u, t, mesh, equations, initial_condition, boundary_conditions::BC, - source_terms::Source, dg::DGMultiFluxDiff{<:GaussSBP}, - cache) where {Source, BC} - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # this function evaluates the solution at volume and face quadrature points (which was previously - # done in `prolong2interfaces` and `calc_volume_integral`) - @trixi_timeit timer() "entropy_projection!" begin - entropy_projection!(cache, u, mesh, equations, dg) + function calc_volume_integral!( + du, u, mesh::DGMultiMesh, + have_nonconservative_terms, equations, + volume_integral::VolumeIntegralFluxDifferencing, + dg::DGMultiFluxDiff{<:GaussSBP}, cache + ) + @threaded for e in eachelement(mesh, dg, cache) + flux_differencing_kernel!( + du, u, e, mesh, + have_nonconservative_terms, equations, + volume_integral.volume_flux, dg, cache + ) + end end - # `du` is stored at Gauss nodes here - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) + # interpolate back to Lobatto nodes after applying the inverse Jacobian at Gauss points + function invert_jacobian_and_interpolate!( + du, mesh::DGMultiMesh, equations, + dg::DGMultiFluxDiff{<:GaussSBP}, cache; + scaling = -1 + ) + (; interp_matrix_gauss_to_lobatto, rhs_volume_local_threaded, invJ) = cache + + @threaded for e in eachelement(mesh, dg, cache) + rhs_volume_local = rhs_volume_local_threaded[Threads.threadid()] + + # At this point, `rhs_volume_local` should still be stored at Gauss points. + # We scale it by the inverse Jacobian before transforming back to Lobatto. + for i in eachindex(rhs_volume_local) + rhs_volume_local[i] = du[i, e] * invJ[i, e] * scaling + end + + # Interpolate result back to Lobatto nodes for ease of analysis, visualization + apply_to_each_field( + mul_by!(interp_matrix_gauss_to_lobatto), + view(du, :, e), rhs_volume_local + ) + end end - # the following functions are the same as in VolumeIntegralWeakForm, and can be reused from dg.jl - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache, dg.surface_integral, mesh, - have_nonconservative_terms(equations), equations, dg) - end + # Specialize RHS so that we can call `invert_jacobian_and_interpolate!` instead of just `invert_jacobian!`, + # since `invert_jacobian!` is also used in other places (e.g., parabolic terms). + function rhs!( + du, u, t, mesh, equations, initial_condition, boundary_conditions::BC, + source_terms::Source, dg::DGMultiFluxDiff{<:GaussSBP}, + cache + ) where {Source, BC} + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # this function evaluates the solution at volume and face quadrature points (which was previously + # done in `prolong2interfaces` and `calc_volume_integral`) + @trixi_timeit timer() "entropy_projection!" begin + entropy_projection!(cache, u, mesh, equations, dg) + end - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, t, boundary_conditions, mesh, - have_nonconservative_terms(equations), equations, dg) - end + # `du` is stored at Gauss nodes here + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - # `du` is stored at Gauss nodes here - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) - end + # the following functions are the same as in VolumeIntegralWeakForm, and can be reused from dg.jl + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache, dg.surface_integral, mesh, + have_nonconservative_terms(equations), equations, dg + ) + end - # invert Jacobian and map `du` from Gauss to Lobatto nodes - @trixi_timeit timer() "Jacobian" begin - invert_jacobian_and_interpolate!(du, mesh, equations, dg, cache) - end + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, t, boundary_conditions, mesh, + have_nonconservative_terms(equations), equations, dg + ) + end - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, mesh, equations, dg, cache) - end + # `du` is stored at Gauss nodes here + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end - return nothing -end + # invert Jacobian and map `du` from Gauss to Lobatto nodes + @trixi_timeit timer() "Jacobian" begin + invert_jacobian_and_interpolate!(du, mesh, equations, dg, cache) + end + + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, mesh, equations, dg, cache) + end + + return nothing + end end # @muladd diff --git a/src/solvers/dgmulti/sbp.jl b/src/solvers/dgmulti/sbp.jl index 232555e18b..0c39bdf5ba 100644 --- a/src/solvers/dgmulti/sbp.jl +++ b/src/solvers/dgmulti/sbp.jl @@ -1,4 +1,3 @@ - """ DGMulti(approximation_type::AbstractDerivativeOperator; element_type::AbstractElemShape, @@ -16,52 +15,62 @@ For more info, see the documentations of and [SummationByPartsOperators.jl](https://ranocha.de/SummationByPartsOperators.jl/stable/). """ -function DGMulti(approximation_type::AbstractDerivativeOperator; - element_type::AbstractElemShape, - surface_flux = flux_central, - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralWeakForm(), - kwargs...) +function DGMulti( + approximation_type::AbstractDerivativeOperator; + element_type::AbstractElemShape, + surface_flux = flux_central, + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralWeakForm(), + kwargs... + ) rd = RefElemData(element_type, approximation_type; kwargs...) # `nothing` is passed as `mortar` return DG(rd, nothing, surface_integral, volume_integral) end -function DGMulti(element_type::AbstractElemShape, - approximation_type::AbstractDerivativeOperator, - volume_integral, - surface_integral; - kwargs...) - DGMulti(approximation_type, element_type = element_type, - surface_integral = surface_integral, volume_integral = volume_integral) +function DGMulti( + element_type::AbstractElemShape, + approximation_type::AbstractDerivativeOperator, + volume_integral, + surface_integral; + kwargs... + ) + DGMulti( + approximation_type, element_type = element_type, + surface_integral = surface_integral, volume_integral = volume_integral + ) end # type alias for specializing on a periodic SBP operator -const DGMultiPeriodicFDSBP{NDIMS, ApproxType, ElemType} = DGMulti{NDIMS, ElemType, - ApproxType, - SurfaceIntegral, - VolumeIntegral} where { - NDIMS, - ElemType, - ApproxType <: - SummationByPartsOperators.AbstractPeriodicDerivativeOperator, - SurfaceIntegral, - VolumeIntegral - } - -const DGMultiFluxDiffPeriodicFDSBP{NDIMS, ApproxType, ElemType} = DGMulti{NDIMS, ElemType, - ApproxType, - SurfaceIntegral, - VolumeIntegral} where { - NDIMS, - ElemType, - ApproxType <: - SummationByPartsOperators.AbstractPeriodicDerivativeOperator, - SurfaceIntegral <: - SurfaceIntegralWeakForm, - VolumeIntegral <: - VolumeIntegralFluxDifferencing - } +const DGMultiPeriodicFDSBP{NDIMS, ApproxType, ElemType} = DGMulti{ + NDIMS, ElemType, + ApproxType, + SurfaceIntegral, + VolumeIntegral, +} where { + NDIMS, + ElemType, + ApproxType <: + SummationByPartsOperators.AbstractPeriodicDerivativeOperator, + SurfaceIntegral, + VolumeIntegral, +} + +const DGMultiFluxDiffPeriodicFDSBP{NDIMS, ApproxType, ElemType} = DGMulti{ + NDIMS, ElemType, + ApproxType, + SurfaceIntegral, + VolumeIntegral, +} where { + NDIMS, + ElemType, + ApproxType <: + SummationByPartsOperators.AbstractPeriodicDerivativeOperator, + SurfaceIntegral <: + SurfaceIntegralWeakForm, + VolumeIntegral <: + VolumeIntegralFluxDifferencing, +} """ DGMultiMesh(dg::DGMulti) @@ -70,9 +79,11 @@ Constructs a single-element [`DGMultiMesh`](@ref) for a single periodic element a DGMulti with `approximation_type` set to a periodic (finite difference) SBP operator from SummationByPartsOperators.jl. """ -function DGMultiMesh(dg::DGMultiPeriodicFDSBP{NDIMS}; - coordinates_min = ntuple(_ -> -one(real(dg)), NDIMS), - coordinates_max = ntuple(_ -> one(real(dg)), NDIMS)) where {NDIMS} +function DGMultiMesh( + dg::DGMultiPeriodicFDSBP{NDIMS}; + coordinates_min = ntuple(_ -> -one(real(dg)), NDIMS), + coordinates_max = ntuple(_ -> one(real(dg)), NDIMS) + ) where {NDIMS} rd = dg.basis e = Ones{eltype(rd.r)}(size(rd.r)) @@ -107,12 +118,12 @@ function DGMultiMesh(dg::DGMultiPeriodicFDSBP{NDIMS}; elseif NDIMS == 2 rxJ = J_scalar * 2 / coord_diffs[1] syJ = J_scalar * 2 / coord_diffs[2] - rstxyzJ = @SMatrix [rxJ*e z; z syJ*e] + rstxyzJ = @SMatrix [rxJ * e z; z syJ * e] elseif NDIMS == 3 rxJ = J_scalar * 2 / coord_diffs[1] syJ = J_scalar * 2 / coord_diffs[2] tzJ = J_scalar * 2 / coord_diffs[3] - rstxyzJ = @SMatrix [rxJ*e z z; z syJ*e z; z z tzJ*e] + rstxyzJ = @SMatrix [rxJ * e z z; z syJ * e z; z z tzJ * e] end # surface geofacs @@ -129,14 +140,18 @@ function DGMultiMesh(dg::DGMultiPeriodicFDSBP{NDIMS}; mesh_type = Hex() end - md = MeshData(StartUpDG.VertexMappedMesh(mesh_type, VXYZ, EToV), FToF, xyz, xyzf, xyzq, - wJq, - mapM, mapP, mapB, rstxyzJ, J, nxyzJ, Jf, - periodicity) + md = MeshData( + StartUpDG.VertexMappedMesh(mesh_type, VXYZ, EToV), FToF, xyz, xyzf, xyzq, + wJq, + mapM, mapP, mapB, rstxyzJ, J, nxyzJ, Jf, + periodicity + ) boundary_faces = [] - return DGMultiMesh{NDIMS, rd.element_type, typeof(md), typeof(boundary_faces)}(md, - boundary_faces) + return DGMultiMesh{NDIMS, rd.element_type, typeof(md), typeof(boundary_faces)}( + md, + boundary_faces + ) end # By default, Julia/LLVM does not use fused multiply-add operations (FMAs). @@ -144,108 +159,120 @@ end # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# specialized for DGMultiPeriodicFDSBP since there are no face nodes -# and thus no inverse trace constant for periodic domains. -function estimate_dt(mesh::DGMultiMesh, dg::DGMultiPeriodicFDSBP) - rd = dg.basis # RefElemData - return StartUpDG.estimate_h(rd, mesh.md) -end + # specialized for DGMultiPeriodicFDSBP since there are no face nodes + # and thus no inverse trace constant for periodic domains. + function estimate_dt(mesh::DGMultiMesh, dg::DGMultiPeriodicFDSBP) + rd = dg.basis # RefElemData + return StartUpDG.estimate_h(rd, mesh.md) + end -# do nothing for interface terms if using a periodic operator -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache, u, mesh::DGMultiMesh, equations, - surface_integral, dg::DGMultiPeriodicFDSBP) - @assert nelements(mesh, dg, cache) == 1 - nothing -end + # do nothing for interface terms if using a periodic operator + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache, u, mesh::DGMultiMesh, equations, + surface_integral, dg::DGMultiPeriodicFDSBP + ) + @assert nelements(mesh, dg, cache) == 1 + nothing + end -function calc_interface_flux!(cache, surface_integral::SurfaceIntegralWeakForm, - mesh::DGMultiMesh, - have_nonconservative_terms::False, equations, - dg::DGMultiPeriodicFDSBP) - @assert nelements(mesh, dg, cache) == 1 - nothing -end + function calc_interface_flux!( + cache, surface_integral::SurfaceIntegralWeakForm, + mesh::DGMultiMesh, + have_nonconservative_terms::False, equations, + dg::DGMultiPeriodicFDSBP + ) + @assert nelements(mesh, dg, cache) == 1 + nothing + end -function calc_surface_integral!(du, u, mesh::DGMultiMesh, equations, - surface_integral::SurfaceIntegralWeakForm, - dg::DGMultiPeriodicFDSBP, cache) - @assert nelements(mesh, dg, cache) == 1 - nothing -end + function calc_surface_integral!( + du, u, mesh::DGMultiMesh, equations, + surface_integral::SurfaceIntegralWeakForm, + dg::DGMultiPeriodicFDSBP, cache + ) + @assert nelements(mesh, dg, cache) == 1 + nothing + end -function create_cache(mesh::DGMultiMesh, equations, - dg::DGMultiFluxDiffPeriodicFDSBP, RealT, uEltype) - md = mesh.md + function create_cache( + mesh::DGMultiMesh, equations, + dg::DGMultiFluxDiffPeriodicFDSBP, RealT, uEltype + ) + md = mesh.md - # storage for volume quadrature values, face quadrature values, flux values - nvars = nvariables(equations) - u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg) - return (; u_values, invJ = inv.(md.J)) -end + # storage for volume quadrature values, face quadrature values, flux values + nvars = nvariables(equations) + u_values = allocate_nested_array(uEltype, nvars, size(md.xq), dg) + return (; u_values, invJ = inv.(md.J)) + end -# Specialize calc_volume_integral for periodic SBP operators (assumes the operator is sparse). -function calc_volume_integral!(du, u, mesh::DGMultiMesh, - have_nonconservative_terms::False, equations, - volume_integral::VolumeIntegralFluxDifferencing, - dg::DGMultiFluxDiffPeriodicFDSBP, cache) - @unpack volume_flux = volume_integral - - # We expect speedup over the serial version only when using two or more threads - # since the threaded version below does not exploit the symmetry properties, - # resulting in a performance penalty of 1/2 - if Threads.nthreads() > 1 - for dim in eachdim(mesh) - normal_direction = get_contravariant_vector(1, dim, mesh, cache) - - # These are strong-form operators of the form `D = M \ Q` where `M` is diagonal - # and `Q` is skew-symmetric. Since `M` is diagonal, `inv(M)` scales the rows of `Q`. - # Then, `1 / M[i,i] * ∑_j Q[i,j] * volume_flux(u[i], u[j])` is equivalent to - # `= ∑_j (1 / M[i,i] * Q[i,j]) * volume_flux(u[i], u[j])` - # `= ∑_j D[i,j] * volume_flux(u[i], u[j])` - # TODO: DGMulti. - # This would have to be changed if `has_nonconservative_terms = False()` - # because then `volume_flux` is non-symmetric. - A = dg.basis.Drst[dim] - - A_base = parent(A) # the adjoint of a SparseMatrixCSC is basically a SparseMatrixCSR - row_ids = axes(A, 2) - rows = rowvals(A_base) - vals = nonzeros(A_base) - - @threaded for i in row_ids - u_i = u[i] - du_i = du[i] - for id in nzrange(A_base, i) - j = rows[id] - u_j = u[j] - A_ij = vals[id] - AF_ij = 2 * A_ij * + # Specialize calc_volume_integral for periodic SBP operators (assumes the operator is sparse). + function calc_volume_integral!( + du, u, mesh::DGMultiMesh, + have_nonconservative_terms::False, equations, + volume_integral::VolumeIntegralFluxDifferencing, + dg::DGMultiFluxDiffPeriodicFDSBP, cache + ) + @unpack volume_flux = volume_integral + + # We expect speedup over the serial version only when using two or more threads + # since the threaded version below does not exploit the symmetry properties, + # resulting in a performance penalty of 1/2 + if Threads.nthreads() > 1 + for dim in eachdim(mesh) + normal_direction = get_contravariant_vector(1, dim, mesh, cache) + + # These are strong-form operators of the form `D = M \ Q` where `M` is diagonal + # and `Q` is skew-symmetric. Since `M` is diagonal, `inv(M)` scales the rows of `Q`. + # Then, `1 / M[i,i] * ∑_j Q[i,j] * volume_flux(u[i], u[j])` is equivalent to + # `= ∑_j (1 / M[i,i] * Q[i,j]) * volume_flux(u[i], u[j])` + # `= ∑_j D[i,j] * volume_flux(u[i], u[j])` + # TODO: DGMulti. + # This would have to be changed if `has_nonconservative_terms = False()` + # because then `volume_flux` is non-symmetric. + A = dg.basis.Drst[dim] + + A_base = parent(A) # the adjoint of a SparseMatrixCSC is basically a SparseMatrixCSR + row_ids = axes(A, 2) + rows = rowvals(A_base) + vals = nonzeros(A_base) + + @threaded for i in row_ids + u_i = u[i] + du_i = du[i] + for id in nzrange(A_base, i) + j = rows[id] + u_j = u[j] + A_ij = vals[id] + AF_ij = 2 * A_ij * volume_flux(u_i, u_j, normal_direction, equations) - du_i = du_i + AF_ij + du_i = du_i + AF_ij + end + du[i] = du_i end - du[i] = du_i end - end - else # if using two threads or fewer + else # if using two threads or fewer - # Calls `hadamard_sum!``, which uses symmetry to reduce flux evaluations. Symmetry - # is expected to yield about a 2x speedup, so we default to the symmetry-exploiting - # volume integral unless we have >2 threads (which should yield >2 speedup). - for dim in eachdim(mesh) - normal_direction = get_contravariant_vector(1, dim, mesh, cache) + # Calls `hadamard_sum!``, which uses symmetry to reduce flux evaluations. Symmetry + # is expected to yield about a 2x speedup, so we default to the symmetry-exploiting + # volume integral unless we have >2 threads (which should yield >2 speedup). + for dim in eachdim(mesh) + normal_direction = get_contravariant_vector(1, dim, mesh, cache) - A = dg.basis.Drst[dim] + A = dg.basis.Drst[dim] - # since has_nonconservative_terms::False, - # the volume flux is symmetric. - flux_is_symmetric = True() - hadamard_sum!(du, A, flux_is_symmetric, volume_flux, - normal_direction, u, equations) + # since has_nonconservative_terms::False, + # the volume flux is symmetric. + flux_is_symmetric = True() + hadamard_sum!( + du, A, flux_is_symmetric, volume_flux, + normal_direction, u, equations + ) + end end end -end end # @muladd diff --git a/src/solvers/dgmulti/shock_capturing.jl b/src/solvers/dgmulti/shock_capturing.jl index 99261b82ed..ff76b7d807 100644 --- a/src/solvers/dgmulti/shock_capturing.jl +++ b/src/solvers/dgmulti/shock_capturing.jl @@ -1,7 +1,9 @@ # by default, return an empty tuple for volume integral caches -function create_cache(mesh::DGMultiMesh{NDIMS}, equations, - volume_integral::VolumeIntegralShockCapturingHG, - dg::DGMultiFluxDiff{<:GaussSBP}, RealT, uEltype) where {NDIMS} +function create_cache( + mesh::DGMultiMesh{NDIMS}, equations, + volume_integral::VolumeIntegralShockCapturingHG, + dg::DGMultiFluxDiff{<:GaussSBP}, RealT, uEltype + ) where {NDIMS} element_ids_dg = Int[] element_ids_dgfv = Int[] @@ -23,22 +25,28 @@ function create_cache(mesh::DGMultiMesh{NDIMS}, equations, # create sparse hybridized operators for low order scheme Qrst, E = StartUpDG.sparse_low_order_SBP_operators(dg.basis) Brst = map(n -> Diagonal(n .* dg.basis.wf), dg.basis.nrstJ) - sparse_hybridized_SBP_operators = map((Q, B) -> 0.5 * [Q-Q' E'*B; -B*E zeros(size(B))], - Qrst, Brst) + sparse_hybridized_SBP_operators = map( + (Q, B) -> 0.5 * [Q - Q' E' * B; -B * E zeros(size(B))], + Qrst, Brst + ) # Find the joint sparsity pattern of the entire matrix. We store the sparsity pattern as # an adjoint for faster iteration through the rows. sparsity_pattern = sum(map(A -> abs.(A)', sparse_hybridized_SBP_operators)) .> - 100 * eps() + 100 * eps() - return (; element_ids_dg, element_ids_dgfv, - sparse_hybridized_SBP_operators, sparsity_pattern, - element_to_element_connectivity) + return (; + element_ids_dg, element_ids_dgfv, + sparse_hybridized_SBP_operators, sparsity_pattern, + element_to_element_connectivity, + ) end # this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorHennemannGassner}, equations::AbstractEquations, - basis::RefElemData{NDIMS}) where {NDIMS} +function create_cache( + ::Type{IndicatorHennemannGassner}, equations::AbstractEquations, + basis::RefElemData{NDIMS} + ) where {NDIMS} alpha = Vector{real(basis)}() alpha_tmp = similar(alpha) @@ -55,9 +63,11 @@ function create_cache(::Type{IndicatorHennemannGassner}, equations::AbstractEqua return (; alpha, alpha_tmp, indicator_threaded, modal_threaded, inverse_vandermonde) end -function (indicator_hg::IndicatorHennemannGassner)(u, mesh::DGMultiMesh, - equations, dg::DGMulti{NDIMS}, cache; - kwargs...) where {NDIMS} +function (indicator_hg::IndicatorHennemannGassner)( + u, mesh::DGMultiMesh, + equations, dg::DGMulti{NDIMS}, cache; + kwargs... + ) where {NDIMS} (; alpha_max, alpha_min, alpha_smooth, variable) = indicator_hg (; alpha, alpha_tmp, indicator_threaded, modal_threaded, inverse_vandermonde) = indicator_hg.cache @@ -156,8 +166,10 @@ end # Given blending factors `alpha` and the solver `dg`, fill # `element_ids_dg` with the IDs of elements using a pure DG scheme and # `element_ids_dgfv` with the IDs of elements using a blended DG-FV scheme. -function pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, - mesh::DGMultiMesh, dg::DGMulti) +function pure_and_blended_element_ids!( + element_ids_dg, element_ids_dgfv, alpha, + mesh::DGMultiMesh, dg::DGMulti + ) empty!(element_ids_dg) empty!(element_ids_dgfv) # For `Float64`, this gives 1.8189894035458565e-12 @@ -178,17 +190,21 @@ function pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, return nothing end -function calc_volume_integral!(du, u, - mesh::DGMultiMesh, - have_nonconservative_terms, equations, - volume_integral::VolumeIntegralShockCapturingHG, - dg::DGMultiFluxDiff, cache) +function calc_volume_integral!( + du, u, + mesh::DGMultiMesh, + have_nonconservative_terms, equations, + volume_integral::VolumeIntegralShockCapturingHG, + dg::DGMultiFluxDiff, cache + ) (; element_ids_dg, element_ids_dgfv) = cache (; volume_flux_dg, volume_flux_fv, indicator) = volume_integral # Calculate blending factors α: u = u_DG * (1 - α) + u_FV * α - alpha = @trixi_timeit timer() "blending factors" indicator(u, mesh, equations, dg, - cache) + alpha = @trixi_timeit timer() "blending factors" indicator( + u, mesh, equations, dg, + cache + ) # Determine element ids for DG-only and blended DG-FV volume integral pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, mesh, dg) @@ -196,8 +212,10 @@ function calc_volume_integral!(du, u, # Loop over pure DG elements @trixi_timeit timer() "pure DG" @threaded for idx_element in eachindex(element_ids_dg) element = element_ids_dg[idx_element] - flux_differencing_kernel!(du, u, element, mesh, have_nonconservative_terms, - equations, volume_flux_dg, dg, cache) + flux_differencing_kernel!( + du, u, element, mesh, have_nonconservative_terms, + equations, volume_flux_dg, dg, cache + ) end # Loop over blended DG-FV elements, blend the high and low order RHS contributions @@ -207,14 +225,18 @@ function calc_volume_integral!(du, u, alpha_element = alpha[element] # Calculate DG volume integral contribution - flux_differencing_kernel!(du, u, element, mesh, - have_nonconservative_terms, equations, - volume_flux_dg, dg, cache, 1 - alpha_element) + flux_differencing_kernel!( + du, u, element, mesh, + have_nonconservative_terms, equations, + volume_flux_dg, dg, cache, 1 - alpha_element + ) # Calculate "FV" low order volume integral contribution - low_order_flux_differencing_kernel!(du, u, element, mesh, - have_nonconservative_terms, equations, - volume_flux_fv, dg, cache, alpha_element) + low_order_flux_differencing_kernel!( + du, u, element, mesh, + have_nonconservative_terms, equations, + volume_flux_fv, dg, cache, alpha_element + ) end return nothing @@ -240,48 +262,60 @@ end function get_contravariant_matrix(element, mesh::DGMultiMesh{2, <:Affine}, cache) (; dxidxhatj) = cache - return SMatrix{2, 2}(dxidxhatj[1, 1][1, element], dxidxhatj[2, 1][1, element], - dxidxhatj[1, 2][1, element], dxidxhatj[2, 2][1, element]) + return SMatrix{2, 2}( + dxidxhatj[1, 1][1, element], dxidxhatj[2, 1][1, element], + dxidxhatj[1, 2][1, element], dxidxhatj[2, 2][1, element] + ) end function get_contravariant_matrix(element, mesh::DGMultiMesh{3, <:Affine}, cache) (; dxidxhatj) = cache - return SMatrix{3, 3}(dxidxhatj[1, 1][1, element], dxidxhatj[2, 1][1, element], - dxidxhatj[3, 1][1, element], - dxidxhatj[1, 2][1, element], dxidxhatj[2, 2][1, element], - dxidxhatj[3, 2][1, element], - dxidxhatj[1, 3][1, element], dxidxhatj[2, 3][1, element], - dxidxhatj[3, 3][1, element]) + return SMatrix{3, 3}( + dxidxhatj[1, 1][1, element], dxidxhatj[2, 1][1, element], + dxidxhatj[3, 1][1, element], + dxidxhatj[1, 2][1, element], dxidxhatj[2, 2][1, element], + dxidxhatj[3, 2][1, element], + dxidxhatj[1, 3][1, element], dxidxhatj[2, 3][1, element], + dxidxhatj[3, 3][1, element] + ) end function get_contravariant_matrix(i, element, mesh::DGMultiMesh{2}, cache) (; dxidxhatj) = cache - return SMatrix{2, 2}(dxidxhatj[1, 1][i, element], dxidxhatj[2, 1][i, element], - dxidxhatj[1, 2][i, element], dxidxhatj[2, 2][i, element]) + return SMatrix{2, 2}( + dxidxhatj[1, 1][i, element], dxidxhatj[2, 1][i, element], + dxidxhatj[1, 2][i, element], dxidxhatj[2, 2][i, element] + ) end function get_contravariant_matrix(i, element, mesh::DGMultiMesh{3}, cache) (; dxidxhatj) = cache - return SMatrix{3, 3}(dxidxhatj[1, 1][i, element], dxidxhatj[2, 1][i, element], - dxidxhatj[3, 1][i, element], - dxidxhatj[1, 2][i, element], dxidxhatj[2, 2][i, element], - dxidxhatj[3, 2][i, element], - dxidxhatj[1, 3][i, element], dxidxhatj[2, 3][i, element], - dxidxhatj[3, 3][i, element]) + return SMatrix{3, 3}( + dxidxhatj[1, 1][i, element], dxidxhatj[2, 1][i, element], + dxidxhatj[3, 1][i, element], + dxidxhatj[1, 2][i, element], dxidxhatj[2, 2][i, element], + dxidxhatj[3, 2][i, element], + dxidxhatj[1, 3][i, element], dxidxhatj[2, 3][i, element], + dxidxhatj[3, 3][i, element] + ) end function get_avg_contravariant_matrix(i, j, element, mesh::DGMultiMesh, cache) - 0.5 * (get_contravariant_matrix(i, element, mesh, cache) + - get_contravariant_matrix(j, element, mesh, cache)) + 0.5 * ( + get_contravariant_matrix(i, element, mesh, cache) + + get_contravariant_matrix(j, element, mesh, cache) + ) end # computes an algebraic low order method with internal dissipation. # This method is for affine/Cartesian meshes -function low_order_flux_differencing_kernel!(du, u, element, mesh::DGMultiMesh, - have_nonconservative_terms::False, equations, - volume_flux_fv, - dg::DGMultiFluxDiff{<:GaussSBP}, - cache, alpha = true) +function low_order_flux_differencing_kernel!( + du, u, element, mesh::DGMultiMesh, + have_nonconservative_terms::False, equations, + volume_flux_fv, + dg::DGMultiFluxDiff{<:GaussSBP}, + cache, alpha = true + ) # accumulates output from flux differencing rhs_local = cache.rhs_local_threaded[Threads.threadid()] @@ -318,12 +352,14 @@ function low_order_flux_differencing_kernel!(du, u, element, mesh::DGMultiMesh, project_rhs_to_gauss_nodes!(du, rhs_local, element, mesh, dg, cache, alpha) end -function low_order_flux_differencing_kernel!(du, u, element, - mesh::DGMultiMesh{NDIMS, <:NonAffine}, - have_nonconservative_terms::False, equations, - volume_flux_fv, - dg::DGMultiFluxDiff{<:GaussSBP}, - cache, alpha = true) where {NDIMS} +function low_order_flux_differencing_kernel!( + du, u, element, + mesh::DGMultiMesh{NDIMS, <:NonAffine}, + have_nonconservative_terms::False, equations, + volume_flux_fv, + dg::DGMultiFluxDiff{<:GaussSBP}, + cache, alpha = true + ) where {NDIMS} # accumulates output from flux differencing rhs_local = cache.rhs_local_threaded[Threads.threadid()] diff --git a/src/solvers/dgmulti/types.jl b/src/solvers/dgmulti/types.jl index ef9d7d2bf0..f13c2c43e9 100644 --- a/src/solvers/dgmulti/types.jl +++ b/src/solvers/dgmulti/types.jl @@ -4,432 +4,493 @@ # `DGMulti` refers to both multiple DG types (polynomial/SBP, simplices/quads/hexes) as well as # the use of multi-dimensional operators in the solver. -const DGMulti{NDIMS, ElemType, ApproxType, SurfaceIntegral, VolumeIntegral} = DG{<:RefElemData{NDIMS, - ElemType, - ApproxType}, - Mortar, - SurfaceIntegral, - VolumeIntegral} where { - Mortar - } +const DGMulti{NDIMS, ElemType, ApproxType, SurfaceIntegral, VolumeIntegral} = DG{ + <:RefElemData{ + NDIMS, + ElemType, + ApproxType, + }, + Mortar, + SurfaceIntegral, + VolumeIntegral, +} where { + Mortar, +} # Type aliases. The first parameter is `ApproxType` since it is more commonly used for dispatch. -const DGMultiWeakForm{ApproxType, ElemType} = DGMulti{NDIMS, ElemType, ApproxType, - <:SurfaceIntegralWeakForm, - <:VolumeIntegralWeakForm} where {NDIMS - } - -const DGMultiFluxDiff{ApproxType, ElemType} = DGMulti{NDIMS, ElemType, ApproxType, - <:SurfaceIntegralWeakForm, - <:Union{VolumeIntegralFluxDifferencing, - VolumeIntegralShockCapturingHG}} where { - NDIMS - } - -const DGMultiFluxDiffSBP{ApproxType, ElemType} = DGMulti{NDIMS, ElemType, ApproxType, - <:SurfaceIntegralWeakForm, - <:Union{VolumeIntegralFluxDifferencing, - VolumeIntegralShockCapturingHG}} where { - NDIMS, - ApproxType <: - Union{SBP, - AbstractDerivativeOperator} - } - -const DGMultiSBP{ApproxType, ElemType} = DGMulti{NDIMS, ElemType, ApproxType, - SurfaceIntegral, - VolumeIntegral} where {NDIMS, ElemType, - ApproxType <: - Union{SBP, - AbstractDerivativeOperator}, - SurfaceIntegral, - VolumeIntegral} +const DGMultiWeakForm{ApproxType, ElemType} = DGMulti{ + NDIMS, ElemType, ApproxType, + <:SurfaceIntegralWeakForm, + <:VolumeIntegralWeakForm, +} where { + NDIMS, +} + +const DGMultiFluxDiff{ApproxType, ElemType} = DGMulti{ + NDIMS, ElemType, ApproxType, + <:SurfaceIntegralWeakForm, + <:Union{ + VolumeIntegralFluxDifferencing, + VolumeIntegralShockCapturingHG, + }, +} where { + NDIMS, +} + +const DGMultiFluxDiffSBP{ApproxType, ElemType} = DGMulti{ + NDIMS, ElemType, ApproxType, + <:SurfaceIntegralWeakForm, + <:Union{ + VolumeIntegralFluxDifferencing, + VolumeIntegralShockCapturingHG, + }, +} where { + NDIMS, + ApproxType <: + Union{ + SBP, + AbstractDerivativeOperator, + }, +} + +const DGMultiSBP{ApproxType, ElemType} = DGMulti{ + NDIMS, ElemType, ApproxType, + SurfaceIntegral, + VolumeIntegral, +} where { + NDIMS, ElemType, + ApproxType <: + Union{ + SBP, + AbstractDerivativeOperator, + }, + SurfaceIntegral, + VolumeIntegral, +} # By default, Julia/LLVM does not use fused multiply-add operations (FMAs). # Since these FMAs can increase the performance of many numerical algorithms, # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# these are necessary for pretty printing -polydeg(dg::DGMulti) = dg.basis.N -function Base.summary(io::IO, dg::DG) where {DG <: DGMulti} - print(io, "DGMulti(polydeg=$(polydeg(dg)))") -end - -# real(rd) is the eltype of the nodes `rd.r`. -Base.real(rd::RefElemData) = eltype(rd.r) - -""" - DGMulti(; polydeg::Integer, - element_type::AbstractElemShape, - approximation_type=Polynomial(), - surface_flux=flux_central, - surface_integral=SurfaceIntegralWeakForm(surface_flux), - volume_integral=VolumeIntegralWeakForm(), - RefElemData_kwargs...) - -Create a discontinuous Galerkin method which uses -- approximations of polynomial degree `polydeg` -- element type `element_type` (`Tri()`, `Quad()`, `Tet()`, and `Hex()` currently supported) - -Optional: -- `approximation_type` (default is `Polynomial()`; `SBP()` also supported for `Tri()`, `Quad()`, - and `Hex()` element types). -- `RefElemData_kwargs` are additional keyword arguments for `RefElemData`, such as `quad_rule_vol`. - For more info, see the [StartUpDG.jl docs](https://jlchan.github.io/StartUpDG.jl/dev/). -""" -function DGMulti(; polydeg = nothing, - element_type::AbstractElemShape, - approximation_type = Polynomial(), - surface_flux = flux_central, - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralWeakForm(), - kwargs...) - - # call dispatchable constructor - DGMulti(element_type, approximation_type, volume_integral, surface_integral; - polydeg = polydeg, kwargs...) -end - -# dispatchable constructor for DGMulti using a TensorProductWedge -function DGMulti(element_type::Wedge, - approximation_type, - volume_integral, - surface_integral; - polydeg::Tuple, - kwargs...) - factor_a = RefElemData(Tri(), approximation_type, polydeg[1]; kwargs...) - factor_b = RefElemData(Line(), approximation_type, polydeg[2]; kwargs...) - - tensor = TensorProductWedge(factor_a, factor_b) - rd = RefElemData(element_type, tensor; kwargs...) - return DG(rd, nothing, surface_integral, volume_integral) -end - -# dispatchable constructor for DGMulti to allow for specialization -function DGMulti(element_type::AbstractElemShape, - approximation_type, - volume_integral, - surface_integral; - polydeg::Integer, - kwargs...) - rd = RefElemData(element_type, approximation_type, polydeg; kwargs...) - # `nothing` is passed as `mortar` - return DG(rd, nothing, surface_integral, volume_integral) -end - -function DGMulti(basis::RefElemData; volume_integral, surface_integral) - # `nothing` is passed as `mortar` - DG(basis, nothing, surface_integral, volume_integral) -end - -""" - DGMultiBasis(element_type, polydeg; approximation_type = Polynomial(), kwargs...) - -Constructs a basis for DGMulti solvers. Returns a "StartUpDG.RefElemData" object. - The `kwargs` arguments are additional keyword arguments for `RefElemData`, such as `quad_rule_vol`. - These are the same as the `RefElemData_kwargs` used in [`DGMulti`](@ref). - For more info, see the [StartUpDG.jl docs](https://jlchan.github.io/StartUpDG.jl/dev/). - -""" -function DGMultiBasis(element_type, polydeg; approximation_type = Polynomial(), - kwargs...) - RefElemData(element_type, approximation_type, polydeg; kwargs...) -end - -######################################## -# DGMultiMesh -######################################## - -# now that `DGMulti` is defined, we can define constructors for `DGMultiMesh` which use `dg::DGMulti` - -function DGMultiMesh(dg::DGMulti, geometric_term_type, md::MeshData{NDIMS}, - boundary_faces) where {NDIMS} - return DGMultiMesh{NDIMS, typeof(geometric_term_type), typeof(md), - typeof(boundary_faces)}(md, boundary_faces) -end - -# Mesh types used internally for trait dispatch -struct Cartesian end -struct VertexMapped end # where element geometry is determined by vertices. -struct Curved end - -# type parameters for dispatch using `DGMultiMesh` -abstract type GeometricTermsType end -struct Affine <: GeometricTermsType end # mesh produces constant geometric terms -struct NonAffine <: GeometricTermsType end # mesh produces non-constant geometric terms - -# choose MeshType based on the constructor and element type -function GeometricTermsType(mesh_type, dg::DGMulti) - GeometricTermsType(mesh_type, dg.basis.element_type) -end -GeometricTermsType(mesh_type::Cartesian, element_type::AbstractElemShape) = Affine() -GeometricTermsType(mesh_type::TriangulateIO, element_type::Tri) = Affine() -GeometricTermsType(mesh_type::VertexMapped, element_type::Union{Tri, Tet}) = Affine() -function GeometricTermsType(mesh_type::VertexMapped, element_type::Union{Quad, Hex}) - NonAffine() -end -GeometricTermsType(mesh_type::Curved, element_type::AbstractElemShape) = NonAffine() - -# other potential constructor types to add later: Bilinear, Isoparametric{polydeg_geo}, Rational/Exact? -# other potential mesh types to add later: Polynomial{polydeg_geo}? - -""" - DGMultiMesh(dg::DGMulti{NDIMS}, vertex_coordinates, EToV; - is_on_boundary=nothing, - periodicity=ntuple(_->false, NDIMS)) where {NDIMS} - -- `dg::DGMulti` contains information associated with to the reference element (e.g., quadrature, - basis evaluation, differentiation, etc). -- `vertex_coordinates` is a tuple of vectors containing x,y,... components of the vertex coordinates -- `EToV` is a 2D array containing element-to-vertex connectivities for each element -- `is_on_boundary` specifies boundary using a `Dict{Symbol, <:Function}` -- `periodicity` is a tuple of booleans specifying if the domain is periodic `true`/`false` in the - (x,y,z) direction. -""" -function DGMultiMesh(dg::DGMulti{NDIMS}, vertex_coordinates, EToV::AbstractArray; - is_on_boundary = nothing, - periodicity = ntuple(_ -> false, NDIMS), kwargs...) where {NDIMS} - md = MeshData(vertex_coordinates, EToV, dg.basis) - if NDIMS == 1 - md = StartUpDG.make_periodic(md, periodicity...) - else - md = StartUpDG.make_periodic(md, periodicity) + #! format: noindent + + # these are necessary for pretty printing + polydeg(dg::DGMulti) = dg.basis.N + function Base.summary(io::IO, dg::DG) where {DG <: DGMulti} + print(io, "DGMulti(polydeg=$(polydeg(dg)))") + end + + # real(rd) is the eltype of the nodes `rd.r`. + Base.real(rd::RefElemData) = eltype(rd.r) + + """ + DGMulti(; polydeg::Integer, + element_type::AbstractElemShape, + approximation_type=Polynomial(), + surface_flux=flux_central, + surface_integral=SurfaceIntegralWeakForm(surface_flux), + volume_integral=VolumeIntegralWeakForm(), + RefElemData_kwargs...) + + Create a discontinuous Galerkin method which uses + - approximations of polynomial degree `polydeg` + - element type `element_type` (`Tri()`, `Quad()`, `Tet()`, and `Hex()` currently supported) + + Optional: + - `approximation_type` (default is `Polynomial()`; `SBP()` also supported for `Tri()`, `Quad()`, + and `Hex()` element types). + - `RefElemData_kwargs` are additional keyword arguments for `RefElemData`, such as `quad_rule_vol`. + For more info, see the [StartUpDG.jl docs](https://jlchan.github.io/StartUpDG.jl/dev/). + """ + function DGMulti(; + polydeg = nothing, + element_type::AbstractElemShape, + approximation_type = Polynomial(), + surface_flux = flux_central, + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralWeakForm(), + kwargs... + ) + + # call dispatchable constructor + DGMulti( + element_type, approximation_type, volume_integral, surface_integral; + polydeg = polydeg, kwargs... + ) end - boundary_faces = StartUpDG.tag_boundary_faces(md, is_on_boundary) - return DGMultiMesh(dg, GeometricTermsType(VertexMapped(), dg), md, boundary_faces) -end - -""" - DGMultiMesh(dg::DGMulti{2, Tri}, triangulateIO, boundary_dict::Dict{Symbol, Int}) - -- `dg::DGMulti` contains information associated with to the reference element (e.g., quadrature, - basis evaluation, differentiation, etc). -- `triangulateIO` is a `TriangulateIO` mesh representation -- `boundary_dict` is a `Dict{Symbol, Int}` which associates each integer `TriangulateIO` boundary - tag with a `Symbol`. -""" -function DGMultiMesh(dg::DGMulti{2, Tri}, triangulateIO, - boundary_dict::Dict{Symbol, Int}; - periodicity = (false, false)) - vertex_coordinates, EToV = StartUpDG.triangulateIO_to_VXYEToV(triangulateIO) - md = MeshData(vertex_coordinates, EToV, dg.basis) - md = StartUpDG.make_periodic(md, periodicity) - boundary_faces = StartUpDG.tag_boundary_faces(triangulateIO, dg.basis, md, - boundary_dict) - return DGMultiMesh(dg, GeometricTermsType(TriangulateIO(), dg), md, boundary_faces) -end - -""" - DGMultiMesh(dg::DGMulti, cells_per_dimension; - coordinates_min=(-1.0, -1.0), coordinates_max=(1.0, 1.0), - is_on_boundary=nothing, - periodicity=ntuple(_ -> false, NDIMS)) - -Constructs a Cartesian [`DGMultiMesh`](@ref) with element type `dg.basis.element_type`. The domain is -the tensor product of the intervals `[coordinates_min[i], coordinates_max[i]]`. -- `is_on_boundary` specifies boundary using a `Dict{Symbol, <:Function}` -- `periodicity` is a tuple of `Bool`s specifying periodicity = `true`/`false` in the (x,y,z) direction. -""" -function DGMultiMesh(dg::DGMulti{NDIMS}, cells_per_dimension; - coordinates_min = ntuple(_ -> -one(real(dg)), NDIMS), - coordinates_max = ntuple(_ -> one(real(dg)), NDIMS), - is_on_boundary = nothing, - periodicity = ntuple(_ -> false, NDIMS), kwargs...) where {NDIMS} - vertex_coordinates, EToV = StartUpDG.uniform_mesh(dg.basis.element_type, - cells_per_dimension...) - domain_lengths = coordinates_max .- coordinates_min - for i in 1:NDIMS - @. vertex_coordinates[i] = 0.5 * (vertex_coordinates[i] + 1) * - domain_lengths[i] + coordinates_min[i] + + # dispatchable constructor for DGMulti using a TensorProductWedge + function DGMulti( + element_type::Wedge, + approximation_type, + volume_integral, + surface_integral; + polydeg::Tuple, + kwargs... + ) + factor_a = RefElemData(Tri(), approximation_type, polydeg[1]; kwargs...) + factor_b = RefElemData(Line(), approximation_type, polydeg[2]; kwargs...) + + tensor = TensorProductWedge(factor_a, factor_b) + rd = RefElemData(element_type, tensor; kwargs...) + return DG(rd, nothing, surface_integral, volume_integral) end - md = MeshData(vertex_coordinates, EToV, dg.basis) - if NDIMS == 1 && first(periodicity) == true - md = StartUpDG.make_periodic(md) + # dispatchable constructor for DGMulti to allow for specialization + function DGMulti( + element_type::AbstractElemShape, + approximation_type, + volume_integral, + surface_integral; + polydeg::Integer, + kwargs... + ) + rd = RefElemData(element_type, approximation_type, polydeg; kwargs...) + # `nothing` is passed as `mortar` + return DG(rd, nothing, surface_integral, volume_integral) end - if NDIMS > 1 - md = StartUpDG.make_periodic(md, periodicity) + + function DGMulti(basis::RefElemData; volume_integral, surface_integral) + # `nothing` is passed as `mortar` + DG(basis, nothing, surface_integral, volume_integral) end - boundary_faces = StartUpDG.tag_boundary_faces(md, is_on_boundary) - return DGMultiMesh(dg, GeometricTermsType(Cartesian(), dg), md, boundary_faces) -end - -""" - DGMultiMesh(dg::DGMulti{NDIMS}, cells_per_dimension, mapping; - is_on_boundary=nothing, - periodicity=ntuple(_ -> false, NDIMS), kwargs...) where {NDIMS} - -Constructs a `Curved()` [`DGMultiMesh`](@ref) with element type `dg.basis.element_type`. -- `mapping` is a function which maps from a reference [-1, 1]^NDIMS domain to a mapped domain, - e.g., `xy = mapping(x, y)` in 2D. -- `is_on_boundary` specifies boundary using a `Dict{Symbol, <:Function}` -- `periodicity` is a tuple of `Bool`s specifying periodicity = `true`/`false` in the (x,y,z) direction. -""" -function DGMultiMesh(dg::DGMulti{NDIMS}, cells_per_dimension, mapping; - is_on_boundary = nothing, - periodicity = ntuple(_ -> false, NDIMS), kwargs...) where {NDIMS} - vertex_coordinates, EToV = StartUpDG.uniform_mesh(dg.basis.element_type, - cells_per_dimension...) - md = MeshData(vertex_coordinates, EToV, dg.basis) - md = NDIMS == 1 ? StartUpDG.make_periodic(md, periodicity...) : - StartUpDG.make_periodic(md, periodicity) - - @unpack xyz = md - for i in eachindex(xyz[1]) - new_xyz = mapping(getindex.(xyz, i)...) - setindex!.(xyz, new_xyz, i) + + """ + DGMultiBasis(element_type, polydeg; approximation_type = Polynomial(), kwargs...) + + Constructs a basis for DGMulti solvers. Returns a "StartUpDG.RefElemData" object. + The `kwargs` arguments are additional keyword arguments for `RefElemData`, such as `quad_rule_vol`. + These are the same as the `RefElemData_kwargs` used in [`DGMulti`](@ref). + For more info, see the [StartUpDG.jl docs](https://jlchan.github.io/StartUpDG.jl/dev/). + + """ + function DGMultiBasis( + element_type, polydeg; approximation_type = Polynomial(), + kwargs... + ) + RefElemData(element_type, approximation_type, polydeg; kwargs...) end - md_curved = MeshData(dg.basis, md, xyz...) - - boundary_faces = StartUpDG.tag_boundary_faces(md_curved, is_on_boundary) - return DGMultiMesh(dg, GeometricTermsType(Curved(), dg), md_curved, boundary_faces) -end - -""" - DGMultiMesh(dg::DGMulti, filename::String) - -- `dg::DGMulti` contains information associated with the reference element (e.g., quadrature, - basis evaluation, differentiation, etc). -- `filename` is a path specifying a `.mesh` file generated by - [HOHQMesh](https://github.com/trixi-framework/HOHQMesh). -""" -function DGMultiMesh(dg::DGMulti{NDIMS}, filename::String; - periodicity = ntuple(_ -> false, NDIMS)) where {NDIMS} - hohqmesh_data = StartUpDG.read_HOHQMesh(filename) - md = MeshData(hohqmesh_data, dg.basis) - md = StartUpDG.make_periodic(md, periodicity) - boundary_faces = Dict(Pair.(keys(md.mesh_type.boundary_faces), - values(md.mesh_type.boundary_faces))) - return DGMultiMesh(dg, GeometricTermsType(Curved(), dg), md, boundary_faces) -end - -# Matrix type for lazy construction of physical differentiation matrices -# Constructs a lazy linear combination of B = ∑_i coeffs[i] * A[i] -struct LazyMatrixLinearCombo{Tcoeffs, N, Tv, TA <: AbstractMatrix{Tv}} <: - AbstractMatrix{Tv} - matrices::NTuple{N, TA} - coeffs::NTuple{N, Tcoeffs} - function LazyMatrixLinearCombo(matrices, coeffs) - @assert all(matrix -> size(matrix) == size(first(matrices)), matrices) - new{typeof(first(coeffs)), length(matrices), eltype(first(matrices)), - typeof(first(matrices))}(matrices, coeffs) + + ######################################## + # DGMultiMesh + ######################################## + + # now that `DGMulti` is defined, we can define constructors for `DGMultiMesh` which use `dg::DGMulti` + + function DGMultiMesh( + dg::DGMulti, geometric_term_type, md::MeshData{NDIMS}, + boundary_faces + ) where {NDIMS} + return DGMultiMesh{ + NDIMS, typeof(geometric_term_type), typeof(md), + typeof(boundary_faces), + }(md, boundary_faces) end -end -Base.eltype(A::LazyMatrixLinearCombo) = eltype(first(A.matrices)) -Base.IndexStyle(A::LazyMatrixLinearCombo) = IndexCartesian() -Base.size(A::LazyMatrixLinearCombo) = size(first(A.matrices)) - -@inline function Base.getindex(A::LazyMatrixLinearCombo{<:Real, N}, i, j) where {N} - val = zero(eltype(A)) - for k in Base.OneTo(N) - val = val + A.coeffs[k] * getindex(A.matrices[k], i, j) + + # Mesh types used internally for trait dispatch + struct Cartesian end + struct VertexMapped end # where element geometry is determined by vertices. + struct Curved end + + # type parameters for dispatch using `DGMultiMesh` + abstract type GeometricTermsType end + struct Affine <: GeometricTermsType end # mesh produces constant geometric terms + struct NonAffine <: GeometricTermsType end # mesh produces non-constant geometric terms + + # choose MeshType based on the constructor and element type + function GeometricTermsType(mesh_type, dg::DGMulti) + GeometricTermsType(mesh_type, dg.basis.element_type) end - return val -end - -# `SimpleKronecker` lazily stores a Kronecker product `kron(ntuple(A, NDIMS)...)`. -# This object also allocates some temporary storage to enable the fast computation -# of matrix-vector products. -struct SimpleKronecker{NDIMS, TA, Ttmp} - A::TA - tmp_storage::Ttmp # temporary array used for Kronecker multiplication -end - -# constructor for SimpleKronecker which requires specifying only `NDIMS` and -# the 1D matrix `A`. -function SimpleKronecker(NDIMS, A, eltype_A = eltype(A)) - @assert size(A, 1) == size(A, 2) # check if square - tmp_storage = [zeros(eltype_A, ntuple(_ -> size(A, 2), NDIMS)...) - for _ in 1:Threads.nthreads()] - return SimpleKronecker{NDIMS, typeof(A), typeof(tmp_storage)}(A, tmp_storage) -end - -# fall back to mul! for a 1D Kronecker product -LinearAlgebra.mul!(b, A_kronecker::SimpleKronecker{1}, x) = mul!(b, A_kronecker.A, x) - -# Computes `b = kron(A, A) * x` in an optimized fashion -function LinearAlgebra.mul!(b_in, A_kronecker::SimpleKronecker{2}, x_in) - @unpack A = A_kronecker - tmp_storage = A_kronecker.tmp_storage[Threads.threadid()] - n = size(A, 2) - - # copy `x_in` to `tmp_storage` to avoid mutating the input - @assert length(tmp_storage) == length(x_in) - @turbo thread=true for i in eachindex(tmp_storage) - tmp_storage[i] = x_in[i] + GeometricTermsType(mesh_type::Cartesian, element_type::AbstractElemShape) = Affine() + GeometricTermsType(mesh_type::TriangulateIO, element_type::Tri) = Affine() + GeometricTermsType(mesh_type::VertexMapped, element_type::Union{Tri, Tet}) = Affine() + function GeometricTermsType(mesh_type::VertexMapped, element_type::Union{Quad, Hex}) + NonAffine() end - x = reshape(tmp_storage, n, n) - # As of Julia 1.9, Base.ReshapedArray does not produce allocations when setting values. - # Thus, Base.ReshapedArray should be used if you are setting values in the array. - # `reshape` is fine if you are only accessing values. - b = Base.ReshapedArray(b_in, (n, n), ()) - - @turbo thread=true for j in 1:n, i in 1:n - tmp = zero(eltype(x)) - for ii in 1:n - tmp = tmp + A[i, ii] * x[ii, j] + GeometricTermsType(mesh_type::Curved, element_type::AbstractElemShape) = NonAffine() + + # other potential constructor types to add later: Bilinear, Isoparametric{polydeg_geo}, Rational/Exact? + # other potential mesh types to add later: Polynomial{polydeg_geo}? + + """ + DGMultiMesh(dg::DGMulti{NDIMS}, vertex_coordinates, EToV; + is_on_boundary=nothing, + periodicity=ntuple(_->false, NDIMS)) where {NDIMS} + + - `dg::DGMulti` contains information associated with to the reference element (e.g., quadrature, + basis evaluation, differentiation, etc). + - `vertex_coordinates` is a tuple of vectors containing x,y,... components of the vertex coordinates + - `EToV` is a 2D array containing element-to-vertex connectivities for each element + - `is_on_boundary` specifies boundary using a `Dict{Symbol, <:Function}` + - `periodicity` is a tuple of booleans specifying if the domain is periodic `true`/`false` in the + (x,y,z) direction. + """ + function DGMultiMesh( + dg::DGMulti{NDIMS}, vertex_coordinates, EToV::AbstractArray; + is_on_boundary = nothing, + periodicity = ntuple(_ -> false, NDIMS), kwargs... + ) where {NDIMS} + md = MeshData(vertex_coordinates, EToV, dg.basis) + if NDIMS == 1 + md = StartUpDG.make_periodic(md, periodicity...) + else + md = StartUpDG.make_periodic(md, periodicity) end - b[i, j] = tmp + boundary_faces = StartUpDG.tag_boundary_faces(md, is_on_boundary) + return DGMultiMesh(dg, GeometricTermsType(VertexMapped(), dg), md, boundary_faces) end - @turbo thread=true for j in 1:n, i in 1:n - tmp = zero(eltype(x)) - for jj in 1:n - tmp = tmp + A[j, jj] * b[i, jj] - end - x[i, j] = tmp + """ + DGMultiMesh(dg::DGMulti{2, Tri}, triangulateIO, boundary_dict::Dict{Symbol, Int}) + + - `dg::DGMulti` contains information associated with to the reference element (e.g., quadrature, + basis evaluation, differentiation, etc). + - `triangulateIO` is a `TriangulateIO` mesh representation + - `boundary_dict` is a `Dict{Symbol, Int}` which associates each integer `TriangulateIO` boundary + tag with a `Symbol`. + """ + function DGMultiMesh( + dg::DGMulti{2, Tri}, triangulateIO, + boundary_dict::Dict{Symbol, Int}; + periodicity = (false, false) + ) + vertex_coordinates, EToV = StartUpDG.triangulateIO_to_VXYEToV(triangulateIO) + md = MeshData(vertex_coordinates, EToV, dg.basis) + md = StartUpDG.make_periodic(md, periodicity) + boundary_faces = StartUpDG.tag_boundary_faces( + triangulateIO, dg.basis, md, + boundary_dict + ) + return DGMultiMesh(dg, GeometricTermsType(TriangulateIO(), dg), md, boundary_faces) end - @turbo thread=true for i in eachindex(b_in) - b_in[i] = x[i] + """ + DGMultiMesh(dg::DGMulti, cells_per_dimension; + coordinates_min=(-1.0, -1.0), coordinates_max=(1.0, 1.0), + is_on_boundary=nothing, + periodicity=ntuple(_ -> false, NDIMS)) + + Constructs a Cartesian [`DGMultiMesh`](@ref) with element type `dg.basis.element_type`. The domain is + the tensor product of the intervals `[coordinates_min[i], coordinates_max[i]]`. + - `is_on_boundary` specifies boundary using a `Dict{Symbol, <:Function}` + - `periodicity` is a tuple of `Bool`s specifying periodicity = `true`/`false` in the (x,y,z) direction. + """ + function DGMultiMesh( + dg::DGMulti{NDIMS}, cells_per_dimension; + coordinates_min = ntuple(_ -> -one(real(dg)), NDIMS), + coordinates_max = ntuple(_ -> one(real(dg)), NDIMS), + is_on_boundary = nothing, + periodicity = ntuple(_ -> false, NDIMS), kwargs... + ) where {NDIMS} + vertex_coordinates, EToV = StartUpDG.uniform_mesh( + dg.basis.element_type, + cells_per_dimension... + ) + domain_lengths = coordinates_max .- coordinates_min + for i in 1:NDIMS + @. vertex_coordinates[i] = 0.5 * (vertex_coordinates[i] + 1) * + domain_lengths[i] + coordinates_min[i] + end + + md = MeshData(vertex_coordinates, EToV, dg.basis) + if NDIMS == 1 && first(periodicity) == true + md = StartUpDG.make_periodic(md) + end + if NDIMS > 1 + md = StartUpDG.make_periodic(md, periodicity) + end + boundary_faces = StartUpDG.tag_boundary_faces(md, is_on_boundary) + return DGMultiMesh(dg, GeometricTermsType(Cartesian(), dg), md, boundary_faces) end - return nothing -end + """ + DGMultiMesh(dg::DGMulti{NDIMS}, cells_per_dimension, mapping; + is_on_boundary=nothing, + periodicity=ntuple(_ -> false, NDIMS), kwargs...) where {NDIMS} + + Constructs a `Curved()` [`DGMultiMesh`](@ref) with element type `dg.basis.element_type`. + - `mapping` is a function which maps from a reference [-1, 1]^NDIMS domain to a mapped domain, + e.g., `xy = mapping(x, y)` in 2D. + - `is_on_boundary` specifies boundary using a `Dict{Symbol, <:Function}` + - `periodicity` is a tuple of `Bool`s specifying periodicity = `true`/`false` in the (x,y,z) direction. + """ + function DGMultiMesh( + dg::DGMulti{NDIMS}, cells_per_dimension, mapping; + is_on_boundary = nothing, + periodicity = ntuple(_ -> false, NDIMS), kwargs... + ) where {NDIMS} + vertex_coordinates, EToV = StartUpDG.uniform_mesh( + dg.basis.element_type, + cells_per_dimension... + ) + md = MeshData(vertex_coordinates, EToV, dg.basis) + md = NDIMS == 1 ? StartUpDG.make_periodic(md, periodicity...) : + StartUpDG.make_periodic(md, periodicity) + + @unpack xyz = md + for i in eachindex(xyz[1]) + new_xyz = mapping(getindex.(xyz, i)...) + setindex!.(xyz, new_xyz, i) + end + md_curved = MeshData(dg.basis, md, xyz...) + + boundary_faces = StartUpDG.tag_boundary_faces(md_curved, is_on_boundary) + return DGMultiMesh(dg, GeometricTermsType(Curved(), dg), md_curved, boundary_faces) + end -# Computes `b = kron(A, A, A) * x` in an optimized fashion -function LinearAlgebra.mul!(b_in, A_kronecker::SimpleKronecker{3}, x_in) - @unpack A = A_kronecker - tmp_storage = A_kronecker.tmp_storage[Threads.threadid()] - n = size(A, 2) + """ + DGMultiMesh(dg::DGMulti, filename::String) + + - `dg::DGMulti` contains information associated with the reference element (e.g., quadrature, + basis evaluation, differentiation, etc). + - `filename` is a path specifying a `.mesh` file generated by + [HOHQMesh](https://github.com/trixi-framework/HOHQMesh). + """ + function DGMultiMesh( + dg::DGMulti{NDIMS}, filename::String; + periodicity = ntuple(_ -> false, NDIMS) + ) where {NDIMS} + hohqmesh_data = StartUpDG.read_HOHQMesh(filename) + md = MeshData(hohqmesh_data, dg.basis) + md = StartUpDG.make_periodic(md, periodicity) + boundary_faces = Dict( + Pair.( + keys(md.mesh_type.boundary_faces), + values(md.mesh_type.boundary_faces) + ) + ) + return DGMultiMesh(dg, GeometricTermsType(Curved(), dg), md, boundary_faces) + end - # copy `x_in` to `tmp_storage` to avoid mutating the input - @turbo thread=true for i in eachindex(tmp_storage) - tmp_storage[i] = x_in[i] + # Matrix type for lazy construction of physical differentiation matrices + # Constructs a lazy linear combination of B = ∑_i coeffs[i] * A[i] + struct LazyMatrixLinearCombo{Tcoeffs, N, Tv, TA <: AbstractMatrix{Tv}} <: + AbstractMatrix{Tv} + matrices::NTuple{N, TA} + coeffs::NTuple{N, Tcoeffs} + function LazyMatrixLinearCombo(matrices, coeffs) + @assert all(matrix -> size(matrix) == size(first(matrices)), matrices) + new{ + typeof(first(coeffs)), length(matrices), eltype(first(matrices)), + typeof(first(matrices)), + }(matrices, coeffs) + end end - x = reshape(tmp_storage, n, n, n) - # As of Julia 1.9, Base.ReshapedArray does not produce allocations when setting values. - # Thus, Base.ReshapedArray should be used if you are setting values in the array. - # `reshape` is fine if you are only accessing values. - b = Base.ReshapedArray(b_in, (n, n, n), ()) - - @turbo thread=true for k in 1:n, j in 1:n, i in 1:n - tmp = zero(eltype(x)) - for ii in 1:n - tmp = tmp + A[i, ii] * x[ii, j, k] + Base.eltype(A::LazyMatrixLinearCombo) = eltype(first(A.matrices)) + Base.IndexStyle(A::LazyMatrixLinearCombo) = IndexCartesian() + Base.size(A::LazyMatrixLinearCombo) = size(first(A.matrices)) + + @inline function Base.getindex(A::LazyMatrixLinearCombo{<:Real, N}, i, j) where {N} + val = zero(eltype(A)) + for k in Base.OneTo(N) + val = val + A.coeffs[k] * getindex(A.matrices[k], i, j) end - b[i, j, k] = tmp + return val end - @turbo thread=true for k in 1:n, j in 1:n, i in 1:n - tmp = zero(eltype(x)) - for jj in 1:n - tmp = tmp + A[j, jj] * b[i, jj, k] - end - x[i, j, k] = tmp + # `SimpleKronecker` lazily stores a Kronecker product `kron(ntuple(A, NDIMS)...)`. + # This object also allocates some temporary storage to enable the fast computation + # of matrix-vector products. + struct SimpleKronecker{NDIMS, TA, Ttmp} + A::TA + tmp_storage::Ttmp # temporary array used for Kronecker multiplication + end + + # constructor for SimpleKronecker which requires specifying only `NDIMS` and + # the 1D matrix `A`. + function SimpleKronecker(NDIMS, A, eltype_A = eltype(A)) + @assert size(A, 1) == size(A, 2) # check if square + tmp_storage = [ + zeros(eltype_A, ntuple(_ -> size(A, 2), NDIMS)...) + for _ in 1:Threads.nthreads() + ] + return SimpleKronecker{NDIMS, typeof(A), typeof(tmp_storage)}(A, tmp_storage) end - @turbo thread=true for k in 1:n, j in 1:n, i in 1:n - tmp = zero(eltype(x)) - for kk in 1:n - tmp = tmp + A[k, kk] * x[i, j, kk] + # fall back to mul! for a 1D Kronecker product + LinearAlgebra.mul!(b, A_kronecker::SimpleKronecker{1}, x) = mul!(b, A_kronecker.A, x) + + # Computes `b = kron(A, A) * x` in an optimized fashion + function LinearAlgebra.mul!(b_in, A_kronecker::SimpleKronecker{2}, x_in) + @unpack A = A_kronecker + tmp_storage = A_kronecker.tmp_storage[Threads.threadid()] + n = size(A, 2) + + # copy `x_in` to `tmp_storage` to avoid mutating the input + @assert length(tmp_storage) == length(x_in) + @turbo thread = true for i in eachindex(tmp_storage) + tmp_storage[i] = x_in[i] + end + x = reshape(tmp_storage, n, n) + # As of Julia 1.9, Base.ReshapedArray does not produce allocations when setting values. + # Thus, Base.ReshapedArray should be used if you are setting values in the array. + # `reshape` is fine if you are only accessing values. + b = Base.ReshapedArray(b_in, (n, n), ()) + + @turbo thread = true for j in 1:n, i in 1:n + tmp = zero(eltype(x)) + for ii in 1:n + tmp = tmp + A[i, ii] * x[ii, j] + end + b[i, j] = tmp + end + + @turbo thread = true for j in 1:n, i in 1:n + tmp = zero(eltype(x)) + for jj in 1:n + tmp = tmp + A[j, jj] * b[i, jj] + end + x[i, j] = tmp end - b[i, j, k] = tmp + + @turbo thread = true for i in eachindex(b_in) + b_in[i] = x[i] + end + + return nothing end - return nothing -end + # Computes `b = kron(A, A, A) * x` in an optimized fashion + function LinearAlgebra.mul!(b_in, A_kronecker::SimpleKronecker{3}, x_in) + @unpack A = A_kronecker + tmp_storage = A_kronecker.tmp_storage[Threads.threadid()] + n = size(A, 2) + + # copy `x_in` to `tmp_storage` to avoid mutating the input + @turbo thread = true for i in eachindex(tmp_storage) + tmp_storage[i] = x_in[i] + end + x = reshape(tmp_storage, n, n, n) + # As of Julia 1.9, Base.ReshapedArray does not produce allocations when setting values. + # Thus, Base.ReshapedArray should be used if you are setting values in the array. + # `reshape` is fine if you are only accessing values. + b = Base.ReshapedArray(b_in, (n, n, n), ()) + + @turbo thread = true for k in 1:n, j in 1:n, i in 1:n + tmp = zero(eltype(x)) + for ii in 1:n + tmp = tmp + A[i, ii] * x[ii, j, k] + end + b[i, j, k] = tmp + end + + @turbo thread = true for k in 1:n, j in 1:n, i in 1:n + tmp = zero(eltype(x)) + for jj in 1:n + tmp = tmp + A[j, jj] * b[i, jj, k] + end + x[i, j, k] = tmp + end + + @turbo thread = true for k in 1:n, j in 1:n, i in 1:n + tmp = zero(eltype(x)) + for kk in 1:n + tmp = tmp + A[k, kk] * x[i, j, kk] + end + b[i, j, k] = tmp + end + + return nothing + end end # @muladd diff --git a/src/solvers/dgsem/basis_lobatto_legendre.jl b/src/solvers/dgsem/basis_lobatto_legendre.jl index cac1dba9c7..9af5c22f79 100644 --- a/src/solvers/dgsem/basis_lobatto_legendre.jl +++ b/src/solvers/dgsem/basis_lobatto_legendre.jl @@ -3,781 +3,832 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -""" - LobattoLegendreBasis([RealT=Float64,] polydeg::Integer) - -Create a nodal Lobatto-Legendre basis for polynomials of degree `polydeg`. - -For the special case `polydeg=0` the DG method reduces to a finite volume method. -Therefore, this function sets the center point of the cell as single node. -This exceptional case is currently only supported for TreeMesh! -""" -struct LobattoLegendreBasis{RealT <: Real, NNODES, - VectorT <: AbstractVector{RealT}, - InverseVandermondeLegendre <: AbstractMatrix{RealT}, - BoundaryMatrix <: AbstractMatrix{RealT}, - DerivativeMatrix <: AbstractMatrix{RealT}} <: - AbstractBasisSBP{RealT} - nodes::VectorT - weights::VectorT - inverse_weights::VectorT - - inverse_vandermonde_legendre::InverseVandermondeLegendre - boundary_interpolation::BoundaryMatrix # lhat - - derivative_matrix::DerivativeMatrix # strong form derivative matrix - derivative_split::DerivativeMatrix # strong form derivative matrix minus boundary terms - derivative_split_transpose::DerivativeMatrix # transpose of `derivative_split` - derivative_dhat::DerivativeMatrix # weak form matrix "dhat", - # negative adjoint wrt the SBP dot product -end - -function LobattoLegendreBasis(RealT, polydeg::Integer) - nnodes_ = polydeg + 1 - - # compute everything using `Float64` by default - nodes_, weights_ = gauss_lobatto_nodes_weights(nnodes_) - inverse_weights_ = inv.(weights_) - - _, inverse_vandermonde_legendre_ = vandermonde_legendre(nodes_) - - boundary_interpolation_ = zeros(nnodes_, 2) - boundary_interpolation_[:, 1] = calc_lhat(-1.0, nodes_, weights_) - boundary_interpolation_[:, 2] = calc_lhat(1.0, nodes_, weights_) - - derivative_matrix_ = polynomial_derivative_matrix(nodes_) - derivative_split_ = calc_dsplit(nodes_, weights_) - derivative_split_transpose_ = Matrix(derivative_split_') - derivative_dhat_ = calc_dhat(nodes_, weights_) - - # type conversions to get the requested real type and enable possible - # optimizations of runtime performance and latency - nodes = SVector{nnodes_, RealT}(nodes_) - weights = SVector{nnodes_, RealT}(weights_) - inverse_weights = SVector{nnodes_, RealT}(inverse_weights_) - - inverse_vandermonde_legendre = convert.(RealT, inverse_vandermonde_legendre_) - boundary_interpolation = convert.(RealT, boundary_interpolation_) - - # Usually as fast as `SMatrix` (when using `let` in the volume integral/`@threaded`) - derivative_matrix = Matrix{RealT}(derivative_matrix_) - derivative_split = Matrix{RealT}(derivative_split_) - derivative_split_transpose = Matrix{RealT}(derivative_split_transpose_) - derivative_dhat = Matrix{RealT}(derivative_dhat_) - - return LobattoLegendreBasis{RealT, nnodes_, typeof(nodes), - typeof(inverse_vandermonde_legendre), - typeof(boundary_interpolation), - typeof(derivative_matrix)}(nodes, weights, - inverse_weights, - inverse_vandermonde_legendre, - boundary_interpolation, - derivative_matrix, - derivative_split, - derivative_split_transpose, - derivative_dhat) -end - -LobattoLegendreBasis(polydeg::Integer) = LobattoLegendreBasis(Float64, polydeg) - -function Base.show(io::IO, basis::LobattoLegendreBasis) - @nospecialize basis # reduce precompilation time - - print(io, "LobattoLegendreBasis{", real(basis), "}(polydeg=", polydeg(basis), ")") -end -function Base.show(io::IO, ::MIME"text/plain", basis::LobattoLegendreBasis) - @nospecialize basis # reduce precompilation time - - print(io, "LobattoLegendreBasis{", real(basis), "} with polynomials of degree ", - polydeg(basis)) -end - -function Base.:(==)(b1::LobattoLegendreBasis, b2::LobattoLegendreBasis) - if typeof(b1) != typeof(b2) - return false - end - - for field in fieldnames(typeof(b1)) - if getfield(b1, field) != getfield(b2, field) - return false - end + #! format: noindent + + """ + LobattoLegendreBasis([RealT=Float64,] polydeg::Integer) + + Create a nodal Lobatto-Legendre basis for polynomials of degree `polydeg`. + + For the special case `polydeg=0` the DG method reduces to a finite volume method. + Therefore, this function sets the center point of the cell as single node. + This exceptional case is currently only supported for TreeMesh! + """ + struct LobattoLegendreBasis{ + RealT <: Real, NNODES, + VectorT <: AbstractVector{RealT}, + InverseVandermondeLegendre <: AbstractMatrix{RealT}, + BoundaryMatrix <: AbstractMatrix{RealT}, + DerivativeMatrix <: AbstractMatrix{RealT}, + } <: + AbstractBasisSBP{RealT} + nodes::VectorT + weights::VectorT + inverse_weights::VectorT + + inverse_vandermonde_legendre::InverseVandermondeLegendre + boundary_interpolation::BoundaryMatrix # lhat + + derivative_matrix::DerivativeMatrix # strong form derivative matrix + derivative_split::DerivativeMatrix # strong form derivative matrix minus boundary terms + derivative_split_transpose::DerivativeMatrix # transpose of `derivative_split` + derivative_dhat::DerivativeMatrix # weak form matrix "dhat", + # negative adjoint wrt the SBP dot product end - return true -end - -@inline Base.real(basis::LobattoLegendreBasis{RealT}) where {RealT} = RealT - -@inline function nnodes(basis::LobattoLegendreBasis{RealT, NNODES}) where {RealT, NNODES - } - NNODES -end - -""" - eachnode(basis::LobattoLegendreBasis) - -Return an iterator over the indices that specify the location in relevant data structures -for the nodes in `basis`. -In particular, not the nodes themselves are returned. -""" -@inline eachnode(basis::LobattoLegendreBasis) = Base.OneTo(nnodes(basis)) - -@inline polydeg(basis::LobattoLegendreBasis) = nnodes(basis) - 1 - -@inline get_nodes(basis::LobattoLegendreBasis) = basis.nodes - -""" - integrate(f, u, basis::LobattoLegendreBasis) - -Map the function `f` to the coefficients `u` and integrate with respect to the -quadrature rule given by `basis`. -""" -function integrate(f, u, basis::LobattoLegendreBasis) - @unpack weights = basis - - res = zero(f(first(u))) - for i in eachindex(u, weights) - res += f(u[i]) * weights[i] - end - return res -end - -# Return the first/last weight of the quadrature associated with `basis`. -# Since the mass matrix for nodal Lobatto-Legendre bases is diagonal, -# these weights are the only coefficients necessary for the scaling of -# surface terms/integrals in DGSEM. -left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights) -right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights) - -struct LobattoLegendreMortarL2{RealT <: Real, NNODES, - ForwardMatrix <: AbstractMatrix{RealT}, - ReverseMatrix <: AbstractMatrix{RealT}} <: - AbstractMortarL2{RealT} - forward_upper::ForwardMatrix - forward_lower::ForwardMatrix - reverse_upper::ReverseMatrix - reverse_lower::ReverseMatrix -end - -function MortarL2(basis::LobattoLegendreBasis) - RealT = real(basis) - nnodes_ = nnodes(basis) - - # compute everything using `Float64` by default - forward_upper_ = calc_forward_upper(nnodes_) - forward_lower_ = calc_forward_lower(nnodes_) - reverse_upper_ = calc_reverse_upper(nnodes_, Val(:gauss)) - reverse_lower_ = calc_reverse_lower(nnodes_, Val(:gauss)) - - # type conversions to get the requested real type and enable possible - # optimizations of runtime performance and latency - - # Usually as fast as `SMatrix` but better for latency - forward_upper = Matrix{RealT}(forward_upper_) - forward_lower = Matrix{RealT}(forward_lower_) - - # TODO: Taal performance - # Check the performance of different implementations of `mortar_fluxes_to_elements!` - # with different types of the reverse matrices and different types of - # `fstar_upper_threaded` etc. used in the cache. - # Check whether `@turbo` with `eachnode` in `multiply_dimensionwise!` can be faster than - # `@tullio` when the matrix sizes are not necessarily static. - # reverse_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_upper_) - # reverse_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_lower_) - reverse_upper = Matrix{RealT}(reverse_upper_) - reverse_lower = Matrix{RealT}(reverse_lower_) - - LobattoLegendreMortarL2{RealT, nnodes_, typeof(forward_upper), - typeof(reverse_upper)}(forward_upper, forward_lower, - reverse_upper, reverse_lower) -end - -function Base.show(io::IO, mortar::LobattoLegendreMortarL2) - @nospecialize mortar # reduce precompilation time - - print(io, "LobattoLegendreMortarL2{", real(mortar), "}(polydeg=", polydeg(mortar), - ")") -end -function Base.show(io::IO, ::MIME"text/plain", mortar::LobattoLegendreMortarL2) - @nospecialize mortar # reduce precompilation time - - print(io, "LobattoLegendreMortarL2{", real(mortar), "} with polynomials of degree ", - polydeg(mortar)) -end - -@inline Base.real(mortar::LobattoLegendreMortarL2{RealT}) where {RealT} = RealT - -@inline function nnodes(mortar::LobattoLegendreMortarL2{RealT, NNODES}) where {RealT, - NNODES} - NNODES -end - -@inline polydeg(mortar::LobattoLegendreMortarL2) = nnodes(mortar) - 1 - -# TODO: We can create EC mortars along the lines of the following implementation. -# abstract type AbstractMortarEC{RealT} <: AbstractMortar{RealT} end - -# struct LobattoLegendreMortarEC{RealT<:Real, NNODES, MortarMatrix<:AbstractMatrix{RealT}, SurfaceFlux} <: AbstractMortarEC{RealT} -# forward_upper::MortarMatrix -# forward_lower::MortarMatrix -# reverse_upper::MortarMatrix -# reverse_lower::MortarMatrix -# surface_flux::SurfaceFlux -# end - -# function MortarEC(basis::LobattoLegendreBasis{RealT}, surface_flux) -# forward_upper = calc_forward_upper(n_nodes) -# forward_lower = calc_forward_lower(n_nodes) -# l2reverse_upper = calc_reverse_upper(n_nodes, Val(:gauss_lobatto)) -# l2reverse_lower = calc_reverse_lower(n_nodes, Val(:gauss_lobatto)) - -# # type conversions to make use of StaticArrays etc. -# nnodes_ = nnodes(basis) -# forward_upper = SMatrix{nnodes_, nnodes_}(forward_upper) -# forward_lower = SMatrix{nnodes_, nnodes_}(forward_lower) -# l2reverse_upper = SMatrix{nnodes_, nnodes_}(l2reverse_upper) -# l2reverse_lower = SMatrix{nnodes_, nnodes_}(l2reverse_lower) - -# LobattoLegendreMortarEC{RealT, nnodes_, typeof(forward_upper), typeof(surface_flux)}( -# forward_upper, forward_lower, -# l2reverse_upper, l2reverse_lower, -# surface_flux -# ) -# end - -# @inline nnodes(mortar::LobattoLegendreMortarEC{RealT, NNODES}) = NNODES - -struct LobattoLegendreAnalyzer{RealT <: Real, NNODES, - VectorT <: AbstractVector{RealT}, - Vandermonde <: AbstractMatrix{RealT}} <: - SolutionAnalyzer{RealT} - nodes::VectorT - weights::VectorT - vandermonde::Vandermonde -end - -function SolutionAnalyzer(basis::LobattoLegendreBasis; - analysis_polydeg = 2 * polydeg(basis)) - RealT = real(basis) - nnodes_ = analysis_polydeg + 1 - - # compute everything using `Float64` by default - nodes_, weights_ = gauss_lobatto_nodes_weights(nnodes_) - vandermonde_ = polynomial_interpolation_matrix(get_nodes(basis), nodes_) - - # type conversions to get the requested real type and enable possible - # optimizations of runtime performance and latency - nodes = SVector{nnodes_, RealT}(nodes_) - weights = SVector{nnodes_, RealT}(weights_) - - vandermonde = Matrix{RealT}(vandermonde_) - - return LobattoLegendreAnalyzer{RealT, nnodes_, typeof(nodes), typeof(vandermonde)}(nodes, - weights, - vandermonde) -end - -function Base.show(io::IO, analyzer::LobattoLegendreAnalyzer) - @nospecialize analyzer # reduce precompilation time - - print(io, "LobattoLegendreAnalyzer{", real(analyzer), "}(polydeg=", - polydeg(analyzer), ")") -end -function Base.show(io::IO, ::MIME"text/plain", analyzer::LobattoLegendreAnalyzer) - @nospecialize analyzer # reduce precompilation time - - print(io, "LobattoLegendreAnalyzer{", real(analyzer), - "} with polynomials of degree ", polydeg(analyzer)) -end - -@inline Base.real(analyzer::LobattoLegendreAnalyzer{RealT}) where {RealT} = RealT - -@inline function nnodes(analyzer::LobattoLegendreAnalyzer{RealT, NNODES}) where {RealT, - NNODES} - NNODES -end -""" - eachnode(analyzer::LobattoLegendreAnalyzer) - -Return an iterator over the indices that specify the location in relevant data structures -for the nodes in `analyzer`. -In particular, not the nodes themselves are returned. -""" -@inline eachnode(analyzer::LobattoLegendreAnalyzer) = Base.OneTo(nnodes(analyzer)) - -@inline polydeg(analyzer::LobattoLegendreAnalyzer) = nnodes(analyzer) - 1 - -struct LobattoLegendreAdaptorL2{RealT <: Real, NNODES, - ForwardMatrix <: AbstractMatrix{RealT}, - ReverseMatrix <: AbstractMatrix{RealT}} <: - AdaptorL2{RealT} - forward_upper::ForwardMatrix - forward_lower::ForwardMatrix - reverse_upper::ReverseMatrix - reverse_lower::ReverseMatrix -end - -function AdaptorL2(basis::LobattoLegendreBasis{RealT}) where {RealT} - nnodes_ = nnodes(basis) - - # compute everything using `Float64` by default - forward_upper_ = calc_forward_upper(nnodes_) - forward_lower_ = calc_forward_lower(nnodes_) - reverse_upper_ = calc_reverse_upper(nnodes_, Val(:gauss)) - reverse_lower_ = calc_reverse_lower(nnodes_, Val(:gauss)) - - # type conversions to get the requested real type and enable possible - # optimizations of runtime performance and latency - - # TODO: Taal performance - # Check the performance of different implementations of - # `refine_elements!` (forward) and `coarsen_elements!` (reverse) - # with different types of the matrices. - # Check whether `@turbo` with `eachnode` in `multiply_dimensionwise!` - # can be faster than `@tullio` when the matrix sizes are not necessarily - # static. - forward_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(forward_upper_) - forward_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(forward_lower_) - # forward_upper = Matrix{RealT}(forward_upper_) - # forward_lower = Matrix{RealT}(forward_lower_) - - reverse_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_upper_) - reverse_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_lower_) - # reverse_upper = Matrix{RealT}(reverse_upper_) - # reverse_lower = Matrix{RealT}(reverse_lower_) - - LobattoLegendreAdaptorL2{RealT, nnodes_, typeof(forward_upper), - typeof(reverse_upper)}(forward_upper, forward_lower, - reverse_upper, reverse_lower) -end - -function Base.show(io::IO, adaptor::LobattoLegendreAdaptorL2) - @nospecialize adaptor # reduce precompilation time - - print(io, "LobattoLegendreAdaptorL2{", real(adaptor), "}(polydeg=", - polydeg(adaptor), ")") -end -function Base.show(io::IO, ::MIME"text/plain", adaptor::LobattoLegendreAdaptorL2) - @nospecialize adaptor # reduce precompilation time - - print(io, "LobattoLegendreAdaptorL2{", real(adaptor), - "} with polynomials of degree ", polydeg(adaptor)) -end - -@inline Base.real(adaptor::LobattoLegendreAdaptorL2{RealT}) where {RealT} = RealT - -@inline function nnodes(adaptor::LobattoLegendreAdaptorL2{RealT, NNODES}) where {RealT, - NNODES} - NNODES -end - -@inline polydeg(adaptor::LobattoLegendreAdaptorL2) = nnodes(adaptor) - 1 - -############################################################################### -# Polynomial derivative and interpolation functions - -# TODO: Taal refactor, allow other RealT below and adapt constructors above accordingly - -# Calculate the Dhat matrix -function calc_dhat(nodes, weights) - n_nodes = length(nodes) - dhat = Matrix(polynomial_derivative_matrix(nodes)') - - for n in 1:n_nodes, j in 1:n_nodes - dhat[j, n] *= -weights[n] / weights[j] - end - - return dhat -end - -# Calculate the Dsplit matrix for split-form differentiation: dplit = 2D - M⁻¹B -function calc_dsplit(nodes, weights) - # Start with 2 x the normal D matrix - dsplit = 2 .* polynomial_derivative_matrix(nodes) - - # Modify to account for - dsplit[1, 1] += 1 / weights[1] - dsplit[end, end] -= 1 / weights[end] - - return dsplit -end - -# Calculate the polynomial derivative matrix D. -# This implements algorithm 37 "PolynomialDerivativeMatrix" from Kopriva's book. -function polynomial_derivative_matrix(nodes) - n_nodes = length(nodes) - d = zeros(n_nodes, n_nodes) - wbary = barycentric_weights(nodes) - - for i in 1:n_nodes, j in 1:n_nodes - if j != i - d[i, j] = wbary[j] / wbary[i] * 1 / (nodes[i] - nodes[j]) - d[i, i] -= d[i, j] - end + function LobattoLegendreBasis(RealT, polydeg::Integer) + nnodes_ = polydeg + 1 + + # compute everything using `Float64` by default + nodes_, weights_ = gauss_lobatto_nodes_weights(nnodes_) + inverse_weights_ = inv.(weights_) + + _, inverse_vandermonde_legendre_ = vandermonde_legendre(nodes_) + + boundary_interpolation_ = zeros(nnodes_, 2) + boundary_interpolation_[:, 1] = calc_lhat(-1.0, nodes_, weights_) + boundary_interpolation_[:, 2] = calc_lhat(1.0, nodes_, weights_) + + derivative_matrix_ = polynomial_derivative_matrix(nodes_) + derivative_split_ = calc_dsplit(nodes_, weights_) + derivative_split_transpose_ = Matrix(derivative_split_') + derivative_dhat_ = calc_dhat(nodes_, weights_) + + # type conversions to get the requested real type and enable possible + # optimizations of runtime performance and latency + nodes = SVector{nnodes_, RealT}(nodes_) + weights = SVector{nnodes_, RealT}(weights_) + inverse_weights = SVector{nnodes_, RealT}(inverse_weights_) + + inverse_vandermonde_legendre = convert.(RealT, inverse_vandermonde_legendre_) + boundary_interpolation = convert.(RealT, boundary_interpolation_) + + # Usually as fast as `SMatrix` (when using `let` in the volume integral/`@threaded`) + derivative_matrix = Matrix{RealT}(derivative_matrix_) + derivative_split = Matrix{RealT}(derivative_split_) + derivative_split_transpose = Matrix{RealT}(derivative_split_transpose_) + derivative_dhat = Matrix{RealT}(derivative_dhat_) + + return LobattoLegendreBasis{ + RealT, nnodes_, typeof(nodes), + typeof(inverse_vandermonde_legendre), + typeof(boundary_interpolation), + typeof(derivative_matrix), + }( + nodes, weights, + inverse_weights, + inverse_vandermonde_legendre, + boundary_interpolation, + derivative_matrix, + derivative_split, + derivative_split_transpose, + derivative_dhat + ) end - return d -end - -# Calculate and interpolation matrix (Vandermonde matrix) between two given sets of nodes -# See algorithm 32 "PolynomialInterpolationMatrix" from Kopriva's book. -function polynomial_interpolation_matrix(nodes_in, nodes_out, - baryweights_in = barycentric_weights(nodes_in)) - n_nodes_in = length(nodes_in) - n_nodes_out = length(nodes_out) - vandermonde = Matrix{promote_type(eltype(nodes_in), eltype(nodes_out))}(undef, - n_nodes_out, - n_nodes_in) - polynomial_interpolation_matrix!(vandermonde, nodes_in, nodes_out, baryweights_in) - - return vandermonde -end - -# This implements algorithm 32 "PolynomialInterpolationMatrix" from Kopriva's book. -function polynomial_interpolation_matrix!(vandermonde, - nodes_in, nodes_out, - baryweights_in) - fill!(vandermonde, zero(eltype(vandermonde))) - - for k in eachindex(nodes_out) - match = false - for j in eachindex(nodes_in) - if isapprox(nodes_out[k], nodes_in[j]) - match = true - vandermonde[k, j] = 1 - end + LobattoLegendreBasis(polydeg::Integer) = LobattoLegendreBasis(Float64, polydeg) + + function Base.show(io::IO, basis::LobattoLegendreBasis) + @nospecialize basis # reduce precompilation time + + print(io, "LobattoLegendreBasis{", real(basis), "}(polydeg=", polydeg(basis), ")") + end + function Base.show(io::IO, ::MIME"text/plain", basis::LobattoLegendreBasis) + @nospecialize basis # reduce precompilation time + + print( + io, "LobattoLegendreBasis{", real(basis), "} with polynomials of degree ", + polydeg(basis) + ) + end + + function Base.:(==)(b1::LobattoLegendreBasis, b2::LobattoLegendreBasis) + if typeof(b1) != typeof(b2) + return false end - if match == false - s = zero(eltype(vandermonde)) - for j in eachindex(nodes_in) - t = baryweights_in[j] / (nodes_out[k] - nodes_in[j]) - vandermonde[k, j] = t - s += t - end - for j in eachindex(nodes_in) - vandermonde[k, j] = vandermonde[k, j] / s + for field in fieldnames(typeof(b1)) + if getfield(b1, field) != getfield(b2, field) + return false end end + + return true + end + + @inline Base.real(basis::LobattoLegendreBasis{RealT}) where {RealT} = RealT + + @inline function nnodes(basis::LobattoLegendreBasis{RealT, NNODES}) where { + RealT, NNODES, + } + NNODES + end + + """ + eachnode(basis::LobattoLegendreBasis) + + Return an iterator over the indices that specify the location in relevant data structures + for the nodes in `basis`. + In particular, not the nodes themselves are returned. + """ + @inline eachnode(basis::LobattoLegendreBasis) = Base.OneTo(nnodes(basis)) + + @inline polydeg(basis::LobattoLegendreBasis) = nnodes(basis) - 1 + + @inline get_nodes(basis::LobattoLegendreBasis) = basis.nodes + + """ + integrate(f, u, basis::LobattoLegendreBasis) + + Map the function `f` to the coefficients `u` and integrate with respect to the + quadrature rule given by `basis`. + """ + function integrate(f, u, basis::LobattoLegendreBasis) + @unpack weights = basis + + res = zero(f(first(u))) + for i in eachindex(u, weights) + res += f(u[i]) * weights[i] + end + return res end - return vandermonde -end + # Return the first/last weight of the quadrature associated with `basis`. + # Since the mass matrix for nodal Lobatto-Legendre bases is diagonal, + # these weights are the only coefficients necessary for the scaling of + # surface terms/integrals in DGSEM. + left_boundary_weight(basis::LobattoLegendreBasis) = first(basis.weights) + right_boundary_weight(basis::LobattoLegendreBasis) = last(basis.weights) + + struct LobattoLegendreMortarL2{ + RealT <: Real, NNODES, + ForwardMatrix <: AbstractMatrix{RealT}, + ReverseMatrix <: AbstractMatrix{RealT}, + } <: + AbstractMortarL2{RealT} + forward_upper::ForwardMatrix + forward_lower::ForwardMatrix + reverse_upper::ReverseMatrix + reverse_lower::ReverseMatrix + end -""" - barycentric_weights(nodes) + function MortarL2(basis::LobattoLegendreBasis) + RealT = real(basis) + nnodes_ = nnodes(basis) + + # compute everything using `Float64` by default + forward_upper_ = calc_forward_upper(nnodes_) + forward_lower_ = calc_forward_lower(nnodes_) + reverse_upper_ = calc_reverse_upper(nnodes_, Val(:gauss)) + reverse_lower_ = calc_reverse_lower(nnodes_, Val(:gauss)) + + # type conversions to get the requested real type and enable possible + # optimizations of runtime performance and latency + + # Usually as fast as `SMatrix` but better for latency + forward_upper = Matrix{RealT}(forward_upper_) + forward_lower = Matrix{RealT}(forward_lower_) + + # TODO: Taal performance + # Check the performance of different implementations of `mortar_fluxes_to_elements!` + # with different types of the reverse matrices and different types of + # `fstar_upper_threaded` etc. used in the cache. + # Check whether `@turbo` with `eachnode` in `multiply_dimensionwise!` can be faster than + # `@tullio` when the matrix sizes are not necessarily static. + # reverse_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_upper_) + # reverse_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_lower_) + reverse_upper = Matrix{RealT}(reverse_upper_) + reverse_lower = Matrix{RealT}(reverse_lower_) + + LobattoLegendreMortarL2{ + RealT, nnodes_, typeof(forward_upper), + typeof(reverse_upper), + }( + forward_upper, forward_lower, + reverse_upper, reverse_lower + ) + end -Calculate the barycentric weights for a given node distribution, i.e., -```math -w_j = \\frac{1}{ \\prod_{k \\neq j} \\left( x_j - x_k \\right ) } -``` + function Base.show(io::IO, mortar::LobattoLegendreMortarL2) + @nospecialize mortar # reduce precompilation time -For details, see (especially Section 3) -- Jean-Paul Berrut and Lloyd N. Trefethen (2004). - Barycentric Lagrange Interpolation. - [DOI:10.1137/S0036144502417715](https://doi.org/10.1137/S0036144502417715) -""" -function barycentric_weights(nodes) - n_nodes = length(nodes) - weights = ones(n_nodes) + print( + io, "LobattoLegendreMortarL2{", real(mortar), "}(polydeg=", polydeg(mortar), + ")" + ) + end + function Base.show(io::IO, ::MIME"text/plain", mortar::LobattoLegendreMortarL2) + @nospecialize mortar # reduce precompilation time - for j in 2:n_nodes, k in 1:(j - 1) - weights[k] *= nodes[k] - nodes[j] - weights[j] *= nodes[j] - nodes[k] + print( + io, "LobattoLegendreMortarL2{", real(mortar), "} with polynomials of degree ", + polydeg(mortar) + ) end - for j in 1:n_nodes - weights[j] = 1 / weights[j] + @inline Base.real(mortar::LobattoLegendreMortarL2{RealT}) where {RealT} = RealT + + @inline function nnodes(mortar::LobattoLegendreMortarL2{RealT, NNODES}) where { + RealT, + NNODES, + } + NNODES end - return weights -end + @inline polydeg(mortar::LobattoLegendreMortarL2) = nnodes(mortar) - 1 + + # TODO: We can create EC mortars along the lines of the following implementation. + # abstract type AbstractMortarEC{RealT} <: AbstractMortar{RealT} end + + # struct LobattoLegendreMortarEC{RealT<:Real, NNODES, MortarMatrix<:AbstractMatrix{RealT}, SurfaceFlux} <: AbstractMortarEC{RealT} + # forward_upper::MortarMatrix + # forward_lower::MortarMatrix + # reverse_upper::MortarMatrix + # reverse_lower::MortarMatrix + # surface_flux::SurfaceFlux + # end + + # function MortarEC(basis::LobattoLegendreBasis{RealT}, surface_flux) + # forward_upper = calc_forward_upper(n_nodes) + # forward_lower = calc_forward_lower(n_nodes) + # l2reverse_upper = calc_reverse_upper(n_nodes, Val(:gauss_lobatto)) + # l2reverse_lower = calc_reverse_lower(n_nodes, Val(:gauss_lobatto)) + + # # type conversions to make use of StaticArrays etc. + # nnodes_ = nnodes(basis) + # forward_upper = SMatrix{nnodes_, nnodes_}(forward_upper) + # forward_lower = SMatrix{nnodes_, nnodes_}(forward_lower) + # l2reverse_upper = SMatrix{nnodes_, nnodes_}(l2reverse_upper) + # l2reverse_lower = SMatrix{nnodes_, nnodes_}(l2reverse_lower) + + # LobattoLegendreMortarEC{RealT, nnodes_, typeof(forward_upper), typeof(surface_flux)}( + # forward_upper, forward_lower, + # l2reverse_upper, l2reverse_lower, + # surface_flux + # ) + # end + + # @inline nnodes(mortar::LobattoLegendreMortarEC{RealT, NNODES}) = NNODES + + struct LobattoLegendreAnalyzer{ + RealT <: Real, NNODES, + VectorT <: AbstractVector{RealT}, + Vandermonde <: AbstractMatrix{RealT}, + } <: + SolutionAnalyzer{RealT} + nodes::VectorT + weights::VectorT + vandermonde::Vandermonde + end -# Calculate Lhat. -function calc_lhat(x, nodes, weights) - n_nodes = length(nodes) - wbary = barycentric_weights(nodes) + function SolutionAnalyzer( + basis::LobattoLegendreBasis; + analysis_polydeg = 2 * polydeg(basis) + ) + RealT = real(basis) + nnodes_ = analysis_polydeg + 1 + + # compute everything using `Float64` by default + nodes_, weights_ = gauss_lobatto_nodes_weights(nnodes_) + vandermonde_ = polynomial_interpolation_matrix(get_nodes(basis), nodes_) + + # type conversions to get the requested real type and enable possible + # optimizations of runtime performance and latency + nodes = SVector{nnodes_, RealT}(nodes_) + weights = SVector{nnodes_, RealT}(weights_) + + vandermonde = Matrix{RealT}(vandermonde_) + + return LobattoLegendreAnalyzer{RealT, nnodes_, typeof(nodes), typeof(vandermonde)}( + nodes, + weights, + vandermonde + ) + end - lhat = lagrange_interpolating_polynomials(x, nodes, wbary) + function Base.show(io::IO, analyzer::LobattoLegendreAnalyzer) + @nospecialize analyzer # reduce precompilation time - for i in 1:n_nodes - lhat[i] /= weights[i] + print( + io, "LobattoLegendreAnalyzer{", real(analyzer), "}(polydeg=", + polydeg(analyzer), ")" + ) end + function Base.show(io::IO, ::MIME"text/plain", analyzer::LobattoLegendreAnalyzer) + @nospecialize analyzer # reduce precompilation time - return lhat -end + print( + io, "LobattoLegendreAnalyzer{", real(analyzer), + "} with polynomials of degree ", polydeg(analyzer) + ) + end -""" - lagrange_interpolating_polynomials(x, nodes, wbary) + @inline Base.real(analyzer::LobattoLegendreAnalyzer{RealT}) where {RealT} = RealT -Calculate Lagrange polynomials for a given node distribution with -associated barycentric weights `wbary` at a given point `x` on the -reference interval ``[-1, 1]``. + @inline function nnodes(analyzer::LobattoLegendreAnalyzer{RealT, NNODES}) where { + RealT, + NNODES, + } + NNODES + end + """ + eachnode(analyzer::LobattoLegendreAnalyzer) + + Return an iterator over the indices that specify the location in relevant data structures + for the nodes in `analyzer`. + In particular, not the nodes themselves are returned. + """ + @inline eachnode(analyzer::LobattoLegendreAnalyzer) = Base.OneTo(nnodes(analyzer)) + + @inline polydeg(analyzer::LobattoLegendreAnalyzer) = nnodes(analyzer) - 1 + + struct LobattoLegendreAdaptorL2{ + RealT <: Real, NNODES, + ForwardMatrix <: AbstractMatrix{RealT}, + ReverseMatrix <: AbstractMatrix{RealT}, + } <: + AdaptorL2{RealT} + forward_upper::ForwardMatrix + forward_lower::ForwardMatrix + reverse_upper::ReverseMatrix + reverse_lower::ReverseMatrix + end -This returns all ``l_j(x)``, i.e., the Lagrange polynomials for each node ``x_j``. -Thus, to obtain the interpolating polynomial ``p(x)`` at ``x``, one has to -multiply the Lagrange polynomials with the nodal values ``u_j`` and sum them up: -``p(x) = \\sum_{j=1}^{n} u_j l_j(x)``. + function AdaptorL2(basis::LobattoLegendreBasis{RealT}) where {RealT} + nnodes_ = nnodes(basis) + + # compute everything using `Float64` by default + forward_upper_ = calc_forward_upper(nnodes_) + forward_lower_ = calc_forward_lower(nnodes_) + reverse_upper_ = calc_reverse_upper(nnodes_, Val(:gauss)) + reverse_lower_ = calc_reverse_lower(nnodes_, Val(:gauss)) + + # type conversions to get the requested real type and enable possible + # optimizations of runtime performance and latency + + # TODO: Taal performance + # Check the performance of different implementations of + # `refine_elements!` (forward) and `coarsen_elements!` (reverse) + # with different types of the matrices. + # Check whether `@turbo` with `eachnode` in `multiply_dimensionwise!` + # can be faster than `@tullio` when the matrix sizes are not necessarily + # static. + forward_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(forward_upper_) + forward_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(forward_lower_) + # forward_upper = Matrix{RealT}(forward_upper_) + # forward_lower = Matrix{RealT}(forward_lower_) + + reverse_upper = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_upper_) + reverse_lower = SMatrix{nnodes_, nnodes_, RealT, nnodes_^2}(reverse_lower_) + # reverse_upper = Matrix{RealT}(reverse_upper_) + # reverse_lower = Matrix{RealT}(reverse_lower_) + + LobattoLegendreAdaptorL2{ + RealT, nnodes_, typeof(forward_upper), + typeof(reverse_upper), + }( + forward_upper, forward_lower, + reverse_upper, reverse_lower + ) + end -For details, see e.g. Section 2 of -- Jean-Paul Berrut and Lloyd N. Trefethen (2004). - Barycentric Lagrange Interpolation. - [DOI:10.1137/S0036144502417715](https://doi.org/10.1137/S0036144502417715) -""" -function lagrange_interpolating_polynomials(x, nodes, wbary) - n_nodes = length(nodes) - polynomials = zeros(n_nodes) + function Base.show(io::IO, adaptor::LobattoLegendreAdaptorL2) + @nospecialize adaptor # reduce precompilation time - for i in 1:n_nodes - # Avoid division by zero when `x` is close to node by using - # the Kronecker-delta property at nodes - # of the Lagrange interpolation polynomials. - if isapprox(x, nodes[i], rtol = eps(x)) - polynomials[i] = 1 - return polynomials - end + print( + io, "LobattoLegendreAdaptorL2{", real(adaptor), "}(polydeg=", + polydeg(adaptor), ")" + ) + end + function Base.show(io::IO, ::MIME"text/plain", adaptor::LobattoLegendreAdaptorL2) + @nospecialize adaptor # reduce precompilation time + + print( + io, "LobattoLegendreAdaptorL2{", real(adaptor), + "} with polynomials of degree ", polydeg(adaptor) + ) end - for i in 1:n_nodes - polynomials[i] = wbary[i] / (x - nodes[i]) + @inline Base.real(adaptor::LobattoLegendreAdaptorL2{RealT}) where {RealT} = RealT + + @inline function nnodes(adaptor::LobattoLegendreAdaptorL2{RealT, NNODES}) where { + RealT, + NNODES, + } + NNODES end - total = sum(polynomials) - for i in 1:n_nodes - polynomials[i] /= total + @inline polydeg(adaptor::LobattoLegendreAdaptorL2) = nnodes(adaptor) - 1 + + ############################################################################### + # Polynomial derivative and interpolation functions + + # TODO: Taal refactor, allow other RealT below and adapt constructors above accordingly + + # Calculate the Dhat matrix + function calc_dhat(nodes, weights) + n_nodes = length(nodes) + dhat = Matrix(polynomial_derivative_matrix(nodes)') + + for n in 1:n_nodes, j in 1:n_nodes + dhat[j, n] *= -weights[n] / weights[j] + end + + return dhat end - return polynomials -end + # Calculate the Dsplit matrix for split-form differentiation: dplit = 2D - M⁻¹B + function calc_dsplit(nodes, weights) + # Start with 2 x the normal D matrix + dsplit = 2 .* polynomial_derivative_matrix(nodes) -""" - gauss_lobatto_nodes_weights(n_nodes::Integer) + # Modify to account for + dsplit[1, 1] += 1 / weights[1] + dsplit[end, end] -= 1 / weights[end] -Computes nodes ``x_j`` and weights ``w_j`` for the (Legendre-)Gauss-Lobatto quadrature. -This implements algorithm 25 "GaussLobattoNodesAndWeights" from the book + return dsplit + end -- David A. Kopriva, (2009). - Implementing spectral methods for partial differential equations: - Algorithms for scientists and engineers. - [DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) -""" -# From FLUXO (but really from blue book by Kopriva) -function gauss_lobatto_nodes_weights(n_nodes::Integer) - # From Kopriva's book - n_iterations = 10 - tolerance = 1e-15 + # Calculate the polynomial derivative matrix D. + # This implements algorithm 37 "PolynomialDerivativeMatrix" from Kopriva's book. + function polynomial_derivative_matrix(nodes) + n_nodes = length(nodes) + d = zeros(n_nodes, n_nodes) + wbary = barycentric_weights(nodes) + + for i in 1:n_nodes, j in 1:n_nodes + if j != i + d[i, j] = wbary[j] / wbary[i] * 1 / (nodes[i] - nodes[j]) + d[i, i] -= d[i, j] + end + end - # Initialize output - nodes = zeros(n_nodes) - weights = zeros(n_nodes) + return d + end - # Special case for polynomial degree zero (first order finite volume) - if n_nodes == 1 - nodes[1] = 0 - weights[1] = 2 - return nodes, weights + # Calculate and interpolation matrix (Vandermonde matrix) between two given sets of nodes + # See algorithm 32 "PolynomialInterpolationMatrix" from Kopriva's book. + function polynomial_interpolation_matrix( + nodes_in, nodes_out, + baryweights_in = barycentric_weights(nodes_in) + ) + n_nodes_in = length(nodes_in) + n_nodes_out = length(nodes_out) + vandermonde = Matrix{promote_type(eltype(nodes_in), eltype(nodes_out))}( + undef, + n_nodes_out, + n_nodes_in + ) + polynomial_interpolation_matrix!(vandermonde, nodes_in, nodes_out, baryweights_in) + + return vandermonde end - # Get polynomial degree for convenience - N = n_nodes - 1 - - # Calculate values at boundary - nodes[1] = -1.0 - nodes[end] = 1.0 - weights[1] = 2 / (N * (N + 1)) - weights[end] = weights[1] - - # Calculate interior values - if N > 1 - cont1 = pi / N - cont2 = 3 / (8 * N * pi) - - # Use symmetry -> only left side is computed - for i in 1:(div(N + 1, 2) - 1) - # Calculate node - # Initial guess for Newton method - nodes[i + 1] = -cos(cont1 * (i + 0.25) - cont2 / (i + 0.25)) - - # Newton iteration to find root of Legendre polynomial (= integration node) - for k in 0:n_iterations - q, qder, _ = calc_q_and_l(N, nodes[i + 1]) - dx = -q / qder - nodes[i + 1] += dx - if abs(dx) < tolerance * abs(nodes[i + 1]) - break + # This implements algorithm 32 "PolynomialInterpolationMatrix" from Kopriva's book. + function polynomial_interpolation_matrix!( + vandermonde, + nodes_in, nodes_out, + baryweights_in + ) + fill!(vandermonde, zero(eltype(vandermonde))) + + for k in eachindex(nodes_out) + match = false + for j in eachindex(nodes_in) + if isapprox(nodes_out[k], nodes_in[j]) + match = true + vandermonde[k, j] = 1 end end - # Calculate weight - _, _, L = calc_q_and_l(N, nodes[i + 1]) - weights[i + 1] = weights[1] / L^2 + if match == false + s = zero(eltype(vandermonde)) + for j in eachindex(nodes_in) + t = baryweights_in[j] / (nodes_out[k] - nodes_in[j]) + vandermonde[k, j] = t + s += t + end + for j in eachindex(nodes_in) + vandermonde[k, j] = vandermonde[k, j] / s + end + end + end - # Set nodes and weights according to symmetry properties - nodes[N + 1 - i] = -nodes[i + 1] - weights[N + 1 - i] = weights[i + 1] + return vandermonde + end + + """ + barycentric_weights(nodes) + + Calculate the barycentric weights for a given node distribution, i.e., + ```math + w_j = \\frac{1}{ \\prod_{k \\neq j} \\left( x_j - x_k \\right ) } + ``` + + For details, see (especially Section 3) + - Jean-Paul Berrut and Lloyd N. Trefethen (2004). + Barycentric Lagrange Interpolation. + [DOI:10.1137/S0036144502417715](https://doi.org/10.1137/S0036144502417715) + """ + function barycentric_weights(nodes) + n_nodes = length(nodes) + weights = ones(n_nodes) + + for j in 2:n_nodes, k in 1:(j - 1) + weights[k] *= nodes[k] - nodes[j] + weights[j] *= nodes[j] - nodes[k] + end + + for j in 1:n_nodes + weights[j] = 1 / weights[j] end + + return weights end - # If odd number of nodes, set center node to origin (= 0.0) and calculate weight - if n_nodes % 2 == 1 - _, _, L = calc_q_and_l(N, 0) - nodes[div(N, 2) + 1] = 0.0 - weights[div(N, 2) + 1] = weights[1] / L^2 - end - - return nodes, weights -end - -# From FLUXO (but really from blue book by Kopriva, algorithm 24) -function calc_q_and_l(N::Integer, x::Float64) - L_Nm2 = 1.0 - L_Nm1 = x - Lder_Nm2 = 0.0 - Lder_Nm1 = 1.0 - - local L - for i in 2:N - L = ((2 * i - 1) * x * L_Nm1 - (i - 1) * L_Nm2) / i - Lder = Lder_Nm2 + (2 * i - 1) * L_Nm1 - L_Nm2 = L_Nm1 - L_Nm1 = L - Lder_Nm2 = Lder_Nm1 - Lder_Nm1 = Lder - end - - q = (2 * N + 1) / (N + 1) * (x * L - L_Nm2) - qder = (2 * N + 1) * L - - return q, qder, L -end -calc_q_and_l(N::Integer, x::Real) = calc_q_and_l(N, convert(Float64, x)) - -""" - gauss_nodes_weights(n_nodes::Integer) - -Computes nodes ``x_j`` and weights ``w_j`` for the Gauss-Legendre quadrature. -This implements algorithm 23 "LegendreGaussNodesAndWeights" from the book - -- David A. Kopriva, (2009). - Implementing spectral methods for partial differential equations: - Algorithms for scientists and engineers. - [DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) -""" -function gauss_nodes_weights(n_nodes::Integer) - # From Kopriva's book - n_iterations = 10 - tolerance = 1e-15 - - # Initialize output - nodes = ones(n_nodes) * 1000 - weights = zeros(n_nodes) - - # Get polynomial degree for convenience - N = n_nodes - 1 - if N == 0 - nodes .= 0.0 - weights .= 2.0 - return nodes, weights - elseif N == 1 - nodes[1] = -sqrt(1 / 3) - nodes[end] = -nodes[1] - weights .= 1.0 - return nodes, weights - else # N > 1 - # Use symmetry property of the roots of the Legendre polynomials - for i in 0:(div(N + 1, 2) - 1) - # Starting guess for Newton method - nodes[i + 1] = -cos(pi / (2 * N + 2) * (2 * i + 1)) - - # Newton iteration to find root of Legendre polynomial (= integration node) - for k in 0:n_iterations - poly, deriv = legendre_polynomial_and_derivative(N + 1, nodes[i + 1]) - dx = -poly / deriv - nodes[i + 1] += dx - if abs(dx) < tolerance * abs(nodes[i + 1]) - break - end + # Calculate Lhat. + function calc_lhat(x, nodes, weights) + n_nodes = length(nodes) + wbary = barycentric_weights(nodes) + + lhat = lagrange_interpolating_polynomials(x, nodes, wbary) + + for i in 1:n_nodes + lhat[i] /= weights[i] + end + + return lhat + end + + """ + lagrange_interpolating_polynomials(x, nodes, wbary) + + Calculate Lagrange polynomials for a given node distribution with + associated barycentric weights `wbary` at a given point `x` on the + reference interval ``[-1, 1]``. + + This returns all ``l_j(x)``, i.e., the Lagrange polynomials for each node ``x_j``. + Thus, to obtain the interpolating polynomial ``p(x)`` at ``x``, one has to + multiply the Lagrange polynomials with the nodal values ``u_j`` and sum them up: + ``p(x) = \\sum_{j=1}^{n} u_j l_j(x)``. + + For details, see e.g. Section 2 of + - Jean-Paul Berrut and Lloyd N. Trefethen (2004). + Barycentric Lagrange Interpolation. + [DOI:10.1137/S0036144502417715](https://doi.org/10.1137/S0036144502417715) + """ + function lagrange_interpolating_polynomials(x, nodes, wbary) + n_nodes = length(nodes) + polynomials = zeros(n_nodes) + + for i in 1:n_nodes + # Avoid division by zero when `x` is close to node by using + # the Kronecker-delta property at nodes + # of the Lagrange interpolation polynomials. + if isapprox(x, nodes[i], rtol = eps(x)) + polynomials[i] = 1 + return polynomials end + end + + for i in 1:n_nodes + polynomials[i] = wbary[i] / (x - nodes[i]) + end + total = sum(polynomials) + + for i in 1:n_nodes + polynomials[i] /= total + end - # Calculate weight - poly, deriv = legendre_polynomial_and_derivative(N + 1, nodes[i + 1]) - weights[i + 1] = (2 * N + 3) / ((1 - nodes[i + 1]^2) * deriv^2) + return polynomials + end + + """ + gauss_lobatto_nodes_weights(n_nodes::Integer) + + Computes nodes ``x_j`` and weights ``w_j`` for the (Legendre-)Gauss-Lobatto quadrature. + This implements algorithm 25 "GaussLobattoNodesAndWeights" from the book + + - David A. Kopriva, (2009). + Implementing spectral methods for partial differential equations: + Algorithms for scientists and engineers. + [DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) + """ + # From FLUXO (but really from blue book by Kopriva) + function gauss_lobatto_nodes_weights(n_nodes::Integer) + # From Kopriva's book + n_iterations = 10 + tolerance = 1.0e-15 + + # Initialize output + nodes = zeros(n_nodes) + weights = zeros(n_nodes) + + # Special case for polynomial degree zero (first order finite volume) + if n_nodes == 1 + nodes[1] = 0 + weights[1] = 2 + return nodes, weights + end - # Set nodes and weights according to symmetry properties - nodes[N + 1 - i] = -nodes[i + 1] - weights[N + 1 - i] = weights[i + 1] + # Get polynomial degree for convenience + N = n_nodes - 1 + + # Calculate values at boundary + nodes[1] = -1.0 + nodes[end] = 1.0 + weights[1] = 2 / (N * (N + 1)) + weights[end] = weights[1] + + # Calculate interior values + if N > 1 + cont1 = pi / N + cont2 = 3 / (8 * N * pi) + + # Use symmetry -> only left side is computed + for i in 1:(div(N + 1, 2) - 1) + # Calculate node + # Initial guess for Newton method + nodes[i + 1] = -cos(cont1 * (i + 0.25) - cont2 / (i + 0.25)) + + # Newton iteration to find root of Legendre polynomial (= integration node) + for k in 0:n_iterations + q, qder, _ = calc_q_and_l(N, nodes[i + 1]) + dx = -q / qder + nodes[i + 1] += dx + if abs(dx) < tolerance * abs(nodes[i + 1]) + break + end + end + + # Calculate weight + _, _, L = calc_q_and_l(N, nodes[i + 1]) + weights[i + 1] = weights[1] / L^2 + + # Set nodes and weights according to symmetry properties + nodes[N + 1 - i] = -nodes[i + 1] + weights[N + 1 - i] = weights[i + 1] + end end # If odd number of nodes, set center node to origin (= 0.0) and calculate weight if n_nodes % 2 == 1 - poly, deriv = legendre_polynomial_and_derivative(N + 1, 0.0) + _, _, L = calc_q_and_l(N, 0) nodes[div(N, 2) + 1] = 0.0 - weights[div(N, 2) + 1] = (2 * N + 3) / deriv^2 + weights[div(N, 2) + 1] = weights[1] / L^2 end return nodes, weights end -end - -""" - legendre_polynomial_and_derivative(N::Int, x::Real) - -Computes the Legendre polynomial of degree `N` and its derivative at `x`. -This implements algorithm 22 "LegendrePolynomialAndDerivative" from the book - -- David A. Kopriva, (2009). - Implementing spectral methods for partial differential equations: - Algorithms for scientists and engineers. - [DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) -""" -function legendre_polynomial_and_derivative(N::Int, x::Real) - if N == 0 - poly = 1.0 - deriv = 0.0 - elseif N == 1 - poly = convert(Float64, x) - deriv = 1.0 - else - poly_Nm2 = 1.0 - poly_Nm1 = convert(Float64, x) - deriv_Nm2 = 0.0 - deriv_Nm1 = 1.0 - - poly = 0.0 - deriv = 0.0 + + # From FLUXO (but really from blue book by Kopriva, algorithm 24) + function calc_q_and_l(N::Integer, x::Float64) + L_Nm2 = 1.0 + L_Nm1 = x + Lder_Nm2 = 0.0 + Lder_Nm1 = 1.0 + + local L for i in 2:N - poly = ((2 * i - 1) * x * poly_Nm1 - (i - 1) * poly_Nm2) / i - deriv = deriv_Nm2 + (2 * i - 1) * poly_Nm1 - poly_Nm2 = poly_Nm1 - poly_Nm1 = poly - deriv_Nm2 = deriv_Nm1 - deriv_Nm1 = deriv + L = ((2 * i - 1) * x * L_Nm1 - (i - 1) * L_Nm2) / i + Lder = Lder_Nm2 + (2 * i - 1) * L_Nm1 + L_Nm2 = L_Nm1 + L_Nm1 = L + Lder_Nm2 = Lder_Nm1 + Lder_Nm1 = Lder + end + + q = (2 * N + 1) / (N + 1) * (x * L - L_Nm2) + qder = (2 * N + 1) * L + + return q, qder, L + end + calc_q_and_l(N::Integer, x::Real) = calc_q_and_l(N, convert(Float64, x)) + + """ + gauss_nodes_weights(n_nodes::Integer) + + Computes nodes ``x_j`` and weights ``w_j`` for the Gauss-Legendre quadrature. + This implements algorithm 23 "LegendreGaussNodesAndWeights" from the book + + - David A. Kopriva, (2009). + Implementing spectral methods for partial differential equations: + Algorithms for scientists and engineers. + [DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) + """ + function gauss_nodes_weights(n_nodes::Integer) + # From Kopriva's book + n_iterations = 10 + tolerance = 1.0e-15 + + # Initialize output + nodes = ones(n_nodes) * 1000 + weights = zeros(n_nodes) + + # Get polynomial degree for convenience + N = n_nodes - 1 + if N == 0 + nodes .= 0.0 + weights .= 2.0 + return nodes, weights + elseif N == 1 + nodes[1] = -sqrt(1 / 3) + nodes[end] = -nodes[1] + weights .= 1.0 + return nodes, weights + else # N > 1 + # Use symmetry property of the roots of the Legendre polynomials + for i in 0:(div(N + 1, 2) - 1) + # Starting guess for Newton method + nodes[i + 1] = -cos(pi / (2 * N + 2) * (2 * i + 1)) + + # Newton iteration to find root of Legendre polynomial (= integration node) + for k in 0:n_iterations + poly, deriv = legendre_polynomial_and_derivative(N + 1, nodes[i + 1]) + dx = -poly / deriv + nodes[i + 1] += dx + if abs(dx) < tolerance * abs(nodes[i + 1]) + break + end + end + + # Calculate weight + poly, deriv = legendre_polynomial_and_derivative(N + 1, nodes[i + 1]) + weights[i + 1] = (2 * N + 3) / ((1 - nodes[i + 1]^2) * deriv^2) + + # Set nodes and weights according to symmetry properties + nodes[N + 1 - i] = -nodes[i + 1] + weights[N + 1 - i] = weights[i + 1] + end + + # If odd number of nodes, set center node to origin (= 0.0) and calculate weight + if n_nodes % 2 == 1 + poly, deriv = legendre_polynomial_and_derivative(N + 1, 0.0) + nodes[div(N, 2) + 1] = 0.0 + weights[div(N, 2) + 1] = (2 * N + 3) / deriv^2 + end + + return nodes, weights end end - # Normalize - poly = poly * sqrt(N + 0.5) - deriv = deriv * sqrt(N + 0.5) + """ + legendre_polynomial_and_derivative(N::Int, x::Real) + + Computes the Legendre polynomial of degree `N` and its derivative at `x`. + This implements algorithm 22 "LegendrePolynomialAndDerivative" from the book + + - David A. Kopriva, (2009). + Implementing spectral methods for partial differential equations: + Algorithms for scientists and engineers. + [DOI:10.1007/978-90-481-2261-5](https://doi.org/10.1007/978-90-481-2261-5) + """ + function legendre_polynomial_and_derivative(N::Int, x::Real) + if N == 0 + poly = 1.0 + deriv = 0.0 + elseif N == 1 + poly = convert(Float64, x) + deriv = 1.0 + else + poly_Nm2 = 1.0 + poly_Nm1 = convert(Float64, x) + deriv_Nm2 = 0.0 + deriv_Nm1 = 1.0 + + poly = 0.0 + deriv = 0.0 + for i in 2:N + poly = ((2 * i - 1) * x * poly_Nm1 - (i - 1) * poly_Nm2) / i + deriv = deriv_Nm2 + (2 * i - 1) * poly_Nm1 + poly_Nm2 = poly_Nm1 + poly_Nm1 = poly + deriv_Nm2 = deriv_Nm1 + deriv_Nm1 = deriv + end + end + + # Normalize + poly = poly * sqrt(N + 0.5) + deriv = deriv * sqrt(N + 0.5) - return poly, deriv -end + return poly, deriv + end -# Calculate Legendre vandermonde matrix and its inverse -function vandermonde_legendre(nodes, N) - n_nodes = length(nodes) - n_modes = N + 1 - vandermonde = zeros(n_nodes, n_modes) + # Calculate Legendre vandermonde matrix and its inverse + function vandermonde_legendre(nodes, N) + n_nodes = length(nodes) + n_modes = N + 1 + vandermonde = zeros(n_nodes, n_modes) - for i in 1:n_nodes - for m in 1:n_modes - vandermonde[i, m], _ = legendre_polynomial_and_derivative(m - 1, nodes[i]) + for i in 1:n_nodes + for m in 1:n_modes + vandermonde[i, m], _ = legendre_polynomial_and_derivative(m - 1, nodes[i]) + end end + # for very high polynomial degree, this is not well conditioned + inverse_vandermonde = inv(vandermonde) + return vandermonde, inverse_vandermonde end - # for very high polynomial degree, this is not well conditioned - inverse_vandermonde = inv(vandermonde) - return vandermonde, inverse_vandermonde -end -vandermonde_legendre(nodes) = vandermonde_legendre(nodes, length(nodes) - 1) + vandermonde_legendre(nodes) = vandermonde_legendre(nodes, length(nodes) - 1) end # @muladd diff --git a/src/solvers/dgsem/dgsem.jl b/src/solvers/dgsem/dgsem.jl index 27caad4d2d..1014f6a03f 100644 --- a/src/solvers/dgsem/dgsem.jl +++ b/src/solvers/dgsem/dgsem.jl @@ -3,72 +3,86 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Include utilities -include("interpolation.jl") -include("l2projection.jl") -include("basis_lobatto_legendre.jl") + # Include utilities + include("interpolation.jl") + include("l2projection.jl") + include("basis_lobatto_legendre.jl") -""" - DGSEM(; RealT=Float64, polydeg::Integer, - surface_flux=flux_central, - surface_integral=SurfaceIntegralWeakForm(surface_flux), - volume_integral=VolumeIntegralWeakForm(), - mortar=MortarL2(basis)) + """ + DGSEM(; RealT=Float64, polydeg::Integer, + surface_flux=flux_central, + surface_integral=SurfaceIntegralWeakForm(surface_flux), + volume_integral=VolumeIntegralWeakForm(), + mortar=MortarL2(basis)) -Create a discontinuous Galerkin spectral element method (DGSEM) using a -[`LobattoLegendreBasis`](@ref) with polynomials of degree `polydeg`. -""" -const DGSEM = DG{Basis} where {Basis <: LobattoLegendreBasis} + Create a discontinuous Galerkin spectral element method (DGSEM) using a + [`LobattoLegendreBasis`](@ref) with polynomials of degree `polydeg`. + """ + const DGSEM = DG{Basis} where {Basis <: LobattoLegendreBasis} -# TODO: Deprecated in v0.3 (no longer documented) -function DGSEM(basis::LobattoLegendreBasis, - surface_flux = flux_central, - volume_integral = VolumeIntegralWeakForm(), - mortar = MortarL2(basis)) - surface_integral = SurfaceIntegralWeakForm(surface_flux) - return DG{typeof(basis), typeof(mortar), typeof(surface_integral), - typeof(volume_integral)}(basis, mortar, surface_integral, volume_integral) -end + # TODO: Deprecated in v0.3 (no longer documented) + function DGSEM( + basis::LobattoLegendreBasis, + surface_flux = flux_central, + volume_integral = VolumeIntegralWeakForm(), + mortar = MortarL2(basis) + ) + surface_integral = SurfaceIntegralWeakForm(surface_flux) + return DG{ + typeof(basis), typeof(mortar), typeof(surface_integral), + typeof(volume_integral), + }(basis, mortar, surface_integral, volume_integral) + end -# TODO: Deprecated in v0.3 (no longer documented) -function DGSEM(basis::LobattoLegendreBasis, - surface_integral::AbstractSurfaceIntegral, - volume_integral = VolumeIntegralWeakForm(), - mortar = MortarL2(basis)) - return DG{typeof(basis), typeof(mortar), typeof(surface_integral), - typeof(volume_integral)}(basis, mortar, surface_integral, volume_integral) -end + # TODO: Deprecated in v0.3 (no longer documented) + function DGSEM( + basis::LobattoLegendreBasis, + surface_integral::AbstractSurfaceIntegral, + volume_integral = VolumeIntegralWeakForm(), + mortar = MortarL2(basis) + ) + return DG{ + typeof(basis), typeof(mortar), typeof(surface_integral), + typeof(volume_integral), + }(basis, mortar, surface_integral, volume_integral) + end -# TODO: Deprecated in v0.3 (no longer documented) -function DGSEM(RealT, polydeg::Integer, - surface_flux = flux_central, - volume_integral = VolumeIntegralWeakForm(), - mortar = MortarL2(LobattoLegendreBasis(RealT, polydeg))) - basis = LobattoLegendreBasis(RealT, polydeg) + # TODO: Deprecated in v0.3 (no longer documented) + function DGSEM( + RealT, polydeg::Integer, + surface_flux = flux_central, + volume_integral = VolumeIntegralWeakForm(), + mortar = MortarL2(LobattoLegendreBasis(RealT, polydeg)) + ) + basis = LobattoLegendreBasis(RealT, polydeg) - return DGSEM(basis, surface_flux, volume_integral, mortar) -end + return DGSEM(basis, surface_flux, volume_integral, mortar) + end -function DGSEM(polydeg, surface_flux = flux_central, - volume_integral = VolumeIntegralWeakForm()) - DGSEM(Float64, polydeg, surface_flux, volume_integral) -end + function DGSEM( + polydeg, surface_flux = flux_central, + volume_integral = VolumeIntegralWeakForm() + ) + DGSEM(Float64, polydeg, surface_flux, volume_integral) + end -# The constructor using only keyword arguments is convenient for elixirs since -# it allows to modify the polynomial degree and other parameters via -# `trixi_include`. -function DGSEM(; RealT = Float64, - polydeg::Integer, - surface_flux = flux_central, - surface_integral = SurfaceIntegralWeakForm(surface_flux), - volume_integral = VolumeIntegralWeakForm()) - basis = LobattoLegendreBasis(RealT, polydeg) - return DGSEM(basis, surface_integral, volume_integral) -end + # The constructor using only keyword arguments is convenient for elixirs since + # it allows to modify the polynomial degree and other parameters via + # `trixi_include`. + function DGSEM(; + RealT = Float64, + polydeg::Integer, + surface_flux = flux_central, + surface_integral = SurfaceIntegralWeakForm(surface_flux), + volume_integral = VolumeIntegralWeakForm() + ) + basis = LobattoLegendreBasis(RealT, polydeg) + return DGSEM(basis, surface_integral, volume_integral) + end -@inline polydeg(dg::DGSEM) = polydeg(dg.basis) + @inline polydeg(dg::DGSEM) = polydeg(dg.basis) -Base.summary(io::IO, dg::DGSEM) = print(io, "DGSEM(polydeg=$(polydeg(dg)))") + Base.summary(io::IO, dg::DGSEM) = print(io, "DGSEM(polydeg=$(polydeg(dg)))") end # @muladd diff --git a/src/solvers/dgsem/interpolation.jl b/src/solvers/dgsem/interpolation.jl index 3f8f61c072..ca7551c5ac 100644 --- a/src/solvers/dgsem/interpolation.jl +++ b/src/solvers/dgsem/interpolation.jl @@ -1,9 +1,10 @@ - # Naive implementations of multiply_dimensionwise used to demonstrate the functionality # without performance optimizations and for testing correctness of the optimized versions # implemented below. -function multiply_dimensionwise_naive(matrix::AbstractMatrix, - data_in::AbstractArray{<:Any, 2}) +function multiply_dimensionwise_naive( + matrix::AbstractMatrix, + data_in::AbstractArray{<:Any, 2} + ) size_out = size(matrix, 1) size_in = size(matrix, 2) n_vars = size(data_in, 1) @@ -20,13 +21,17 @@ function multiply_dimensionwise_naive(matrix::AbstractMatrix, return data_out end -function multiply_dimensionwise_naive(matrix::AbstractMatrix, - data_in::AbstractArray{<:Any, 3}) +function multiply_dimensionwise_naive( + matrix::AbstractMatrix, + data_in::AbstractArray{<:Any, 3} + ) size_out = size(matrix, 1) size_in = size(matrix, 2) n_vars = size(data_in, 1) - data_out = zeros(promote_type(eltype(data_in), eltype(matrix)), n_vars, size_out, - size_out) + data_out = zeros( + promote_type(eltype(data_in), eltype(matrix)), n_vars, size_out, + size_out + ) for j in 1:size_out, i in 1:size_out for jj in 1:size_in, ii in 1:size_in @@ -39,19 +44,23 @@ function multiply_dimensionwise_naive(matrix::AbstractMatrix, return data_out end -function multiply_dimensionwise_naive(matrix::AbstractMatrix, - data_in::AbstractArray{<:Any, 4}) +function multiply_dimensionwise_naive( + matrix::AbstractMatrix, + data_in::AbstractArray{<:Any, 4} + ) size_out = size(matrix, 1) size_in = size(matrix, 2) n_vars = size(data_in, 1) - data_out = zeros(promote_type(eltype(data_in), eltype(matrix)), n_vars, size_out, - size_out, size_out) + data_out = zeros( + promote_type(eltype(data_in), eltype(matrix)), n_vars, size_out, + size_out, size_out + ) for k in 1:size_out, j in 1:size_out, i in 1:size_out for kk in 1:size_in, jj in 1:size_in, ii in 1:size_in for v in 1:n_vars data_out[v, i, j, k] += matrix[i, ii] * matrix[j, jj] * matrix[k, kk] * - data_in[v, ii, jj, kk] + data_in[v, ii, jj, kk] end end end @@ -83,8 +92,10 @@ function multiply_dimensionwise(matrix::AbstractMatrix, data_in::AbstractArray{< # optimized version of multiply_dimensionwise_naive size_out = size(matrix, 1) n_vars = size(data_in, 1) - data_out = zeros(promote_type(eltype(data_in), eltype(matrix)), n_vars, size_out, - size_out) + data_out = zeros( + promote_type(eltype(data_in), eltype(matrix)), n_vars, size_out, + size_out + ) multiply_dimensionwise!(data_out, matrix, data_in) @@ -96,8 +107,10 @@ function multiply_dimensionwise(matrix::AbstractMatrix, data_in::AbstractArray{< # optimized version of multiply_dimensionwise_naive size_out = size(matrix, 1) n_vars = size(data_in, 1) - data_out = zeros(promote_type(eltype(data_in), eltype(matrix)), n_vars, size_out, - size_out, size_out) + data_out = zeros( + promote_type(eltype(data_in), eltype(matrix)), n_vars, size_out, + size_out, size_out + ) multiply_dimensionwise!(data_out, matrix, data_in) @@ -112,8 +125,10 @@ end # cost of increased latency, at least on some systems... # 1D version -function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 2}, matrix::AbstractMatrix, - data_in::AbstractArray{<:Any, 2}) +function multiply_dimensionwise!( + data_out::AbstractArray{<:Any, 2}, matrix::AbstractMatrix, + data_in::AbstractArray{<:Any, 2} + ) # @tullio threads=false data_out[v, i] = matrix[i, ii] * data_in[v, ii] @turbo for i in axes(data_out, 2), v in axes(data_out, 1) res = zero(eltype(data_out)) @@ -129,9 +144,11 @@ end # 1D version for scalars # Instead of having a leading dimension of size 1 in `data_out, data_in`, this leading dimension # of size unity is dropped, resulting in one dimension less than in `multiply_dimensionwise!`. -function multiply_scalar_dimensionwise!(data_out::AbstractArray{<:Any, 1}, - matrix::AbstractMatrix, - data_in::AbstractArray{<:Any, 1}) +function multiply_scalar_dimensionwise!( + data_out::AbstractArray{<:Any, 1}, + matrix::AbstractMatrix, + data_in::AbstractArray{<:Any, 1} + ) # @tullio threads=false data_out[i] = matrix[i, ii] * data_in[ii] @turbo for i in axes(data_out, 1) res = zero(eltype(data_out)) @@ -145,9 +162,11 @@ function multiply_scalar_dimensionwise!(data_out::AbstractArray{<:Any, 1}, end # 1D version, apply matrixJ to data_inJ -function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 2}, matrix1::AbstractMatrix, - data_in1::AbstractArray{<:Any, 2}, matrix2::AbstractMatrix, - data_in2::AbstractArray{<:Any, 2}) +function multiply_dimensionwise!( + data_out::AbstractArray{<:Any, 2}, matrix1::AbstractMatrix, + data_in1::AbstractArray{<:Any, 2}, matrix2::AbstractMatrix, + data_in2::AbstractArray{<:Any, 2} + ) # @tullio threads=false data_out[v, i] = matrix1[i, ii] * data_in1[v, ii] + matrix2[i, ii] * data_in2[v, ii] # TODO: LoopVectorization upgrade # We would like to use `@turbo` for the outermost loop possibly fuse both inner @@ -173,10 +192,14 @@ function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 2}, matrix1::Abs end # 2D version -function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 3}, matrix::AbstractMatrix, - data_in::AbstractArray{<:Any, 3}, - tmp1 = zeros(eltype(data_out), size(data_out, 1), - size(matrix, 1), size(matrix, 2))) +function multiply_dimensionwise!( + data_out::AbstractArray{<:Any, 3}, matrix::AbstractMatrix, + data_in::AbstractArray{<:Any, 3}, + tmp1 = zeros( + eltype(data_out), size(data_out, 1), + size(matrix, 1), size(matrix, 2) + ) + ) # Interpolate in x-direction # @tullio threads=false tmp1[v, i, j] = matrix[i, ii] * data_in[v, ii, j] @@ -204,11 +227,15 @@ end # 2D version for scalars # Instead of having a leading dimension of size 1 in `data_out, data_in`, this leading dimension # of size unity is dropped, resulting in one dimension less than in `multiply_dimensionwise!`. -function multiply_scalar_dimensionwise!(data_out::AbstractArray{<:Any, 2}, - matrix::AbstractMatrix, - data_in::AbstractArray{<:Any, 2}, - tmp1 = zeros(eltype(data_out), size(matrix, 1), - size(matrix, 2))) +function multiply_scalar_dimensionwise!( + data_out::AbstractArray{<:Any, 2}, + matrix::AbstractMatrix, + data_in::AbstractArray{<:Any, 2}, + tmp1 = zeros( + eltype(data_out), size(matrix, 1), + size(matrix, 2) + ) + ) # Interpolate in x-direction # @tullio threads=false tmp1[i, j] = matrix[i, ii] * data_in[ii, j] @@ -234,11 +261,15 @@ function multiply_scalar_dimensionwise!(data_out::AbstractArray{<:Any, 2}, end # 2D version, apply matrixJ to dimension J of data_in -function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 3}, - matrix1::AbstractMatrix, matrix2::AbstractMatrix, - data_in::AbstractArray{<:Any, 3}, - tmp1 = zeros(eltype(data_out), size(data_out, 1), - size(matrix1, 1), size(matrix1, 2))) +function multiply_dimensionwise!( + data_out::AbstractArray{<:Any, 3}, + matrix1::AbstractMatrix, matrix2::AbstractMatrix, + data_in::AbstractArray{<:Any, 3}, + tmp1 = zeros( + eltype(data_out), size(data_out, 1), + size(matrix1, 1), size(matrix1, 2) + ) + ) # Interpolate in x-direction # @tullio threads=false tmp1[v, i, j] = matrix1[i, ii] * data_in[v, ii, j] @@ -264,11 +295,15 @@ function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 3}, end # 2D version, apply matrixJ to dimension J of data_in and add the result to data_out -function add_multiply_dimensionwise!(data_out::AbstractArray{<:Any, 3}, - matrix1::AbstractMatrix, matrix2::AbstractMatrix, - data_in::AbstractArray{<:Any, 3}, - tmp1 = zeros(eltype(data_out), size(data_out, 1), - size(matrix1, 1), size(matrix1, 2))) +function add_multiply_dimensionwise!( + data_out::AbstractArray{<:Any, 3}, + matrix1::AbstractMatrix, matrix2::AbstractMatrix, + data_in::AbstractArray{<:Any, 3}, + tmp1 = zeros( + eltype(data_out), size(data_out, 1), + size(matrix1, 1), size(matrix1, 2) + ) + ) # Interpolate in x-direction # @tullio threads=false tmp1[v, i, j] = matrix1[i, ii] * data_in[v, ii, j] @@ -294,19 +329,25 @@ function add_multiply_dimensionwise!(data_out::AbstractArray{<:Any, 3}, end # 3D version -function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, matrix::AbstractMatrix, - data_in::AbstractArray{<:Any, 4}, - tmp1 = zeros(eltype(data_out), size(data_out, 1), - size(matrix, 1), size(matrix, 2), - size(matrix, 2)), - tmp2 = zeros(eltype(data_out), size(data_out, 1), - size(matrix, 1), size(matrix, 1), - size(matrix, 2))) +function multiply_dimensionwise!( + data_out::AbstractArray{<:Any, 4}, matrix::AbstractMatrix, + data_in::AbstractArray{<:Any, 4}, + tmp1 = zeros( + eltype(data_out), size(data_out, 1), + size(matrix, 1), size(matrix, 2), + size(matrix, 2) + ), + tmp2 = zeros( + eltype(data_out), size(data_out, 1), + size(matrix, 1), size(matrix, 1), + size(matrix, 2) + ) + ) # Interpolate in x-direction # @tullio threads=false tmp1[v, i, j, k] = matrix[i, ii] * data_in[v, ii, j, k] @turbo for k in axes(tmp1, 4), j in axes(tmp1, 3), i in axes(tmp1, 2), - v in axes(tmp1, 1) + v in axes(tmp1, 1) res = zero(eltype(tmp1)) for ii in axes(matrix, 2) @@ -318,7 +359,7 @@ function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, matrix::Abst # Interpolate in y-direction # @tullio threads=false tmp2[v, i, j, k] = matrix[j, jj] * tmp1[v, i, jj, k] @turbo for k in axes(tmp2, 4), j in axes(tmp2, 3), i in axes(tmp2, 2), - v in axes(tmp2, 1) + v in axes(tmp2, 1) res = zero(eltype(tmp2)) for jj in axes(matrix, 2) @@ -330,7 +371,7 @@ function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, matrix::Abst # Interpolate in z-direction # @tullio threads=false data_out[v, i, j, k] = matrix[k, kk] * tmp2[v, i, j, kk] @turbo for k in axes(data_out, 4), j in axes(data_out, 3), i in axes(data_out, 2), - v in axes(data_out, 1) + v in axes(data_out, 1) res = zero(eltype(data_out)) for kk in axes(matrix, 2) @@ -345,13 +386,19 @@ end # 3D version for scalars # Instead of having a leading dimension of size 1 in `data_out, data_in`, this leading dimension # of size unity is dropped, resulting in one dimension less than in `multiply_dimensionwise!`. -function multiply_scalar_dimensionwise!(data_out::AbstractArray{<:Any, 3}, - matrix::AbstractMatrix, - data_in::AbstractArray{<:Any, 3}, - tmp1 = zeros(eltype(data_out), size(matrix, 1), - size(matrix, 2), size(matrix, 2)), - tmp2 = zeros(eltype(data_out), size(matrix, 1), - size(matrix, 1), size(matrix, 2))) +function multiply_scalar_dimensionwise!( + data_out::AbstractArray{<:Any, 3}, + matrix::AbstractMatrix, + data_in::AbstractArray{<:Any, 3}, + tmp1 = zeros( + eltype(data_out), size(matrix, 1), + size(matrix, 2), size(matrix, 2) + ), + tmp2 = zeros( + eltype(data_out), size(matrix, 1), + size(matrix, 1), size(matrix, 2) + ) + ) # Interpolate in x-direction # @tullio threads=false tmp1[i, j, k] = matrix[i, ii] * data_in[ii, j, k] @@ -387,21 +434,27 @@ function multiply_scalar_dimensionwise!(data_out::AbstractArray{<:Any, 3}, end # 3D version, apply matrixJ to dimension J of data_in -function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, - matrix1::AbstractMatrix, matrix2::AbstractMatrix, - matrix3::AbstractMatrix, - data_in::AbstractArray{<:Any, 4}, - tmp1 = zeros(eltype(data_out), size(data_out, 1), - size(matrix1, 1), size(matrix1, 2), - size(matrix1, 2)), - tmp2 = zeros(eltype(data_out), size(data_out, 1), - size(matrix1, 1), size(matrix1, 1), - size(matrix1, 2))) +function multiply_dimensionwise!( + data_out::AbstractArray{<:Any, 4}, + matrix1::AbstractMatrix, matrix2::AbstractMatrix, + matrix3::AbstractMatrix, + data_in::AbstractArray{<:Any, 4}, + tmp1 = zeros( + eltype(data_out), size(data_out, 1), + size(matrix1, 1), size(matrix1, 2), + size(matrix1, 2) + ), + tmp2 = zeros( + eltype(data_out), size(data_out, 1), + size(matrix1, 1), size(matrix1, 1), + size(matrix1, 2) + ) + ) # Interpolate in x-direction # @tullio threads=false tmp1[v, i, j, k] = matrix1[i, ii] * data_in[v, ii, j, k] @turbo for k in axes(tmp1, 4), j in axes(tmp1, 3), i in axes(tmp1, 2), - v in axes(tmp1, 1) + v in axes(tmp1, 1) res = zero(eltype(tmp1)) for ii in axes(matrix1, 2) @@ -413,7 +466,7 @@ function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, # Interpolate in y-direction # @tullio threads=false tmp2[v, i, j, k] = matrix2[j, jj] * tmp1[v, i, jj, k] @turbo for k in axes(tmp2, 4), j in axes(tmp2, 3), i in axes(tmp2, 2), - v in axes(tmp2, 1) + v in axes(tmp2, 1) res = zero(eltype(tmp1)) for jj in axes(matrix2, 2) @@ -425,7 +478,7 @@ function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, # Interpolate in z-direction # @tullio threads=false data_out[v, i, j, k] = matrix3[k, kk] * tmp2[v, i, j, kk] @turbo for k in axes(data_out, 4), j in axes(data_out, 3), i in axes(data_out, 2), - v in axes(data_out, 1) + v in axes(data_out, 1) res = zero(eltype(data_out)) for kk in axes(matrix3, 2) @@ -438,21 +491,27 @@ function multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, end # 3D version, apply matrixJ to dimension J of data_in and add the result to data_out -function add_multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, - matrix1::AbstractMatrix, matrix2::AbstractMatrix, - matrix3::AbstractMatrix, - data_in::AbstractArray{<:Any, 4}, - tmp1 = zeros(eltype(data_out), size(data_out, 1), - size(matrix1, 1), size(matrix1, 2), - size(matrix1, 2)), - tmp2 = zeros(eltype(data_out), size(data_out, 1), - size(matrix1, 1), size(matrix1, 1), - size(matrix1, 2))) +function add_multiply_dimensionwise!( + data_out::AbstractArray{<:Any, 4}, + matrix1::AbstractMatrix, matrix2::AbstractMatrix, + matrix3::AbstractMatrix, + data_in::AbstractArray{<:Any, 4}, + tmp1 = zeros( + eltype(data_out), size(data_out, 1), + size(matrix1, 1), size(matrix1, 2), + size(matrix1, 2) + ), + tmp2 = zeros( + eltype(data_out), size(data_out, 1), + size(matrix1, 1), size(matrix1, 1), + size(matrix1, 2) + ) + ) # Interpolate in x-direction # @tullio threads=false tmp1[v, i, j, k] = matrix1[i, ii] * data_in[v, ii, j, k] @turbo for k in axes(tmp1, 4), j in axes(tmp1, 3), i in axes(tmp1, 2), - v in axes(tmp1, 1) + v in axes(tmp1, 1) res = zero(eltype(tmp1)) for ii in axes(matrix1, 2) @@ -464,7 +523,7 @@ function add_multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, # Interpolate in y-direction # @tullio threads=false tmp2[v, i, j, k] = matrix2[j, jj] * tmp1[v, i, jj, k] @turbo for k in axes(tmp2, 4), j in axes(tmp2, 3), i in axes(tmp2, 2), - v in axes(tmp2, 1) + v in axes(tmp2, 1) res = zero(eltype(tmp1)) for jj in axes(matrix2, 2) @@ -476,7 +535,7 @@ function add_multiply_dimensionwise!(data_out::AbstractArray{<:Any, 4}, # Interpolate in z-direction # @tullio threads=false data_out[v, i, j, k] += matrix3[k, kk] * tmp2[v, i, j, kk] @turbo for k in axes(data_out, 4), j in axes(data_out, 3), i in axes(data_out, 2), - v in axes(data_out, 1) + v in axes(data_out, 1) res = zero(eltype(data_out)) for kk in axes(matrix3, 2) diff --git a/src/solvers/dgsem/l2projection.jl b/src/solvers/dgsem/l2projection.jl index 0bb46f5ca1..5fa927c2b7 100644 --- a/src/solvers/dgsem/l2projection.jl +++ b/src/solvers/dgsem/l2projection.jl @@ -3,152 +3,156 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This diagram shows what is meant by "lower", "upper", and "large": -# +1 +1 -# | | -# upper | | -# | | -# -1 | -# | large -# +1 | -# | | -# lower | | -# | | -# -1 -1 -# -# That is, we are only concerned with 2:1 subdivision of a surface/element. - -# Calculate forward projection matrix for discrete L2 projection from large to upper -# -# Note: This is actually an interpolation. -function calc_forward_upper(n_nodes) - # Calculate nodes, weights, and barycentric weights - nodes, weights = gauss_lobatto_nodes_weights(n_nodes) - wbary = barycentric_weights(nodes) - - # Calculate projection matrix (actually: interpolation) - operator = zeros(n_nodes, n_nodes) - for j in 1:n_nodes - poly = lagrange_interpolating_polynomials(1 / 2 * (nodes[j] + 1), nodes, wbary) - for i in 1:n_nodes - operator[j, i] = poly[i] + #! format: noindent + + # This diagram shows what is meant by "lower", "upper", and "large": + # +1 +1 + # | | + # upper | | + # | | + # -1 | + # | large + # +1 | + # | | + # lower | | + # | | + # -1 -1 + # + # That is, we are only concerned with 2:1 subdivision of a surface/element. + + # Calculate forward projection matrix for discrete L2 projection from large to upper + # + # Note: This is actually an interpolation. + function calc_forward_upper(n_nodes) + # Calculate nodes, weights, and barycentric weights + nodes, weights = gauss_lobatto_nodes_weights(n_nodes) + wbary = barycentric_weights(nodes) + + # Calculate projection matrix (actually: interpolation) + operator = zeros(n_nodes, n_nodes) + for j in 1:n_nodes + poly = lagrange_interpolating_polynomials(1 / 2 * (nodes[j] + 1), nodes, wbary) + for i in 1:n_nodes + operator[j, i] = poly[i] + end end + + return operator end - return operator -end - -# Calculate forward projection matrix for discrete L2 projection from large to lower -# -# Note: This is actually an interpolation. -function calc_forward_lower(n_nodes) - # Calculate nodes, weights, and barycentric weights - nodes, weights = gauss_lobatto_nodes_weights(n_nodes) - wbary = barycentric_weights(nodes) - - # Calculate projection matrix (actually: interpolation) - operator = zeros(n_nodes, n_nodes) - for j in 1:n_nodes - poly = lagrange_interpolating_polynomials(1 / 2 * (nodes[j] - 1), nodes, wbary) - for i in 1:n_nodes - operator[j, i] = poly[i] + # Calculate forward projection matrix for discrete L2 projection from large to lower + # + # Note: This is actually an interpolation. + function calc_forward_lower(n_nodes) + # Calculate nodes, weights, and barycentric weights + nodes, weights = gauss_lobatto_nodes_weights(n_nodes) + wbary = barycentric_weights(nodes) + + # Calculate projection matrix (actually: interpolation) + operator = zeros(n_nodes, n_nodes) + for j in 1:n_nodes + poly = lagrange_interpolating_polynomials(1 / 2 * (nodes[j] - 1), nodes, wbary) + for i in 1:n_nodes + operator[j, i] = poly[i] + end end + + return operator end - return operator -end - -# Calculate reverse projection matrix for discrete L2 projection from upper to large (Gauss version) -# -# Note: To not make the L2 projection exact, first convert to Gauss nodes, -# perform projection, and convert back to Gauss-Lobatto. -function calc_reverse_upper(n_nodes, ::Val{:gauss}) - # Calculate nodes, weights, and barycentric weights for Legendre-Gauss - gauss_nodes, gauss_weights = gauss_nodes_weights(n_nodes) - gauss_wbary = barycentric_weights(gauss_nodes) - - # Calculate projection matrix (actually: discrete L2 projection with errors) - operator = zeros(n_nodes, n_nodes) - for j in 1:n_nodes - poly = lagrange_interpolating_polynomials(1 / 2 * (gauss_nodes[j] + 1), - gauss_nodes, gauss_wbary) - for i in 1:n_nodes - operator[i, j] = 1 / 2 * poly[i] * gauss_weights[j] / gauss_weights[i] + # Calculate reverse projection matrix for discrete L2 projection from upper to large (Gauss version) + # + # Note: To not make the L2 projection exact, first convert to Gauss nodes, + # perform projection, and convert back to Gauss-Lobatto. + function calc_reverse_upper(n_nodes, ::Val{:gauss}) + # Calculate nodes, weights, and barycentric weights for Legendre-Gauss + gauss_nodes, gauss_weights = gauss_nodes_weights(n_nodes) + gauss_wbary = barycentric_weights(gauss_nodes) + + # Calculate projection matrix (actually: discrete L2 projection with errors) + operator = zeros(n_nodes, n_nodes) + for j in 1:n_nodes + poly = lagrange_interpolating_polynomials( + 1 / 2 * (gauss_nodes[j] + 1), + gauss_nodes, gauss_wbary + ) + for i in 1:n_nodes + operator[i, j] = 1 / 2 * poly[i] * gauss_weights[j] / gauss_weights[i] + end end + + # Calculate Vandermondes + lobatto_nodes, lobatto_weights = gauss_lobatto_nodes_weights(n_nodes) + gauss2lobatto = polynomial_interpolation_matrix(gauss_nodes, lobatto_nodes) + lobatto2gauss = polynomial_interpolation_matrix(lobatto_nodes, gauss_nodes) + + return gauss2lobatto * operator * lobatto2gauss end - # Calculate Vandermondes - lobatto_nodes, lobatto_weights = gauss_lobatto_nodes_weights(n_nodes) - gauss2lobatto = polynomial_interpolation_matrix(gauss_nodes, lobatto_nodes) - lobatto2gauss = polynomial_interpolation_matrix(lobatto_nodes, gauss_nodes) - - return gauss2lobatto * operator * lobatto2gauss -end - -# Calculate reverse projection matrix for discrete L2 projection from lower to large (Gauss version) -# -# Note: To not make the L2 projection exact, first convert to Gauss nodes, -# perform projection, and convert back to Gauss-Lobatto. -function calc_reverse_lower(n_nodes, ::Val{:gauss}) - # Calculate nodes, weights, and barycentric weights for Legendre-Gauss - gauss_nodes, gauss_weights = gauss_nodes_weights(n_nodes) - gauss_wbary = barycentric_weights(gauss_nodes) - - # Calculate projection matrix (actually: discrete L2 projection with errors) - operator = zeros(n_nodes, n_nodes) - for j in 1:n_nodes - poly = lagrange_interpolating_polynomials(1 / 2 * (gauss_nodes[j] - 1), - gauss_nodes, gauss_wbary) - for i in 1:n_nodes - operator[i, j] = 1 / 2 * poly[i] * gauss_weights[j] / gauss_weights[i] + # Calculate reverse projection matrix for discrete L2 projection from lower to large (Gauss version) + # + # Note: To not make the L2 projection exact, first convert to Gauss nodes, + # perform projection, and convert back to Gauss-Lobatto. + function calc_reverse_lower(n_nodes, ::Val{:gauss}) + # Calculate nodes, weights, and barycentric weights for Legendre-Gauss + gauss_nodes, gauss_weights = gauss_nodes_weights(n_nodes) + gauss_wbary = barycentric_weights(gauss_nodes) + + # Calculate projection matrix (actually: discrete L2 projection with errors) + operator = zeros(n_nodes, n_nodes) + for j in 1:n_nodes + poly = lagrange_interpolating_polynomials( + 1 / 2 * (gauss_nodes[j] - 1), + gauss_nodes, gauss_wbary + ) + for i in 1:n_nodes + operator[i, j] = 1 / 2 * poly[i] * gauss_weights[j] / gauss_weights[i] + end end + + # Calculate Vandermondes + lobatto_nodes, lobatto_weights = gauss_lobatto_nodes_weights(n_nodes) + gauss2lobatto = polynomial_interpolation_matrix(gauss_nodes, lobatto_nodes) + lobatto2gauss = polynomial_interpolation_matrix(lobatto_nodes, gauss_nodes) + + return gauss2lobatto * operator * lobatto2gauss end - # Calculate Vandermondes - lobatto_nodes, lobatto_weights = gauss_lobatto_nodes_weights(n_nodes) - gauss2lobatto = polynomial_interpolation_matrix(gauss_nodes, lobatto_nodes) - lobatto2gauss = polynomial_interpolation_matrix(lobatto_nodes, gauss_nodes) - - return gauss2lobatto * operator * lobatto2gauss -end - -# Calculate reverse projection matrix for discrete L2 projection from upper to large (Gauss-Lobatto -# version) -function calc_reverse_upper(n_nodes, ::Val{:gauss_lobatto}) - # Calculate nodes, weights, and barycentric weights - nodes, weights = gauss_lobatto_nodes_weights(n_nodes) - wbary = barycentric_weights(nodes) - - # Calculate projection matrix (actually: discrete L2 projection with errors) - operator = zeros(n_nodes, n_nodes) - for j in 1:n_nodes - poly = lagrange_interpolating_polynomials(1 / 2 * (nodes[j] + 1), nodes, wbary) - for i in 1:n_nodes - operator[i, j] = 1 / 2 * poly[i] * weights[j] / weights[i] + # Calculate reverse projection matrix for discrete L2 projection from upper to large (Gauss-Lobatto + # version) + function calc_reverse_upper(n_nodes, ::Val{:gauss_lobatto}) + # Calculate nodes, weights, and barycentric weights + nodes, weights = gauss_lobatto_nodes_weights(n_nodes) + wbary = barycentric_weights(nodes) + + # Calculate projection matrix (actually: discrete L2 projection with errors) + operator = zeros(n_nodes, n_nodes) + for j in 1:n_nodes + poly = lagrange_interpolating_polynomials(1 / 2 * (nodes[j] + 1), nodes, wbary) + for i in 1:n_nodes + operator[i, j] = 1 / 2 * poly[i] * weights[j] / weights[i] + end end + + return operator end - return operator -end - -# Calculate reverse projection matrix for discrete L2 projection from lower to large (Gauss-Lobatto -# version) -function calc_reverse_lower(n_nodes, ::Val{:gauss_lobatto}) - # Calculate nodes, weights, and barycentric weights - nodes, weights = gauss_lobatto_nodes_weights(n_nodes) - wbary = barycentric_weights(nodes) - - # Calculate projection matrix (actually: discrete L2 projection with errors) - operator = zeros(n_nodes, n_nodes) - for j in 1:n_nodes - poly = lagrange_interpolating_polynomials(1 / 2 * (nodes[j] - 1), nodes, wbary) - for i in 1:n_nodes - operator[i, j] = 1 / 2 * poly[i] * weights[j] / weights[i] + # Calculate reverse projection matrix for discrete L2 projection from lower to large (Gauss-Lobatto + # version) + function calc_reverse_lower(n_nodes, ::Val{:gauss_lobatto}) + # Calculate nodes, weights, and barycentric weights + nodes, weights = gauss_lobatto_nodes_weights(n_nodes) + wbary = barycentric_weights(nodes) + + # Calculate projection matrix (actually: discrete L2 projection with errors) + operator = zeros(n_nodes, n_nodes) + for j in 1:n_nodes + poly = lagrange_interpolating_polynomials(1 / 2 * (nodes[j] - 1), nodes, wbary) + for i in 1:n_nodes + operator[i, j] = 1 / 2 * poly[i] * weights[j] / weights[i] + end end - end - return operator -end + return operator + end end # @muladd diff --git a/src/solvers/dgsem_p4est/containers.jl b/src/solvers/dgsem_p4est/containers.jl index f9830d0011..4aaa9666dc 100644 --- a/src/solvers/dgsem_p4est/containers.jl +++ b/src/solvers/dgsem_p4est/containers.jl @@ -3,726 +3,836 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -mutable struct P4estElementContainer{NDIMS, RealT <: Real, uEltype <: Real, NDIMSP1, - NDIMSP2, NDIMSP3} <: AbstractContainer - # Physical coordinates at each node - node_coordinates::Array{RealT, NDIMSP2} # [orientation, node_i, node_j, node_k, element] - # Jacobian matrix of the transformation - # [jacobian_i, jacobian_j, node_i, node_j, node_k, element] where jacobian_i is the first index of the Jacobian matrix,... - jacobian_matrix::Array{RealT, NDIMSP3} - # Contravariant vectors, scaled by J, in Kopriva's blue book called Ja^i_n (i index, n dimension) - contravariant_vectors::Array{RealT, NDIMSP3} # [dimension, index, node_i, node_j, node_k, element] - # 1/J where J is the Jacobian determinant (determinant of Jacobian matrix) - inverse_jacobian::Array{RealT, NDIMSP1} # [node_i, node_j, node_k, element] - # Buffer for calculated surface flux - surface_flux_values::Array{uEltype, NDIMSP2} # [variable, i, j, direction, element] - - # internal `resize!`able storage - _node_coordinates::Vector{RealT} - _jacobian_matrix::Vector{RealT} - _contravariant_vectors::Vector{RealT} - _inverse_jacobian::Vector{RealT} - _surface_flux_values::Vector{uEltype} -end - -@inline function nelements(elements::P4estElementContainer) - size(elements.node_coordinates, ndims(elements) + 2) -end -@inline Base.ndims(::P4estElementContainer{NDIMS}) where {NDIMS} = NDIMS -@inline function Base.eltype(::P4estElementContainer{NDIMS, RealT, uEltype}) where { - NDIMS, - RealT, - uEltype - } - uEltype -end - -# Only one-dimensional `Array`s are `resize!`able in Julia. -# Hence, we use `Vector`s as internal storage and `resize!` -# them whenever needed. Then, we reuse the same memory by -# `unsafe_wrap`ping multi-dimensional `Array`s around the -# internal storage. -function Base.resize!(elements::P4estElementContainer, capacity) - @unpack _node_coordinates, _jacobian_matrix, _contravariant_vectors, - _inverse_jacobian, _surface_flux_values = elements - - n_dims = ndims(elements) - n_nodes = size(elements.node_coordinates, 2) - n_variables = size(elements.surface_flux_values, 1) - - resize!(_node_coordinates, n_dims * n_nodes^n_dims * capacity) - elements.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (n_dims, ntuple(_ -> n_nodes, n_dims)..., - capacity)) - - resize!(_jacobian_matrix, n_dims^2 * n_nodes^n_dims * capacity) - elements.jacobian_matrix = unsafe_wrap(Array, pointer(_jacobian_matrix), - (n_dims, n_dims, - ntuple(_ -> n_nodes, n_dims)..., capacity)) - - resize!(_contravariant_vectors, length(_jacobian_matrix)) - elements.contravariant_vectors = unsafe_wrap(Array, pointer(_contravariant_vectors), - size(elements.jacobian_matrix)) - - resize!(_inverse_jacobian, n_nodes^n_dims * capacity) - elements.inverse_jacobian = unsafe_wrap(Array, pointer(_inverse_jacobian), - (ntuple(_ -> n_nodes, n_dims)..., capacity)) - - resize!(_surface_flux_values, - n_variables * n_nodes^(n_dims - 1) * (n_dims * 2) * capacity) - elements.surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), - (n_variables, - ntuple(_ -> n_nodes, n_dims - 1)..., - n_dims * 2, capacity)) - - return nothing -end - -# Create element container and initialize element data -function init_elements(mesh::Union{P4estMesh{NDIMS, RealT}, T8codeMesh{NDIMS, RealT}}, - equations, - basis, - ::Type{uEltype}) where {NDIMS, RealT <: Real, uEltype <: Real} - nelements = ncells(mesh) - - _node_coordinates = Vector{RealT}(undef, NDIMS * nnodes(basis)^NDIMS * nelements) - node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (NDIMS, ntuple(_ -> nnodes(basis), NDIMS)..., - nelements)) - - _jacobian_matrix = Vector{RealT}(undef, NDIMS^2 * nnodes(basis)^NDIMS * nelements) - jacobian_matrix = unsafe_wrap(Array, pointer(_jacobian_matrix), - (NDIMS, NDIMS, ntuple(_ -> nnodes(basis), NDIMS)..., - nelements)) - - _contravariant_vectors = similar(_jacobian_matrix) - contravariant_vectors = unsafe_wrap(Array, pointer(_contravariant_vectors), - size(jacobian_matrix)) - - _inverse_jacobian = Vector{RealT}(undef, nnodes(basis)^NDIMS * nelements) - inverse_jacobian = unsafe_wrap(Array, pointer(_inverse_jacobian), - (ntuple(_ -> nnodes(basis), NDIMS)..., nelements)) - - _surface_flux_values = Vector{uEltype}(undef, - nvariables(equations) * - nnodes(basis)^(NDIMS - 1) * (NDIMS * 2) * - nelements) - surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), - (nvariables(equations), - ntuple(_ -> nnodes(basis), NDIMS - 1)..., - NDIMS * 2, nelements)) - - elements = P4estElementContainer{NDIMS, RealT, uEltype, NDIMS + 1, NDIMS + 2, - NDIMS + 3}(node_coordinates, jacobian_matrix, - contravariant_vectors, - inverse_jacobian, surface_flux_values, - _node_coordinates, _jacobian_matrix, - _contravariant_vectors, - _inverse_jacobian, _surface_flux_values) - - init_elements!(elements, mesh, basis) - return elements -end - -mutable struct P4estInterfaceContainer{NDIMS, uEltype <: Real, NDIMSP2} <: - AbstractContainer - u::Array{uEltype, NDIMSP2} # [primary/secondary, variable, i, j, interface] - neighbor_ids::Matrix{Int} # [primary/secondary, interface] - node_indices::Matrix{NTuple{NDIMS, Symbol}} # [primary/secondary, interface] - - # internal `resize!`able storage - _u::Vector{uEltype} - _neighbor_ids::Vector{Int} - _node_indices::Vector{NTuple{NDIMS, Symbol}} -end - -@inline function ninterfaces(interfaces::P4estInterfaceContainer) - size(interfaces.neighbor_ids, 2) -end -@inline Base.ndims(::P4estInterfaceContainer{NDIMS}) where {NDIMS} = NDIMS - -# See explanation of Base.resize! for the element container -function Base.resize!(interfaces::P4estInterfaceContainer, capacity) - @unpack _u, _neighbor_ids, _node_indices = interfaces - - n_dims = ndims(interfaces) - n_nodes = size(interfaces.u, 3) - n_variables = size(interfaces.u, 2) - - resize!(_u, 2 * n_variables * n_nodes^(n_dims - 1) * capacity) - interfaces.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, ntuple(_ -> n_nodes, n_dims - 1)..., - capacity)) - - resize!(_neighbor_ids, 2 * capacity) - interfaces.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), (2, capacity)) - - resize!(_node_indices, 2 * capacity) - interfaces.node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, capacity)) - - return nothing -end - -# Create interface container and initialize interface data. -function init_interfaces(mesh::Union{P4estMesh, T8codeMesh}, equations, basis, elements) - NDIMS = ndims(elements) - uEltype = eltype(elements) - - # Initialize container - n_interfaces = count_required_surfaces(mesh).interfaces - - _u = Vector{uEltype}(undef, - 2 * nvariables(equations) * nnodes(basis)^(NDIMS - 1) * - n_interfaces) - u = unsafe_wrap(Array, pointer(_u), - (2, nvariables(equations), ntuple(_ -> nnodes(basis), NDIMS - 1)..., - n_interfaces)) - - _neighbor_ids = Vector{Int}(undef, 2 * n_interfaces) - neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), (2, n_interfaces)) - - _node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, 2 * n_interfaces) - node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, n_interfaces)) - - interfaces = P4estInterfaceContainer{NDIMS, uEltype, NDIMS + 2}(u, neighbor_ids, - node_indices, - _u, _neighbor_ids, - _node_indices) - - init_interfaces!(interfaces, mesh) - - return interfaces -end - -function init_interfaces!(interfaces, mesh::P4estMesh) - init_surfaces!(interfaces, nothing, nothing, mesh) - - return interfaces -end - -mutable struct P4estBoundaryContainer{NDIMS, uEltype <: Real, NDIMSP1} <: - AbstractContainer - u::Array{uEltype, NDIMSP1} # [variables, i, j, boundary] - neighbor_ids::Vector{Int} # [boundary] - node_indices::Vector{NTuple{NDIMS, Symbol}} # [boundary] - name::Vector{Symbol} # [boundary] + #! format: noindent + + mutable struct P4estElementContainer{ + NDIMS, RealT <: Real, uEltype <: Real, NDIMSP1, + NDIMSP2, NDIMSP3, + } <: AbstractContainer + # Physical coordinates at each node + node_coordinates::Array{RealT, NDIMSP2} # [orientation, node_i, node_j, node_k, element] + # Jacobian matrix of the transformation + # [jacobian_i, jacobian_j, node_i, node_j, node_k, element] where jacobian_i is the first index of the Jacobian matrix,... + jacobian_matrix::Array{RealT, NDIMSP3} + # Contravariant vectors, scaled by J, in Kopriva's blue book called Ja^i_n (i index, n dimension) + contravariant_vectors::Array{RealT, NDIMSP3} # [dimension, index, node_i, node_j, node_k, element] + # 1/J where J is the Jacobian determinant (determinant of Jacobian matrix) + inverse_jacobian::Array{RealT, NDIMSP1} # [node_i, node_j, node_k, element] + # Buffer for calculated surface flux + surface_flux_values::Array{uEltype, NDIMSP2} # [variable, i, j, direction, element] + + # internal `resize!`able storage + _node_coordinates::Vector{RealT} + _jacobian_matrix::Vector{RealT} + _contravariant_vectors::Vector{RealT} + _inverse_jacobian::Vector{RealT} + _surface_flux_values::Vector{uEltype} + end - # internal `resize!`able storage - _u::Vector{uEltype} -end + @inline function nelements(elements::P4estElementContainer) + size(elements.node_coordinates, ndims(elements) + 2) + end + @inline Base.ndims(::P4estElementContainer{NDIMS}) where {NDIMS} = NDIMS + @inline function Base.eltype(::P4estElementContainer{NDIMS, RealT, uEltype}) where { + NDIMS, + RealT, + uEltype, + } + uEltype + end -@inline function nboundaries(boundaries::P4estBoundaryContainer) - length(boundaries.neighbor_ids) -end -@inline Base.ndims(::P4estBoundaryContainer{NDIMS}) where {NDIMS} = NDIMS - -# See explanation of Base.resize! for the element container -function Base.resize!(boundaries::P4estBoundaryContainer, capacity) - @unpack _u, neighbor_ids, node_indices, name = boundaries - - n_dims = ndims(boundaries) - n_nodes = size(boundaries.u, 2) - n_variables = size(boundaries.u, 1) - - resize!(_u, n_variables * n_nodes^(n_dims - 1) * capacity) - boundaries.u = unsafe_wrap(Array, pointer(_u), - (n_variables, ntuple(_ -> n_nodes, n_dims - 1)..., - capacity)) - - resize!(neighbor_ids, capacity) - - resize!(node_indices, capacity) - - resize!(name, capacity) - - return nothing -end - -# Create interface container and initialize interface data in `elements`. -function init_boundaries(mesh::Union{P4estMesh, T8codeMesh}, equations, basis, elements) - NDIMS = ndims(elements) - uEltype = eltype(elements) - - # Initialize container - n_boundaries = count_required_surfaces(mesh).boundaries - - _u = Vector{uEltype}(undef, - nvariables(equations) * nnodes(basis)^(NDIMS - 1) * - n_boundaries) - u = unsafe_wrap(Array, pointer(_u), - (nvariables(equations), ntuple(_ -> nnodes(basis), NDIMS - 1)..., - n_boundaries)) - - neighbor_ids = Vector{Int}(undef, n_boundaries) - node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, n_boundaries) - names = Vector{Symbol}(undef, n_boundaries) - - boundaries = P4estBoundaryContainer{NDIMS, uEltype, NDIMS + 1}(u, neighbor_ids, - node_indices, names, - _u) - - if n_boundaries > 0 - init_boundaries!(boundaries, mesh) - end - - return boundaries -end - -function init_boundaries!(boundaries, mesh::P4estMesh) - init_surfaces!(nothing, nothing, boundaries, mesh) - - return boundaries -end - -# Function barrier for type stability -function init_boundaries_iter_face_inner(info_pw, boundaries, boundary_id, mesh) - # Extract boundary data - side_pw = load_pointerwrapper_side(info_pw) - # Get local tree, one-based indexing - tree_pw = load_pointerwrapper_tree(mesh.p4est, side_pw.treeid[] + 1) - # Quadrant numbering offset of this quadrant - offset = tree_pw.quadrants_offset[] - - # Verify before accessing is.full, but this should never happen - @assert side_pw.is_hanging[] == false - - local_quad_id = side_pw.is.full.quadid[] - # Global ID of this quad - quad_id = offset + local_quad_id - - # Write data to boundaries container - # `p4est` uses zero-based indexing; convert to one-based indexing - boundaries.neighbor_ids[boundary_id] = quad_id + 1 - - # Face at which the boundary lies - face = side_pw.face[] - - # Save boundaries.node_indices dimension specific in containers_[23]d.jl - init_boundary_node_indices!(boundaries, face, boundary_id) - - # One-based indexing - boundaries.name[boundary_id] = mesh.boundary_names[face + 1, side_pw.treeid[] + 1] - - return nothing -end - -# Container data structure (structure-of-arrays style) for DG L2 mortars -# -# The positions used in `neighbor_ids` are 1:3 (in 2D) or 1:5 (in 3D), where 1:2 (in 2D) -# or 1:4 (in 3D) are the small elements numbered in z-order and 3 or 5 is the large element. -# The solution values on the mortar element are saved in `u`, where `position` is the number -# of the small element that corresponds to the respective part of the mortar element. -# The first dimension `small/large side` takes 1 for small side and 2 for large side. -# -# Illustration of the positions in `neighbor_ids` in 3D, where ξ and η are the local coordinates -# of the mortar element, which are precisely the local coordinates that span -# the surface of the smaller side. -# Note that the orientation in the physical space is completely irrelevant here. -# ┌─────────────┬─────────────┐ ┌───────────────────────────┐ -# │ │ │ │ │ -# │ small │ small │ │ │ -# │ 3 │ 4 │ │ │ -# │ │ │ │ large │ -# ├─────────────┼─────────────┤ │ 5 │ -# η │ │ │ │ │ -# │ small │ small │ │ │ -# ↑ │ 1 │ 2 │ │ │ -# │ │ │ │ │ │ -# │ └─────────────┴─────────────┘ └───────────────────────────┘ -# │ -# ⋅────> ξ -mutable struct P4estMortarContainer{NDIMS, uEltype <: Real, NDIMSP1, NDIMSP3} <: - AbstractContainer - u::Array{uEltype, NDIMSP3} # [small/large side, variable, position, i, j, mortar] - neighbor_ids::Matrix{Int} # [position, mortar] - node_indices::Matrix{NTuple{NDIMS, Symbol}} # [small/large, mortar] - - # internal `resize!`able storage - _u::Vector{uEltype} - _neighbor_ids::Vector{Int} - _node_indices::Vector{NTuple{NDIMS, Symbol}} -end - -@inline nmortars(mortars::P4estMortarContainer) = size(mortars.neighbor_ids, 2) -@inline Base.ndims(::P4estMortarContainer{NDIMS}) where {NDIMS} = NDIMS - -# See explanation of Base.resize! for the element container -function Base.resize!(mortars::P4estMortarContainer, capacity) - @unpack _u, _neighbor_ids, _node_indices = mortars - - n_dims = ndims(mortars) - n_nodes = size(mortars.u, 4) - n_variables = size(mortars.u, 2) - - resize!(_u, 2 * n_variables * 2^(n_dims - 1) * n_nodes^(n_dims - 1) * capacity) - mortars.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, 2^(n_dims - 1), - ntuple(_ -> n_nodes, n_dims - 1)..., capacity)) - - resize!(_neighbor_ids, (2^(n_dims - 1) + 1) * capacity) - mortars.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (2^(n_dims - 1) + 1, capacity)) - - resize!(_node_indices, 2 * capacity) - mortars.node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, capacity)) - - return nothing -end - -# Create mortar container and initialize mortar data. -function init_mortars(mesh::Union{P4estMesh, T8codeMesh}, equations, basis, elements) - NDIMS = ndims(elements) - uEltype = eltype(elements) - - # Initialize container - n_mortars = count_required_surfaces(mesh).mortars - - _u = Vector{uEltype}(undef, - 2 * nvariables(equations) * 2^(NDIMS - 1) * - nnodes(basis)^(NDIMS - 1) * n_mortars) - u = unsafe_wrap(Array, pointer(_u), - (2, nvariables(equations), 2^(NDIMS - 1), - ntuple(_ -> nnodes(basis), NDIMS - 1)..., n_mortars)) - - _neighbor_ids = Vector{Int}(undef, (2^(NDIMS - 1) + 1) * n_mortars) - neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (2^(NDIMS - 1) + 1, n_mortars)) - - _node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, 2 * n_mortars) - node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, n_mortars)) - - mortars = P4estMortarContainer{NDIMS, uEltype, NDIMS + 1, NDIMS + 3}(u, - neighbor_ids, - node_indices, - _u, - _neighbor_ids, - _node_indices) - - if n_mortars > 0 - init_mortars!(mortars, mesh) - end - - return mortars -end - -function init_mortars!(mortars, mesh::P4estMesh) - init_surfaces!(nothing, mortars, nothing, mesh) - - return mortars -end - -function reinitialize_containers!(mesh::P4estMesh, equations, dg::DGSEM, cache) - # Re-initialize elements container - @unpack elements = cache - resize!(elements, ncells(mesh)) - init_elements!(elements, mesh, dg.basis) - - required = count_required_surfaces(mesh) - - # resize interfaces container - @unpack interfaces = cache - resize!(interfaces, required.interfaces) - - # resize boundaries container - @unpack boundaries = cache - resize!(boundaries, required.boundaries) - - # re-initialize mortars container - if hasproperty(cache, :mortars) # cache_parabolic does not carry mortars - @unpack mortars = cache - resize!(mortars, required.mortars) - - # re-initialize containers together to reduce - # the number of iterations over the mesh in `p4est` - init_surfaces!(interfaces, mortars, boundaries, mesh) - else - init_surfaces!(interfaces, nothing, boundaries, mesh) - end -end - -# A helper struct used in initialization methods below -mutable struct InitSurfacesIterFaceUserData{Interfaces, Mortars, Boundaries, Mesh} - interfaces::Interfaces - interface_id::Int - mortars::Mortars - mortar_id::Int - boundaries::Boundaries - boundary_id::Int - mesh::Mesh -end - -function InitSurfacesIterFaceUserData(interfaces, mortars, boundaries, mesh) - return InitSurfacesIterFaceUserData{typeof(interfaces), typeof(mortars), - typeof(boundaries), typeof(mesh)}(interfaces, 1, - mortars, 1, - boundaries, 1, - mesh) -end - -function init_surfaces_iter_face(info, user_data) - # Unpack user_data - data = unsafe_pointer_to_objref(Ptr{InitSurfacesIterFaceUserData}(user_data)) - - # Function barrier because the unpacked user_data above is type-unstable - init_surfaces_iter_face_inner(info, data) -end - -# 2D -function cfunction(::typeof(init_surfaces_iter_face), ::Val{2}) - @cfunction(init_surfaces_iter_face, Cvoid, - (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(init_surfaces_iter_face), ::Val{3}) - @cfunction(init_surfaces_iter_face, Cvoid, - (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid})) -end - -# Function barrier for type stability -function init_surfaces_iter_face_inner(info, user_data) - @unpack interfaces, mortars, boundaries = user_data - info_pw = PointerWrapper(info) - elem_count = info_pw.sides.elem_count[] - - if elem_count == 2 - # Two neighboring elements => Interface or mortar - - # Extract surface data - sides_pw = (load_pointerwrapper_side(info_pw, 1), - load_pointerwrapper_side(info_pw, 2)) - - if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false - # No hanging nodes => normal interface - if interfaces !== nothing - init_interfaces_iter_face_inner(info_pw, sides_pw, user_data) - end - else - # Hanging nodes => mortar - if mortars !== nothing - init_mortars_iter_face_inner(info_pw, sides_pw, user_data) - end - end - elseif elem_count == 1 - # One neighboring elements => boundary - if boundaries !== nothing - init_boundaries_iter_face_inner(info_pw, user_data) + # Only one-dimensional `Array`s are `resize!`able in Julia. + # Hence, we use `Vector`s as internal storage and `resize!` + # them whenever needed. Then, we reuse the same memory by + # `unsafe_wrap`ping multi-dimensional `Array`s around the + # internal storage. + function Base.resize!(elements::P4estElementContainer, capacity) + @unpack _node_coordinates, _jacobian_matrix, _contravariant_vectors, + _inverse_jacobian, _surface_flux_values = elements + + n_dims = ndims(elements) + n_nodes = size(elements.node_coordinates, 2) + n_variables = size(elements.surface_flux_values, 1) + + resize!(_node_coordinates, n_dims * n_nodes^n_dims * capacity) + elements.node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + ( + n_dims, ntuple(_ -> n_nodes, n_dims)..., + capacity, + ) + ) + + resize!(_jacobian_matrix, n_dims^2 * n_nodes^n_dims * capacity) + elements.jacobian_matrix = unsafe_wrap( + Array, pointer(_jacobian_matrix), + ( + n_dims, n_dims, + ntuple(_ -> n_nodes, n_dims)..., capacity, + ) + ) + + resize!(_contravariant_vectors, length(_jacobian_matrix)) + elements.contravariant_vectors = unsafe_wrap( + Array, pointer(_contravariant_vectors), + size(elements.jacobian_matrix) + ) + + resize!(_inverse_jacobian, n_nodes^n_dims * capacity) + elements.inverse_jacobian = unsafe_wrap( + Array, pointer(_inverse_jacobian), + (ntuple(_ -> n_nodes, n_dims)..., capacity) + ) + + resize!( + _surface_flux_values, + n_variables * n_nodes^(n_dims - 1) * (n_dims * 2) * capacity + ) + elements.surface_flux_values = unsafe_wrap( + Array, pointer(_surface_flux_values), + ( + n_variables, + ntuple(_ -> n_nodes, n_dims - 1)..., + n_dims * 2, capacity, + ) + ) + + return nothing + end + + # Create element container and initialize element data + function init_elements( + mesh::Union{P4estMesh{NDIMS, RealT}, T8codeMesh{NDIMS, RealT}}, + equations, + basis, + ::Type{uEltype} + ) where {NDIMS, RealT <: Real, uEltype <: Real} + nelements = ncells(mesh) + + _node_coordinates = Vector{RealT}(undef, NDIMS * nnodes(basis)^NDIMS * nelements) + node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + ( + NDIMS, ntuple(_ -> nnodes(basis), NDIMS)..., + nelements, + ) + ) + + _jacobian_matrix = Vector{RealT}(undef, NDIMS^2 * nnodes(basis)^NDIMS * nelements) + jacobian_matrix = unsafe_wrap( + Array, pointer(_jacobian_matrix), + ( + NDIMS, NDIMS, ntuple(_ -> nnodes(basis), NDIMS)..., + nelements, + ) + ) + + _contravariant_vectors = similar(_jacobian_matrix) + contravariant_vectors = unsafe_wrap( + Array, pointer(_contravariant_vectors), + size(jacobian_matrix) + ) + + _inverse_jacobian = Vector{RealT}(undef, nnodes(basis)^NDIMS * nelements) + inverse_jacobian = unsafe_wrap( + Array, pointer(_inverse_jacobian), + (ntuple(_ -> nnodes(basis), NDIMS)..., nelements) + ) + + _surface_flux_values = Vector{uEltype}( + undef, + nvariables(equations) * + nnodes(basis)^(NDIMS - 1) * (NDIMS * 2) * + nelements + ) + surface_flux_values = unsafe_wrap( + Array, pointer(_surface_flux_values), + ( + nvariables(equations), + ntuple(_ -> nnodes(basis), NDIMS - 1)..., + NDIMS * 2, nelements, + ) + ) + + elements = P4estElementContainer{ + NDIMS, RealT, uEltype, NDIMS + 1, NDIMS + 2, + NDIMS + 3, + }( + node_coordinates, jacobian_matrix, + contravariant_vectors, + inverse_jacobian, surface_flux_values, + _node_coordinates, _jacobian_matrix, + _contravariant_vectors, + _inverse_jacobian, _surface_flux_values + ) + + init_elements!(elements, mesh, basis) + return elements + end + + mutable struct P4estInterfaceContainer{NDIMS, uEltype <: Real, NDIMSP2} <: + AbstractContainer + u::Array{uEltype, NDIMSP2} # [primary/secondary, variable, i, j, interface] + neighbor_ids::Matrix{Int} # [primary/secondary, interface] + node_indices::Matrix{NTuple{NDIMS, Symbol}} # [primary/secondary, interface] + + # internal `resize!`able storage + _u::Vector{uEltype} + _neighbor_ids::Vector{Int} + _node_indices::Vector{NTuple{NDIMS, Symbol}} + end + + @inline function ninterfaces(interfaces::P4estInterfaceContainer) + size(interfaces.neighbor_ids, 2) + end + @inline Base.ndims(::P4estInterfaceContainer{NDIMS}) where {NDIMS} = NDIMS + + # See explanation of Base.resize! for the element container + function Base.resize!(interfaces::P4estInterfaceContainer, capacity) + @unpack _u, _neighbor_ids, _node_indices = interfaces + + n_dims = ndims(interfaces) + n_nodes = size(interfaces.u, 3) + n_variables = size(interfaces.u, 2) + + resize!(_u, 2 * n_variables * n_nodes^(n_dims - 1) * capacity) + interfaces.u = unsafe_wrap( + Array, pointer(_u), + ( + 2, n_variables, ntuple(_ -> n_nodes, n_dims - 1)..., + capacity, + ) + ) + + resize!(_neighbor_ids, 2 * capacity) + interfaces.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), (2, capacity)) + + resize!(_node_indices, 2 * capacity) + interfaces.node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, capacity)) + + return nothing + end + + # Create interface container and initialize interface data. + function init_interfaces(mesh::Union{P4estMesh, T8codeMesh}, equations, basis, elements) + NDIMS = ndims(elements) + uEltype = eltype(elements) + + # Initialize container + n_interfaces = count_required_surfaces(mesh).interfaces + + _u = Vector{uEltype}( + undef, + 2 * nvariables(equations) * nnodes(basis)^(NDIMS - 1) * + n_interfaces + ) + u = unsafe_wrap( + Array, pointer(_u), + ( + 2, nvariables(equations), ntuple(_ -> nnodes(basis), NDIMS - 1)..., + n_interfaces, + ) + ) + + _neighbor_ids = Vector{Int}(undef, 2 * n_interfaces) + neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), (2, n_interfaces)) + + _node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, 2 * n_interfaces) + node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, n_interfaces)) + + interfaces = P4estInterfaceContainer{NDIMS, uEltype, NDIMS + 2}( + u, neighbor_ids, + node_indices, + _u, _neighbor_ids, + _node_indices + ) + + init_interfaces!(interfaces, mesh) + + return interfaces + end + + function init_interfaces!(interfaces, mesh::P4estMesh) + init_surfaces!(interfaces, nothing, nothing, mesh) + + return interfaces + end + + mutable struct P4estBoundaryContainer{NDIMS, uEltype <: Real, NDIMSP1} <: + AbstractContainer + u::Array{uEltype, NDIMSP1} # [variables, i, j, boundary] + neighbor_ids::Vector{Int} # [boundary] + node_indices::Vector{NTuple{NDIMS, Symbol}} # [boundary] + name::Vector{Symbol} # [boundary] + + # internal `resize!`able storage + _u::Vector{uEltype} + end + + @inline function nboundaries(boundaries::P4estBoundaryContainer) + length(boundaries.neighbor_ids) + end + @inline Base.ndims(::P4estBoundaryContainer{NDIMS}) where {NDIMS} = NDIMS + + # See explanation of Base.resize! for the element container + function Base.resize!(boundaries::P4estBoundaryContainer, capacity) + @unpack _u, neighbor_ids, node_indices, name = boundaries + + n_dims = ndims(boundaries) + n_nodes = size(boundaries.u, 2) + n_variables = size(boundaries.u, 1) + + resize!(_u, n_variables * n_nodes^(n_dims - 1) * capacity) + boundaries.u = unsafe_wrap( + Array, pointer(_u), + ( + n_variables, ntuple(_ -> n_nodes, n_dims - 1)..., + capacity, + ) + ) + + resize!(neighbor_ids, capacity) + + resize!(node_indices, capacity) + + resize!(name, capacity) + + return nothing + end + + # Create interface container and initialize interface data in `elements`. + function init_boundaries(mesh::Union{P4estMesh, T8codeMesh}, equations, basis, elements) + NDIMS = ndims(elements) + uEltype = eltype(elements) + + # Initialize container + n_boundaries = count_required_surfaces(mesh).boundaries + + _u = Vector{uEltype}( + undef, + nvariables(equations) * nnodes(basis)^(NDIMS - 1) * + n_boundaries + ) + u = unsafe_wrap( + Array, pointer(_u), + ( + nvariables(equations), ntuple(_ -> nnodes(basis), NDIMS - 1)..., + n_boundaries, + ) + ) + + neighbor_ids = Vector{Int}(undef, n_boundaries) + node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, n_boundaries) + names = Vector{Symbol}(undef, n_boundaries) + + boundaries = P4estBoundaryContainer{NDIMS, uEltype, NDIMS + 1}( + u, neighbor_ids, + node_indices, names, + _u + ) + + if n_boundaries > 0 + init_boundaries!(boundaries, mesh) end + + return boundaries end - return nothing -end -function init_surfaces!(interfaces, mortars, boundaries, mesh::P4estMesh) - # Let `p4est` iterate over all interfaces and call init_surfaces_iter_face - iter_face_c = cfunction(init_surfaces_iter_face, Val(ndims(mesh))) - user_data = InitSurfacesIterFaceUserData(interfaces, mortars, boundaries, mesh) + function init_boundaries!(boundaries, mesh::P4estMesh) + init_surfaces!(nothing, nothing, boundaries, mesh) - iterate_p4est(mesh.p4est, user_data; iter_face_c = iter_face_c) + return boundaries + end - return interfaces -end + # Function barrier for type stability + function init_boundaries_iter_face_inner(info_pw, boundaries, boundary_id, mesh) + # Extract boundary data + side_pw = load_pointerwrapper_side(info_pw) + # Get local tree, one-based indexing + tree_pw = load_pointerwrapper_tree(mesh.p4est, side_pw.treeid[] + 1) + # Quadrant numbering offset of this quadrant + offset = tree_pw.quadrants_offset[] -# Initialization of interfaces after the function barrier -function init_interfaces_iter_face_inner(info_pw, sides_pw, user_data) - @unpack interfaces, interface_id, mesh = user_data - user_data.interface_id += 1 + # Verify before accessing is.full, but this should never happen + @assert side_pw.is_hanging[] == false - # Get Tuple of local trees, one-based indexing - trees_pw = (load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1), - load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1)) - # Quadrant numbering offsets of the quadrants at this interface - offsets = SVector(trees_pw[1].quadrants_offset[], - trees_pw[2].quadrants_offset[]) + local_quad_id = side_pw.is.full.quadid[] + # Global ID of this quad + quad_id = offset + local_quad_id - local_quad_ids = SVector(sides_pw[1].is.full.quadid[], sides_pw[2].is.full.quadid[]) - # Global IDs of the neighboring quads - quad_ids = offsets + local_quad_ids + # Write data to boundaries container + # `p4est` uses zero-based indexing; convert to one-based indexing + boundaries.neighbor_ids[boundary_id] = quad_id + 1 - # Write data to interfaces container - # `p4est` uses zero-based indexing; convert to one-based indexing - interfaces.neighbor_ids[1, interface_id] = quad_ids[1] + 1 - interfaces.neighbor_ids[2, interface_id] = quad_ids[2] + 1 + # Face at which the boundary lies + face = side_pw.face[] - # Face at which the interface lies - faces = (sides_pw[1].face[], sides_pw[2].face[]) + # Save boundaries.node_indices dimension specific in containers_[23]d.jl + init_boundary_node_indices!(boundaries, face, boundary_id) - # Save interfaces.node_indices dimension specific in containers_[23]d.jl - init_interface_node_indices!(interfaces, faces, info_pw.orientation[], interface_id) + # One-based indexing + boundaries.name[boundary_id] = mesh.boundary_names[face + 1, side_pw.treeid[] + 1] - return nothing -end + return nothing + end -# Initialization of boundaries after the function barrier -function init_boundaries_iter_face_inner(info_pw, user_data) - @unpack boundaries, boundary_id, mesh = user_data - user_data.boundary_id += 1 + # Container data structure (structure-of-arrays style) for DG L2 mortars + # + # The positions used in `neighbor_ids` are 1:3 (in 2D) or 1:5 (in 3D), where 1:2 (in 2D) + # or 1:4 (in 3D) are the small elements numbered in z-order and 3 or 5 is the large element. + # The solution values on the mortar element are saved in `u`, where `position` is the number + # of the small element that corresponds to the respective part of the mortar element. + # The first dimension `small/large side` takes 1 for small side and 2 for large side. + # + # Illustration of the positions in `neighbor_ids` in 3D, where ξ and η are the local coordinates + # of the mortar element, which are precisely the local coordinates that span + # the surface of the smaller side. + # Note that the orientation in the physical space is completely irrelevant here. + # ┌─────────────┬─────────────┐ ┌───────────────────────────┐ + # │ │ │ │ │ + # │ small │ small │ │ │ + # │ 3 │ 4 │ │ │ + # │ │ │ │ large │ + # ├─────────────┼─────────────┤ │ 5 │ + # η │ │ │ │ │ + # │ small │ small │ │ │ + # ↑ │ 1 │ 2 │ │ │ + # │ │ │ │ │ │ + # │ └─────────────┴─────────────┘ └───────────────────────────┘ + # │ + # ⋅────> ξ + mutable struct P4estMortarContainer{NDIMS, uEltype <: Real, NDIMSP1, NDIMSP3} <: + AbstractContainer + u::Array{uEltype, NDIMSP3} # [small/large side, variable, position, i, j, mortar] + neighbor_ids::Matrix{Int} # [position, mortar] + node_indices::Matrix{NTuple{NDIMS, Symbol}} # [small/large, mortar] + + # internal `resize!`able storage + _u::Vector{uEltype} + _neighbor_ids::Vector{Int} + _node_indices::Vector{NTuple{NDIMS, Symbol}} + end - # Extract boundary data - side_pw = load_pointerwrapper_side(info_pw) - # Get local tree, one-based indexing - tree_pw = load_pointerwrapper_tree(mesh.p4est, side_pw.treeid[] + 1) - # Quadrant numbering offset of this quadrant - offset = tree_pw.quadrants_offset[] + @inline nmortars(mortars::P4estMortarContainer) = size(mortars.neighbor_ids, 2) + @inline Base.ndims(::P4estMortarContainer{NDIMS}) where {NDIMS} = NDIMS - # Verify before accessing is.full, but this should never happen - @assert side_pw.is_hanging[] == false + # See explanation of Base.resize! for the element container + function Base.resize!(mortars::P4estMortarContainer, capacity) + @unpack _u, _neighbor_ids, _node_indices = mortars - local_quad_id = side_pw.is.full.quadid[] - # Global ID of this quad - quad_id = offset + local_quad_id + n_dims = ndims(mortars) + n_nodes = size(mortars.u, 4) + n_variables = size(mortars.u, 2) - # Write data to boundaries container - # `p4est` uses zero-based indexing; convert to one-based indexing - boundaries.neighbor_ids[boundary_id] = quad_id + 1 + resize!(_u, 2 * n_variables * 2^(n_dims - 1) * n_nodes^(n_dims - 1) * capacity) + mortars.u = unsafe_wrap( + Array, pointer(_u), + ( + 2, n_variables, 2^(n_dims - 1), + ntuple(_ -> n_nodes, n_dims - 1)..., capacity, + ) + ) - # Face at which the boundary lies - face = side_pw.face[] + resize!(_neighbor_ids, (2^(n_dims - 1) + 1) * capacity) + mortars.neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (2^(n_dims - 1) + 1, capacity) + ) - # Save boundaries.node_indices dimension specific in containers_[23]d.jl - init_boundary_node_indices!(boundaries, face, boundary_id) + resize!(_node_indices, 2 * capacity) + mortars.node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, capacity)) - # One-based indexing - boundaries.name[boundary_id] = mesh.boundary_names[face + 1, side_pw.treeid[] + 1] + return nothing + end - return nothing -end + # Create mortar container and initialize mortar data. + function init_mortars(mesh::Union{P4estMesh, T8codeMesh}, equations, basis, elements) + NDIMS = ndims(elements) + uEltype = eltype(elements) + + # Initialize container + n_mortars = count_required_surfaces(mesh).mortars + + _u = Vector{uEltype}( + undef, + 2 * nvariables(equations) * 2^(NDIMS - 1) * + nnodes(basis)^(NDIMS - 1) * n_mortars + ) + u = unsafe_wrap( + Array, pointer(_u), + ( + 2, nvariables(equations), 2^(NDIMS - 1), + ntuple(_ -> nnodes(basis), NDIMS - 1)..., n_mortars, + ) + ) + + _neighbor_ids = Vector{Int}(undef, (2^(NDIMS - 1) + 1) * n_mortars) + neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (2^(NDIMS - 1) + 1, n_mortars) + ) + + _node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, 2 * n_mortars) + node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, n_mortars)) + + mortars = P4estMortarContainer{NDIMS, uEltype, NDIMS + 1, NDIMS + 3}( + u, + neighbor_ids, + node_indices, + _u, + _neighbor_ids, + _node_indices + ) + + if n_mortars > 0 + init_mortars!(mortars, mesh) + end -# Initialization of mortars after the function barrier -function init_mortars_iter_face_inner(info_pw, sides_pw, user_data) - @unpack mortars, mortar_id, mesh = user_data - user_data.mortar_id += 1 + return mortars + end - # Get Tuple of local trees, one-based indexing - trees_pw = (load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1), - load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1)) - # Quadrant numbering offsets of the quadrants at this interface - offsets = SVector(trees_pw[1].quadrants_offset[], - trees_pw[2].quadrants_offset[]) + function init_mortars!(mortars, mesh::P4estMesh) + init_surfaces!(nothing, mortars, nothing, mesh) - if sides_pw[1].is_hanging[] == true - # Left is small, right is large - faces = (sides_pw[1].face[], sides_pw[2].face[]) + return mortars + end - local_small_quad_ids = sides_pw[1].is.hanging.quadid[] - # Global IDs of the two small quads - small_quad_ids = offsets[1] .+ local_small_quad_ids - - # Just be sure before accessing is.full - @assert sides_pw[2].is_hanging[] == false - large_quad_id = offsets[2] + sides_pw[2].is.full.quadid[] - else # sides_pw[2].is_hanging[] == true - # Right is small, left is large. - # init_mortar_node_indices! below expects side 1 to contain the small elements. - faces = (sides_pw[2].face[], sides_pw[1].face[]) - - local_small_quad_ids = sides_pw[2].is.hanging.quadid[] - # Global IDs of the two small quads - small_quad_ids = offsets[2] .+ local_small_quad_ids - - # Just be sure before accessing is.full - @assert sides_pw[1].is_hanging[] == false - large_quad_id = offsets[1] + sides_pw[1].is.full.quadid[] - end - - # Write data to mortar container, 1 and 2 are the small elements - # `p4est` uses zero-based indexing; convert to one-based indexing - mortars.neighbor_ids[1:(end - 1), mortar_id] .= small_quad_ids[:] .+ 1 - # Last entry is the large element - mortars.neighbor_ids[end, mortar_id] = large_quad_id + 1 - - init_mortar_node_indices!(mortars, faces, info_pw.orientation[], mortar_id) - - return nothing -end - -# Iterate over all interfaces and count -# - (inner) interfaces -# - mortars -# - boundaries -# and collect the numbers in `user_data` in this order. -function count_surfaces_iter_face(info, user_data) - info_pw = PointerWrapper(info) - elem_count = info_pw.sides.elem_count[] - - if elem_count == 2 - # Two neighboring elements => Interface or mortar - - # Extract surface data - sides_pw = (load_pointerwrapper_side(info_pw, 1), - load_pointerwrapper_side(info_pw, 2)) - - if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false - # No hanging nodes => normal interface - # Unpack user_data = [interface_count] and increment interface_count - pw = PointerWrapper(Int, user_data) - id = pw[1] - pw[1] = id + 1 + function reinitialize_containers!(mesh::P4estMesh, equations, dg::DGSEM, cache) + # Re-initialize elements container + @unpack elements = cache + resize!(elements, ncells(mesh)) + init_elements!(elements, mesh, dg.basis) + + required = count_required_surfaces(mesh) + + # resize interfaces container + @unpack interfaces = cache + resize!(interfaces, required.interfaces) + + # resize boundaries container + @unpack boundaries = cache + resize!(boundaries, required.boundaries) + + # re-initialize mortars container + if hasproperty(cache, :mortars) # cache_parabolic does not carry mortars + @unpack mortars = cache + resize!(mortars, required.mortars) + + # re-initialize containers together to reduce + # the number of iterations over the mesh in `p4est` + init_surfaces!(interfaces, mortars, boundaries, mesh) else - # Hanging nodes => mortar - # Unpack user_data = [mortar_count] and increment mortar_count - pw = PointerWrapper(Int, user_data) - id = pw[2] - pw[2] = id + 1 + init_surfaces!(interfaces, nothing, boundaries, mesh) + end + end + + # A helper struct used in initialization methods below + mutable struct InitSurfacesIterFaceUserData{Interfaces, Mortars, Boundaries, Mesh} + interfaces::Interfaces + interface_id::Int + mortars::Mortars + mortar_id::Int + boundaries::Boundaries + boundary_id::Int + mesh::Mesh + end + + function InitSurfacesIterFaceUserData(interfaces, mortars, boundaries, mesh) + return InitSurfacesIterFaceUserData{ + typeof(interfaces), typeof(mortars), + typeof(boundaries), typeof(mesh), + }( + interfaces, 1, + mortars, 1, + boundaries, 1, + mesh + ) + end + + function init_surfaces_iter_face(info, user_data) + # Unpack user_data + data = unsafe_pointer_to_objref(Ptr{InitSurfacesIterFaceUserData}(user_data)) + + # Function barrier because the unpacked user_data above is type-unstable + init_surfaces_iter_face_inner(info, data) + end + + # 2D + function cfunction(::typeof(init_surfaces_iter_face), ::Val{2}) + @cfunction( + init_surfaces_iter_face, Cvoid, + (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid}) + ) + end + # 3D + function cfunction(::typeof(init_surfaces_iter_face), ::Val{3}) + @cfunction( + init_surfaces_iter_face, Cvoid, + (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid}) + ) + end + + # Function barrier for type stability + function init_surfaces_iter_face_inner(info, user_data) + @unpack interfaces, mortars, boundaries = user_data + info_pw = PointerWrapper(info) + elem_count = info_pw.sides.elem_count[] + + if elem_count == 2 + # Two neighboring elements => Interface or mortar + + # Extract surface data + sides_pw = ( + load_pointerwrapper_side(info_pw, 1), + load_pointerwrapper_side(info_pw, 2), + ) + + if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false + # No hanging nodes => normal interface + if interfaces !== nothing + init_interfaces_iter_face_inner(info_pw, sides_pw, user_data) + end + else + # Hanging nodes => mortar + if mortars !== nothing + init_mortars_iter_face_inner(info_pw, sides_pw, user_data) + end + end + elseif elem_count == 1 + # One neighboring elements => boundary + if boundaries !== nothing + init_boundaries_iter_face_inner(info_pw, user_data) + end + end + return nothing + end + + function init_surfaces!(interfaces, mortars, boundaries, mesh::P4estMesh) + # Let `p4est` iterate over all interfaces and call init_surfaces_iter_face + iter_face_c = cfunction(init_surfaces_iter_face, Val(ndims(mesh))) + user_data = InitSurfacesIterFaceUserData(interfaces, mortars, boundaries, mesh) + + iterate_p4est(mesh.p4est, user_data; iter_face_c = iter_face_c) + + return interfaces + end + + # Initialization of interfaces after the function barrier + function init_interfaces_iter_face_inner(info_pw, sides_pw, user_data) + @unpack interfaces, interface_id, mesh = user_data + user_data.interface_id += 1 + + # Get Tuple of local trees, one-based indexing + trees_pw = ( + load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1), + load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1), + ) + # Quadrant numbering offsets of the quadrants at this interface + offsets = SVector( + trees_pw[1].quadrants_offset[], + trees_pw[2].quadrants_offset[] + ) + + local_quad_ids = SVector(sides_pw[1].is.full.quadid[], sides_pw[2].is.full.quadid[]) + # Global IDs of the neighboring quads + quad_ids = offsets + local_quad_ids + + # Write data to interfaces container + # `p4est` uses zero-based indexing; convert to one-based indexing + interfaces.neighbor_ids[1, interface_id] = quad_ids[1] + 1 + interfaces.neighbor_ids[2, interface_id] = quad_ids[2] + 1 + + # Face at which the interface lies + faces = (sides_pw[1].face[], sides_pw[2].face[]) + + # Save interfaces.node_indices dimension specific in containers_[23]d.jl + init_interface_node_indices!(interfaces, faces, info_pw.orientation[], interface_id) + + return nothing + end + + # Initialization of boundaries after the function barrier + function init_boundaries_iter_face_inner(info_pw, user_data) + @unpack boundaries, boundary_id, mesh = user_data + user_data.boundary_id += 1 + + # Extract boundary data + side_pw = load_pointerwrapper_side(info_pw) + # Get local tree, one-based indexing + tree_pw = load_pointerwrapper_tree(mesh.p4est, side_pw.treeid[] + 1) + # Quadrant numbering offset of this quadrant + offset = tree_pw.quadrants_offset[] + + # Verify before accessing is.full, but this should never happen + @assert side_pw.is_hanging[] == false + + local_quad_id = side_pw.is.full.quadid[] + # Global ID of this quad + quad_id = offset + local_quad_id + + # Write data to boundaries container + # `p4est` uses zero-based indexing; convert to one-based indexing + boundaries.neighbor_ids[boundary_id] = quad_id + 1 + + # Face at which the boundary lies + face = side_pw.face[] + + # Save boundaries.node_indices dimension specific in containers_[23]d.jl + init_boundary_node_indices!(boundaries, face, boundary_id) + + # One-based indexing + boundaries.name[boundary_id] = mesh.boundary_names[face + 1, side_pw.treeid[] + 1] + + return nothing + end + + # Initialization of mortars after the function barrier + function init_mortars_iter_face_inner(info_pw, sides_pw, user_data) + @unpack mortars, mortar_id, mesh = user_data + user_data.mortar_id += 1 + + # Get Tuple of local trees, one-based indexing + trees_pw = ( + load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1), + load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1), + ) + # Quadrant numbering offsets of the quadrants at this interface + offsets = SVector( + trees_pw[1].quadrants_offset[], + trees_pw[2].quadrants_offset[] + ) + + if sides_pw[1].is_hanging[] == true + # Left is small, right is large + faces = (sides_pw[1].face[], sides_pw[2].face[]) + + local_small_quad_ids = sides_pw[1].is.hanging.quadid[] + # Global IDs of the two small quads + small_quad_ids = offsets[1] .+ local_small_quad_ids + + # Just be sure before accessing is.full + @assert sides_pw[2].is_hanging[] == false + large_quad_id = offsets[2] + sides_pw[2].is.full.quadid[] + else # sides_pw[2].is_hanging[] == true + # Right is small, left is large. + # init_mortar_node_indices! below expects side 1 to contain the small elements. + faces = (sides_pw[2].face[], sides_pw[1].face[]) + + local_small_quad_ids = sides_pw[2].is.hanging.quadid[] + # Global IDs of the two small quads + small_quad_ids = offsets[2] .+ local_small_quad_ids + + # Just be sure before accessing is.full + @assert sides_pw[1].is_hanging[] == false + large_quad_id = offsets[1] + sides_pw[1].is.full.quadid[] end - elseif elem_count == 1 - # One neighboring elements => boundary - # Unpack user_data = [boundary_count] and increment boundary_count - pw = PointerWrapper(Int, user_data) - id = pw[3] - pw[3] = id + 1 + # Write data to mortar container, 1 and 2 are the small elements + # `p4est` uses zero-based indexing; convert to one-based indexing + mortars.neighbor_ids[1:(end - 1), mortar_id] .= small_quad_ids[:] .+ 1 + # Last entry is the large element + mortars.neighbor_ids[end, mortar_id] = large_quad_id + 1 + + init_mortar_node_indices!(mortars, faces, info_pw.orientation[], mortar_id) + + return nothing end - return nothing -end + # Iterate over all interfaces and count + # - (inner) interfaces + # - mortars + # - boundaries + # and collect the numbers in `user_data` in this order. + function count_surfaces_iter_face(info, user_data) + info_pw = PointerWrapper(info) + elem_count = info_pw.sides.elem_count[] + + if elem_count == 2 + # Two neighboring elements => Interface or mortar + + # Extract surface data + sides_pw = ( + load_pointerwrapper_side(info_pw, 1), + load_pointerwrapper_side(info_pw, 2), + ) + + if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false + # No hanging nodes => normal interface + # Unpack user_data = [interface_count] and increment interface_count + pw = PointerWrapper(Int, user_data) + id = pw[1] + pw[1] = id + 1 + else + # Hanging nodes => mortar + # Unpack user_data = [mortar_count] and increment mortar_count + pw = PointerWrapper(Int, user_data) + id = pw[2] + pw[2] = id + 1 + end + elseif elem_count == 1 + # One neighboring elements => boundary -# 2D -function cfunction(::typeof(count_surfaces_iter_face), ::Val{2}) - @cfunction(count_surfaces_iter_face, Cvoid, - (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(count_surfaces_iter_face), ::Val{3}) - @cfunction(count_surfaces_iter_face, Cvoid, - (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid})) -end + # Unpack user_data = [boundary_count] and increment boundary_count + pw = PointerWrapper(Int, user_data) + id = pw[3] + pw[3] = id + 1 + end -function count_required_surfaces(mesh::P4estMesh) - # Let `p4est` iterate over all interfaces and call count_surfaces_iter_face - iter_face_c = cfunction(count_surfaces_iter_face, Val(ndims(mesh))) + return nothing + end + + # 2D + function cfunction(::typeof(count_surfaces_iter_face), ::Val{2}) + @cfunction( + count_surfaces_iter_face, Cvoid, + (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid}) + ) + end + # 3D + function cfunction(::typeof(count_surfaces_iter_face), ::Val{3}) + @cfunction( + count_surfaces_iter_face, Cvoid, + (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid}) + ) + end - # interfaces, mortars, boundaries - user_data = [0, 0, 0] + function count_required_surfaces(mesh::P4estMesh) + # Let `p4est` iterate over all interfaces and call count_surfaces_iter_face + iter_face_c = cfunction(count_surfaces_iter_face, Val(ndims(mesh))) - iterate_p4est(mesh.p4est, user_data; iter_face_c = iter_face_c) + # interfaces, mortars, boundaries + user_data = [0, 0, 0] - # Return counters - return (interfaces = user_data[1], + iterate_p4est(mesh.p4est, user_data; iter_face_c = iter_face_c) + + # Return counters + return ( + interfaces = user_data[1], mortars = user_data[2], - boundaries = user_data[3]) -end - -# Return direction of the face, which is indexed by node_indices -@inline function indices2direction(indices) - if indices[1] === :begin - return 1 - elseif indices[1] === :end - return 2 - elseif indices[2] === :begin - return 3 - elseif indices[2] === :end - return 4 - elseif indices[3] === :begin - return 5 - else # if indices[3] === :end - return 6 - end -end - -include("containers_2d.jl") -include("containers_3d.jl") -include("containers_parallel.jl") -include("containers_parallel_2d.jl") -include("containers_parallel_3d.jl") + boundaries = user_data[3], + ) + end + + # Return direction of the face, which is indexed by node_indices + @inline function indices2direction(indices) + if indices[1] === :begin + return 1 + elseif indices[1] === :end + return 2 + elseif indices[2] === :begin + return 3 + elseif indices[2] === :end + return 4 + elseif indices[3] === :begin + return 5 + else # if indices[3] === :end + return 6 + end + end + + include("containers_2d.jl") + include("containers_3d.jl") + include("containers_parallel.jl") + include("containers_parallel_2d.jl") + include("containers_parallel_3d.jl") end # @muladd diff --git a/src/solvers/dgsem_p4est/containers_2d.jl b/src/solvers/dgsem_p4est/containers_2d.jl index 236d7d24c0..cadabe2005 100644 --- a/src/solvers/dgsem_p4est/containers_2d.jl +++ b/src/solvers/dgsem_p4est/containers_2d.jl @@ -3,171 +3,195 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Initialize data structures in element container -function init_elements!(elements, mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - basis::LobattoLegendreBasis) - @unpack node_coordinates, jacobian_matrix, - contravariant_vectors, inverse_jacobian = elements + # Initialize data structures in element container + function init_elements!( + elements, mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + basis::LobattoLegendreBasis + ) + @unpack node_coordinates, jacobian_matrix, + contravariant_vectors, inverse_jacobian = elements - calc_node_coordinates!(node_coordinates, mesh, basis) + calc_node_coordinates!(node_coordinates, mesh, basis) - for element in 1:ncells(mesh) - calc_jacobian_matrix!(jacobian_matrix, element, node_coordinates, basis) + for element in 1:ncells(mesh) + calc_jacobian_matrix!(jacobian_matrix, element, node_coordinates, basis) - calc_contravariant_vectors!(contravariant_vectors, element, jacobian_matrix) + calc_contravariant_vectors!(contravariant_vectors, element, jacobian_matrix) - calc_inverse_jacobian!(inverse_jacobian, element, jacobian_matrix) + calc_inverse_jacobian!(inverse_jacobian, element, jacobian_matrix) + end + + return nothing + end + + # Interpolate tree_node_coordinates to each quadrant at the nodes of the specified basis + function calc_node_coordinates!( + node_coordinates, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + basis::LobattoLegendreBasis + ) + # Hanging nodes will cause holes in the mesh if its polydeg is higher + # than the polydeg of the solver. + @assert length(basis.nodes) >= length(mesh.nodes) "The solver can't have a lower polydeg than the mesh" + + calc_node_coordinates!(node_coordinates, mesh, basis.nodes) end - return nothing -end - -# Interpolate tree_node_coordinates to each quadrant at the nodes of the specified basis -function calc_node_coordinates!(node_coordinates, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - basis::LobattoLegendreBasis) - # Hanging nodes will cause holes in the mesh if its polydeg is higher - # than the polydeg of the solver. - @assert length(basis.nodes)>=length(mesh.nodes) "The solver can't have a lower polydeg than the mesh" - - calc_node_coordinates!(node_coordinates, mesh, basis.nodes) -end - -# Interpolate tree_node_coordinates to each quadrant at the specified nodes -function calc_node_coordinates!(node_coordinates, - mesh::P4estMesh{2}, - nodes::AbstractVector) - # We use `StrideArray`s here since these buffers are used in performance-critical - # places and the additional information passed to the compiler makes them faster - # than native `Array`s. - tmp1 = StrideArray(undef, real(mesh), - StaticInt(2), static_length(nodes), static_length(mesh.nodes)) - matrix1 = StrideArray(undef, real(mesh), - static_length(nodes), static_length(mesh.nodes)) - matrix2 = similar(matrix1) - baryweights_in = barycentric_weights(mesh.nodes) - - # Macros from `p4est` - p4est_root_len = 1 << P4EST_MAXLEVEL - p4est_quadrant_len(l) = 1 << (P4EST_MAXLEVEL - l) - - trees = unsafe_wrap_sc(p4est_tree_t, mesh.p4est.trees) - - for tree in eachindex(trees) - offset = trees[tree].quadrants_offset - quadrants = unsafe_wrap_sc(p4est_quadrant_t, trees[tree].quadrants) - - for i in eachindex(quadrants) - element = offset + i - quad = quadrants[i] - - quad_length = p4est_quadrant_len(quad.level) / p4est_root_len - - nodes_out_x = 2 * (quad_length * 1 / 2 * (nodes .+ 1) .+ - quad.x / p4est_root_len) .- 1 - nodes_out_y = 2 * (quad_length * 1 / 2 * (nodes .+ 1) .+ - quad.y / p4est_root_len) .- 1 - polynomial_interpolation_matrix!(matrix1, mesh.nodes, nodes_out_x, - baryweights_in) - polynomial_interpolation_matrix!(matrix2, mesh.nodes, nodes_out_y, - baryweights_in) - - multiply_dimensionwise!(view(node_coordinates, :, :, :, element), - matrix1, matrix2, - view(mesh.tree_node_coordinates, :, :, :, tree), - tmp1) + # Interpolate tree_node_coordinates to each quadrant at the specified nodes + function calc_node_coordinates!( + node_coordinates, + mesh::P4estMesh{2}, + nodes::AbstractVector + ) + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + tmp1 = StrideArray( + undef, real(mesh), + StaticInt(2), static_length(nodes), static_length(mesh.nodes) + ) + matrix1 = StrideArray( + undef, real(mesh), + static_length(nodes), static_length(mesh.nodes) + ) + matrix2 = similar(matrix1) + baryweights_in = barycentric_weights(mesh.nodes) + + # Macros from `p4est` + p4est_root_len = 1 << P4EST_MAXLEVEL + p4est_quadrant_len(l) = 1 << (P4EST_MAXLEVEL - l) + + trees = unsafe_wrap_sc(p4est_tree_t, mesh.p4est.trees) + + for tree in eachindex(trees) + offset = trees[tree].quadrants_offset + quadrants = unsafe_wrap_sc(p4est_quadrant_t, trees[tree].quadrants) + + for i in eachindex(quadrants) + element = offset + i + quad = quadrants[i] + + quad_length = p4est_quadrant_len(quad.level) / p4est_root_len + + nodes_out_x = 2 * ( + quad_length * 1 / 2 * (nodes .+ 1) .+ + quad.x / p4est_root_len + ) .- 1 + nodes_out_y = 2 * ( + quad_length * 1 / 2 * (nodes .+ 1) .+ + quad.y / p4est_root_len + ) .- 1 + polynomial_interpolation_matrix!( + matrix1, mesh.nodes, nodes_out_x, + baryweights_in + ) + polynomial_interpolation_matrix!( + matrix2, mesh.nodes, nodes_out_y, + baryweights_in + ) + + multiply_dimensionwise!( + view(node_coordinates, :, :, :, element), + matrix1, matrix2, + view(mesh.tree_node_coordinates, :, :, :, tree), + tmp1 + ) + end end + + return node_coordinates end - return node_coordinates -end - -# Initialize node_indices of interface container -@inline function init_interface_node_indices!(interfaces::P4estInterfaceContainer{2}, - faces, orientation, interface_id) - # Iterate over primary and secondary element - for side in 1:2 - # Align interface in positive coordinate direction of primary element. - # For orientation == 1, the secondary element needs to be indexed backwards - # relative to the interface. - if side == 1 || orientation == 0 - # Forward indexing - i = :i_forward - else - # Backward indexing - i = :i_backward + # Initialize node_indices of interface container + @inline function init_interface_node_indices!( + interfaces::P4estInterfaceContainer{2}, + faces, orientation, interface_id + ) + # Iterate over primary and secondary element + for side in 1:2 + # Align interface in positive coordinate direction of primary element. + # For orientation == 1, the secondary element needs to be indexed backwards + # relative to the interface. + if side == 1 || orientation == 0 + # Forward indexing + i = :i_forward + else + # Backward indexing + i = :i_backward + end + + if faces[side] == 0 + # Index face in negative x-direction + interfaces.node_indices[side, interface_id] = (:begin, i) + elseif faces[side] == 1 + # Index face in positive x-direction + interfaces.node_indices[side, interface_id] = (:end, i) + elseif faces[side] == 2 + # Index face in negative y-direction + interfaces.node_indices[side, interface_id] = (i, :begin) + else # faces[side] == 3 + # Index face in positive y-direction + interfaces.node_indices[side, interface_id] = (i, :end) + end end - if faces[side] == 0 + return interfaces + end + + # Initialize node_indices of boundary container + @inline function init_boundary_node_indices!( + boundaries::P4estBoundaryContainer{2}, + face, boundary_id + ) + if face == 0 # Index face in negative x-direction - interfaces.node_indices[side, interface_id] = (:begin, i) - elseif faces[side] == 1 + boundaries.node_indices[boundary_id] = (:begin, :i_forward) + elseif face == 1 # Index face in positive x-direction - interfaces.node_indices[side, interface_id] = (:end, i) - elseif faces[side] == 2 + boundaries.node_indices[boundary_id] = (:end, :i_forward) + elseif face == 2 # Index face in negative y-direction - interfaces.node_indices[side, interface_id] = (i, :begin) - else # faces[side] == 3 + boundaries.node_indices[boundary_id] = (:i_forward, :begin) + else # face == 3 # Index face in positive y-direction - interfaces.node_indices[side, interface_id] = (i, :end) + boundaries.node_indices[boundary_id] = (:i_forward, :end) end - end - return interfaces -end - -# Initialize node_indices of boundary container -@inline function init_boundary_node_indices!(boundaries::P4estBoundaryContainer{2}, - face, boundary_id) - if face == 0 - # Index face in negative x-direction - boundaries.node_indices[boundary_id] = (:begin, :i_forward) - elseif face == 1 - # Index face in positive x-direction - boundaries.node_indices[boundary_id] = (:end, :i_forward) - elseif face == 2 - # Index face in negative y-direction - boundaries.node_indices[boundary_id] = (:i_forward, :begin) - else # face == 3 - # Index face in positive y-direction - boundaries.node_indices[boundary_id] = (:i_forward, :end) + return boundaries end - return boundaries -end - -# Initialize node_indices of mortar container -# faces[1] is expected to be the face of the small side. -@inline function init_mortar_node_indices!(mortars, faces, orientation, mortar_id) - for side in 1:2 - # Align mortar in positive coordinate direction of small side. - # For orientation == 1, the large side needs to be indexed backwards - # relative to the mortar. - if side == 1 || orientation == 0 - # Forward indexing for small side or orientation == 0 - i = :i_forward - else - # Backward indexing for large side with reversed orientation - i = :i_backward + # Initialize node_indices of mortar container + # faces[1] is expected to be the face of the small side. + @inline function init_mortar_node_indices!(mortars, faces, orientation, mortar_id) + for side in 1:2 + # Align mortar in positive coordinate direction of small side. + # For orientation == 1, the large side needs to be indexed backwards + # relative to the mortar. + if side == 1 || orientation == 0 + # Forward indexing for small side or orientation == 0 + i = :i_forward + else + # Backward indexing for large side with reversed orientation + i = :i_backward + end + + if faces[side] == 0 + # Index face in negative x-direction + mortars.node_indices[side, mortar_id] = (:begin, i) + elseif faces[side] == 1 + # Index face in positive x-direction + mortars.node_indices[side, mortar_id] = (:end, i) + elseif faces[side] == 2 + # Index face in negative y-direction + mortars.node_indices[side, mortar_id] = (i, :begin) + else # faces[side] == 3 + # Index face in positive y-direction + mortars.node_indices[side, mortar_id] = (i, :end) + end end - if faces[side] == 0 - # Index face in negative x-direction - mortars.node_indices[side, mortar_id] = (:begin, i) - elseif faces[side] == 1 - # Index face in positive x-direction - mortars.node_indices[side, mortar_id] = (:end, i) - elseif faces[side] == 2 - # Index face in negative y-direction - mortars.node_indices[side, mortar_id] = (i, :begin) - else # faces[side] == 3 - # Index face in positive y-direction - mortars.node_indices[side, mortar_id] = (i, :end) - end + return mortars end - - return mortars -end end # @muladd diff --git a/src/solvers/dgsem_p4est/containers_3d.jl b/src/solvers/dgsem_p4est/containers_3d.jl index 7e383924ba..0c8948e3fb 100644 --- a/src/solvers/dgsem_p4est/containers_3d.jl +++ b/src/solvers/dgsem_p4est/containers_3d.jl @@ -3,328 +3,382 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Initialize data structures in element container -function init_elements!(elements, mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - basis::LobattoLegendreBasis) - @unpack node_coordinates, jacobian_matrix, - contravariant_vectors, inverse_jacobian = elements + # Initialize data structures in element container + function init_elements!( + elements, mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + basis::LobattoLegendreBasis + ) + @unpack node_coordinates, jacobian_matrix, + contravariant_vectors, inverse_jacobian = elements - calc_node_coordinates!(node_coordinates, mesh, basis) + calc_node_coordinates!(node_coordinates, mesh, basis) - for element in 1:ncells(mesh) - calc_jacobian_matrix!(jacobian_matrix, element, node_coordinates, basis) + for element in 1:ncells(mesh) + calc_jacobian_matrix!(jacobian_matrix, element, node_coordinates, basis) - calc_contravariant_vectors!(contravariant_vectors, element, jacobian_matrix, - node_coordinates, basis) + calc_contravariant_vectors!( + contravariant_vectors, element, jacobian_matrix, + node_coordinates, basis + ) - calc_inverse_jacobian!(inverse_jacobian, element, jacobian_matrix, basis) - end + calc_inverse_jacobian!(inverse_jacobian, element, jacobian_matrix, basis) + end - return nothing -end + return nothing + end -# Interpolate tree_node_coordinates to each quadrant at the nodes of the specified basis -function calc_node_coordinates!(node_coordinates, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - basis::LobattoLegendreBasis) - # Hanging nodes will cause holes in the mesh if its polydeg is higher - # than the polydeg of the solver. - @assert length(basis.nodes)>=length(mesh.nodes) "The solver can't have a lower polydeg than the mesh" + # Interpolate tree_node_coordinates to each quadrant at the nodes of the specified basis + function calc_node_coordinates!( + node_coordinates, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + basis::LobattoLegendreBasis + ) + # Hanging nodes will cause holes in the mesh if its polydeg is higher + # than the polydeg of the solver. + @assert length(basis.nodes) >= length(mesh.nodes) "The solver can't have a lower polydeg than the mesh" - calc_node_coordinates!(node_coordinates, mesh, basis.nodes) -end + calc_node_coordinates!(node_coordinates, mesh, basis.nodes) + end -# Interpolate tree_node_coordinates to each quadrant at the specified nodes -function calc_node_coordinates!(node_coordinates, - mesh::P4estMesh{3}, - nodes::AbstractVector) - # Macros from `p4est` - p4est_root_len = 1 << P4EST_MAXLEVEL - p4est_quadrant_len(l) = 1 << (P4EST_MAXLEVEL - l) + # Interpolate tree_node_coordinates to each quadrant at the specified nodes + function calc_node_coordinates!( + node_coordinates, + mesh::P4estMesh{3}, + nodes::AbstractVector + ) + # Macros from `p4est` + p4est_root_len = 1 << P4EST_MAXLEVEL + p4est_quadrant_len(l) = 1 << (P4EST_MAXLEVEL - l) - trees = unsafe_wrap_sc(p8est_tree_t, mesh.p4est.trees) + trees = unsafe_wrap_sc(p8est_tree_t, mesh.p4est.trees) - for tree in eachindex(trees) - offset = trees[tree].quadrants_offset - quadrants = unsafe_wrap_sc(p8est_quadrant_t, trees[tree].quadrants) + for tree in eachindex(trees) + offset = trees[tree].quadrants_offset + quadrants = unsafe_wrap_sc(p8est_quadrant_t, trees[tree].quadrants) - for i in eachindex(quadrants) - element = offset + i - quad = quadrants[i] + for i in eachindex(quadrants) + element = offset + i + quad = quadrants[i] - quad_length = p4est_quadrant_len(quad.level) / p4est_root_len + quad_length = p4est_quadrant_len(quad.level) / p4est_root_len - nodes_out_x = 2 * (quad_length * 1 / 2 * (nodes .+ 1) .+ - quad.x / p4est_root_len) .- 1 - nodes_out_y = 2 * (quad_length * 1 / 2 * (nodes .+ 1) .+ - quad.y / p4est_root_len) .- 1 - nodes_out_z = 2 * (quad_length * 1 / 2 * (nodes .+ 1) .+ - quad.z / p4est_root_len) .- 1 + nodes_out_x = 2 * ( + quad_length * 1 / 2 * (nodes .+ 1) .+ + quad.x / p4est_root_len + ) .- 1 + nodes_out_y = 2 * ( + quad_length * 1 / 2 * (nodes .+ 1) .+ + quad.y / p4est_root_len + ) .- 1 + nodes_out_z = 2 * ( + quad_length * 1 / 2 * (nodes .+ 1) .+ + quad.z / p4est_root_len + ) .- 1 - matrix1 = polynomial_interpolation_matrix(mesh.nodes, nodes_out_x) - matrix2 = polynomial_interpolation_matrix(mesh.nodes, nodes_out_y) - matrix3 = polynomial_interpolation_matrix(mesh.nodes, nodes_out_z) + matrix1 = polynomial_interpolation_matrix(mesh.nodes, nodes_out_x) + matrix2 = polynomial_interpolation_matrix(mesh.nodes, nodes_out_y) + matrix3 = polynomial_interpolation_matrix(mesh.nodes, nodes_out_z) - multiply_dimensionwise!(view(node_coordinates, :, :, :, :, element), - matrix1, matrix2, matrix3, - view(mesh.tree_node_coordinates, :, :, :, :, tree)) + multiply_dimensionwise!( + view(node_coordinates, :, :, :, :, element), + matrix1, matrix2, matrix3, + view(mesh.tree_node_coordinates, :, :, :, :, tree) + ) + end end + + return node_coordinates end - return node_coordinates -end + # Initialize node_indices of interface container + @inline function init_interface_node_indices!( + interfaces::P4estInterfaceContainer{3}, + faces, orientation, interface_id + ) + # Iterate over primary and secondary element + for side in 1:2 + # Align interface at the primary element (primary element has surface indices (:i_forward, :j_forward)). + # The secondary element needs to be indexed differently. + if side == 1 + surface_index1 = :i_forward + surface_index2 = :j_forward + else + surface_index1, surface_index2 = orientation_to_indices_p4est( + faces[2], + faces[1], + orientation + ) + end -# Initialize node_indices of interface container -@inline function init_interface_node_indices!(interfaces::P4estInterfaceContainer{3}, - faces, orientation, interface_id) - # Iterate over primary and secondary element - for side in 1:2 - # Align interface at the primary element (primary element has surface indices (:i_forward, :j_forward)). - # The secondary element needs to be indexed differently. - if side == 1 - surface_index1 = :i_forward - surface_index2 = :j_forward - else - surface_index1, surface_index2 = orientation_to_indices_p4est(faces[2], - faces[1], - orientation) + if faces[side] == 0 + # Index face in negative x-direction + interfaces.node_indices[side, interface_id] = ( + :begin, surface_index1, + surface_index2, + ) + elseif faces[side] == 1 + # Index face in positive x-direction + interfaces.node_indices[side, interface_id] = ( + :end, surface_index1, + surface_index2, + ) + elseif faces[side] == 2 + # Index face in negative y-direction + interfaces.node_indices[side, interface_id] = ( + surface_index1, :begin, + surface_index2, + ) + elseif faces[side] == 3 + # Index face in positive y-direction + interfaces.node_indices[side, interface_id] = ( + surface_index1, :end, + surface_index2, + ) + elseif faces[side] == 4 + # Index face in negative z-direction + interfaces.node_indices[side, interface_id] = ( + surface_index1, + surface_index2, :begin, + ) + else # faces[side] == 5 + # Index face in positive z-direction + interfaces.node_indices[side, interface_id] = ( + surface_index1, + surface_index2, :end, + ) + end end - if faces[side] == 0 + return interfaces + end + + # Initialize node_indices of boundary container + @inline function init_boundary_node_indices!( + boundaries::P4estBoundaryContainer{3}, + face, boundary_id + ) + if face == 0 # Index face in negative x-direction - interfaces.node_indices[side, interface_id] = (:begin, surface_index1, - surface_index2) - elseif faces[side] == 1 + boundaries.node_indices[boundary_id] = (:begin, :i_forward, :j_forward) + elseif face == 1 # Index face in positive x-direction - interfaces.node_indices[side, interface_id] = (:end, surface_index1, - surface_index2) - elseif faces[side] == 2 + boundaries.node_indices[boundary_id] = (:end, :i_forward, :j_forward) + elseif face == 2 # Index face in negative y-direction - interfaces.node_indices[side, interface_id] = (surface_index1, :begin, - surface_index2) - elseif faces[side] == 3 + boundaries.node_indices[boundary_id] = (:i_forward, :begin, :j_forward) + elseif face == 3 # Index face in positive y-direction - interfaces.node_indices[side, interface_id] = (surface_index1, :end, - surface_index2) - elseif faces[side] == 4 + boundaries.node_indices[boundary_id] = (:i_forward, :end, :j_forward) + elseif face == 4 # Index face in negative z-direction - interfaces.node_indices[side, interface_id] = (surface_index1, - surface_index2, :begin) - else # faces[side] == 5 + boundaries.node_indices[boundary_id] = (:i_forward, :j_forward, :begin) + else # face == 5 # Index face in positive z-direction - interfaces.node_indices[side, interface_id] = (surface_index1, - surface_index2, :end) + boundaries.node_indices[boundary_id] = (:i_forward, :j_forward, :end) end - end - return interfaces -end - -# Initialize node_indices of boundary container -@inline function init_boundary_node_indices!(boundaries::P4estBoundaryContainer{3}, - face, boundary_id) - if face == 0 - # Index face in negative x-direction - boundaries.node_indices[boundary_id] = (:begin, :i_forward, :j_forward) - elseif face == 1 - # Index face in positive x-direction - boundaries.node_indices[boundary_id] = (:end, :i_forward, :j_forward) - elseif face == 2 - # Index face in negative y-direction - boundaries.node_indices[boundary_id] = (:i_forward, :begin, :j_forward) - elseif face == 3 - # Index face in positive y-direction - boundaries.node_indices[boundary_id] = (:i_forward, :end, :j_forward) - elseif face == 4 - # Index face in negative z-direction - boundaries.node_indices[boundary_id] = (:i_forward, :j_forward, :begin) - else # face == 5 - # Index face in positive z-direction - boundaries.node_indices[boundary_id] = (:i_forward, :j_forward, :end) + return boundaries end - return boundaries -end + # Initialize node_indices of mortar container + # faces[1] is expected to be the face of the small side. + @inline function init_mortar_node_indices!( + mortars::P4estMortarContainer{3}, + faces, orientation, mortar_id + ) + for side in 1:2 + # Align mortar at small side. + # The large side needs to be indexed differently. + if side == 1 + surface_index1 = :i_forward + surface_index2 = :j_forward + else + surface_index1, surface_index2 = orientation_to_indices_p4est( + faces[2], + faces[1], + orientation + ) + end -# Initialize node_indices of mortar container -# faces[1] is expected to be the face of the small side. -@inline function init_mortar_node_indices!(mortars::P4estMortarContainer{3}, - faces, orientation, mortar_id) - for side in 1:2 - # Align mortar at small side. - # The large side needs to be indexed differently. - if side == 1 - surface_index1 = :i_forward - surface_index2 = :j_forward - else - surface_index1, surface_index2 = orientation_to_indices_p4est(faces[2], - faces[1], - orientation) + if faces[side] == 0 + # Index face in negative x-direction + mortars.node_indices[side, mortar_id] = ( + :begin, surface_index1, + surface_index2, + ) + elseif faces[side] == 1 + # Index face in positive x-direction + mortars.node_indices[side, mortar_id] = ( + :end, surface_index1, + surface_index2, + ) + elseif faces[side] == 2 + # Index face in negative y-direction + mortars.node_indices[side, mortar_id] = ( + surface_index1, :begin, + surface_index2, + ) + elseif faces[side] == 3 + # Index face in positive y-direction + mortars.node_indices[side, mortar_id] = ( + surface_index1, :end, + surface_index2, + ) + elseif faces[side] == 4 + # Index face in negative z-direction + mortars.node_indices[side, mortar_id] = ( + surface_index1, surface_index2, + :begin, + ) + else # faces[side] == 5 + # Index face in positive z-direction + mortars.node_indices[side, mortar_id] = ( + surface_index1, surface_index2, + :end, + ) + end end - if faces[side] == 0 - # Index face in negative x-direction - mortars.node_indices[side, mortar_id] = (:begin, surface_index1, - surface_index2) - elseif faces[side] == 1 - # Index face in positive x-direction - mortars.node_indices[side, mortar_id] = (:end, surface_index1, - surface_index2) - elseif faces[side] == 2 - # Index face in negative y-direction - mortars.node_indices[side, mortar_id] = (surface_index1, :begin, - surface_index2) - elseif faces[side] == 3 - # Index face in positive y-direction - mortars.node_indices[side, mortar_id] = (surface_index1, :end, - surface_index2) - elseif faces[side] == 4 - # Index face in negative z-direction - mortars.node_indices[side, mortar_id] = (surface_index1, surface_index2, - :begin) - else # faces[side] == 5 - # Index face in positive z-direction - mortars.node_indices[side, mortar_id] = (surface_index1, surface_index2, - :end) - end + return mortars end - return mortars -end - -# Convert `p4est` orientation code to node indices. -# Return node indices that index "my side" wrt "other side", -# i.e., i and j are indices of other side. -function orientation_to_indices_p4est(my_face, other_face, orientation_code) - # my_face and other_face are the face directions (zero-based) - # of "my side" and "other side" respectively. - # Face corner 0 of the face with the lower face direction connects to a corner of the other face. - # The number of this corner is the orientation code in `p4est`. - lower = my_face <= other_face + # Convert `p4est` orientation code to node indices. + # Return node indices that index "my side" wrt "other side", + # i.e., i and j are indices of other side. + function orientation_to_indices_p4est(my_face, other_face, orientation_code) + # my_face and other_face are the face directions (zero-based) + # of "my side" and "other side" respectively. + # Face corner 0 of the face with the lower face direction connects to a corner of the other face. + # The number of this corner is the orientation code in `p4est`. + lower = my_face <= other_face - # x_pos, y_neg, and z_pos are the directions in which the face has right-handed coordinates - # when looked at from the outside. - my_right_handed = my_face in (1, 2, 5) - other_right_handed = other_face in (1, 2, 5) + # x_pos, y_neg, and z_pos are the directions in which the face has right-handed coordinates + # when looked at from the outside. + my_right_handed = my_face in (1, 2, 5) + other_right_handed = other_face in (1, 2, 5) - # If both or none are right-handed when looked at from the outside, they will have different - # orientations when looked at from the same side of the interface. - flipped = my_right_handed == other_right_handed + # If both or none are right-handed when looked at from the outside, they will have different + # orientations when looked at from the same side of the interface. + flipped = my_right_handed == other_right_handed - # In the following illustrations, the face corner numbering of `p4est` is shown. - # ξ and η are the local coordinates of the respective face. - # We're looking at both faces from the same side of the interface, so that "other side" - # (in the illustrations on the left) has right-handed coordinates. - if !flipped - if orientation_code == 0 - # Corner 0 of other side matches corner 0 of my side - # 2┌──────┐3 2┌──────┐3 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 0└──────┘1 - # η η - # ↑ ↑ - # │ │ - # └───> ξ └───> ξ - surface_index1 = :i_forward - surface_index2 = :j_forward - elseif ((lower && orientation_code == 2) # Corner 0 of my side matches corner 2 of other side - || - (!lower && orientation_code == 1)) # Corner 0 of other side matches corner 1 of my side - # 2┌──────┐3 0┌──────┐2 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 1└──────┘3 - # η ┌───> η - # ↑ │ - # │ ↓ - # └───> ξ ξ - surface_index1 = :j_backward - surface_index2 = :i_forward - elseif ((lower && orientation_code == 1) # Corner 0 of my side matches corner 1 of other side - || - (!lower && orientation_code == 2)) # Corner 0 of other side matches corner 2 of my side - # 2┌──────┐3 3┌──────┐1 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 2└──────┘0 - # η ξ - # ↑ ↑ - # │ │ - # └───> ξ η <───┘ - surface_index1 = :j_forward - surface_index2 = :i_backward - else # orientation_code == 3 - # Corner 0 of my side matches corner 3 of other side and - # corner 0 of other side matches corner 3 of my side. - # 2┌──────┐3 1┌──────┐0 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 3└──────┘2 - # η ξ <───┐ - # ↑ │ - # │ ↓ - # └───> ξ η - surface_index1 = :i_backward - surface_index2 = :j_backward - end - else # flipped - if orientation_code == 0 - # Corner 0 of other side matches corner 0 of my side - # 2┌──────┐3 1┌──────┐3 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 0└──────┘2 - # η ξ - # ↑ ↑ - # │ │ - # └───> ξ └───> η - surface_index1 = :j_forward - surface_index2 = :i_forward - elseif orientation_code == 2 - # Corner 0 of my side matches corner 2 of other side and - # corner 0 of other side matches corner 2 of my side. - # 2┌──────┐3 0┌──────┐1 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 2└──────┘3 - # η ┌───> ξ - # ↑ │ - # │ ↓ - # └───> ξ η - surface_index1 = :i_forward - surface_index2 = :j_backward - elseif orientation_code == 1 - # Corner 0 of my side matches corner 1 of other side and - # corner 0 of other side matches corner 1 of my side. - # 2┌──────┐3 3┌──────┐2 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 1└──────┘0 - # η η - # ↑ ↑ - # │ │ - # └───> ξ ξ <───┘ - surface_index1 = :i_backward - surface_index2 = :j_forward - else # orientation_code == 3 - # Corner 0 of my side matches corner 3 of other side and - # corner 0 of other side matches corner 3 of my side. - # 2┌──────┐3 2┌──────┐0 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 3└──────┘1 - # η η <───┐ - # ↑ │ - # │ ↓ - # └───> ξ ξ - surface_index1 = :j_backward - surface_index2 = :i_backward + # In the following illustrations, the face corner numbering of `p4est` is shown. + # ξ and η are the local coordinates of the respective face. + # We're looking at both faces from the same side of the interface, so that "other side" + # (in the illustrations on the left) has right-handed coordinates. + if !flipped + if orientation_code == 0 + # Corner 0 of other side matches corner 0 of my side + # 2┌──────┐3 2┌──────┐3 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 0└──────┘1 + # η η + # ↑ ↑ + # │ │ + # └───> ξ └───> ξ + surface_index1 = :i_forward + surface_index2 = :j_forward + elseif ( + (lower && orientation_code == 2) # Corner 0 of my side matches corner 2 of other side + || + (!lower && orientation_code == 1) + ) # Corner 0 of other side matches corner 1 of my side + # 2┌──────┐3 0┌──────┐2 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 1└──────┘3 + # η ┌───> η + # ↑ │ + # │ ↓ + # └───> ξ ξ + surface_index1 = :j_backward + surface_index2 = :i_forward + elseif ( + (lower && orientation_code == 1) # Corner 0 of my side matches corner 1 of other side + || + (!lower && orientation_code == 2) + ) # Corner 0 of other side matches corner 2 of my side + # 2┌──────┐3 3┌──────┐1 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 2└──────┘0 + # η ξ + # ↑ ↑ + # │ │ + # └───> ξ η <───┘ + surface_index1 = :j_forward + surface_index2 = :i_backward + else # orientation_code == 3 + # Corner 0 of my side matches corner 3 of other side and + # corner 0 of other side matches corner 3 of my side. + # 2┌──────┐3 1┌──────┐0 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 3└──────┘2 + # η ξ <───┐ + # ↑ │ + # │ ↓ + # └───> ξ η + surface_index1 = :i_backward + surface_index2 = :j_backward + end + else # flipped + if orientation_code == 0 + # Corner 0 of other side matches corner 0 of my side + # 2┌──────┐3 1┌──────┐3 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 0└──────┘2 + # η ξ + # ↑ ↑ + # │ │ + # └───> ξ └───> η + surface_index1 = :j_forward + surface_index2 = :i_forward + elseif orientation_code == 2 + # Corner 0 of my side matches corner 2 of other side and + # corner 0 of other side matches corner 2 of my side. + # 2┌──────┐3 0┌──────┐1 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 2└──────┘3 + # η ┌───> ξ + # ↑ │ + # │ ↓ + # └───> ξ η + surface_index1 = :i_forward + surface_index2 = :j_backward + elseif orientation_code == 1 + # Corner 0 of my side matches corner 1 of other side and + # corner 0 of other side matches corner 1 of my side. + # 2┌──────┐3 3┌──────┐2 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 1└──────┘0 + # η η + # ↑ ↑ + # │ │ + # └───> ξ ξ <───┘ + surface_index1 = :i_backward + surface_index2 = :j_forward + else # orientation_code == 3 + # Corner 0 of my side matches corner 3 of other side and + # corner 0 of other side matches corner 3 of my side. + # 2┌──────┐3 2┌──────┐0 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 3└──────┘1 + # η η <───┐ + # ↑ │ + # │ ↓ + # └───> ξ ξ + surface_index1 = :j_backward + surface_index2 = :i_backward + end end - end - return surface_index1, surface_index2 -end + return surface_index1, surface_index2 + end end # @muladd diff --git a/src/solvers/dgsem_p4est/containers_parallel.jl b/src/solvers/dgsem_p4est/containers_parallel.jl index 676b37efff..30bde514d4 100644 --- a/src/solvers/dgsem_p4est/containers_parallel.jl +++ b/src/solvers/dgsem_p4est/containers_parallel.jl @@ -3,585 +3,673 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -mutable struct P4estMPIInterfaceContainer{NDIMS, uEltype <: Real, NDIMSP2} <: - AbstractContainer - u::Array{uEltype, NDIMSP2} # [primary/secondary, variable, i, j, interface] - local_neighbor_ids::Vector{Int} # [interface] - node_indices::Vector{NTuple{NDIMS, Symbol}} # [interface] - local_sides::Vector{Int} # [interface] - - # internal `resize!`able storage - _u::Vector{uEltype} -end - -@inline function nmpiinterfaces(interfaces::P4estMPIInterfaceContainer) - length(interfaces.local_sides) -end -@inline Base.ndims(::P4estMPIInterfaceContainer{NDIMS}) where {NDIMS} = NDIMS - -function Base.resize!(mpi_interfaces::P4estMPIInterfaceContainer, capacity) - @unpack _u, local_neighbor_ids, node_indices, local_sides = mpi_interfaces - - n_dims = ndims(mpi_interfaces) - n_nodes = size(mpi_interfaces.u, 3) - n_variables = size(mpi_interfaces.u, 2) - - resize!(_u, 2 * n_variables * n_nodes^(n_dims - 1) * capacity) - mpi_interfaces.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, ntuple(_ -> n_nodes, n_dims - 1)..., - capacity)) - - resize!(local_neighbor_ids, capacity) - - resize!(node_indices, capacity) - - resize!(local_sides, capacity) - - return nothing -end - -# Create MPI interface container and initialize interface data -function init_mpi_interfaces(mesh::Union{ParallelP4estMesh, ParallelT8codeMesh}, - equations, basis, elements) - NDIMS = ndims(elements) - uEltype = eltype(elements) - - # Initialize container - n_mpi_interfaces = count_required_surfaces(mesh).mpi_interfaces - - _u = Vector{uEltype}(undef, - 2 * nvariables(equations) * nnodes(basis)^(NDIMS - 1) * - n_mpi_interfaces) - u = unsafe_wrap(Array, pointer(_u), - (2, nvariables(equations), ntuple(_ -> nnodes(basis), NDIMS - 1)..., - n_mpi_interfaces)) - - local_neighbor_ids = Vector{Int}(undef, n_mpi_interfaces) - - node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, n_mpi_interfaces) - - local_sides = Vector{Int}(undef, n_mpi_interfaces) - - mpi_interfaces = P4estMPIInterfaceContainer{NDIMS, uEltype, NDIMS + 2}(u, - local_neighbor_ids, - node_indices, - local_sides, - _u) - - init_mpi_interfaces!(mpi_interfaces, mesh) - - return mpi_interfaces -end - -function init_mpi_interfaces!(mpi_interfaces, mesh::ParallelP4estMesh) - init_surfaces!(nothing, nothing, nothing, mpi_interfaces, nothing, mesh) - - return mpi_interfaces -end - -# Container data structure (structure-of-arrays style) for DG L2 mortars -# -# Similar to `P4estMortarContainer`. The field `neighbor_ids` has been split up into -# `local_neighbor_ids` and `local_neighbor_positions` to describe the ids and positions of the locally -# available elements belonging to a particular MPI mortar. Furthermore, `normal_directions` holds -# the normal vectors on the surface of the small elements for each mortar. -mutable struct P4estMPIMortarContainer{NDIMS, uEltype <: Real, RealT <: Real, NDIMSP1, - NDIMSP2, NDIMSP3} <: AbstractContainer - u::Array{uEltype, NDIMSP3} # [small/large side, variable, position, i, j, mortar] - local_neighbor_ids::Vector{Vector{Int}} # [mortar][ids] - local_neighbor_positions::Vector{Vector{Int}} # [mortar][positions] - node_indices::Matrix{NTuple{NDIMS, Symbol}} # [small/large, mortar] - normal_directions::Array{RealT, NDIMSP2} # [dimension, i, j, position, mortar] - # internal `resize!`able storage - _u::Vector{uEltype} - _node_indices::Vector{NTuple{NDIMS, Symbol}} - _normal_directions::Vector{RealT} -end - -@inline function nmpimortars(mpi_mortars::P4estMPIMortarContainer) - length(mpi_mortars.local_neighbor_ids) -end -@inline Base.ndims(::P4estMPIMortarContainer{NDIMS}) where {NDIMS} = NDIMS - -function Base.resize!(mpi_mortars::P4estMPIMortarContainer, capacity) - @unpack _u, _node_indices, _normal_directions = mpi_mortars - - n_dims = ndims(mpi_mortars) - n_nodes = size(mpi_mortars.u, 4) - n_variables = size(mpi_mortars.u, 2) - - resize!(_u, 2 * n_variables * 2^(n_dims - 1) * n_nodes^(n_dims - 1) * capacity) - mpi_mortars.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, 2^(n_dims - 1), - ntuple(_ -> n_nodes, n_dims - 1)..., capacity)) - - resize!(mpi_mortars.local_neighbor_ids, capacity) - resize!(mpi_mortars.local_neighbor_positions, capacity) - - resize!(_node_indices, 2 * capacity) - mpi_mortars.node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, capacity)) - - resize!(_normal_directions, - n_dims * n_nodes^(n_dims - 1) * 2^(n_dims - 1) * capacity) - mpi_mortars.normal_directions = unsafe_wrap(Array, pointer(_normal_directions), - (n_dims, - ntuple(_ -> n_nodes, n_dims - 1)..., - 2^(n_dims - 1), capacity)) - - return nothing -end - -# Create MPI mortar container and initialize MPI mortar data -function init_mpi_mortars(mesh::Union{ParallelP4estMesh, ParallelT8codeMesh}, equations, - basis, elements) - NDIMS = ndims(mesh) - RealT = real(mesh) - uEltype = eltype(elements) - - # Initialize container - n_mpi_mortars = count_required_surfaces(mesh).mpi_mortars - - _u = Vector{uEltype}(undef, - 2 * nvariables(equations) * 2^(NDIMS - 1) * - nnodes(basis)^(NDIMS - 1) * n_mpi_mortars) - u = unsafe_wrap(Array, pointer(_u), - (2, nvariables(equations), 2^(NDIMS - 1), - ntuple(_ -> nnodes(basis), NDIMS - 1)..., n_mpi_mortars)) - - local_neighbor_ids = fill(Vector{Int}(), n_mpi_mortars) - local_neighbor_positions = fill(Vector{Int}(), n_mpi_mortars) - - _node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, 2 * n_mpi_mortars) - node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, n_mpi_mortars)) - - _normal_directions = Vector{RealT}(undef, - NDIMS * nnodes(basis)^(NDIMS - 1) * - 2^(NDIMS - 1) * n_mpi_mortars) - normal_directions = unsafe_wrap(Array, pointer(_normal_directions), - (NDIMS, ntuple(_ -> nnodes(basis), NDIMS - 1)..., - 2^(NDIMS - 1), n_mpi_mortars)) - - mpi_mortars = P4estMPIMortarContainer{NDIMS, uEltype, RealT, NDIMS + 1, NDIMS + 2, - NDIMS + 3}(u, local_neighbor_ids, - local_neighbor_positions, - node_indices, normal_directions, - _u, _node_indices, - _normal_directions) - - if n_mpi_mortars > 0 - init_mpi_mortars!(mpi_mortars, mesh, basis, elements) + #! format: noindent + + mutable struct P4estMPIInterfaceContainer{NDIMS, uEltype <: Real, NDIMSP2} <: + AbstractContainer + u::Array{uEltype, NDIMSP2} # [primary/secondary, variable, i, j, interface] + local_neighbor_ids::Vector{Int} # [interface] + node_indices::Vector{NTuple{NDIMS, Symbol}} # [interface] + local_sides::Vector{Int} # [interface] + + # internal `resize!`able storage + _u::Vector{uEltype} + end + + @inline function nmpiinterfaces(interfaces::P4estMPIInterfaceContainer) + length(interfaces.local_sides) end + @inline Base.ndims(::P4estMPIInterfaceContainer{NDIMS}) where {NDIMS} = NDIMS + + function Base.resize!(mpi_interfaces::P4estMPIInterfaceContainer, capacity) + @unpack _u, local_neighbor_ids, node_indices, local_sides = mpi_interfaces + + n_dims = ndims(mpi_interfaces) + n_nodes = size(mpi_interfaces.u, 3) + n_variables = size(mpi_interfaces.u, 2) + + resize!(_u, 2 * n_variables * n_nodes^(n_dims - 1) * capacity) + mpi_interfaces.u = unsafe_wrap( + Array, pointer(_u), + ( + 2, n_variables, ntuple(_ -> n_nodes, n_dims - 1)..., + capacity, + ) + ) - return mpi_mortars -end - -function init_mpi_mortars!(mpi_mortars, mesh::ParallelP4estMesh, basis, elements) - init_surfaces!(nothing, nothing, nothing, nothing, mpi_mortars, mesh) - init_normal_directions!(mpi_mortars, basis, elements) - - return mpi_mortars -end - -# Overload init! function for regular interfaces, regular mortars and boundaries since they must -# call the appropriate init_surfaces! function for parallel p4est meshes -function init_interfaces!(interfaces, mesh::ParallelP4estMesh) - init_surfaces!(interfaces, nothing, nothing, nothing, nothing, mesh) - - return interfaces -end - -function init_mortars!(mortars, mesh::ParallelP4estMesh) - init_surfaces!(nothing, mortars, nothing, nothing, nothing, mesh) - - return mortars -end - -function init_boundaries!(boundaries, mesh::ParallelP4estMesh) - init_surfaces!(nothing, nothing, boundaries, nothing, nothing, mesh) - - return boundaries -end - -function reinitialize_containers!(mesh::ParallelP4estMesh, equations, dg::DGSEM, cache) - # Make sure to re-create ghost layer before reinitializing MPI-related containers - update_ghost_layer!(mesh) - - # Re-initialize elements container - @unpack elements = cache - resize!(elements, ncells(mesh)) - init_elements!(elements, mesh, dg.basis) - - required = count_required_surfaces(mesh) - - # resize interfaces container - @unpack interfaces = cache - resize!(interfaces, required.interfaces) - - # resize boundaries container - @unpack boundaries = cache - resize!(boundaries, required.boundaries) - - # resize mortars container - @unpack mortars = cache - resize!(mortars, required.mortars) - - # resize mpi_interfaces container - @unpack mpi_interfaces = cache - resize!(mpi_interfaces, required.mpi_interfaces) - - # resize mpi_mortars container - @unpack mpi_mortars = cache - resize!(mpi_mortars, required.mpi_mortars) - - # re-initialize containers together to reduce - # the number of iterations over the mesh in p4est - init_surfaces!(interfaces, mortars, boundaries, mpi_interfaces, mpi_mortars, mesh) - - # re-initialize MPI cache - @unpack mpi_cache = cache - init_mpi_cache!(mpi_cache, mesh, mpi_interfaces, mpi_mortars, - nvariables(equations), nnodes(dg), eltype(elements)) - - # re-initialize and distribute normal directions of MPI mortars; requires MPI communication, so - # the MPI cache must be re-initialized before - init_normal_directions!(mpi_mortars, dg.basis, elements) - exchange_normal_directions!(mpi_mortars, mpi_cache, mesh, nnodes(dg)) -end - -# A helper struct used in initialization methods below -mutable struct ParallelInitSurfacesIterFaceUserData{Interfaces, Mortars, Boundaries, - MPIInterfaces, MPIMortars, Mesh} - interfaces::Interfaces - interface_id::Int - mortars::Mortars - mortar_id::Int - boundaries::Boundaries - boundary_id::Int - mpi_interfaces::MPIInterfaces - mpi_interface_id::Int - mpi_mortars::MPIMortars - mpi_mortar_id::Int - mesh::Mesh -end - -function ParallelInitSurfacesIterFaceUserData(interfaces, mortars, boundaries, - mpi_interfaces, mpi_mortars, mesh) - return ParallelInitSurfacesIterFaceUserData{typeof(interfaces), typeof(mortars), - typeof(boundaries), - typeof(mpi_interfaces), - typeof(mpi_mortars), typeof(mesh)}(interfaces, - 1, - mortars, - 1, - boundaries, - 1, - mpi_interfaces, - 1, - mpi_mortars, - 1, - mesh) -end - -function init_surfaces_iter_face_parallel(info, user_data) - # Unpack user_data - data = unsafe_pointer_to_objref(Ptr{ParallelInitSurfacesIterFaceUserData}(user_data)) - - # Function barrier because the unpacked user_data above is type-unstable - init_surfaces_iter_face_inner(info, data) -end - -# 2D -function cfunction(::typeof(init_surfaces_iter_face_parallel), ::Val{2}) - @cfunction(init_surfaces_iter_face_parallel, Cvoid, - (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(init_surfaces_iter_face_parallel), ::Val{3}) - @cfunction(init_surfaces_iter_face_parallel, Cvoid, - (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid})) -end - -# Function barrier for type stability, overload for parallel P4estMesh -function init_surfaces_iter_face_inner(info, - user_data::ParallelInitSurfacesIterFaceUserData) - @unpack interfaces, mortars, boundaries, mpi_interfaces, mpi_mortars = user_data - # This function is called during `init_surfaces!`, more precisely it is called for each face - # while p4est iterates over the forest. Since `init_surfaces!` can be used to initialize all - # surfaces at once or any subset of them, some of the unpacked values above may be `nothing` if - # they're not supposed to be initialized during this call. That is why we need additional - # `!== nothing` checks below before initializing individual faces. - info_pw = PointerWrapper(info) - if info_pw.sides.elem_count[] == 2 - # Two neighboring elements => Interface or mortar - - # Extract surface data - sides_pw = (load_pointerwrapper_side(info_pw, 1), - load_pointerwrapper_side(info_pw, 2)) - - if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false - # No hanging nodes => normal interface or MPI interface - if sides_pw[1].is.full.is_ghost[] == true || - sides_pw[2].is.full.is_ghost[] == true # remote side => MPI interface - if mpi_interfaces !== nothing - init_mpi_interfaces_iter_face_inner(info_pw, sides_pw, user_data) + resize!(local_neighbor_ids, capacity) + + resize!(node_indices, capacity) + + resize!(local_sides, capacity) + + return nothing + end + + # Create MPI interface container and initialize interface data + function init_mpi_interfaces( + mesh::Union{ParallelP4estMesh, ParallelT8codeMesh}, + equations, basis, elements + ) + NDIMS = ndims(elements) + uEltype = eltype(elements) + + # Initialize container + n_mpi_interfaces = count_required_surfaces(mesh).mpi_interfaces + + _u = Vector{uEltype}( + undef, + 2 * nvariables(equations) * nnodes(basis)^(NDIMS - 1) * + n_mpi_interfaces + ) + u = unsafe_wrap( + Array, pointer(_u), + ( + 2, nvariables(equations), ntuple(_ -> nnodes(basis), NDIMS - 1)..., + n_mpi_interfaces, + ) + ) + + local_neighbor_ids = Vector{Int}(undef, n_mpi_interfaces) + + node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, n_mpi_interfaces) + + local_sides = Vector{Int}(undef, n_mpi_interfaces) + + mpi_interfaces = P4estMPIInterfaceContainer{NDIMS, uEltype, NDIMS + 2}( + u, + local_neighbor_ids, + node_indices, + local_sides, + _u + ) + + init_mpi_interfaces!(mpi_interfaces, mesh) + + return mpi_interfaces + end + + function init_mpi_interfaces!(mpi_interfaces, mesh::ParallelP4estMesh) + init_surfaces!(nothing, nothing, nothing, mpi_interfaces, nothing, mesh) + + return mpi_interfaces + end + + # Container data structure (structure-of-arrays style) for DG L2 mortars + # + # Similar to `P4estMortarContainer`. The field `neighbor_ids` has been split up into + # `local_neighbor_ids` and `local_neighbor_positions` to describe the ids and positions of the locally + # available elements belonging to a particular MPI mortar. Furthermore, `normal_directions` holds + # the normal vectors on the surface of the small elements for each mortar. + mutable struct P4estMPIMortarContainer{ + NDIMS, uEltype <: Real, RealT <: Real, NDIMSP1, + NDIMSP2, NDIMSP3, + } <: AbstractContainer + u::Array{uEltype, NDIMSP3} # [small/large side, variable, position, i, j, mortar] + local_neighbor_ids::Vector{Vector{Int}} # [mortar][ids] + local_neighbor_positions::Vector{Vector{Int}} # [mortar][positions] + node_indices::Matrix{NTuple{NDIMS, Symbol}} # [small/large, mortar] + normal_directions::Array{RealT, NDIMSP2} # [dimension, i, j, position, mortar] + # internal `resize!`able storage + _u::Vector{uEltype} + _node_indices::Vector{NTuple{NDIMS, Symbol}} + _normal_directions::Vector{RealT} + end + + @inline function nmpimortars(mpi_mortars::P4estMPIMortarContainer) + length(mpi_mortars.local_neighbor_ids) + end + @inline Base.ndims(::P4estMPIMortarContainer{NDIMS}) where {NDIMS} = NDIMS + + function Base.resize!(mpi_mortars::P4estMPIMortarContainer, capacity) + @unpack _u, _node_indices, _normal_directions = mpi_mortars + + n_dims = ndims(mpi_mortars) + n_nodes = size(mpi_mortars.u, 4) + n_variables = size(mpi_mortars.u, 2) + + resize!(_u, 2 * n_variables * 2^(n_dims - 1) * n_nodes^(n_dims - 1) * capacity) + mpi_mortars.u = unsafe_wrap( + Array, pointer(_u), + ( + 2, n_variables, 2^(n_dims - 1), + ntuple(_ -> n_nodes, n_dims - 1)..., capacity, + ) + ) + + resize!(mpi_mortars.local_neighbor_ids, capacity) + resize!(mpi_mortars.local_neighbor_positions, capacity) + + resize!(_node_indices, 2 * capacity) + mpi_mortars.node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, capacity)) + + resize!( + _normal_directions, + n_dims * n_nodes^(n_dims - 1) * 2^(n_dims - 1) * capacity + ) + mpi_mortars.normal_directions = unsafe_wrap( + Array, pointer(_normal_directions), + ( + n_dims, + ntuple(_ -> n_nodes, n_dims - 1)..., + 2^(n_dims - 1), capacity, + ) + ) + + return nothing + end + + # Create MPI mortar container and initialize MPI mortar data + function init_mpi_mortars( + mesh::Union{ParallelP4estMesh, ParallelT8codeMesh}, equations, + basis, elements + ) + NDIMS = ndims(mesh) + RealT = real(mesh) + uEltype = eltype(elements) + + # Initialize container + n_mpi_mortars = count_required_surfaces(mesh).mpi_mortars + + _u = Vector{uEltype}( + undef, + 2 * nvariables(equations) * 2^(NDIMS - 1) * + nnodes(basis)^(NDIMS - 1) * n_mpi_mortars + ) + u = unsafe_wrap( + Array, pointer(_u), + ( + 2, nvariables(equations), 2^(NDIMS - 1), + ntuple(_ -> nnodes(basis), NDIMS - 1)..., n_mpi_mortars, + ) + ) + + local_neighbor_ids = fill(Vector{Int}(), n_mpi_mortars) + local_neighbor_positions = fill(Vector{Int}(), n_mpi_mortars) + + _node_indices = Vector{NTuple{NDIMS, Symbol}}(undef, 2 * n_mpi_mortars) + node_indices = unsafe_wrap(Array, pointer(_node_indices), (2, n_mpi_mortars)) + + _normal_directions = Vector{RealT}( + undef, + NDIMS * nnodes(basis)^(NDIMS - 1) * + 2^(NDIMS - 1) * n_mpi_mortars + ) + normal_directions = unsafe_wrap( + Array, pointer(_normal_directions), + ( + NDIMS, ntuple(_ -> nnodes(basis), NDIMS - 1)..., + 2^(NDIMS - 1), n_mpi_mortars, + ) + ) + + mpi_mortars = P4estMPIMortarContainer{ + NDIMS, uEltype, RealT, NDIMS + 1, NDIMS + 2, + NDIMS + 3, + }( + u, local_neighbor_ids, + local_neighbor_positions, + node_indices, normal_directions, + _u, _node_indices, + _normal_directions + ) + + if n_mpi_mortars > 0 + init_mpi_mortars!(mpi_mortars, mesh, basis, elements) + end + + return mpi_mortars + end + + function init_mpi_mortars!(mpi_mortars, mesh::ParallelP4estMesh, basis, elements) + init_surfaces!(nothing, nothing, nothing, nothing, mpi_mortars, mesh) + init_normal_directions!(mpi_mortars, basis, elements) + + return mpi_mortars + end + + # Overload init! function for regular interfaces, regular mortars and boundaries since they must + # call the appropriate init_surfaces! function for parallel p4est meshes + function init_interfaces!(interfaces, mesh::ParallelP4estMesh) + init_surfaces!(interfaces, nothing, nothing, nothing, nothing, mesh) + + return interfaces + end + + function init_mortars!(mortars, mesh::ParallelP4estMesh) + init_surfaces!(nothing, mortars, nothing, nothing, nothing, mesh) + + return mortars + end + + function init_boundaries!(boundaries, mesh::ParallelP4estMesh) + init_surfaces!(nothing, nothing, boundaries, nothing, nothing, mesh) + + return boundaries + end + + function reinitialize_containers!(mesh::ParallelP4estMesh, equations, dg::DGSEM, cache) + # Make sure to re-create ghost layer before reinitializing MPI-related containers + update_ghost_layer!(mesh) + + # Re-initialize elements container + @unpack elements = cache + resize!(elements, ncells(mesh)) + init_elements!(elements, mesh, dg.basis) + + required = count_required_surfaces(mesh) + + # resize interfaces container + @unpack interfaces = cache + resize!(interfaces, required.interfaces) + + # resize boundaries container + @unpack boundaries = cache + resize!(boundaries, required.boundaries) + + # resize mortars container + @unpack mortars = cache + resize!(mortars, required.mortars) + + # resize mpi_interfaces container + @unpack mpi_interfaces = cache + resize!(mpi_interfaces, required.mpi_interfaces) + + # resize mpi_mortars container + @unpack mpi_mortars = cache + resize!(mpi_mortars, required.mpi_mortars) + + # re-initialize containers together to reduce + # the number of iterations over the mesh in p4est + init_surfaces!(interfaces, mortars, boundaries, mpi_interfaces, mpi_mortars, mesh) + + # re-initialize MPI cache + @unpack mpi_cache = cache + init_mpi_cache!( + mpi_cache, mesh, mpi_interfaces, mpi_mortars, + nvariables(equations), nnodes(dg), eltype(elements) + ) + + # re-initialize and distribute normal directions of MPI mortars; requires MPI communication, so + # the MPI cache must be re-initialized before + init_normal_directions!(mpi_mortars, dg.basis, elements) + exchange_normal_directions!(mpi_mortars, mpi_cache, mesh, nnodes(dg)) + end + + # A helper struct used in initialization methods below + mutable struct ParallelInitSurfacesIterFaceUserData{ + Interfaces, Mortars, Boundaries, + MPIInterfaces, MPIMortars, Mesh, + } + interfaces::Interfaces + interface_id::Int + mortars::Mortars + mortar_id::Int + boundaries::Boundaries + boundary_id::Int + mpi_interfaces::MPIInterfaces + mpi_interface_id::Int + mpi_mortars::MPIMortars + mpi_mortar_id::Int + mesh::Mesh + end + + function ParallelInitSurfacesIterFaceUserData( + interfaces, mortars, boundaries, + mpi_interfaces, mpi_mortars, mesh + ) + return ParallelInitSurfacesIterFaceUserData{ + typeof(interfaces), typeof(mortars), + typeof(boundaries), + typeof(mpi_interfaces), + typeof(mpi_mortars), typeof(mesh), + }( + interfaces, + 1, + mortars, + 1, + boundaries, + 1, + mpi_interfaces, + 1, + mpi_mortars, + 1, + mesh + ) + end + + function init_surfaces_iter_face_parallel(info, user_data) + # Unpack user_data + data = unsafe_pointer_to_objref(Ptr{ParallelInitSurfacesIterFaceUserData}(user_data)) + + # Function barrier because the unpacked user_data above is type-unstable + init_surfaces_iter_face_inner(info, data) + end + + # 2D + function cfunction(::typeof(init_surfaces_iter_face_parallel), ::Val{2}) + @cfunction( + init_surfaces_iter_face_parallel, Cvoid, + (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid}) + ) + end + # 3D + function cfunction(::typeof(init_surfaces_iter_face_parallel), ::Val{3}) + @cfunction( + init_surfaces_iter_face_parallel, Cvoid, + (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid}) + ) + end + + # Function barrier for type stability, overload for parallel P4estMesh + function init_surfaces_iter_face_inner( + info, + user_data::ParallelInitSurfacesIterFaceUserData + ) + @unpack interfaces, mortars, boundaries, mpi_interfaces, mpi_mortars = user_data + # This function is called during `init_surfaces!`, more precisely it is called for each face + # while p4est iterates over the forest. Since `init_surfaces!` can be used to initialize all + # surfaces at once or any subset of them, some of the unpacked values above may be `nothing` if + # they're not supposed to be initialized during this call. That is why we need additional + # `!== nothing` checks below before initializing individual faces. + info_pw = PointerWrapper(info) + if info_pw.sides.elem_count[] == 2 + # Two neighboring elements => Interface or mortar + + # Extract surface data + sides_pw = ( + load_pointerwrapper_side(info_pw, 1), + load_pointerwrapper_side(info_pw, 2), + ) + + if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false + # No hanging nodes => normal interface or MPI interface + if sides_pw[1].is.full.is_ghost[] == true || + sides_pw[2].is.full.is_ghost[] == true # remote side => MPI interface + if mpi_interfaces !== nothing + init_mpi_interfaces_iter_face_inner(info_pw, sides_pw, user_data) + end + else + if interfaces !== nothing + init_interfaces_iter_face_inner(info_pw, sides_pw, user_data) + end end else - if interfaces !== nothing - init_interfaces_iter_face_inner(info_pw, sides_pw, user_data) - end - end - else - # Hanging nodes => mortar or MPI mortar - # First, we check which side is hanging, i.e., on which side we have the refined cells. - # Then we check if any of the refined cells or the coarse cell are "ghost" cells, i.e., they - # belong to another rank. That way we can determine if this is a regular mortar or MPI mortar - if sides_pw[1].is_hanging[] == true - @assert sides_pw[2].is_hanging[] == false - if any(sides_pw[1].is.hanging.is_ghost[] .== true) || - sides_pw[2].is.full.is_ghost[] == true - face_has_ghost_side = true - else - face_has_ghost_side = false + # Hanging nodes => mortar or MPI mortar + # First, we check which side is hanging, i.e., on which side we have the refined cells. + # Then we check if any of the refined cells or the coarse cell are "ghost" cells, i.e., they + # belong to another rank. That way we can determine if this is a regular mortar or MPI mortar + if sides_pw[1].is_hanging[] == true + @assert sides_pw[2].is_hanging[] == false + if any(sides_pw[1].is.hanging.is_ghost[] .== true) || + sides_pw[2].is.full.is_ghost[] == true + face_has_ghost_side = true + else + face_has_ghost_side = false + end + else # sides_pw[2].is_hanging[] == true + @assert sides_pw[1].is_hanging[] == false + if sides_pw[1].is.full.is_ghost[] == true || + any(sides_pw[2].is.hanging.is_ghost[] .== true) + face_has_ghost_side = true + else + face_has_ghost_side = false + end end - else # sides_pw[2].is_hanging[] == true - @assert sides_pw[1].is_hanging[] == false - if sides_pw[1].is.full.is_ghost[] == true || - any(sides_pw[2].is.hanging.is_ghost[] .== true) - face_has_ghost_side = true - else - face_has_ghost_side = false + # Initialize mortar or MPI mortar + if face_has_ghost_side && mpi_mortars !== nothing + init_mpi_mortars_iter_face_inner(info_pw, sides_pw, user_data) + elseif !face_has_ghost_side && mortars !== nothing + init_mortars_iter_face_inner(info_pw, sides_pw, user_data) end end - # Initialize mortar or MPI mortar - if face_has_ghost_side && mpi_mortars !== nothing - init_mpi_mortars_iter_face_inner(info_pw, sides_pw, user_data) - elseif !face_has_ghost_side && mortars !== nothing - init_mortars_iter_face_inner(info_pw, sides_pw, user_data) + elseif info_pw.sides.elem_count[] == 1 + # One neighboring elements => boundary + if boundaries !== nothing + init_boundaries_iter_face_inner(info_pw, user_data) end end - elseif info_pw.sides.elem_count[] == 1 - # One neighboring elements => boundary - if boundaries !== nothing - init_boundaries_iter_face_inner(info_pw, user_data) - end - end - return nothing -end - -function init_surfaces!(interfaces, mortars, boundaries, mpi_interfaces, mpi_mortars, - mesh::ParallelP4estMesh) - # Let p4est iterate over all interfaces and call init_surfaces_iter_face - iter_face_c = cfunction(init_surfaces_iter_face_parallel, Val(ndims(mesh))) - user_data = ParallelInitSurfacesIterFaceUserData(interfaces, mortars, boundaries, - mpi_interfaces, mpi_mortars, mesh) - - iterate_p4est(mesh.p4est, user_data; ghost_layer = mesh.ghost, - iter_face_c = iter_face_c) - - return nothing -end - -# Initialization of MPI interfaces after the function barrier -function init_mpi_interfaces_iter_face_inner(info_pw, sides_pw, user_data) - @unpack mpi_interfaces, mpi_interface_id, mesh = user_data - user_data.mpi_interface_id += 1 - - if sides_pw[1].is.full.is_ghost[] == true - local_side = 2 - elseif sides_pw[2].is.full.is_ghost[] == true - local_side = 1 - else - error("should not happen") + return nothing end - # Get local tree, one-based indexing - tree_pw = load_pointerwrapper_tree(mesh.p4est, sides_pw[local_side].treeid[] + 1) - # Quadrant numbering offset of the local quadrant at this interface - offset = tree_pw.quadrants_offset[] - tree_quad_id = sides_pw[local_side].is.full.quadid[] # quadid in the local tree - # ID of the local neighboring quad, cumulative over local trees - local_quad_id = offset + tree_quad_id - - # p4est uses zero-based indexing, convert to one-based indexing - mpi_interfaces.local_neighbor_ids[mpi_interface_id] = local_quad_id + 1 - mpi_interfaces.local_sides[mpi_interface_id] = local_side - - # Face at which the interface lies - faces = (sides_pw[1].face[], sides_pw[2].face[]) - - # Save mpi_interfaces.node_indices dimension specific in containers_[23]d_parallel.jl - init_mpi_interface_node_indices!(mpi_interfaces, faces, local_side, - info_pw.orientation[], - mpi_interface_id) - - return nothing -end - -# Initialization of MPI mortars after the function barrier -function init_mpi_mortars_iter_face_inner(info_pw, sides_pw, user_data) - @unpack mpi_mortars, mpi_mortar_id, mesh = user_data - user_data.mpi_mortar_id += 1 - - # Get Tuple of adjacent trees, one-based indexing - trees_pw = (load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1), - load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1)) - # Quadrant numbering offsets of the quadrants at this mortar - offsets = SVector(trees_pw[1].quadrants_offset[], - trees_pw[2].quadrants_offset[]) - - if sides_pw[1].is_hanging[] == true - hanging_side = 1 - full_side = 2 - else # sides_pw[2].is_hanging[] == true - hanging_side = 2 - full_side = 1 - end - # Just be sure before accessing is.full or is.hanging later - @assert sides_pw[full_side].is_hanging[] == false - @assert sides_pw[hanging_side].is_hanging[] == true - - # Find small quads that are locally available - local_small_quad_positions = findall(sides_pw[hanging_side].is.hanging.is_ghost[] .== - false) - - # Get id of local small quadrants within their tree - # Indexing CBinding.Caccessor via a Vector does not work here -> use map instead - tree_small_quad_ids = map(p -> sides_pw[hanging_side].is.hanging.quadid[][p], - local_small_quad_positions) - local_small_quad_ids = offsets[hanging_side] .+ tree_small_quad_ids # ids cumulative over local trees - - # Determine if large quadrant is available and if yes, determine its id - if sides_pw[full_side].is.full.is_ghost[] == false - local_large_quad_id = offsets[full_side] + sides_pw[full_side].is.full.quadid[] - else - local_large_quad_id = -1 # large quad is ghost + function init_surfaces!( + interfaces, mortars, boundaries, mpi_interfaces, mpi_mortars, + mesh::ParallelP4estMesh + ) + # Let p4est iterate over all interfaces and call init_surfaces_iter_face + iter_face_c = cfunction(init_surfaces_iter_face_parallel, Val(ndims(mesh))) + user_data = ParallelInitSurfacesIterFaceUserData( + interfaces, mortars, boundaries, + mpi_interfaces, mpi_mortars, mesh + ) + + iterate_p4est( + mesh.p4est, user_data; ghost_layer = mesh.ghost, + iter_face_c = iter_face_c + ) + + return nothing end - # Write data to mortar container, convert to 1-based indexing - # Start with small elements - local_neighbor_ids = local_small_quad_ids .+ 1 - local_neighbor_positions = local_small_quad_positions - # Add large element information if it is locally available - if local_large_quad_id > -1 - push!(local_neighbor_ids, local_large_quad_id + 1) # convert to 1-based index - push!(local_neighbor_positions, 2^(ndims(mesh) - 1) + 1) + # Initialization of MPI interfaces after the function barrier + function init_mpi_interfaces_iter_face_inner(info_pw, sides_pw, user_data) + @unpack mpi_interfaces, mpi_interface_id, mesh = user_data + user_data.mpi_interface_id += 1 + + if sides_pw[1].is.full.is_ghost[] == true + local_side = 2 + elseif sides_pw[2].is.full.is_ghost[] == true + local_side = 1 + else + error("should not happen") + end + + # Get local tree, one-based indexing + tree_pw = load_pointerwrapper_tree(mesh.p4est, sides_pw[local_side].treeid[] + 1) + # Quadrant numbering offset of the local quadrant at this interface + offset = tree_pw.quadrants_offset[] + tree_quad_id = sides_pw[local_side].is.full.quadid[] # quadid in the local tree + # ID of the local neighboring quad, cumulative over local trees + local_quad_id = offset + tree_quad_id + + # p4est uses zero-based indexing, convert to one-based indexing + mpi_interfaces.local_neighbor_ids[mpi_interface_id] = local_quad_id + 1 + mpi_interfaces.local_sides[mpi_interface_id] = local_side + + # Face at which the interface lies + faces = (sides_pw[1].face[], sides_pw[2].face[]) + + # Save mpi_interfaces.node_indices dimension specific in containers_[23]d_parallel.jl + init_mpi_interface_node_indices!( + mpi_interfaces, faces, local_side, + info_pw.orientation[], + mpi_interface_id + ) + + return nothing end - mpi_mortars.local_neighbor_ids[mpi_mortar_id] = local_neighbor_ids - mpi_mortars.local_neighbor_positions[mpi_mortar_id] = local_neighbor_positions - - # init_mortar_node_indices! expects side 1 to contain small elements - faces = (sides_pw[hanging_side].face[], sides_pw[full_side].face[]) - init_mortar_node_indices!(mpi_mortars, faces, info_pw.orientation[], mpi_mortar_id) - - return nothing -end - -# Iterate over all interfaces and count -# - (inner) interfaces -# - mortars -# - boundaries -# - (MPI) interfaces at subdomain boundaries -# - (MPI) mortars at subdomain boundaries -# and collect the numbers in `user_data` in this order. -function count_surfaces_iter_face_parallel(info, user_data) - info_pw = PointerWrapper(info) - if info_pw.sides.elem_count[] == 2 - # Two neighboring elements => Interface or mortar - - # Extract surface data - sides_pw = (load_pointerwrapper_side(info_pw, 1), - load_pointerwrapper_side(info_pw, 2)) - - if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false - # No hanging nodes => normal interface or MPI interface - if sides_pw[1].is.full.is_ghost[] == true || - sides_pw[2].is.full.is_ghost[] == true # remote side => MPI interface - # Unpack user_data = [mpi_interface_count] and increment mpi_interface_count - pw = PointerWrapper(Int, user_data) - id = pw[4] - pw[4] = id + 1 - else - # Unpack user_data = [interface_count] and increment interface_count - pw = PointerWrapper(Int, user_data) - id = pw[1] - pw[1] = id + 1 - end + # Initialization of MPI mortars after the function barrier + function init_mpi_mortars_iter_face_inner(info_pw, sides_pw, user_data) + @unpack mpi_mortars, mpi_mortar_id, mesh = user_data + user_data.mpi_mortar_id += 1 + + # Get Tuple of adjacent trees, one-based indexing + trees_pw = ( + load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1), + load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1), + ) + # Quadrant numbering offsets of the quadrants at this mortar + offsets = SVector( + trees_pw[1].quadrants_offset[], + trees_pw[2].quadrants_offset[] + ) + + if sides_pw[1].is_hanging[] == true + hanging_side = 1 + full_side = 2 + else # sides_pw[2].is_hanging[] == true + hanging_side = 2 + full_side = 1 + end + # Just be sure before accessing is.full or is.hanging later + @assert sides_pw[full_side].is_hanging[] == false + @assert sides_pw[hanging_side].is_hanging[] == true + + # Find small quads that are locally available + local_small_quad_positions = findall( + sides_pw[hanging_side].is.hanging.is_ghost[] .== + false + ) + + # Get id of local small quadrants within their tree + # Indexing CBinding.Caccessor via a Vector does not work here -> use map instead + tree_small_quad_ids = map( + p -> sides_pw[hanging_side].is.hanging.quadid[][p], + local_small_quad_positions + ) + local_small_quad_ids = offsets[hanging_side] .+ tree_small_quad_ids # ids cumulative over local trees + + # Determine if large quadrant is available and if yes, determine its id + if sides_pw[full_side].is.full.is_ghost[] == false + local_large_quad_id = offsets[full_side] + sides_pw[full_side].is.full.quadid[] else - # Hanging nodes => mortar or MPI mortar - # First, we check which side is hanging, i.e., on which side we have the refined cells. - # Then we check if any of the refined cells or the coarse cell are "ghost" cells, i.e., they - # belong to another rank. That way we can determine if this is a regular mortar or MPI mortar - if sides_pw[1].is_hanging[] == true - @assert sides_pw[2].is_hanging[] == false - if any(sides_pw[1].is.hanging.is_ghost[] .== true) || - sides_pw[2].is.full.is_ghost[] == true - face_has_ghost_side = true - else - face_has_ghost_side = false - end - else # sides_pw[2].is_hanging[] == true - @assert sides_pw[1].is_hanging[] == false + local_large_quad_id = -1 # large quad is ghost + end + + # Write data to mortar container, convert to 1-based indexing + # Start with small elements + local_neighbor_ids = local_small_quad_ids .+ 1 + local_neighbor_positions = local_small_quad_positions + # Add large element information if it is locally available + if local_large_quad_id > -1 + push!(local_neighbor_ids, local_large_quad_id + 1) # convert to 1-based index + push!(local_neighbor_positions, 2^(ndims(mesh) - 1) + 1) + end + + mpi_mortars.local_neighbor_ids[mpi_mortar_id] = local_neighbor_ids + mpi_mortars.local_neighbor_positions[mpi_mortar_id] = local_neighbor_positions + + # init_mortar_node_indices! expects side 1 to contain small elements + faces = (sides_pw[hanging_side].face[], sides_pw[full_side].face[]) + init_mortar_node_indices!(mpi_mortars, faces, info_pw.orientation[], mpi_mortar_id) + + return nothing + end + + # Iterate over all interfaces and count + # - (inner) interfaces + # - mortars + # - boundaries + # - (MPI) interfaces at subdomain boundaries + # - (MPI) mortars at subdomain boundaries + # and collect the numbers in `user_data` in this order. + function count_surfaces_iter_face_parallel(info, user_data) + info_pw = PointerWrapper(info) + if info_pw.sides.elem_count[] == 2 + # Two neighboring elements => Interface or mortar + + # Extract surface data + sides_pw = ( + load_pointerwrapper_side(info_pw, 1), + load_pointerwrapper_side(info_pw, 2), + ) + + if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false + # No hanging nodes => normal interface or MPI interface if sides_pw[1].is.full.is_ghost[] == true || - any(sides_pw[2].is.hanging.is_ghost[] .== true) - face_has_ghost_side = true + sides_pw[2].is.full.is_ghost[] == true # remote side => MPI interface + # Unpack user_data = [mpi_interface_count] and increment mpi_interface_count + pw = PointerWrapper(Int, user_data) + id = pw[4] + pw[4] = id + 1 else - face_has_ghost_side = false + # Unpack user_data = [interface_count] and increment interface_count + pw = PointerWrapper(Int, user_data) + id = pw[1] + pw[1] = id + 1 end - end - if face_has_ghost_side - # Unpack user_data = [mpi_mortar_count] and increment mpi_mortar_count - pw = PointerWrapper(Int, user_data) - id = pw[5] - pw[5] = id + 1 else - # Unpack user_data = [mortar_count] and increment mortar_count - pw = PointerWrapper(Int, user_data) - id = pw[2] - pw[2] = id + 1 + # Hanging nodes => mortar or MPI mortar + # First, we check which side is hanging, i.e., on which side we have the refined cells. + # Then we check if any of the refined cells or the coarse cell are "ghost" cells, i.e., they + # belong to another rank. That way we can determine if this is a regular mortar or MPI mortar + if sides_pw[1].is_hanging[] == true + @assert sides_pw[2].is_hanging[] == false + if any(sides_pw[1].is.hanging.is_ghost[] .== true) || + sides_pw[2].is.full.is_ghost[] == true + face_has_ghost_side = true + else + face_has_ghost_side = false + end + else # sides_pw[2].is_hanging[] == true + @assert sides_pw[1].is_hanging[] == false + if sides_pw[1].is.full.is_ghost[] == true || + any(sides_pw[2].is.hanging.is_ghost[] .== true) + face_has_ghost_side = true + else + face_has_ghost_side = false + end + end + if face_has_ghost_side + # Unpack user_data = [mpi_mortar_count] and increment mpi_mortar_count + pw = PointerWrapper(Int, user_data) + id = pw[5] + pw[5] = id + 1 + else + # Unpack user_data = [mortar_count] and increment mortar_count + pw = PointerWrapper(Int, user_data) + id = pw[2] + pw[2] = id + 1 + end end + elseif info_pw.sides.elem_count[] == 1 + # One neighboring elements => boundary + + # Unpack user_data = [boundary_count] and increment boundary_count + pw = PointerWrapper(Int, user_data) + id = pw[3] + pw[3] = id + 1 end - elseif info_pw.sides.elem_count[] == 1 - # One neighboring elements => boundary - # Unpack user_data = [boundary_count] and increment boundary_count - pw = PointerWrapper(Int, user_data) - id = pw[3] - pw[3] = id + 1 + return nothing end - return nothing -end - -# 2D -function cfunction(::typeof(count_surfaces_iter_face_parallel), ::Val{2}) - @cfunction(count_surfaces_iter_face_parallel, Cvoid, - (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(count_surfaces_iter_face_parallel), ::Val{3}) - @cfunction(count_surfaces_iter_face_parallel, Cvoid, - (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid})) -end + # 2D + function cfunction(::typeof(count_surfaces_iter_face_parallel), ::Val{2}) + @cfunction( + count_surfaces_iter_face_parallel, Cvoid, + (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid}) + ) + end + # 3D + function cfunction(::typeof(count_surfaces_iter_face_parallel), ::Val{3}) + @cfunction( + count_surfaces_iter_face_parallel, Cvoid, + (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid}) + ) + end -function count_required_surfaces(mesh::ParallelP4estMesh) - # Let p4est iterate over all interfaces and call count_surfaces_iter_face_parallel - iter_face_c = cfunction(count_surfaces_iter_face_parallel, Val(ndims(mesh))) + function count_required_surfaces(mesh::ParallelP4estMesh) + # Let p4est iterate over all interfaces and call count_surfaces_iter_face_parallel + iter_face_c = cfunction(count_surfaces_iter_face_parallel, Val(ndims(mesh))) - # interfaces, mortars, boundaries, mpi_interfaces, mpi_mortars - user_data = [0, 0, 0, 0, 0] + # interfaces, mortars, boundaries, mpi_interfaces, mpi_mortars + user_data = [0, 0, 0, 0, 0] - iterate_p4est(mesh.p4est, user_data; ghost_layer = mesh.ghost, - iter_face_c = iter_face_c) + iterate_p4est( + mesh.p4est, user_data; ghost_layer = mesh.ghost, + iter_face_c = iter_face_c + ) - # Return counters - return (interfaces = user_data[1], + # Return counters + return ( + interfaces = user_data[1], mortars = user_data[2], boundaries = user_data[3], mpi_interfaces = user_data[4], - mpi_mortars = user_data[5]) -end + mpi_mortars = user_data[5], + ) + end end # @muladd diff --git a/src/solvers/dgsem_p4est/containers_parallel_2d.jl b/src/solvers/dgsem_p4est/containers_parallel_2d.jl index d531d33821..8cdcf23b50 100644 --- a/src/solvers/dgsem_p4est/containers_parallel_2d.jl +++ b/src/solvers/dgsem_p4est/containers_parallel_2d.jl @@ -3,79 +3,91 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Initialize node_indices of MPI interface container -@inline function init_mpi_interface_node_indices!(mpi_interfaces::P4estMPIInterfaceContainer{2}, - faces, local_side, orientation, - mpi_interface_id) - # Align interface in positive coordinate direction of primary element. - # For orientation == 1, the secondary element needs to be indexed backwards - # relative to the interface. - if local_side == 1 || orientation == 0 - # Forward indexing - i = :i_forward - else - # Backward indexing - i = :i_backward - end + # Initialize node_indices of MPI interface container + @inline function init_mpi_interface_node_indices!( + mpi_interfaces::P4estMPIInterfaceContainer{2}, + faces, local_side, orientation, + mpi_interface_id + ) + # Align interface in positive coordinate direction of primary element. + # For orientation == 1, the secondary element needs to be indexed backwards + # relative to the interface. + if local_side == 1 || orientation == 0 + # Forward indexing + i = :i_forward + else + # Backward indexing + i = :i_backward + end - if faces[local_side] == 0 - # Index face in negative x-direction - mpi_interfaces.node_indices[mpi_interface_id] = (:begin, i) - elseif faces[local_side] == 1 - # Index face in positive x-direction - mpi_interfaces.node_indices[mpi_interface_id] = (:end, i) - elseif faces[local_side] == 2 - # Index face in negative y-direction - mpi_interfaces.node_indices[mpi_interface_id] = (i, :begin) - else # faces[local_side] == 3 - # Index face in positive y-direction - mpi_interfaces.node_indices[mpi_interface_id] = (i, :end) - end + if faces[local_side] == 0 + # Index face in negative x-direction + mpi_interfaces.node_indices[mpi_interface_id] = (:begin, i) + elseif faces[local_side] == 1 + # Index face in positive x-direction + mpi_interfaces.node_indices[mpi_interface_id] = (:end, i) + elseif faces[local_side] == 2 + # Index face in negative y-direction + mpi_interfaces.node_indices[mpi_interface_id] = (i, :begin) + else # faces[local_side] == 3 + # Index face in positive y-direction + mpi_interfaces.node_indices[mpi_interface_id] = (i, :end) + end - return mpi_interfaces -end + return mpi_interfaces + end -# Normal directions of small element surfaces are needed to calculate the mortar fluxes. Initialize -# them for locally available small elements. -function init_normal_directions!(mpi_mortars::P4estMPIMortarContainer{2}, basis, - elements) - @unpack local_neighbor_ids, local_neighbor_positions, node_indices = mpi_mortars - @unpack contravariant_vectors = elements - index_range = eachnode(basis) + # Normal directions of small element surfaces are needed to calculate the mortar fluxes. Initialize + # them for locally available small elements. + function init_normal_directions!( + mpi_mortars::P4estMPIMortarContainer{2}, basis, + elements + ) + @unpack local_neighbor_ids, local_neighbor_positions, node_indices = mpi_mortars + @unpack contravariant_vectors = elements + index_range = eachnode(basis) - @threaded for mortar in 1:nmpimortars(mpi_mortars) - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) + @threaded for mortar in 1:nmpimortars(mpi_mortars) + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) - i_small_start, i_small_step = index_to_start_step_2d(small_indices[1], - index_range) - j_small_start, j_small_step = index_to_start_step_2d(small_indices[2], - index_range) + i_small_start, i_small_step = index_to_start_step_2d( + small_indices[1], + index_range + ) + j_small_start, j_small_step = index_to_start_step_2d( + small_indices[2], + index_range + ) - for (element, position) in zip(local_neighbor_ids[mortar], - local_neighbor_positions[mortar]) - # ignore large elements - if position == 3 - continue - end + for (element, position) in zip( + local_neighbor_ids[mortar], + local_neighbor_positions[mortar] + ) + # ignore large elements + if position == 3 + continue + end - i_small = i_small_start - j_small = j_small_start - for node in eachnode(basis) - # Get the normal direction on the small element. - # Note, contravariant vectors at interfaces in negative coordinate direction - # are pointing inwards. This is handled by `get_normal_direction`. - normal_direction = get_normal_direction(small_direction, - contravariant_vectors, - i_small, j_small, element) - @views mpi_mortars.normal_directions[:, node, position, mortar] .= normal_direction + i_small = i_small_start + j_small = j_small_start + for node in eachnode(basis) + # Get the normal direction on the small element. + # Note, contravariant vectors at interfaces in negative coordinate direction + # are pointing inwards. This is handled by `get_normal_direction`. + normal_direction = get_normal_direction( + small_direction, + contravariant_vectors, + i_small, j_small, element + ) + @views mpi_mortars.normal_directions[:, node, position, mortar] .= normal_direction - i_small += i_small_step - j_small += j_small_step + i_small += i_small_step + j_small += j_small_step + end end end end -end end # muladd diff --git a/src/solvers/dgsem_p4est/containers_parallel_3d.jl b/src/solvers/dgsem_p4est/containers_parallel_3d.jl index 56f0a543b9..1cdceef257 100644 --- a/src/solvers/dgsem_p4est/containers_parallel_3d.jl +++ b/src/solvers/dgsem_p4est/containers_parallel_3d.jl @@ -3,147 +3,191 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Initialize node_indices of MPI interface container -@inline function init_mpi_interface_node_indices!(mpi_interfaces::P4estMPIInterfaceContainer{3}, - faces, local_side, orientation, - mpi_interface_id) - # Align interface at the primary element (primary element has surface indices (:i_forward, :j_forward)). - # The secondary element needs to be indexed differently. - if local_side == 1 - surface_index1 = :i_forward - surface_index2 = :j_forward - else # local_side == 2 - surface_index1, surface_index2 = orientation_to_indices_p4est(faces[2], - faces[1], - orientation) - end - - if faces[local_side] == 0 - # Index face in negative x-direction - mpi_interfaces.node_indices[mpi_interface_id] = (:begin, surface_index1, - surface_index2) - elseif faces[local_side] == 1 - # Index face in positive x-direction - mpi_interfaces.node_indices[mpi_interface_id] = (:end, surface_index1, - surface_index2) - elseif faces[local_side] == 2 - # Index face in negative y-direction - mpi_interfaces.node_indices[mpi_interface_id] = (surface_index1, :begin, - surface_index2) - elseif faces[local_side] == 3 - # Index face in positive y-direction - mpi_interfaces.node_indices[mpi_interface_id] = (surface_index1, :end, - surface_index2) - elseif faces[local_side] == 4 - # Index face in negative z-direction - mpi_interfaces.node_indices[mpi_interface_id] = (surface_index1, surface_index2, - :begin) - else # faces[local_side] == 5 - # Index face in positive z-direction - mpi_interfaces.node_indices[mpi_interface_id] = (surface_index1, surface_index2, - :end) - end - - return mpi_interfaces -end - -# Initialize node_indices of MPI mortar container. Works the same as for its serial counterpart. -# faces[1] is expected to be the face of the small side. -@inline function init_mortar_node_indices!(mortars::P4estMPIMortarContainer{3}, - faces, orientation, mortar_id) - for side in 1:2 - # Align mortar at small side. - # The large side needs to be indexed differently. - if side == 1 + # Initialize node_indices of MPI interface container + @inline function init_mpi_interface_node_indices!( + mpi_interfaces::P4estMPIInterfaceContainer{3}, + faces, local_side, orientation, + mpi_interface_id + ) + # Align interface at the primary element (primary element has surface indices (:i_forward, :j_forward)). + # The secondary element needs to be indexed differently. + if local_side == 1 surface_index1 = :i_forward surface_index2 = :j_forward - else - surface_index1, surface_index2 = orientation_to_indices_p4est(faces[2], - faces[1], - orientation) + else # local_side == 2 + surface_index1, surface_index2 = orientation_to_indices_p4est( + faces[2], + faces[1], + orientation + ) end - if faces[side] == 0 + if faces[local_side] == 0 # Index face in negative x-direction - mortars.node_indices[side, mortar_id] = (:begin, surface_index1, - surface_index2) - elseif faces[side] == 1 + mpi_interfaces.node_indices[mpi_interface_id] = ( + :begin, surface_index1, + surface_index2, + ) + elseif faces[local_side] == 1 # Index face in positive x-direction - mortars.node_indices[side, mortar_id] = (:end, surface_index1, - surface_index2) - elseif faces[side] == 2 + mpi_interfaces.node_indices[mpi_interface_id] = ( + :end, surface_index1, + surface_index2, + ) + elseif faces[local_side] == 2 # Index face in negative y-direction - mortars.node_indices[side, mortar_id] = (surface_index1, :begin, - surface_index2) - elseif faces[side] == 3 + mpi_interfaces.node_indices[mpi_interface_id] = ( + surface_index1, :begin, + surface_index2, + ) + elseif faces[local_side] == 3 # Index face in positive y-direction - mortars.node_indices[side, mortar_id] = (surface_index1, :end, - surface_index2) - elseif faces[side] == 4 + mpi_interfaces.node_indices[mpi_interface_id] = ( + surface_index1, :end, + surface_index2, + ) + elseif faces[local_side] == 4 # Index face in negative z-direction - mortars.node_indices[side, mortar_id] = (surface_index1, surface_index2, - :begin) - else # faces[side] == 5 + mpi_interfaces.node_indices[mpi_interface_id] = ( + surface_index1, surface_index2, + :begin, + ) + else # faces[local_side] == 5 # Index face in positive z-direction - mortars.node_indices[side, mortar_id] = (surface_index1, surface_index2, - :end) + mpi_interfaces.node_indices[mpi_interface_id] = ( + surface_index1, surface_index2, + :end, + ) end + + return mpi_interfaces end - return mortars -end + # Initialize node_indices of MPI mortar container. Works the same as for its serial counterpart. + # faces[1] is expected to be the face of the small side. + @inline function init_mortar_node_indices!( + mortars::P4estMPIMortarContainer{3}, + faces, orientation, mortar_id + ) + for side in 1:2 + # Align mortar at small side. + # The large side needs to be indexed differently. + if side == 1 + surface_index1 = :i_forward + surface_index2 = :j_forward + else + surface_index1, surface_index2 = orientation_to_indices_p4est( + faces[2], + faces[1], + orientation + ) + end + + if faces[side] == 0 + # Index face in negative x-direction + mortars.node_indices[side, mortar_id] = ( + :begin, surface_index1, + surface_index2, + ) + elseif faces[side] == 1 + # Index face in positive x-direction + mortars.node_indices[side, mortar_id] = ( + :end, surface_index1, + surface_index2, + ) + elseif faces[side] == 2 + # Index face in negative y-direction + mortars.node_indices[side, mortar_id] = ( + surface_index1, :begin, + surface_index2, + ) + elseif faces[side] == 3 + # Index face in positive y-direction + mortars.node_indices[side, mortar_id] = ( + surface_index1, :end, + surface_index2, + ) + elseif faces[side] == 4 + # Index face in negative z-direction + mortars.node_indices[side, mortar_id] = ( + surface_index1, surface_index2, + :begin, + ) + else # faces[side] == 5 + # Index face in positive z-direction + mortars.node_indices[side, mortar_id] = ( + surface_index1, surface_index2, + :end, + ) + end + end -# Normal directions of small element surfaces are needed to calculate the mortar fluxes. Initialize -# them for locally available small elements. -function init_normal_directions!(mpi_mortars::P4estMPIMortarContainer{3}, basis, - elements) - @unpack local_neighbor_ids, local_neighbor_positions, node_indices = mpi_mortars - @unpack contravariant_vectors = elements - index_range = eachnode(basis) + return mortars + end - @threaded for mortar in 1:nmpimortars(mpi_mortars) - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) + # Normal directions of small element surfaces are needed to calculate the mortar fluxes. Initialize + # them for locally available small elements. + function init_normal_directions!( + mpi_mortars::P4estMPIMortarContainer{3}, basis, + elements + ) + @unpack local_neighbor_ids, local_neighbor_positions, node_indices = mpi_mortars + @unpack contravariant_vectors = elements + index_range = eachnode(basis) - i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1], - index_range) - j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2], - index_range) - k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3], - index_range) + @threaded for mortar in 1:nmpimortars(mpi_mortars) + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) - for (element, position) in zip(local_neighbor_ids[mortar], - local_neighbor_positions[mortar]) - # ignore large elements - if position == 5 - continue - end + i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d( + small_indices[1], + index_range + ) + j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d( + small_indices[2], + index_range + ) + k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d( + small_indices[3], + index_range + ) + + for (element, position) in zip( + local_neighbor_ids[mortar], + local_neighbor_positions[mortar] + ) + # ignore large elements + if position == 5 + continue + end - i_small = i_small_start - j_small = j_small_start - k_small = k_small_start - for j in eachnode(basis) - for i in eachnode(basis) - # Get the normal direction on the small element. - # Note, contravariant vectors at interfaces in negative coordinate direction - # are pointing inwards. This is handled by `get_normal_direction`. - normal_direction = get_normal_direction(small_direction, - contravariant_vectors, - i_small, j_small, k_small, - element) - @views mpi_mortars.normal_directions[:, i, j, position, mortar] .= normal_direction + i_small = i_small_start + j_small = j_small_start + k_small = k_small_start + for j in eachnode(basis) + for i in eachnode(basis) + # Get the normal direction on the small element. + # Note, contravariant vectors at interfaces in negative coordinate direction + # are pointing inwards. This is handled by `get_normal_direction`. + normal_direction = get_normal_direction( + small_direction, + contravariant_vectors, + i_small, j_small, k_small, + element + ) + @views mpi_mortars.normal_directions[:, i, j, position, mortar] .= normal_direction - i_small += i_small_step_i - j_small += j_small_step_i - k_small += k_small_step_i + i_small += i_small_step_i + j_small += j_small_step_i + k_small += k_small_step_i + end + i_small += i_small_step_j + j_small += j_small_step_j + k_small += k_small_step_j end - i_small += i_small_step_j - j_small += j_small_step_j - k_small += k_small_step_j end end end -end end # muladd diff --git a/src/solvers/dgsem_p4est/dg.jl b/src/solvers/dgsem_p4est/dg.jl index 8197ad4a2d..4e851d9459 100644 --- a/src/solvers/dgsem_p4est/dg.jl +++ b/src/solvers/dgsem_p4est/dg.jl @@ -3,56 +3,60 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::P4estMesh, equations::AbstractEquations, dg::DG, ::Any, - ::Type{uEltype}) where {uEltype <: Real} - # Make sure to balance the `p4est` before creating any containers - # in case someone has tampered with the `p4est` after creating the mesh - balance!(mesh) - - elements = init_elements(mesh, equations, dg.basis, uEltype) - interfaces = init_interfaces(mesh, equations, dg.basis, elements) - boundaries = init_boundaries(mesh, equations, dg.basis, elements) - mortars = init_mortars(mesh, equations, dg.basis, elements) - - cache = (; elements, interfaces, boundaries, mortars) - - # Add specialized parts of the cache required to compute the volume integral etc. - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) - - return cache -end - -# Extract outward-pointing normal direction -# (contravariant vector ±Ja^i, i = index) -# Note that this vector is not normalized -@inline function get_normal_direction(direction, contravariant_vectors, indices...) - orientation = (direction + 1) >> 1 - normal = get_contravariant_vector(orientation, contravariant_vectors, indices...) - - # Contravariant vectors at interfaces in negative coordinate direction are pointing inwards, - # flip sign to make them point outwards - if isodd(direction) - return -normal - else - return normal + #! format: noindent + + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::P4estMesh, equations::AbstractEquations, dg::DG, ::Any, + ::Type{uEltype} + ) where {uEltype <: Real} + # Make sure to balance the `p4est` before creating any containers + # in case someone has tampered with the `p4est` after creating the mesh + balance!(mesh) + + elements = init_elements(mesh, equations, dg.basis, uEltype) + interfaces = init_interfaces(mesh, equations, dg.basis, elements) + boundaries = init_boundaries(mesh, equations, dg.basis, elements) + mortars = init_mortars(mesh, equations, dg.basis, elements) + + cache = (; elements, interfaces, boundaries, mortars) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) + + return cache end -end -include("containers.jl") + # Extract outward-pointing normal direction + # (contravariant vector ±Ja^i, i = index) + # Note that this vector is not normalized + @inline function get_normal_direction(direction, contravariant_vectors, indices...) + orientation = (direction + 1) >> 1 + normal = get_contravariant_vector(orientation, contravariant_vectors, indices...) + + # Contravariant vectors at interfaces in negative coordinate direction are pointing inwards, + # flip sign to make them point outwards + if isodd(direction) + return -normal + else + return normal + end + end + + include("containers.jl") -include("dg_2d.jl") -include("dg_2d_parabolic.jl") + include("dg_2d.jl") + include("dg_2d_parabolic.jl") -include("dg_3d.jl") -include("dg_3d_parabolic.jl") -include("dg_parallel.jl") + include("dg_3d.jl") + include("dg_3d_parabolic.jl") + include("dg_parallel.jl") -include("subcell_limiters_2d.jl") + include("subcell_limiters_2d.jl") end # @muladd diff --git a/src/solvers/dgsem_p4est/dg_2d.jl b/src/solvers/dgsem_p4est/dg_2d.jl index 17b9af0446..52b4a7859f 100644 --- a/src/solvers/dgsem_p4est/dg_2d.jl +++ b/src/solvers/dgsem_p4est/dg_2d.jl @@ -3,668 +3,792 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# The methods below are specialized on the mortar type -# and called from the basic `create_cache` method at the top. -function create_cache(mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, - mortar_l2::LobattoLegendreMortarL2, uEltype) - # TODO: Taal performance using different types - MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_l2)}, - uEltype, 2, - nvariables(equations) * nnodes(mortar_l2)} - fstar_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] - fstar_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] - u_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] - - (; fstar_upper_threaded, fstar_lower_threaded, u_threaded) -end - -# index_to_start_step_2d(index::Symbol, index_range) -# -# Given a symbolic `index` and an `indexrange` (usually `eachnode(dg)`), -# return `index_start, index_step`, i.e., a tuple containing -# - `index_start`, an index value to begin a loop -# - `index_step`, an index step to update during a loop -# The resulting indices translate surface indices to volume indices. -# -# !!! warning -# This assumes that loops using the return values are written as -# -# i_volume_start, i_volume_step = index_to_start_step_2d(symbolic_index_i, index_range) -# j_volume_start, j_volume_step = index_to_start_step_2d(symbolic_index_j, index_range) -# -# i_volume, j_volume = i_volume_start, j_volume_start -# for i_surface in index_range -# # do stuff with `i_surface` and `(i_volume, j_volume)` -# -# i_volume += i_volume_step -# j_volume += j_volume_step -# end -@inline function index_to_start_step_2d(index::Symbol, index_range) - index_begin = first(index_range) - index_end = last(index_range) - - if index === :begin - return index_begin, 0 - elseif index === :end - return index_end, 0 - elseif index === :i_forward - return index_begin, 1 - else # if index === :i_backward - return index_end, -1 + #! format: noindent + + # The methods below are specialized on the mortar type + # and called from the basic `create_cache` method at the top. + function create_cache( + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, + mortar_l2::LobattoLegendreMortarL2, uEltype + ) + # TODO: Taal performance using different types + MA2d = MArray{ + Tuple{nvariables(equations), nnodes(mortar_l2)}, + uEltype, 2, + nvariables(equations) * nnodes(mortar_l2), + } + fstar_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] + fstar_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] + u_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] + + (; fstar_upper_threaded, fstar_lower_threaded, u_threaded) end -end - -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache, u, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - equations, surface_integral, dg::DG) - @unpack interfaces = cache - index_range = eachnode(dg) - - @threaded for interface in eachinterface(dg, cache) - # Copy solution data from the primary element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - # Note that in the current implementation, the interface will be - # "aligned at the primary element", i.e., the index of the primary side - # will always run forwards. - primary_element = interfaces.neighbor_ids[1, interface] - primary_indices = interfaces.node_indices[1, interface] - - i_primary_start, i_primary_step = index_to_start_step_2d(primary_indices[1], - index_range) - j_primary_start, j_primary_step = index_to_start_step_2d(primary_indices[2], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - for i in eachnode(dg) - for v in eachvariable(equations) - interfaces.u[1, v, i, interface] = u[v, i_primary, j_primary, - primary_element] - end - i_primary += i_primary_step - j_primary += j_primary_step + + # index_to_start_step_2d(index::Symbol, index_range) + # + # Given a symbolic `index` and an `indexrange` (usually `eachnode(dg)`), + # return `index_start, index_step`, i.e., a tuple containing + # - `index_start`, an index value to begin a loop + # - `index_step`, an index step to update during a loop + # The resulting indices translate surface indices to volume indices. + # + # !!! warning + # This assumes that loops using the return values are written as + # + # i_volume_start, i_volume_step = index_to_start_step_2d(symbolic_index_i, index_range) + # j_volume_start, j_volume_step = index_to_start_step_2d(symbolic_index_j, index_range) + # + # i_volume, j_volume = i_volume_start, j_volume_start + # for i_surface in index_range + # # do stuff with `i_surface` and `(i_volume, j_volume)` + # + # i_volume += i_volume_step + # j_volume += j_volume_step + # end + @inline function index_to_start_step_2d(index::Symbol, index_range) + index_begin = first(index_range) + index_end = last(index_range) + + if index === :begin + return index_begin, 0 + elseif index === :end + return index_end, 0 + elseif index === :i_forward + return index_begin, 1 + else # if index === :i_backward + return index_end, -1 end + end - # Copy solution data from the secondary element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - secondary_element = interfaces.neighbor_ids[2, interface] - secondary_indices = interfaces.node_indices[2, interface] - - i_secondary_start, i_secondary_step = index_to_start_step_2d(secondary_indices[1], - index_range) - j_secondary_start, j_secondary_step = index_to_start_step_2d(secondary_indices[2], - index_range) - - i_secondary = i_secondary_start - j_secondary = j_secondary_start - for i in eachnode(dg) - for v in eachvariable(equations) - interfaces.u[2, v, i, interface] = u[v, i_secondary, j_secondary, - secondary_element] + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache, u, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + equations, surface_integral, dg::DG + ) + @unpack interfaces = cache + index_range = eachnode(dg) + + @threaded for interface in eachinterface(dg, cache) + # Copy solution data from the primary element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + # Note that in the current implementation, the interface will be + # "aligned at the primary element", i.e., the index of the primary side + # will always run forwards. + primary_element = interfaces.neighbor_ids[1, interface] + primary_indices = interfaces.node_indices[1, interface] + + i_primary_start, i_primary_step = index_to_start_step_2d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step = index_to_start_step_2d( + primary_indices[2], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + for i in eachnode(dg) + for v in eachvariable(equations) + interfaces.u[1, v, i, interface] = u[ + v, i_primary, j_primary, + primary_element, + ] + end + i_primary += i_primary_step + j_primary += j_primary_step + end + + # Copy solution data from the secondary element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + secondary_element = interfaces.neighbor_ids[2, interface] + secondary_indices = interfaces.node_indices[2, interface] + + i_secondary_start, i_secondary_step = index_to_start_step_2d( + secondary_indices[1], + index_range + ) + j_secondary_start, j_secondary_step = index_to_start_step_2d( + secondary_indices[2], + index_range + ) + + i_secondary = i_secondary_start + j_secondary = j_secondary_start + for i in eachnode(dg) + for v in eachvariable(equations) + interfaces.u[2, v, i, interface] = u[ + v, i_secondary, j_secondary, + secondary_element, + ] + end + i_secondary += i_secondary_step + j_secondary += j_secondary_step end - i_secondary += i_secondary_step - j_secondary += j_secondary_step end + + return nothing end - return nothing -end - -function calc_interface_flux!(surface_flux_values, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms, - equations, surface_integral, dg::DG, cache) - @unpack neighbor_ids, node_indices = cache.interfaces - @unpack contravariant_vectors = cache.elements - index_range = eachnode(dg) - index_end = last(index_range) - - @threaded for interface in eachinterface(dg, cache) - # Get element and side index information on the primary element - primary_element = neighbor_ids[1, interface] - primary_indices = node_indices[1, interface] - primary_direction = indices2direction(primary_indices) - - # Create the local i,j indexing on the primary element used to pull normal direction information - i_primary_start, i_primary_step = index_to_start_step_2d(primary_indices[1], - index_range) - j_primary_start, j_primary_step = index_to_start_step_2d(primary_indices[2], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - - # Get element and side index information on the secondary element - secondary_element = neighbor_ids[2, interface] - secondary_indices = node_indices[2, interface] - secondary_direction = indices2direction(secondary_indices) - - # Initiate the secondary index to be used in the surface for loop. - # This index on the primary side will always run forward but - # the secondary index might need to run backwards for flipped sides. - if :i_backward in secondary_indices - node_secondary = index_end - node_secondary_step = -1 - else - node_secondary = 1 - node_secondary_step = 1 - end + function calc_interface_flux!( + surface_flux_values, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + nonconservative_terms, + equations, surface_integral, dg::DG, cache + ) + @unpack neighbor_ids, node_indices = cache.interfaces + @unpack contravariant_vectors = cache.elements + index_range = eachnode(dg) + index_end = last(index_range) + + @threaded for interface in eachinterface(dg, cache) + # Get element and side index information on the primary element + primary_element = neighbor_ids[1, interface] + primary_indices = node_indices[1, interface] + primary_direction = indices2direction(primary_indices) + + # Create the local i,j indexing on the primary element used to pull normal direction information + i_primary_start, i_primary_step = index_to_start_step_2d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step = index_to_start_step_2d( + primary_indices[2], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + + # Get element and side index information on the secondary element + secondary_element = neighbor_ids[2, interface] + secondary_indices = node_indices[2, interface] + secondary_direction = indices2direction(secondary_indices) + + # Initiate the secondary index to be used in the surface for loop. + # This index on the primary side will always run forward but + # the secondary index might need to run backwards for flipped sides. + if :i_backward in secondary_indices + node_secondary = index_end + node_secondary_step = -1 + else + node_secondary = 1 + node_secondary_step = 1 + end - for node in eachnode(dg) - # Get the normal direction on the primary element. - # Contravariant vectors at interfaces in negative coordinate direction - # are pointing inwards. This is handled by `get_normal_direction`. - normal_direction = get_normal_direction(primary_direction, - contravariant_vectors, - i_primary, j_primary, - primary_element) - - calc_interface_flux!(surface_flux_values, mesh, nonconservative_terms, - equations, - surface_integral, dg, cache, - interface, normal_direction, - node, primary_direction, primary_element, - node_secondary, secondary_direction, secondary_element) - - # Increment primary element indices to pull the normal direction - i_primary += i_primary_step - j_primary += j_primary_step - # Increment the surface node index along the secondary element - node_secondary += node_secondary_step + for node in eachnode(dg) + # Get the normal direction on the primary element. + # Contravariant vectors at interfaces in negative coordinate direction + # are pointing inwards. This is handled by `get_normal_direction`. + normal_direction = get_normal_direction( + primary_direction, + contravariant_vectors, + i_primary, j_primary, + primary_element + ) + + calc_interface_flux!( + surface_flux_values, mesh, nonconservative_terms, + equations, + surface_integral, dg, cache, + interface, normal_direction, + node, primary_direction, primary_element, + node_secondary, secondary_direction, secondary_element + ) + + # Increment primary element indices to pull the normal direction + i_primary += i_primary_step + j_primary += j_primary_step + # Increment the surface node index along the secondary element + node_secondary += node_secondary_step + end end + + return nothing end - return nothing -end - -# Inlined version of the interface flux computation for conservation laws -@inline function calc_interface_flux!(surface_flux_values, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - interface_index, normal_direction, - primary_node_index, primary_direction_index, - primary_element_index, - secondary_node_index, secondary_direction_index, - secondary_element_index) - @unpack u = cache.interfaces - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, primary_node_index, - interface_index) - - flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) - - for v in eachvariable(equations) - surface_flux_values[v, primary_node_index, primary_direction_index, primary_element_index] = flux_[v] - surface_flux_values[v, secondary_node_index, secondary_direction_index, secondary_element_index] = -flux_[v] + # Inlined version of the interface flux computation for conservation laws + @inline function calc_interface_flux!( + surface_flux_values, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + interface_index, normal_direction, + primary_node_index, primary_direction_index, + primary_element_index, + secondary_node_index, secondary_direction_index, + secondary_element_index + ) + @unpack u = cache.interfaces + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, primary_node_index, + interface_index + ) + + flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) + + for v in eachvariable(equations) + surface_flux_values[v, primary_node_index, primary_direction_index, primary_element_index] = flux_[v] + surface_flux_values[v, secondary_node_index, secondary_direction_index, secondary_element_index] = -flux_[v] + end end -end - -# Inlined version of the interface flux computation for equations with conservative and nonconservative terms -@inline function calc_interface_flux!(surface_flux_values, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache, - interface_index, normal_direction, - primary_node_index, primary_direction_index, - primary_element_index, - secondary_node_index, secondary_direction_index, - secondary_element_index) - @unpack u = cache.interfaces - surface_flux, nonconservative_flux = surface_integral.surface_flux - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, primary_node_index, - interface_index) - - flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) - - # Compute both nonconservative fluxes - # In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. - # However, both are the same at watertight interfaces, so we pass the - # `normal_direction` twice. - noncons_primary = nonconservative_flux(u_ll, u_rr, normal_direction, - normal_direction, equations) - noncons_secondary = nonconservative_flux(u_rr, u_ll, normal_direction, - normal_direction, equations) - - # Store the flux with nonconservative terms on the primary and secondary elements - for v in eachvariable(equations) - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - surface_flux_values[v, primary_node_index, primary_direction_index, primary_element_index] = (flux_[v] + - 0.5f0 * - noncons_primary[v]) - surface_flux_values[v, secondary_node_index, secondary_direction_index, secondary_element_index] = -(flux_[v] + - 0.5f0 * - noncons_secondary[v]) + + # Inlined version of the interface flux computation for equations with conservative and nonconservative terms + @inline function calc_interface_flux!( + surface_flux_values, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache, + interface_index, normal_direction, + primary_node_index, primary_direction_index, + primary_element_index, + secondary_node_index, secondary_direction_index, + secondary_element_index + ) + @unpack u = cache.interfaces + surface_flux, nonconservative_flux = surface_integral.surface_flux + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, primary_node_index, + interface_index + ) + + flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) + + # Compute both nonconservative fluxes + # In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. + # However, both are the same at watertight interfaces, so we pass the + # `normal_direction` twice. + noncons_primary = nonconservative_flux( + u_ll, u_rr, normal_direction, + normal_direction, equations + ) + noncons_secondary = nonconservative_flux( + u_rr, u_ll, normal_direction, + normal_direction, equations + ) + + # Store the flux with nonconservative terms on the primary and secondary elements + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[v, primary_node_index, primary_direction_index, primary_element_index] = ( + flux_[v] + + 0.5f0 * + noncons_primary[v] + ) + surface_flux_values[v, secondary_node_index, secondary_direction_index, secondary_element_index] = -( + flux_[v] + + 0.5f0 * + noncons_secondary[v] + ) + end end -end - -function prolong2boundaries!(cache, u, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - equations, surface_integral, dg::DG) - @unpack boundaries = cache - index_range = eachnode(dg) - - @threaded for boundary in eachboundary(dg, cache) - # Copy solution data from the element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - - i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) - j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) - - i_node = i_node_start - j_node = j_node_start - for i in eachnode(dg) - for v in eachvariable(equations) - boundaries.u[v, i, boundary] = u[v, i_node, j_node, element] + + function prolong2boundaries!( + cache, u, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + equations, surface_integral, dg::DG + ) + @unpack boundaries = cache + index_range = eachnode(dg) + + @threaded for boundary in eachboundary(dg, cache) + # Copy solution data from the element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + + i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) + j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) + + i_node = i_node_start + j_node = j_node_start + for i in eachnode(dg) + for v in eachvariable(equations) + boundaries.u[v, i, boundary] = u[v, i_node, j_node, element] + end + i_node += i_node_step + j_node += j_node_step end - i_node += i_node_step - j_node += j_node_step end + + return nothing end - return nothing -end - -function calc_boundary_flux!(cache, t, boundary_condition::BC, boundary_indexing, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - equations, surface_integral, dg::DG) where {BC} - @unpack boundaries = cache - @unpack surface_flux_values = cache.elements - index_range = eachnode(dg) - - @threaded for local_index in eachindex(boundary_indexing) - # Use the local index to get the global boundary index from the pre-sorted list - boundary = boundary_indexing[local_index] - - # Get information on the adjacent element, compute the surface fluxes, - # and store them - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - direction = indices2direction(node_indices) - - i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) - j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) - - i_node = i_node_start - j_node = j_node_start - for node in eachnode(dg) - calc_boundary_flux!(surface_flux_values, t, boundary_condition, - mesh, have_nonconservative_terms(equations), - equations, surface_integral, dg, cache, - i_node, j_node, - node, direction, element, boundary) - - i_node += i_node_step - j_node += j_node_step + function calc_boundary_flux!( + cache, t, boundary_condition::BC, boundary_indexing, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + equations, surface_integral, dg::DG + ) where {BC} + @unpack boundaries = cache + @unpack surface_flux_values = cache.elements + index_range = eachnode(dg) + + @threaded for local_index in eachindex(boundary_indexing) + # Use the local index to get the global boundary index from the pre-sorted list + boundary = boundary_indexing[local_index] + + # Get information on the adjacent element, compute the surface fluxes, + # and store them + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) + j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) + + i_node = i_node_start + j_node = j_node_start + for node in eachnode(dg) + calc_boundary_flux!( + surface_flux_values, t, boundary_condition, + mesh, have_nonconservative_terms(equations), + equations, surface_integral, dg, cache, + i_node, j_node, + node, direction, element, boundary + ) + + i_node += i_node_step + j_node += j_node_step + end end end -end - -# inlined version of the boundary flux calculation along a physical interface -@inline function calc_boundary_flux!(surface_flux_values, t, boundary_condition, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - i_index, j_index, - node_index, direction_index, element_index, - boundary_index) - @unpack boundaries = cache - @unpack node_coordinates, contravariant_vectors = cache.elements - @unpack surface_flux = surface_integral - - # Extract solution data from boundary container - u_inner = get_node_vars(boundaries.u, equations, dg, node_index, boundary_index) - - # Outward-pointing normal direction (not normalized) - normal_direction = get_normal_direction(direction_index, contravariant_vectors, - i_index, j_index, element_index) - - # Coordinates at boundary node - x = get_node_coords(node_coordinates, equations, dg, i_index, j_index, - element_index) - - flux_ = boundary_condition(u_inner, normal_direction, x, t, surface_flux, equations) - - # Copy flux to element storage in the correct orientation - for v in eachvariable(equations) - surface_flux_values[v, node_index, direction_index, element_index] = flux_[v] + + # inlined version of the boundary flux calculation along a physical interface + @inline function calc_boundary_flux!( + surface_flux_values, t, boundary_condition, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + i_index, j_index, + node_index, direction_index, element_index, + boundary_index + ) + @unpack boundaries = cache + @unpack node_coordinates, contravariant_vectors = cache.elements + @unpack surface_flux = surface_integral + + # Extract solution data from boundary container + u_inner = get_node_vars(boundaries.u, equations, dg, node_index, boundary_index) + + # Outward-pointing normal direction (not normalized) + normal_direction = get_normal_direction( + direction_index, contravariant_vectors, + i_index, j_index, element_index + ) + + # Coordinates at boundary node + x = get_node_coords( + node_coordinates, equations, dg, i_index, j_index, + element_index + ) + + flux_ = boundary_condition(u_inner, normal_direction, x, t, surface_flux, equations) + + # Copy flux to element storage in the correct orientation + for v in eachvariable(equations) + surface_flux_values[v, node_index, direction_index, element_index] = flux_[v] + end end -end - -# inlined version of the boundary flux with nonconservative terms calculation along a physical interface -@inline function calc_boundary_flux!(surface_flux_values, t, boundary_condition, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache, - i_index, j_index, - node_index, direction_index, element_index, - boundary_index) - @unpack boundaries = cache - @unpack node_coordinates, contravariant_vectors = cache.elements - surface_flux, nonconservative_flux = surface_integral.surface_flux - - # Extract solution data from boundary container - u_inner = get_node_vars(boundaries.u, equations, dg, node_index, boundary_index) - - # Outward-pointing normal direction (not normalized) - normal_direction = get_normal_direction(direction_index, contravariant_vectors, - i_index, j_index, element_index) - - # Coordinates at boundary node - x = get_node_coords(node_coordinates, equations, dg, i_index, j_index, - element_index) - - # Call pointwise numerical flux function for the conservative part - # in the normal direction on the boundary - flux_ = boundary_condition(u_inner, normal_direction, x, t, surface_flux, equations) - - # Compute pointwise nonconservative numerical flux at the boundary. - # Note: This does not set any type of boundary condition for the nonconservative term - noncons_ = nonconservative_flux(u_inner, u_inner, normal_direction, - normal_direction, equations) - - # Copy flux to element storage in the correct orientation - for v in eachvariable(equations) - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - surface_flux_values[v, node_index, direction_index, element_index] = flux_[v] + - 0.5f0 * - noncons_[v] + + # inlined version of the boundary flux with nonconservative terms calculation along a physical interface + @inline function calc_boundary_flux!( + surface_flux_values, t, boundary_condition, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache, + i_index, j_index, + node_index, direction_index, element_index, + boundary_index + ) + @unpack boundaries = cache + @unpack node_coordinates, contravariant_vectors = cache.elements + surface_flux, nonconservative_flux = surface_integral.surface_flux + + # Extract solution data from boundary container + u_inner = get_node_vars(boundaries.u, equations, dg, node_index, boundary_index) + + # Outward-pointing normal direction (not normalized) + normal_direction = get_normal_direction( + direction_index, contravariant_vectors, + i_index, j_index, element_index + ) + + # Coordinates at boundary node + x = get_node_coords( + node_coordinates, equations, dg, i_index, j_index, + element_index + ) + + # Call pointwise numerical flux function for the conservative part + # in the normal direction on the boundary + flux_ = boundary_condition(u_inner, normal_direction, x, t, surface_flux, equations) + + # Compute pointwise nonconservative numerical flux at the boundary. + # Note: This does not set any type of boundary condition for the nonconservative term + noncons_ = nonconservative_flux( + u_inner, u_inner, normal_direction, + normal_direction, equations + ) + + # Copy flux to element storage in the correct orientation + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[v, node_index, direction_index, element_index] = flux_[v] + + 0.5f0 * + noncons_[v] + end end -end - -function prolong2mortars!(cache, u, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DGSEM) - @unpack neighbor_ids, node_indices = cache.mortars - index_range = eachnode(dg) - - @threaded for mortar in eachmortar(dg, cache) - # Copy solution data from the small elements using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - small_indices = node_indices[1, mortar] - i_small_start, i_small_step = index_to_start_step_2d(small_indices[1], - index_range) - j_small_start, j_small_step = index_to_start_step_2d(small_indices[2], - index_range) + function prolong2mortars!( + cache, u, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DGSEM + ) + @unpack neighbor_ids, node_indices = cache.mortars + index_range = eachnode(dg) + + @threaded for mortar in eachmortar(dg, cache) + # Copy solution data from the small elements using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + small_indices = node_indices[1, mortar] + + i_small_start, i_small_step = index_to_start_step_2d( + small_indices[1], + index_range + ) + j_small_start, j_small_step = index_to_start_step_2d( + small_indices[2], + index_range + ) + + for position in 1:2 + i_small = i_small_start + j_small = j_small_start + element = neighbor_ids[position, mortar] + for i in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u[1, v, position, i, mortar] = u[ + v, i_small, j_small, + element, + ] + end + i_small += i_small_step + j_small += j_small_step + end + end - for position in 1:2 - i_small = i_small_start - j_small = j_small_start - element = neighbor_ids[position, mortar] + # Buffer to copy solution values of the large element in the correct orientation + # before interpolating + u_buffer = cache.u_threaded[Threads.threadid()] + + # Copy solution of large element face to buffer in the + # correct orientation + large_indices = node_indices[2, mortar] + + i_large_start, i_large_step = index_to_start_step_2d( + large_indices[1], + index_range + ) + j_large_start, j_large_step = index_to_start_step_2d( + large_indices[2], + index_range + ) + + i_large = i_large_start + j_large = j_large_start + element = neighbor_ids[3, mortar] for i in eachnode(dg) for v in eachvariable(equations) - cache.mortars.u[1, v, position, i, mortar] = u[v, i_small, j_small, - element] + u_buffer[v, i] = u[v, i_large, j_large, element] end - i_small += i_small_step - j_small += j_small_step + i_large += i_large_step + j_large += j_large_step end - end - # Buffer to copy solution values of the large element in the correct orientation - # before interpolating - u_buffer = cache.u_threaded[Threads.threadid()] + # Interpolate large element face data from buffer to small face locations + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 1, :, mortar), + mortar_l2.forward_lower, + u_buffer + ) + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 2, :, mortar), + mortar_l2.forward_upper, + u_buffer + ) + end - # Copy solution of large element face to buffer in the - # correct orientation - large_indices = node_indices[2, mortar] + return nothing + end - i_large_start, i_large_step = index_to_start_step_2d(large_indices[1], - index_range) - j_large_start, j_large_step = index_to_start_step_2d(large_indices[2], - index_range) - - i_large = i_large_start - j_large = j_large_start - element = neighbor_ids[3, mortar] - for i in eachnode(dg) - for v in eachvariable(equations) - u_buffer[v, i] = u[v, i_large, j_large, element] + function calc_mortar_flux!( + surface_flux_values, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + nonconservative_terms, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack neighbor_ids, node_indices = cache.mortars + @unpack contravariant_vectors = cache.elements + @unpack fstar_upper_threaded, fstar_lower_threaded = cache + index_range = eachnode(dg) + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar = ( + fstar_lower_threaded[Threads.threadid()], + fstar_upper_threaded[Threads.threadid()], + ) + + # Get index information on the small elements + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) + + i_small_start, i_small_step = index_to_start_step_2d( + small_indices[1], + index_range + ) + j_small_start, j_small_step = index_to_start_step_2d( + small_indices[2], + index_range + ) + + for position in 1:2 + i_small = i_small_start + j_small = j_small_start + element = neighbor_ids[position, mortar] + for node in eachnode(dg) + # Get the normal direction on the small element. + # Note, contravariant vectors at interfaces in negative coordinate direction + # are pointing inwards. This is handled by `get_normal_direction`. + normal_direction = get_normal_direction( + small_direction, + contravariant_vectors, + i_small, j_small, element + ) + + calc_mortar_flux!( + fstar, mesh, nonconservative_terms, equations, + surface_integral, dg, cache, + mortar, position, normal_direction, + node + ) + + i_small += i_small_step + j_small += j_small_step + end end - i_large += i_large_step - j_large += j_large_step + + # Buffer to interpolate flux values of the large element to before + # copying in the correct orientation + u_buffer = cache.u_threaded[Threads.threadid()] + + # in calc_interface_flux!, the interface flux is computed once over each + # interface using the normal from the "primary" element. The result is then + # passed back to the "secondary" element, flipping the sign to account for the + # change in the normal direction. For mortars, this sign flip occurs in + # "mortar_fluxes_to_elements!" instead. + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, + mortar, fstar, u_buffer + ) end - # Interpolate large element face data from buffer to small face locations - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 1, :, mortar), - mortar_l2.forward_lower, - u_buffer) - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 2, :, mortar), - mortar_l2.forward_upper, - u_buffer) + return nothing end - return nothing -end - -function calc_mortar_flux!(surface_flux_values, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack neighbor_ids, node_indices = cache.mortars - @unpack contravariant_vectors = cache.elements - @unpack fstar_upper_threaded, fstar_lower_threaded = cache - index_range = eachnode(dg) - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar = (fstar_lower_threaded[Threads.threadid()], - fstar_upper_threaded[Threads.threadid()]) - - # Get index information on the small elements - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) + # Inlined version of the mortar flux computation on small elements for conservation laws + @inline function calc_mortar_flux!( + fstar, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + mortar_index, position_index, normal_direction, + node_index + ) + @unpack u = cache.mortars + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, position_index, node_index, + mortar_index + ) + + flux = surface_flux(u_ll, u_rr, normal_direction, equations) + + # Copy flux to buffer + set_node_vars!(fstar[position_index], flux, equations, dg, node_index) + end - i_small_start, i_small_step = index_to_start_step_2d(small_indices[1], - index_range) - j_small_start, j_small_step = index_to_start_step_2d(small_indices[2], - index_range) + # Inlined version of the mortar flux computation on small elements for equations with conservative and + # nonconservative terms + @inline function calc_mortar_flux!( + fstar, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache, + mortar_index, position_index, normal_direction, + node_index + ) + @unpack u = cache.mortars + surface_flux, nonconservative_flux = surface_integral.surface_flux + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, position_index, node_index, + mortar_index + ) + + # Compute conservative flux + flux = surface_flux(u_ll, u_rr, normal_direction, equations) + + # Compute nonconservative flux and add it to the conservative flux. + # The nonconservative flux is scaled by a factor of 0.5 based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + noncons = nonconservative_flux( + u_ll, u_rr, normal_direction, normal_direction, + equations + ) - for position in 1:2 - i_small = i_small_start - j_small = j_small_start - element = neighbor_ids[position, mortar] - for node in eachnode(dg) - # Get the normal direction on the small element. - # Note, contravariant vectors at interfaces in negative coordinate direction - # are pointing inwards. This is handled by `get_normal_direction`. - normal_direction = get_normal_direction(small_direction, - contravariant_vectors, - i_small, j_small, element) + flux_plus_noncons = flux + 0.5f0 * noncons - calc_mortar_flux!(fstar, mesh, nonconservative_terms, equations, - surface_integral, dg, cache, - mortar, position, normal_direction, - node) + # Copy to buffer + set_node_vars!(fstar[position_index], flux_plus_noncons, equations, dg, node_index) + end - i_small += i_small_step - j_small += j_small_step - end - end + @inline function mortar_fluxes_to_elements!( + surface_flux_values, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + equations, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, mortar, fstar, u_buffer + ) + @unpack neighbor_ids, node_indices = cache.mortars - # Buffer to interpolate flux values of the large element to before - # copying in the correct orientation - u_buffer = cache.u_threaded[Threads.threadid()] - - # in calc_interface_flux!, the interface flux is computed once over each - # interface using the normal from the "primary" element. The result is then - # passed back to the "secondary" element, flipping the sign to account for the - # change in the normal direction. For mortars, this sign flip occurs in - # "mortar_fluxes_to_elements!" instead. - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, - mortar, fstar, u_buffer) - end + # Copy solution small to small + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) - return nothing -end - -# Inlined version of the mortar flux computation on small elements for conservation laws -@inline function calc_mortar_flux!(fstar, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - mortar_index, position_index, normal_direction, - node_index) - @unpack u = cache.mortars - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, node_index, - mortar_index) - - flux = surface_flux(u_ll, u_rr, normal_direction, equations) - - # Copy flux to buffer - set_node_vars!(fstar[position_index], flux, equations, dg, node_index) -end - -# Inlined version of the mortar flux computation on small elements for equations with conservative and -# nonconservative terms -@inline function calc_mortar_flux!(fstar, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache, - mortar_index, position_index, normal_direction, - node_index) - @unpack u = cache.mortars - surface_flux, nonconservative_flux = surface_integral.surface_flux - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, node_index, - mortar_index) - - # Compute conservative flux - flux = surface_flux(u_ll, u_rr, normal_direction, equations) - - # Compute nonconservative flux and add it to the conservative flux. - # The nonconservative flux is scaled by a factor of 0.5 based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - noncons = nonconservative_flux(u_ll, u_rr, normal_direction, normal_direction, - equations) - - flux_plus_noncons = flux + 0.5f0 * noncons - - # Copy to buffer - set_node_vars!(fstar[position_index], flux_plus_noncons, equations, dg, node_index) -end - -@inline function mortar_fluxes_to_elements!(surface_flux_values, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - equations, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, mortar, fstar, u_buffer) - @unpack neighbor_ids, node_indices = cache.mortars - - # Copy solution small to small - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) - - for position in 1:2 - element = neighbor_ids[position, mortar] - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i, small_direction, element] = fstar[position][v, - i] + for position in 1:2 + element = neighbor_ids[position, mortar] + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i, small_direction, element] = fstar[position][ + v, + i, + ] + end end end - end - # Project small fluxes to large element. - multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_upper, fstar[2], - mortar_l2.reverse_lower, fstar[1]) - - # The flux is calculated in the outward direction of the small elements, - # so the sign must be switched to get the flux in outward direction - # of the large element. - # The contravariant vectors of the large element (and therefore the normal - # vectors of the large element as well) are twice as large as the - # contravariant vectors of the small elements. Therefore, the flux needs - # to be scaled by a factor of 2 to obtain the flux of the large element. - u_buffer .*= -2 - - # Copy interpolated flux values from buffer to large element face in the - # correct orientation. - # Note that the index of the small sides will always run forward but - # the index of the large side might need to run backwards for flipped sides. - large_element = neighbor_ids[3, mortar] - large_indices = node_indices[2, mortar] - large_direction = indices2direction(large_indices) - - if :i_backward in large_indices - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, end + 1 - i, large_direction, large_element] = u_buffer[v, - i] + # Project small fluxes to large element. + multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_upper, fstar[2], + mortar_l2.reverse_lower, fstar[1] + ) + + # The flux is calculated in the outward direction of the small elements, + # so the sign must be switched to get the flux in outward direction + # of the large element. + # The contravariant vectors of the large element (and therefore the normal + # vectors of the large element as well) are twice as large as the + # contravariant vectors of the small elements. Therefore, the flux needs + # to be scaled by a factor of 2 to obtain the flux of the large element. + u_buffer .*= -2 + + # Copy interpolated flux values from buffer to large element face in the + # correct orientation. + # Note that the index of the small sides will always run forward but + # the index of the large side might need to run backwards for flipped sides. + large_element = neighbor_ids[3, mortar] + large_indices = node_indices[2, mortar] + large_direction = indices2direction(large_indices) + + if :i_backward in large_indices + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, end + 1 - i, large_direction, large_element] = u_buffer[ + v, + i, + ] + end end - end - else - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i, large_direction, large_element] = u_buffer[v, - i] + else + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i, large_direction, large_element] = u_buffer[ + v, + i, + ] + end end end + + return nothing end - return nothing -end - -function calc_surface_integral!(du, u, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - equations, - surface_integral::SurfaceIntegralWeakForm, - dg::DGSEM, cache) - @unpack boundary_interpolation = dg.basis - @unpack surface_flux_values = cache.elements - - # Note that all fluxes have been computed with outward-pointing normal vectors. - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` to let `@muladd` turn these - # into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] - @threaded for element in eachelement(dg, cache) - for l in eachnode(dg) - for v in eachvariable(equations) - # surface at -x - du[v, 1, l, element] = (du[v, 1, l, element] + - surface_flux_values[v, l, 1, element] * - factor_1) - - # surface at +x - du[v, nnodes(dg), l, element] = (du[v, nnodes(dg), l, element] + - surface_flux_values[v, l, 2, element] * - factor_2) - - # surface at -y - du[v, l, 1, element] = (du[v, l, 1, element] + - surface_flux_values[v, l, 3, element] * - factor_1) - - # surface at +y - du[v, l, nnodes(dg), element] = (du[v, l, nnodes(dg), element] + - surface_flux_values[v, l, 4, element] * - factor_2) + function calc_surface_integral!( + du, u, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + equations, + surface_integral::SurfaceIntegralWeakForm, + dg::DGSEM, cache + ) + @unpack boundary_interpolation = dg.basis + @unpack surface_flux_values = cache.elements + + # Note that all fluxes have been computed with outward-pointing normal vectors. + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg) + for v in eachvariable(equations) + # surface at -x + du[v, 1, l, element] = ( + du[v, 1, l, element] + + surface_flux_values[v, l, 1, element] * + factor_1 + ) + + # surface at +x + du[v, nnodes(dg), l, element] = ( + du[v, nnodes(dg), l, element] + + surface_flux_values[v, l, 2, element] * + factor_2 + ) + + # surface at -y + du[v, l, 1, element] = ( + du[v, l, 1, element] + + surface_flux_values[v, l, 3, element] * + factor_1 + ) + + # surface at +y + du[v, l, nnodes(dg), element] = ( + du[v, l, nnodes(dg), element] + + surface_flux_values[v, l, 4, element] * + factor_2 + ) + end end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_p4est/dg_2d_parabolic.jl b/src/solvers/dgsem_p4est/dg_2d_parabolic.jl index 4bf4c79ebe..7ede331c42 100644 --- a/src/solvers/dgsem_p4est/dg_2d_parabolic.jl +++ b/src/solvers/dgsem_p4est/dg_2d_parabolic.jl @@ -3,31 +3,35 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This method is called when a SemidiscretizationHyperbolicParabolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache_parabolic(mesh::P4estMesh{2}, - equations_hyperbolic::AbstractEquations, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, parabolic_scheme, RealT, uEltype) - balance!(mesh) - - elements = init_elements(mesh, equations_hyperbolic, dg.basis, uEltype) - interfaces = init_interfaces(mesh, equations_hyperbolic, dg.basis, elements) - boundaries = init_boundaries(mesh, equations_hyperbolic, dg.basis, elements) - - viscous_container = init_viscous_container_2d(nvariables(equations_hyperbolic), - nnodes(dg.basis), nelements(elements), - uEltype) - - cache = (; elements, interfaces, boundaries, viscous_container) - - return cache -end + #! format: noindent + + # This method is called when a SemidiscretizationHyperbolicParabolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache_parabolic( + mesh::P4estMesh{2}, + equations_hyperbolic::AbstractEquations, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, parabolic_scheme, RealT, uEltype + ) + balance!(mesh) + + elements = init_elements(mesh, equations_hyperbolic, dg.basis, uEltype) + interfaces = init_interfaces(mesh, equations_hyperbolic, dg.basis, elements) + boundaries = init_boundaries(mesh, equations_hyperbolic, dg.basis, elements) + + viscous_container = init_viscous_container_2d( + nvariables(equations_hyperbolic), + nnodes(dg.basis), nelements(elements), + uEltype + ) + + cache = (; elements, interfaces, boundaries, viscous_container) + + return cache + end -#= + #= Reusing `rhs_parabolic!` for `TreeMesh`es is not easily possible as for `P4estMesh`es we call @@ -49,968 +53,1222 @@ instead of dg.mortar, dg.surface_integral, dg, cache) ``` =# -function rhs_parabolic!(du, u, t, mesh::Union{P4estMesh{2}, P4estMesh{3}}, - equations_parabolic::AbstractEquationsParabolic, - initial_condition, boundary_conditions_parabolic, source_terms, - dg::DG, parabolic_scheme, cache, cache_parabolic) - @unpack viscous_container = cache_parabolic - @unpack u_transformed, gradients, flux_viscous = viscous_container - - # Convert conservative variables to a form more suitable for viscous flux calculations - @trixi_timeit timer() "transform variables" begin - transform_variables!(u_transformed, u, mesh, equations_parabolic, - dg, parabolic_scheme, cache, cache_parabolic) - end - - # Compute the gradients of the transformed variables - @trixi_timeit timer() "calculate gradient" begin - calc_gradient!(gradients, u_transformed, t, mesh, equations_parabolic, - boundary_conditions_parabolic, dg, cache, cache_parabolic) - end - - # Compute and store the viscous fluxes - @trixi_timeit timer() "calculate viscous fluxes" begin - calc_viscous_fluxes!(flux_viscous, gradients, u_transformed, mesh, - equations_parabolic, dg, cache, cache_parabolic) - end + function rhs_parabolic!( + du, u, t, mesh::Union{P4estMesh{2}, P4estMesh{3}}, + equations_parabolic::AbstractEquationsParabolic, + initial_condition, boundary_conditions_parabolic, source_terms, + dg::DG, parabolic_scheme, cache, cache_parabolic + ) + @unpack viscous_container = cache_parabolic + @unpack u_transformed, gradients, flux_viscous = viscous_container + + # Convert conservative variables to a form more suitable for viscous flux calculations + @trixi_timeit timer() "transform variables" begin + transform_variables!( + u_transformed, u, mesh, equations_parabolic, + dg, parabolic_scheme, cache, cache_parabolic + ) + end - # The remainder of this function is essentially a regular rhs! for parabolic - # equations (i.e., it computes the divergence of the viscous fluxes) - # - # OBS! In `calc_viscous_fluxes!`, the viscous flux values at the volume nodes of each element have - # been computed and stored in `fluxes_viscous`. In the following, we *reuse* (abuse) the - # `interfaces` and `boundaries` containers in `cache_parabolic` to interpolate and store the - # *fluxes* at the element surfaces, as opposed to interpolating and storing the *solution* (as it - # is done in the hyperbolic operator). That is, `interfaces.u`/`boundaries.u` store *viscous flux values* - # and *not the solution*. The advantage is that a) we do not need to allocate more storage, b) we - # do not need to recreate the existing data structure only with a different name, and c) we do not - # need to interpolate solutions *and* gradients to the surfaces. - - # TODO: parabolic; reconsider current data structure reuse strategy - - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, flux_viscous, mesh, equations_parabolic, dg, cache) - end + # Compute the gradients of the transformed variables + @trixi_timeit timer() "calculate gradient" begin + calc_gradient!( + gradients, u_transformed, t, mesh, equations_parabolic, + boundary_conditions_parabolic, dg, cache, cache_parabolic + ) + end - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache_parabolic, flux_viscous, mesh, equations_parabolic, - dg.surface_integral, dg, cache) - end + # Compute and store the viscous fluxes + @trixi_timeit timer() "calculate viscous fluxes" begin + calc_viscous_fluxes!( + flux_viscous, gradients, u_transformed, mesh, + equations_parabolic, dg, cache, cache_parabolic + ) + end - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache_parabolic.elements.surface_flux_values, mesh, - equations_parabolic, dg, cache_parabolic) - end + # The remainder of this function is essentially a regular rhs! for parabolic + # equations (i.e., it computes the divergence of the viscous fluxes) + # + # OBS! In `calc_viscous_fluxes!`, the viscous flux values at the volume nodes of each element have + # been computed and stored in `fluxes_viscous`. In the following, we *reuse* (abuse) the + # `interfaces` and `boundaries` containers in `cache_parabolic` to interpolate and store the + # *fluxes* at the element surfaces, as opposed to interpolating and storing the *solution* (as it + # is done in the hyperbolic operator). That is, `interfaces.u`/`boundaries.u` store *viscous flux values* + # and *not the solution*. The advantage is that a) we do not need to allocate more storage, b) we + # do not need to recreate the existing data structure only with a different name, and c) we do not + # need to interpolate solutions *and* gradients to the surfaces. + + # TODO: parabolic; reconsider current data structure reuse strategy + + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!(du, flux_viscous, mesh, equations_parabolic, dg, cache) + end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache_parabolic, flux_viscous, mesh, equations_parabolic, - dg.surface_integral, dg, cache) - end + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache_parabolic, flux_viscous, mesh, equations_parabolic, + dg.surface_integral, dg, cache + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux_divergence!(cache_parabolic, t, - boundary_conditions_parabolic, mesh, - equations_parabolic, - dg.surface_integral, dg) - end + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache_parabolic.elements.surface_flux_values, mesh, + equations_parabolic, dg, cache_parabolic + ) + end - # Prolong solution to mortars (specialized for AbstractEquationsParabolic) - # !!! NOTE: we reuse the hyperbolic cache here since it contains "mortars" and "u_threaded". See https://github.com/trixi-framework/Trixi.jl/issues/1674 for a discussion - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars_divergence!(cache, flux_viscous, mesh, equations_parabolic, - dg.mortar, dg.surface_integral, dg) - end + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache_parabolic, flux_viscous, mesh, equations_parabolic, + dg.surface_integral, dg, cache + ) + end - # Calculate mortar fluxes (specialized for AbstractEquationsParabolic) - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux_divergence!(cache_parabolic.elements.surface_flux_values, - mesh, equations_parabolic, dg.mortar, - dg.surface_integral, dg, cache) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux_divergence!( + cache_parabolic, t, + boundary_conditions_parabolic, mesh, + equations_parabolic, + dg.surface_integral, dg + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations_parabolic, - dg.surface_integral, dg, cache_parabolic) - end + # Prolong solution to mortars (specialized for AbstractEquationsParabolic) + # !!! NOTE: we reuse the hyperbolic cache here since it contains "mortars" and "u_threaded". See https://github.com/trixi-framework/Trixi.jl/issues/1674 for a discussion + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars_divergence!( + cache, flux_viscous, mesh, equations_parabolic, + dg.mortar, dg.surface_integral, dg + ) + end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" begin - apply_jacobian_parabolic!(du, mesh, equations_parabolic, dg, cache_parabolic) - end + # Calculate mortar fluxes (specialized for AbstractEquationsParabolic) + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux_divergence!( + cache_parabolic.elements.surface_flux_values, + mesh, equations_parabolic, dg.mortar, + dg.surface_integral, dg, cache + ) + end - return nothing -end + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations_parabolic, + dg.surface_integral, dg, cache_parabolic + ) + end -function calc_gradient!(gradients, u_transformed, t, - mesh::P4estMesh{2}, equations_parabolic, - boundary_conditions_parabolic, dg::DG, - cache, cache_parabolic) - gradients_x, gradients_y = gradients + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" begin + apply_jacobian_parabolic!(du, mesh, equations_parabolic, dg, cache_parabolic) + end - # Reset du - @trixi_timeit timer() "reset gradients" begin - reset_du!(gradients_x, dg, cache) - reset_du!(gradients_y, dg, cache) + return nothing end - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - (; derivative_dhat) = dg.basis - (; contravariant_vectors) = cache.elements - - @threaded for element in eachelement(dg, cache) - - # Calculate gradients with respect to reference coordinates in one element - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, j, - element) + function calc_gradient!( + gradients, u_transformed, t, + mesh::P4estMesh{2}, equations_parabolic, + boundary_conditions_parabolic, dg::DG, + cache, cache_parabolic + ) + gradients_x, gradients_y = gradients + + # Reset du + @trixi_timeit timer() "reset gradients" begin + reset_du!(gradients_x, dg, cache) + reset_du!(gradients_y, dg, cache) + end - for ii in eachnode(dg) - multiply_add_to_node_vars!(gradients_x, derivative_dhat[ii, i], - u_node, - equations_parabolic, dg, ii, j, element) + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + (; derivative_dhat) = dg.basis + (; contravariant_vectors) = cache.elements + + @threaded for element in eachelement(dg, cache) + + # Calculate gradients with respect to reference coordinates in one element + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars( + u_transformed, equations_parabolic, dg, i, j, + element + ) + + for ii in eachnode(dg) + multiply_add_to_node_vars!( + gradients_x, derivative_dhat[ii, i], + u_node, + equations_parabolic, dg, ii, j, element + ) + end + + for jj in eachnode(dg) + multiply_add_to_node_vars!( + gradients_y, derivative_dhat[jj, j], + u_node, + equations_parabolic, dg, i, jj, element + ) + end end - for jj in eachnode(dg) - multiply_add_to_node_vars!(gradients_y, derivative_dhat[jj, j], - u_node, - equations_parabolic, dg, i, jj, element) + # now that the reference coordinate gradients are computed, transform them node-by-node to physical gradients + # using the contravariant vectors + for j in eachnode(dg), i in eachnode(dg) + Ja11, Ja12 = get_contravariant_vector( + 1, contravariant_vectors, i, j, + element + ) + Ja21, Ja22 = get_contravariant_vector( + 2, contravariant_vectors, i, j, + element + ) + + gradients_reference_1 = get_node_vars( + gradients_x, equations_parabolic, + dg, + i, j, element + ) + gradients_reference_2 = get_node_vars( + gradients_y, equations_parabolic, + dg, + i, j, element + ) + + # note that the contravariant vectors are transposed compared with computations of flux + # divergences in `calc_volume_integral!`. See + # https://github.com/trixi-framework/Trixi.jl/pull/1490#discussion_r1213345190 + # for a more detailed discussion. + gradient_x_node = Ja11 * gradients_reference_1 + + Ja21 * gradients_reference_2 + gradient_y_node = Ja12 * gradients_reference_1 + + Ja22 * gradients_reference_2 + + set_node_vars!( + gradients_x, gradient_x_node, equations_parabolic, dg, i, + j, + element + ) + set_node_vars!( + gradients_y, gradient_y_node, equations_parabolic, dg, i, + j, + element + ) end end - - # now that the reference coordinate gradients are computed, transform them node-by-node to physical gradients - # using the contravariant vectors - for j in eachnode(dg), i in eachnode(dg) - Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, - element) - Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, - element) - - gradients_reference_1 = get_node_vars(gradients_x, equations_parabolic, - dg, - i, j, element) - gradients_reference_2 = get_node_vars(gradients_y, equations_parabolic, - dg, - i, j, element) - - # note that the contravariant vectors are transposed compared with computations of flux - # divergences in `calc_volume_integral!`. See - # https://github.com/trixi-framework/Trixi.jl/pull/1490#discussion_r1213345190 - # for a more detailed discussion. - gradient_x_node = Ja11 * gradients_reference_1 + - Ja21 * gradients_reference_2 - gradient_y_node = Ja12 * gradients_reference_1 + - Ja22 * gradients_reference_2 - - set_node_vars!(gradients_x, gradient_x_node, equations_parabolic, dg, i, - j, - element) - set_node_vars!(gradients_y, gradient_y_node, equations_parabolic, dg, i, - j, - element) - end end - end - # Prolong solution to interfaces. - # This reuses `prolong2interfaces` for the purely hyperbolic case. - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache_parabolic, u_transformed, mesh, - equations_parabolic, dg.surface_integral, dg) - end - - # Calculate interface fluxes for the gradient. - # This reuses `calc_interface_flux!` for the purely hyperbolic case. - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache_parabolic.elements.surface_flux_values, - mesh, False(), # False() = no nonconservative terms - equations_parabolic, dg.surface_integral, dg, - cache_parabolic) - end - - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache_parabolic, u_transformed, mesh, - equations_parabolic, dg.surface_integral, dg) - end + # Prolong solution to interfaces. + # This reuses `prolong2interfaces` for the purely hyperbolic case. + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache_parabolic, u_transformed, mesh, + equations_parabolic, dg.surface_integral, dg + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux_gradients!(cache_parabolic, t, boundary_conditions_parabolic, - mesh, equations_parabolic, dg.surface_integral, - dg) - end + # Calculate interface fluxes for the gradient. + # This reuses `calc_interface_flux!` for the purely hyperbolic case. + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache_parabolic.elements.surface_flux_values, + mesh, False(), # False() = no nonconservative terms + equations_parabolic, dg.surface_integral, dg, + cache_parabolic + ) + end - # Prolong solution to mortars. This resues the hyperbolic version of `prolong2mortars` - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u_transformed, mesh, equations_parabolic, - dg.mortar, dg.surface_integral, dg) - end + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache_parabolic, u_transformed, mesh, + equations_parabolic, dg.surface_integral, dg + ) + end - # Calculate mortar fluxes. This reuses the hyperbolic version of `calc_mortar_flux`, - # along with a specialization on `calc_mortar_flux!(fstar, ...)` and `mortar_fluxes_to_elements!` for - # AbstractEquationsParabolic. - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(cache_parabolic.elements.surface_flux_values, - mesh, False(), # False() = no nonconservative terms - equations_parabolic, - dg.mortar, dg.surface_integral, dg, cache) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux_gradients!( + cache_parabolic, t, boundary_conditions_parabolic, + mesh, equations_parabolic, dg.surface_integral, + dg + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - (; boundary_interpolation) = dg.basis - (; surface_flux_values) = cache_parabolic.elements - (; contravariant_vectors) = cache.elements + # Prolong solution to mortars. This resues the hyperbolic version of `prolong2mortars` + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!( + cache, u_transformed, mesh, equations_parabolic, + dg.mortar, dg.surface_integral, dg + ) + end - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` to let `@muladd` turn these - # into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] - @threaded for element in eachelement(dg, cache) - for l in eachnode(dg) - for v in eachvariable(equations_parabolic) + # Calculate mortar fluxes. This reuses the hyperbolic version of `calc_mortar_flux`, + # along with a specialization on `calc_mortar_flux!(fstar, ...)` and `mortar_fluxes_to_elements!` for + # AbstractEquationsParabolic. + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!( + cache_parabolic.elements.surface_flux_values, + mesh, False(), # False() = no nonconservative terms + equations_parabolic, + dg.mortar, dg.surface_integral, dg, cache + ) + end - # Compute x-component of gradients - - # surface at -x - normal_direction_x, _ = get_normal_direction(1, - contravariant_vectors, - 1, l, element) - gradients_x[v, 1, l, element] = (gradients_x[v, 1, l, element] + - surface_flux_values[v, l, 1, - element] * - factor_1 * normal_direction_x) - - # surface at +x - normal_direction_x, _ = get_normal_direction(2, - contravariant_vectors, - nnodes(dg), l, element) - gradients_x[v, nnodes(dg), l, element] = (gradients_x[v, nnodes(dg), - l, - element] + - surface_flux_values[v, l, - 2, - element] * - factor_2 * - normal_direction_x) - - # surface at -y - normal_direction_x, _ = get_normal_direction(3, - contravariant_vectors, - l, 1, element) - gradients_x[v, l, 1, element] = (gradients_x[v, l, 1, element] + - surface_flux_values[v, l, 3, - element] * - factor_1 * normal_direction_x) - - # surface at +y - normal_direction_x, _ = get_normal_direction(4, - contravariant_vectors, - l, nnodes(dg), element) - gradients_x[v, l, nnodes(dg), element] = (gradients_x[v, l, - nnodes(dg), - element] + - surface_flux_values[v, l, - 4, - element] * - factor_2 * - normal_direction_x) - - # Compute y-component of gradients - - # surface at -x - _, normal_direction_y = get_normal_direction(1, - contravariant_vectors, - 1, l, element) - gradients_y[v, 1, l, element] = (gradients_y[v, 1, l, element] + - surface_flux_values[v, l, 1, - element] * - factor_1 * normal_direction_y) - - # surface at +x - _, normal_direction_y = get_normal_direction(2, - contravariant_vectors, - nnodes(dg), l, element) - gradients_y[v, nnodes(dg), l, element] = (gradients_y[v, nnodes(dg), - l, - element] + - surface_flux_values[v, l, - 2, - element] * - factor_2 * - normal_direction_y) - - # surface at -y - _, normal_direction_y = get_normal_direction(3, - contravariant_vectors, - l, 1, element) - gradients_y[v, l, 1, element] = (gradients_y[v, l, 1, element] + - surface_flux_values[v, l, 3, - element] * - factor_1 * normal_direction_y) - - # surface at +y - _, normal_direction_y = get_normal_direction(4, - contravariant_vectors, - l, nnodes(dg), element) - gradients_y[v, l, nnodes(dg), element] = (gradients_y[v, l, - nnodes(dg), - element] + - surface_flux_values[v, l, - 4, - element] * - factor_2 * - normal_direction_y) + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + (; boundary_interpolation) = dg.basis + (; surface_flux_values) = cache_parabolic.elements + (; contravariant_vectors) = cache.elements + + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg) + for v in eachvariable(equations_parabolic) + + # Compute x-component of gradients + + # surface at -x + normal_direction_x, _ = get_normal_direction( + 1, + contravariant_vectors, + 1, l, element + ) + gradients_x[v, 1, l, element] = ( + gradients_x[v, 1, l, element] + + surface_flux_values[ + v, l, 1, + element, + ] * + factor_1 * normal_direction_x + ) + + # surface at +x + normal_direction_x, _ = get_normal_direction( + 2, + contravariant_vectors, + nnodes(dg), l, element + ) + gradients_x[v, nnodes(dg), l, element] = ( + gradients_x[ + v, nnodes(dg), + l, + element, + ] + + surface_flux_values[ + v, l, + 2, + element, + ] * + factor_2 * + normal_direction_x + ) + + # surface at -y + normal_direction_x, _ = get_normal_direction( + 3, + contravariant_vectors, + l, 1, element + ) + gradients_x[v, l, 1, element] = ( + gradients_x[v, l, 1, element] + + surface_flux_values[ + v, l, 3, + element, + ] * + factor_1 * normal_direction_x + ) + + # surface at +y + normal_direction_x, _ = get_normal_direction( + 4, + contravariant_vectors, + l, nnodes(dg), element + ) + gradients_x[v, l, nnodes(dg), element] = ( + gradients_x[ + v, l, + nnodes(dg), + element, + ] + + surface_flux_values[ + v, l, + 4, + element, + ] * + factor_2 * + normal_direction_x + ) + + # Compute y-component of gradients + + # surface at -x + _, normal_direction_y = get_normal_direction( + 1, + contravariant_vectors, + 1, l, element + ) + gradients_y[v, 1, l, element] = ( + gradients_y[v, 1, l, element] + + surface_flux_values[ + v, l, 1, + element, + ] * + factor_1 * normal_direction_y + ) + + # surface at +x + _, normal_direction_y = get_normal_direction( + 2, + contravariant_vectors, + nnodes(dg), l, element + ) + gradients_y[v, nnodes(dg), l, element] = ( + gradients_y[ + v, nnodes(dg), + l, + element, + ] + + surface_flux_values[ + v, l, + 2, + element, + ] * + factor_2 * + normal_direction_y + ) + + # surface at -y + _, normal_direction_y = get_normal_direction( + 3, + contravariant_vectors, + l, 1, element + ) + gradients_y[v, l, 1, element] = ( + gradients_y[v, l, 1, element] + + surface_flux_values[ + v, l, 3, + element, + ] * + factor_1 * normal_direction_y + ) + + # surface at +y + _, normal_direction_y = get_normal_direction( + 4, + contravariant_vectors, + l, nnodes(dg), element + ) + gradients_y[v, l, nnodes(dg), element] = ( + gradients_y[ + v, l, + nnodes(dg), + element, + ] + + surface_flux_values[ + v, l, + 4, + element, + ] * + factor_2 * + normal_direction_y + ) + end end end end - end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" begin - apply_jacobian_parabolic!(gradients_x, mesh, equations_parabolic, dg, - cache_parabolic) - apply_jacobian_parabolic!(gradients_y, mesh, equations_parabolic, dg, - cache_parabolic) + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" begin + apply_jacobian_parabolic!( + gradients_x, mesh, equations_parabolic, dg, + cache_parabolic + ) + apply_jacobian_parabolic!( + gradients_y, mesh, equations_parabolic, dg, + cache_parabolic + ) + end + + return nothing end - return nothing -end - -# This version is called during `calc_gradients!` and must be specialized because the -# flux for the gradient is {u}, which doesn't depend on the outward normal. Thus, -# you don't need to scale by 2 (e.g., the scaling factor in the normals (and in the -# contravariant vectors) along large/small elements across a non-conforming -# interface in 2D) and flip the sign when storing the mortar fluxes back -# into `surface_flux_values`. -@inline function mortar_fluxes_to_elements!(surface_flux_values, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - equations::AbstractEquationsParabolic, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, mortar, fstar, u_buffer) - @unpack neighbor_ids, node_indices = cache.mortars - # Copy solution small to small - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) - - for position in 1:2 - element = neighbor_ids[position, mortar] - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i, small_direction, element] = fstar[position][v, - i] + # This version is called during `calc_gradients!` and must be specialized because the + # flux for the gradient is {u}, which doesn't depend on the outward normal. Thus, + # you don't need to scale by 2 (e.g., the scaling factor in the normals (and in the + # contravariant vectors) along large/small elements across a non-conforming + # interface in 2D) and flip the sign when storing the mortar fluxes back + # into `surface_flux_values`. + @inline function mortar_fluxes_to_elements!( + surface_flux_values, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + equations::AbstractEquationsParabolic, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, mortar, fstar, u_buffer + ) + @unpack neighbor_ids, node_indices = cache.mortars + # Copy solution small to small + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) + + for position in 1:2 + element = neighbor_ids[position, mortar] + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i, small_direction, element] = fstar[position][ + v, + i, + ] + end end end - end - # Project small fluxes to large element. - multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_upper, fstar[2], - mortar_l2.reverse_lower, fstar[1]) - - # Copy interpolated flux values from buffer to large element face in the - # correct orientation. - # Note that the index of the small sides will always run forward but - # the index of the large side might need to run backwards for flipped sides. - large_element = neighbor_ids[3, mortar] - large_indices = node_indices[2, mortar] - large_direction = indices2direction(large_indices) - - if :i_backward in large_indices - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, end + 1 - i, large_direction, large_element] = u_buffer[v, - i] + # Project small fluxes to large element. + multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_upper, fstar[2], + mortar_l2.reverse_lower, fstar[1] + ) + + # Copy interpolated flux values from buffer to large element face in the + # correct orientation. + # Note that the index of the small sides will always run forward but + # the index of the large side might need to run backwards for flipped sides. + large_element = neighbor_ids[3, mortar] + large_indices = node_indices[2, mortar] + large_direction = indices2direction(large_indices) + + if :i_backward in large_indices + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, end + 1 - i, large_direction, large_element] = u_buffer[ + v, + i, + ] + end end - end - else - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i, large_direction, large_element] = u_buffer[v, - i] + else + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i, large_direction, large_element] = u_buffer[ + v, + i, + ] + end end end - end - return nothing -end - -# This version is used for parabolic gradient computations -@inline function calc_interface_flux!(surface_flux_values, mesh::P4estMesh{2}, - nonconservative_terms::False, - equations::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - interface_index, normal_direction, - primary_node_index, primary_direction_index, - primary_element_index, - secondary_node_index, secondary_direction_index, - secondary_element_index) - @unpack u = cache.interfaces - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, primary_node_index, - interface_index) - - flux_ = 0.5f0 * (u_ll + u_rr) # we assume that the gradient computations utilize a central flux - - # Note that we don't flip the sign on the secondary flux. This is because for parabolic terms, - # the normals are not embedded in `flux_` for the parabolic gradient computations. - for v in eachvariable(equations) - surface_flux_values[v, primary_node_index, primary_direction_index, primary_element_index] = flux_[v] - surface_flux_values[v, secondary_node_index, secondary_direction_index, secondary_element_index] = flux_[v] + return nothing end -end - -# This is the version used when calculating the divergence of the viscous fluxes -function calc_volume_integral!(du, flux_viscous, - mesh::P4estMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - dg::DGSEM, cache) - (; derivative_dhat) = dg.basis - (; contravariant_vectors) = cache.elements - flux_viscous_x, flux_viscous_y = flux_viscous - - @threaded for element in eachelement(dg, cache) - # Calculate volume terms in one element - for j in eachnode(dg), i in eachnode(dg) - flux1 = get_node_vars(flux_viscous_x, equations_parabolic, dg, i, j, - element) - flux2 = get_node_vars(flux_viscous_y, equations_parabolic, dg, i, j, - element) - - # Compute the contravariant flux by taking the scalar product of the - # first contravariant vector Ja^1 and the flux vector - Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, - element) - contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[ii, i], - contravariant_flux1, - equations_parabolic, dg, ii, j, element) - end - # Compute the contravariant flux by taking the scalar product of the - # second contravariant vector Ja^2 and the flux vector - Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, - element) - contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 - for jj in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[jj, j], - contravariant_flux2, - equations_parabolic, dg, i, jj, element) - end + # This version is used for parabolic gradient computations + @inline function calc_interface_flux!( + surface_flux_values, mesh::P4estMesh{2}, + nonconservative_terms::False, + equations::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + interface_index, normal_direction, + primary_node_index, primary_direction_index, + primary_element_index, + secondary_node_index, secondary_direction_index, + secondary_element_index + ) + @unpack u = cache.interfaces + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, primary_node_index, + interface_index + ) + + flux_ = 0.5f0 * (u_ll + u_rr) # we assume that the gradient computations utilize a central flux + + # Note that we don't flip the sign on the secondary flux. This is because for parabolic terms, + # the normals are not embedded in `flux_` for the parabolic gradient computations. + for v in eachvariable(equations) + surface_flux_values[v, primary_node_index, primary_direction_index, primary_element_index] = flux_[v] + surface_flux_values[v, secondary_node_index, secondary_direction_index, secondary_element_index] = flux_[v] end end - return nothing -end - -# This is the version used when calculating the divergence of the viscous fluxes -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache_parabolic, flux_viscous, - mesh::P4estMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - (; interfaces) = cache_parabolic - (; contravariant_vectors) = cache_parabolic.elements - index_range = eachnode(dg) - flux_viscous_x, flux_viscous_y = flux_viscous - - @threaded for interface in eachinterface(dg, cache) - # Copy solution data from the primary element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - # Note that in the current implementation, the interface will be - # "aligned at the primary element", i.e., the index of the primary side - # will always run forwards. - primary_element = interfaces.neighbor_ids[1, interface] - primary_indices = interfaces.node_indices[1, interface] - primary_direction = indices2direction(primary_indices) - - i_primary_start, i_primary_step = index_to_start_step_2d(primary_indices[1], - index_range) - j_primary_start, j_primary_step = index_to_start_step_2d(primary_indices[2], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - for i in eachnode(dg) - - # this is the outward normal direction on the primary element - normal_direction = get_normal_direction(primary_direction, - contravariant_vectors, - i_primary, j_primary, - primary_element) - - for v in eachvariable(equations_parabolic) - # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! - flux_viscous = SVector(flux_viscous_x[v, i_primary, j_primary, - primary_element], - flux_viscous_y[v, i_primary, j_primary, - primary_element]) - - interfaces.u[1, v, i, interface] = dot(flux_viscous, normal_direction) - end - i_primary += i_primary_step - j_primary += j_primary_step - end - - # Copy solution data from the secondary element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - secondary_element = interfaces.neighbor_ids[2, interface] - secondary_indices = interfaces.node_indices[2, interface] - secondary_direction = indices2direction(secondary_indices) - - i_secondary_start, i_secondary_step = index_to_start_step_2d(secondary_indices[1], - index_range) - j_secondary_start, j_secondary_step = index_to_start_step_2d(secondary_indices[2], - index_range) - - i_secondary = i_secondary_start - j_secondary = j_secondary_start - for i in eachnode(dg) - # This is the outward normal direction on the secondary element. - # Here, we assume that normal_direction on the secondary element is - # the negative of normal_direction on the primary element. - normal_direction = get_normal_direction(secondary_direction, - contravariant_vectors, - i_secondary, j_secondary, - secondary_element) - - for v in eachvariable(equations_parabolic) - # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! - flux_viscous = SVector(flux_viscous_x[v, i_secondary, j_secondary, - secondary_element], - flux_viscous_y[v, i_secondary, j_secondary, - secondary_element]) - # store the normal flux with respect to the primary normal direction - interfaces.u[2, v, i, interface] = -dot(flux_viscous, normal_direction) + # This is the version used when calculating the divergence of the viscous fluxes + function calc_volume_integral!( + du, flux_viscous, + mesh::P4estMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + dg::DGSEM, cache + ) + (; derivative_dhat) = dg.basis + (; contravariant_vectors) = cache.elements + flux_viscous_x, flux_viscous_y = flux_viscous + + @threaded for element in eachelement(dg, cache) + # Calculate volume terms in one element + for j in eachnode(dg), i in eachnode(dg) + flux1 = get_node_vars( + flux_viscous_x, equations_parabolic, dg, i, j, + element + ) + flux2 = get_node_vars( + flux_viscous_y, equations_parabolic, dg, i, j, + element + ) + + # Compute the contravariant flux by taking the scalar product of the + # first contravariant vector Ja^1 and the flux vector + Ja11, Ja12 = get_contravariant_vector( + 1, contravariant_vectors, i, j, + element + ) + contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 + for ii in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[ii, i], + contravariant_flux1, + equations_parabolic, dg, ii, j, element + ) + end + + # Compute the contravariant flux by taking the scalar product of the + # second contravariant vector Ja^2 and the flux vector + Ja21, Ja22 = get_contravariant_vector( + 2, contravariant_vectors, i, j, + element + ) + contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 + for jj in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[jj, j], + contravariant_flux2, + equations_parabolic, dg, i, jj, element + ) + end end - i_secondary += i_secondary_step - j_secondary += j_secondary_step end - end - return nothing -end - -# This version is used for divergence flux computations -function calc_interface_flux!(surface_flux_values, - mesh::P4estMesh{2}, equations_parabolic, - dg::DG, cache_parabolic) - (; neighbor_ids, node_indices) = cache_parabolic.interfaces - (; contravariant_vectors) = cache_parabolic.elements - index_range = eachnode(dg) - index_end = last(index_range) - - @threaded for interface in eachinterface(dg, cache_parabolic) - # Get element and side index information on the primary element - primary_element = neighbor_ids[1, interface] - primary_indices = node_indices[1, interface] - primary_direction_index = indices2direction(primary_indices) - - # Create the local i,j indexing on the primary element used to pull normal direction information - i_primary_start, i_primary_step = index_to_start_step_2d(primary_indices[1], - index_range) - j_primary_start, j_primary_step = index_to_start_step_2d(primary_indices[2], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - - # Get element and side index information on the secondary element - secondary_element = neighbor_ids[2, interface] - secondary_indices = node_indices[2, interface] - secondary_direction_index = indices2direction(secondary_indices) - - # Initiate the secondary index to be used in the surface for loop. - # This index on the primary side will always run forward but - # the secondary index might need to run backwards for flipped sides. - if :i_backward in secondary_indices - node_secondary = index_end - node_secondary_step = -1 - else - node_secondary = 1 - node_secondary_step = 1 - end + return nothing + end - for node in eachnode(dg) - # We prolong the viscous flux dotted with respect the outward normal on the - # primary element. We assume a BR-1 type of flux. - viscous_flux_normal_ll, viscous_flux_normal_rr = get_surface_node_vars(cache_parabolic.interfaces.u, - equations_parabolic, - dg, - node, - interface) + # This is the version used when calculating the divergence of the viscous fluxes + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache_parabolic, flux_viscous, + mesh::P4estMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + (; interfaces) = cache_parabolic + (; contravariant_vectors) = cache_parabolic.elements + index_range = eachnode(dg) + flux_viscous_x, flux_viscous_y = flux_viscous + + @threaded for interface in eachinterface(dg, cache) + # Copy solution data from the primary element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + # Note that in the current implementation, the interface will be + # "aligned at the primary element", i.e., the index of the primary side + # will always run forwards. + primary_element = interfaces.neighbor_ids[1, interface] + primary_indices = interfaces.node_indices[1, interface] + primary_direction = indices2direction(primary_indices) + + i_primary_start, i_primary_step = index_to_start_step_2d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step = index_to_start_step_2d( + primary_indices[2], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + for i in eachnode(dg) - flux = 0.5f0 * (viscous_flux_normal_ll + viscous_flux_normal_rr) + # this is the outward normal direction on the primary element + normal_direction = get_normal_direction( + primary_direction, + contravariant_vectors, + i_primary, j_primary, + primary_element + ) - for v in eachvariable(equations_parabolic) - surface_flux_values[v, node, primary_direction_index, primary_element] = flux[v] - surface_flux_values[v, node_secondary, secondary_direction_index, secondary_element] = -flux[v] + for v in eachvariable(equations_parabolic) + # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! + flux_viscous = SVector( + flux_viscous_x[ + v, i_primary, j_primary, + primary_element, + ], + flux_viscous_y[ + v, i_primary, j_primary, + primary_element, + ] + ) + + interfaces.u[1, v, i, interface] = dot(flux_viscous, normal_direction) + end + i_primary += i_primary_step + j_primary += j_primary_step end - # Increment primary element indices to pull the normal direction - i_primary += i_primary_step - j_primary += j_primary_step - # Increment the surface node index along the secondary element - node_secondary += node_secondary_step + # Copy solution data from the secondary element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + secondary_element = interfaces.neighbor_ids[2, interface] + secondary_indices = interfaces.node_indices[2, interface] + secondary_direction = indices2direction(secondary_indices) + + i_secondary_start, i_secondary_step = index_to_start_step_2d( + secondary_indices[1], + index_range + ) + j_secondary_start, j_secondary_step = index_to_start_step_2d( + secondary_indices[2], + index_range + ) + + i_secondary = i_secondary_start + j_secondary = j_secondary_start + for i in eachnode(dg) + # This is the outward normal direction on the secondary element. + # Here, we assume that normal_direction on the secondary element is + # the negative of normal_direction on the primary element. + normal_direction = get_normal_direction( + secondary_direction, + contravariant_vectors, + i_secondary, j_secondary, + secondary_element + ) + + for v in eachvariable(equations_parabolic) + # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! + flux_viscous = SVector( + flux_viscous_x[ + v, i_secondary, j_secondary, + secondary_element, + ], + flux_viscous_y[ + v, i_secondary, j_secondary, + secondary_element, + ] + ) + # store the normal flux with respect to the primary normal direction + interfaces.u[2, v, i, interface] = -dot(flux_viscous, normal_direction) + end + i_secondary += i_secondary_step + j_secondary += j_secondary_step + end end + + return nothing end - return nothing -end + # This version is used for divergence flux computations + function calc_interface_flux!( + surface_flux_values, + mesh::P4estMesh{2}, equations_parabolic, + dg::DG, cache_parabolic + ) + (; neighbor_ids, node_indices) = cache_parabolic.interfaces + (; contravariant_vectors) = cache_parabolic.elements + index_range = eachnode(dg) + index_end = last(index_range) + + @threaded for interface in eachinterface(dg, cache_parabolic) + # Get element and side index information on the primary element + primary_element = neighbor_ids[1, interface] + primary_indices = node_indices[1, interface] + primary_direction_index = indices2direction(primary_indices) + + # Create the local i,j indexing on the primary element used to pull normal direction information + i_primary_start, i_primary_step = index_to_start_step_2d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step = index_to_start_step_2d( + primary_indices[2], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + + # Get element and side index information on the secondary element + secondary_element = neighbor_ids[2, interface] + secondary_indices = node_indices[2, interface] + secondary_direction_index = indices2direction(secondary_indices) + + # Initiate the secondary index to be used in the surface for loop. + # This index on the primary side will always run forward but + # the secondary index might need to run backwards for flipped sides. + if :i_backward in secondary_indices + node_secondary = index_end + node_secondary_step = -1 + else + node_secondary = 1 + node_secondary_step = 1 + end -function prolong2mortars_divergence!(cache, flux_viscous::Vector{Array{uEltype, 4}}, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, - dg::DGSEM) where {uEltype <: Real} - @unpack neighbor_ids, node_indices = cache.mortars - @unpack contravariant_vectors = cache.elements - index_range = eachnode(dg) + for node in eachnode(dg) + # We prolong the viscous flux dotted with respect the outward normal on the + # primary element. We assume a BR-1 type of flux. + viscous_flux_normal_ll, viscous_flux_normal_rr = get_surface_node_vars( + cache_parabolic.interfaces.u, + equations_parabolic, + dg, + node, + interface + ) + + flux = 0.5f0 * (viscous_flux_normal_ll + viscous_flux_normal_rr) - flux_viscous_x, flux_viscous_y = flux_viscous + for v in eachvariable(equations_parabolic) + surface_flux_values[v, node, primary_direction_index, primary_element] = flux[v] + surface_flux_values[v, node_secondary, secondary_direction_index, secondary_element] = -flux[v] + end - @threaded for mortar in eachmortar(dg, cache) - # Copy solution data from the small elements using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - small_indices = node_indices[1, mortar] - direction_index = indices2direction(small_indices) + # Increment primary element indices to pull the normal direction + i_primary += i_primary_step + j_primary += j_primary_step + # Increment the surface node index along the secondary element + node_secondary += node_secondary_step + end + end - i_small_start, i_small_step = index_to_start_step_2d(small_indices[1], - index_range) - j_small_start, j_small_step = index_to_start_step_2d(small_indices[2], - index_range) + return nothing + end - for position in 1:2 - i_small = i_small_start - j_small = j_small_start - element = neighbor_ids[position, mortar] + function prolong2mortars_divergence!( + cache, flux_viscous::Vector{Array{uEltype, 4}}, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, + dg::DGSEM + ) where {uEltype <: Real} + @unpack neighbor_ids, node_indices = cache.mortars + @unpack contravariant_vectors = cache.elements + index_range = eachnode(dg) + + flux_viscous_x, flux_viscous_y = flux_viscous + + @threaded for mortar in eachmortar(dg, cache) + # Copy solution data from the small elements using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + small_indices = node_indices[1, mortar] + direction_index = indices2direction(small_indices) + + i_small_start, i_small_step = index_to_start_step_2d( + small_indices[1], + index_range + ) + j_small_start, j_small_step = index_to_start_step_2d( + small_indices[2], + index_range + ) + + for position in 1:2 + i_small = i_small_start + j_small = j_small_start + element = neighbor_ids[position, mortar] + for i in eachnode(dg) + normal_direction = get_normal_direction( + direction_index, + contravariant_vectors, + i_small, j_small, element + ) + + for v in eachvariable(equations) + flux_viscous = SVector( + flux_viscous_x[v, i_small, j_small, element], + flux_viscous_y[v, i_small, j_small, element] + ) + + cache.mortars.u[1, v, position, i, mortar] = dot( + flux_viscous, + normal_direction + ) + end + i_small += i_small_step + j_small += j_small_step + end + end + + # Buffer to copy solution values of the large element in the correct orientation + # before interpolating + u_buffer = cache.u_threaded[Threads.threadid()] + + # Copy solution of large element face to buffer in the + # correct orientation + large_indices = node_indices[2, mortar] + direction_index = indices2direction(large_indices) + + i_large_start, i_large_step = index_to_start_step_2d( + large_indices[1], + index_range + ) + j_large_start, j_large_step = index_to_start_step_2d( + large_indices[2], + index_range + ) + + i_large = i_large_start + j_large = j_large_start + element = neighbor_ids[3, mortar] for i in eachnode(dg) - normal_direction = get_normal_direction(direction_index, - contravariant_vectors, - i_small, j_small, element) + normal_direction = get_normal_direction( + direction_index, + contravariant_vectors, + i_large, j_large, element + ) for v in eachvariable(equations) - flux_viscous = SVector(flux_viscous_x[v, i_small, j_small, element], - flux_viscous_y[v, i_small, j_small, element]) - - cache.mortars.u[1, v, position, i, mortar] = dot(flux_viscous, - normal_direction) + flux_viscous = SVector( + flux_viscous_x[v, i_large, j_large, element], + flux_viscous_y[v, i_large, j_large, element] + ) + + # We prolong the viscous flux dotted with respect the outward normal + # on the small element. We scale by -1/2 here because the normal + # direction on the large element is negative 2x that of the small + # element (these normal directions are "scaled" by the surface Jacobian) + u_buffer[v, i] = -0.5f0 * dot(flux_viscous, normal_direction) end - i_small += i_small_step - j_small += j_small_step + i_large += i_large_step + j_large += j_large_step end + + # Interpolate large element face data from buffer to small face locations + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 1, :, mortar), + mortar_l2.forward_lower, + u_buffer + ) + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 2, :, mortar), + mortar_l2.forward_upper, + u_buffer + ) end - # Buffer to copy solution values of the large element in the correct orientation - # before interpolating - u_buffer = cache.u_threaded[Threads.threadid()] + return nothing + end - # Copy solution of large element face to buffer in the - # correct orientation - large_indices = node_indices[2, mortar] - direction_index = indices2direction(large_indices) - - i_large_start, i_large_step = index_to_start_step_2d(large_indices[1], - index_range) - j_large_start, j_large_step = index_to_start_step_2d(large_indices[2], - index_range) - - i_large = i_large_start - j_large = j_large_start - element = neighbor_ids[3, mortar] - for i in eachnode(dg) - normal_direction = get_normal_direction(direction_index, - contravariant_vectors, - i_large, j_large, element) - - for v in eachvariable(equations) - flux_viscous = SVector(flux_viscous_x[v, i_large, j_large, element], - flux_viscous_y[v, i_large, j_large, element]) - - # We prolong the viscous flux dotted with respect the outward normal - # on the small element. We scale by -1/2 here because the normal - # direction on the large element is negative 2x that of the small - # element (these normal directions are "scaled" by the surface Jacobian) - u_buffer[v, i] = -0.5f0 * dot(flux_viscous, normal_direction) + # We specialize `calc_mortar_flux!` for the divergence part of + # the parabolic terms. + function calc_mortar_flux_divergence!( + surface_flux_values, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + equations::AbstractEquationsParabolic, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack neighbor_ids, node_indices = cache.mortars + @unpack contravariant_vectors = cache.elements + @unpack fstar_upper_threaded, fstar_lower_threaded = cache + index_range = eachnode(dg) + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar = ( + fstar_lower_threaded[Threads.threadid()], + fstar_upper_threaded[Threads.threadid()], + ) + + for position in 1:2 + for node in eachnode(dg) + for v in eachvariable(equations) + viscous_flux_normal_ll = cache.mortars.u[ + 1, v, position, node, + mortar, + ] + viscous_flux_normal_rr = cache.mortars.u[ + 2, v, position, node, + mortar, + ] + + # TODO: parabolic; only BR1 at the moment + fstar[position][v, node] = 0.5f0 * ( + viscous_flux_normal_ll + + viscous_flux_normal_rr + ) + end + end end - i_large += i_large_step - j_large += j_large_step + + # Buffer to interpolate flux values of the large element to before + # copying in the correct orientation + u_buffer = cache.u_threaded[Threads.threadid()] + + # this reuses the hyperbolic version of `mortar_fluxes_to_elements!` + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, + mortar, fstar, u_buffer + ) end - # Interpolate large element face data from buffer to small face locations - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 1, :, mortar), - mortar_l2.forward_lower, - u_buffer) - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 2, :, mortar), - mortar_l2.forward_upper, - u_buffer) + return nothing end - return nothing -end - -# We specialize `calc_mortar_flux!` for the divergence part of -# the parabolic terms. -function calc_mortar_flux_divergence!(surface_flux_values, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - equations::AbstractEquationsParabolic, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack neighbor_ids, node_indices = cache.mortars - @unpack contravariant_vectors = cache.elements - @unpack fstar_upper_threaded, fstar_lower_threaded = cache - index_range = eachnode(dg) - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar = (fstar_lower_threaded[Threads.threadid()], - fstar_upper_threaded[Threads.threadid()]) + # We structure `calc_interface_flux!` similarly to "calc_mortar_flux!" for + # hyperbolic equations with no nonconservative terms. + # The reasoning is that parabolic fluxes are treated like conservative + # terms (e.g., we compute a viscous conservative "flux") and thus no + # non-conservative terms are present. + @inline function calc_mortar_flux!( + fstar, + mesh::Union{P4estMesh{2}, T8codeMesh{2}}, + nonconservative_terms::False, + equations::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + mortar_index, position_index, normal_direction, + node_index + ) + @unpack u = cache.mortars + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, position_index, node_index, + mortar_index + ) + + # TODO: parabolic; only BR1 at the moment + flux_ = 0.5f0 * (u_ll + u_rr) + + # Copy flux to buffer + set_node_vars!(fstar[position_index], flux_, equations, dg, node_index) + end - for position in 1:2 - for node in eachnode(dg) - for v in eachvariable(equations) - viscous_flux_normal_ll = cache.mortars.u[1, v, position, node, - mortar] - viscous_flux_normal_rr = cache.mortars.u[2, v, position, node, - mortar] - - # TODO: parabolic; only BR1 at the moment - fstar[position][v, node] = 0.5f0 * (viscous_flux_normal_ll + - viscous_flux_normal_rr) + # TODO: parabolic, finish implementing `calc_boundary_flux_gradients!` and `calc_boundary_flux_divergence!` + function prolong2boundaries!( + cache_parabolic, flux_viscous, + mesh::P4estMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + (; boundaries) = cache_parabolic + (; contravariant_vectors) = cache_parabolic.elements + index_range = eachnode(dg) + + flux_viscous_x, flux_viscous_y = flux_viscous + + @threaded for boundary in eachboundary(dg, cache_parabolic) + # Copy solution data from the element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) + j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) + + i_node = i_node_start + j_node = j_node_start + for i in eachnode(dg) + # this is the outward normal direction on the primary element + normal_direction = get_normal_direction( + direction, contravariant_vectors, + i_node, j_node, element + ) + + for v in eachvariable(equations_parabolic) + flux_viscous = SVector( + flux_viscous_x[v, i_node, j_node, element], + flux_viscous_y[v, i_node, j_node, element] + ) + + boundaries.u[v, i, boundary] = dot(flux_viscous, normal_direction) end + i_node += i_node_step + j_node += j_node_step end end + return nothing + end - # Buffer to interpolate flux values of the large element to before - # copying in the correct orientation - u_buffer = cache.u_threaded[Threads.threadid()] + function calc_boundary_flux_gradients!( + cache, t, + boundary_condition::Union{ + BoundaryConditionPeriodic, + BoundaryConditionDoNothing, + }, + mesh::P4estMesh, equations, surface_integral, + dg::DG + ) + @assert isempty(eachboundary(dg, cache)) + end - # this reuses the hyperbolic version of `mortar_fluxes_to_elements!` - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, - mortar, fstar, u_buffer) + # Function barrier for type stability + function calc_boundary_flux_gradients!( + cache, t, boundary_conditions, mesh::P4estMesh, + equations, surface_integral, dg::DG + ) + (; boundary_condition_types, boundary_indices) = boundary_conditions + + calc_boundary_flux_by_type!( + cache, t, boundary_condition_types, boundary_indices, + Gradient(), mesh, equations, surface_integral, dg + ) + return nothing end - return nothing -end - -# We structure `calc_interface_flux!` similarly to "calc_mortar_flux!" for -# hyperbolic equations with no nonconservative terms. -# The reasoning is that parabolic fluxes are treated like conservative -# terms (e.g., we compute a viscous conservative "flux") and thus no -# non-conservative terms are present. -@inline function calc_mortar_flux!(fstar, - mesh::Union{P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms::False, - equations::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - mortar_index, position_index, normal_direction, - node_index) - @unpack u = cache.mortars - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, node_index, - mortar_index) - - # TODO: parabolic; only BR1 at the moment - flux_ = 0.5f0 * (u_ll + u_rr) - - # Copy flux to buffer - set_node_vars!(fstar[position_index], flux_, equations, dg, node_index) -end - -# TODO: parabolic, finish implementing `calc_boundary_flux_gradients!` and `calc_boundary_flux_divergence!` -function prolong2boundaries!(cache_parabolic, flux_viscous, - mesh::P4estMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - (; boundaries) = cache_parabolic - (; contravariant_vectors) = cache_parabolic.elements - index_range = eachnode(dg) - - flux_viscous_x, flux_viscous_y = flux_viscous - - @threaded for boundary in eachboundary(dg, cache_parabolic) - # Copy solution data from the element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - direction = indices2direction(node_indices) - - i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) - j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) - - i_node = i_node_start - j_node = j_node_start - for i in eachnode(dg) - # this is the outward normal direction on the primary element - normal_direction = get_normal_direction(direction, contravariant_vectors, - i_node, j_node, element) - - for v in eachvariable(equations_parabolic) - flux_viscous = SVector(flux_viscous_x[v, i_node, j_node, element], - flux_viscous_y[v, i_node, j_node, element]) - - boundaries.u[v, i, boundary] = dot(flux_viscous, normal_direction) - end - i_node += i_node_step - j_node += j_node_step - end + function calc_boundary_flux_divergence!( + cache, t, boundary_conditions, mesh::P4estMesh, + equations, surface_integral, dg::DG + ) + (; boundary_condition_types, boundary_indices) = boundary_conditions + + calc_boundary_flux_by_type!( + cache, t, boundary_condition_types, boundary_indices, + Divergence(), mesh, equations, surface_integral, dg + ) + return nothing end - return nothing -end - -function calc_boundary_flux_gradients!(cache, t, - boundary_condition::Union{BoundaryConditionPeriodic, - BoundaryConditionDoNothing}, - mesh::P4estMesh, equations, surface_integral, - dg::DG) - @assert isempty(eachboundary(dg, cache)) -end - -# Function barrier for type stability -function calc_boundary_flux_gradients!(cache, t, boundary_conditions, mesh::P4estMesh, - equations, surface_integral, dg::DG) - (; boundary_condition_types, boundary_indices) = boundary_conditions - - calc_boundary_flux_by_type!(cache, t, boundary_condition_types, boundary_indices, - Gradient(), mesh, equations, surface_integral, dg) - return nothing -end - -function calc_boundary_flux_divergence!(cache, t, boundary_conditions, mesh::P4estMesh, - equations, surface_integral, dg::DG) - (; boundary_condition_types, boundary_indices) = boundary_conditions - - calc_boundary_flux_by_type!(cache, t, boundary_condition_types, boundary_indices, - Divergence(), mesh, equations, surface_integral, dg) - return nothing -end - -# Iterate over tuples of boundary condition types and associated indices -# in a type-stable way using "lispy tuple programming". -function calc_boundary_flux_by_type!(cache, t, BCs::NTuple{N, Any}, - BC_indices::NTuple{N, Vector{Int}}, - operator_type, - mesh::P4estMesh, - equations, surface_integral, dg::DG) where {N} - # Extract the boundary condition type and index vector - boundary_condition = first(BCs) - boundary_condition_indices = first(BC_indices) - # Extract the remaining types and indices to be processed later - remaining_boundary_conditions = Base.tail(BCs) - remaining_boundary_condition_indices = Base.tail(BC_indices) - - # process the first boundary condition type - calc_boundary_flux!(cache, t, boundary_condition, boundary_condition_indices, - operator_type, mesh, equations, surface_integral, dg) - - # recursively call this method with the unprocessed boundary types - calc_boundary_flux_by_type!(cache, t, remaining_boundary_conditions, - remaining_boundary_condition_indices, - operator_type, - mesh, equations, surface_integral, dg) - - return nothing -end - -# terminate the type-stable iteration over tuples -function calc_boundary_flux_by_type!(cache, t, BCs::Tuple{}, BC_indices::Tuple{}, - operator_type, mesh::P4estMesh, equations, - surface_integral, dg::DG) - nothing -end - -function calc_boundary_flux!(cache, t, - boundary_condition_parabolic, # works with Dict types - boundary_condition_indices, - operator_type, mesh::P4estMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - (; boundaries) = cache - (; node_coordinates, surface_flux_values) = cache.elements - (; contravariant_vectors) = cache.elements - index_range = eachnode(dg) - - @threaded for local_index in eachindex(boundary_condition_indices) - # Use the local index to get the global boundary index from the pre-sorted list - boundary_index = boundary_condition_indices[local_index] - - # Get information on the adjacent element, compute the surface fluxes, - # and store them - element = boundaries.neighbor_ids[boundary_index] - node_indices = boundaries.node_indices[boundary_index] - direction_index = indices2direction(node_indices) - - i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) - j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) - - i_node = i_node_start - j_node = j_node_start - for node_index in eachnode(dg) - # Extract solution data from boundary container - u_inner = get_node_vars(boundaries.u, equations_parabolic, dg, node_index, - boundary_index) - - # Outward-pointing normal direction (not normalized) - normal_direction = get_normal_direction(direction_index, - contravariant_vectors, - i_node, j_node, element) - - # TODO: revisit if we want more general boundary treatments. - # This assumes the gradient numerical flux at the boundary is the gradient variable, - # which is consistent with BR1, LDG. - flux_inner = u_inner - - # Coordinates at boundary node - x = get_node_coords(node_coordinates, equations_parabolic, dg, i_node, - j_node, - element) - - flux_ = boundary_condition_parabolic(flux_inner, u_inner, normal_direction, - x, t, operator_type, - equations_parabolic) - - # Copy flux to element storage in the correct orientation - for v in eachvariable(equations_parabolic) - surface_flux_values[v, node_index, direction_index, element] = flux_[v] - end - i_node += i_node_step - j_node += j_node_step - end + # Iterate over tuples of boundary condition types and associated indices + # in a type-stable way using "lispy tuple programming". + function calc_boundary_flux_by_type!( + cache, t, BCs::NTuple{N, Any}, + BC_indices::NTuple{N, Vector{Int}}, + operator_type, + mesh::P4estMesh, + equations, surface_integral, dg::DG + ) where {N} + # Extract the boundary condition type and index vector + boundary_condition = first(BCs) + boundary_condition_indices = first(BC_indices) + # Extract the remaining types and indices to be processed later + remaining_boundary_conditions = Base.tail(BCs) + remaining_boundary_condition_indices = Base.tail(BC_indices) + + # process the first boundary condition type + calc_boundary_flux!( + cache, t, boundary_condition, boundary_condition_indices, + operator_type, mesh, equations, surface_integral, dg + ) + + # recursively call this method with the unprocessed boundary types + calc_boundary_flux_by_type!( + cache, t, remaining_boundary_conditions, + remaining_boundary_condition_indices, + operator_type, + mesh, equations, surface_integral, dg + ) + + return nothing end -end -function apply_jacobian_parabolic!(du, mesh::P4estMesh{2}, - equations::AbstractEquationsParabolic, - dg::DG, cache) - @unpack inverse_jacobian = cache.elements + # terminate the type-stable iteration over tuples + function calc_boundary_flux_by_type!( + cache, t, BCs::Tuple{}, BC_indices::Tuple{}, + operator_type, mesh::P4estMesh, equations, + surface_integral, dg::DG + ) + nothing + end - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - factor = inverse_jacobian[i, j, element] + function calc_boundary_flux!( + cache, t, + boundary_condition_parabolic, # works with Dict types + boundary_condition_indices, + operator_type, mesh::P4estMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + (; boundaries) = cache + (; node_coordinates, surface_flux_values) = cache.elements + (; contravariant_vectors) = cache.elements + index_range = eachnode(dg) + + @threaded for local_index in eachindex(boundary_condition_indices) + # Use the local index to get the global boundary index from the pre-sorted list + boundary_index = boundary_condition_indices[local_index] + + # Get information on the adjacent element, compute the surface fluxes, + # and store them + element = boundaries.neighbor_ids[boundary_index] + node_indices = boundaries.node_indices[boundary_index] + direction_index = indices2direction(node_indices) + + i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], index_range) + j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], index_range) + + i_node = i_node_start + j_node = j_node_start + for node_index in eachnode(dg) + # Extract solution data from boundary container + u_inner = get_node_vars( + boundaries.u, equations_parabolic, dg, node_index, + boundary_index + ) + + # Outward-pointing normal direction (not normalized) + normal_direction = get_normal_direction( + direction_index, + contravariant_vectors, + i_node, j_node, element + ) + + # TODO: revisit if we want more general boundary treatments. + # This assumes the gradient numerical flux at the boundary is the gradient variable, + # which is consistent with BR1, LDG. + flux_inner = u_inner + + # Coordinates at boundary node + x = get_node_coords( + node_coordinates, equations_parabolic, dg, i_node, + j_node, + element + ) + + flux_ = boundary_condition_parabolic( + flux_inner, u_inner, normal_direction, + x, t, operator_type, + equations_parabolic + ) + + # Copy flux to element storage in the correct orientation + for v in eachvariable(equations_parabolic) + surface_flux_values[v, node_index, direction_index, element] = flux_[v] + end - for v in eachvariable(equations) - du[v, i, j, element] *= factor + i_node += i_node_step + j_node += j_node_step end end end - return nothing -end + function apply_jacobian_parabolic!( + du, mesh::P4estMesh{2}, + equations::AbstractEquationsParabolic, + dg::DG, cache + ) + @unpack inverse_jacobian = cache.elements + + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + factor = inverse_jacobian[i, j, element] + + for v in eachvariable(equations) + du[v, i, j, element] *= factor + end + end + end + + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_p4est/dg_2d_parallel.jl b/src/solvers/dgsem_p4est/dg_2d_parallel.jl index 3bf0cd0cab..11135a6b1a 100644 --- a/src/solvers/dgsem_p4est/dg_2d_parallel.jl +++ b/src/solvers/dgsem_p4est/dg_2d_parallel.jl @@ -3,337 +3,415 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function prolong2mpiinterfaces!(cache, u, - mesh::Union{ParallelP4estMesh{2}, - ParallelT8codeMesh{2}}, - equations, surface_integral, dg::DG) - @unpack mpi_interfaces = cache - index_range = eachnode(dg) - - @threaded for interface in eachmpiinterface(dg, cache) - # Copy solution data from the local element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - # Note that in the current implementation, the interface will be - # "aligned at the primary element", i.e., the index of the primary side - # will always run forwards. - local_side = mpi_interfaces.local_sides[interface] - local_element = mpi_interfaces.local_neighbor_ids[interface] - local_indices = mpi_interfaces.node_indices[interface] - - i_element_start, i_element_step = index_to_start_step_2d(local_indices[1], - index_range) - j_element_start, j_element_step = index_to_start_step_2d(local_indices[2], - index_range) - - i_element = i_element_start - j_element = j_element_start - for i in eachnode(dg) - for v in eachvariable(equations) - mpi_interfaces.u[local_side, v, i, interface] = u[v, i_element, - j_element, - local_element] + #! format: noindent + + function prolong2mpiinterfaces!( + cache, u, + mesh::Union{ + ParallelP4estMesh{2}, + ParallelT8codeMesh{2}, + }, + equations, surface_integral, dg::DG + ) + @unpack mpi_interfaces = cache + index_range = eachnode(dg) + + @threaded for interface in eachmpiinterface(dg, cache) + # Copy solution data from the local element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + # Note that in the current implementation, the interface will be + # "aligned at the primary element", i.e., the index of the primary side + # will always run forwards. + local_side = mpi_interfaces.local_sides[interface] + local_element = mpi_interfaces.local_neighbor_ids[interface] + local_indices = mpi_interfaces.node_indices[interface] + + i_element_start, i_element_step = index_to_start_step_2d( + local_indices[1], + index_range + ) + j_element_start, j_element_step = index_to_start_step_2d( + local_indices[2], + index_range + ) + + i_element = i_element_start + j_element = j_element_start + for i in eachnode(dg) + for v in eachvariable(equations) + mpi_interfaces.u[local_side, v, i, interface] = u[ + v, i_element, + j_element, + local_element, + ] + end + i_element += i_element_step + j_element += j_element_step end - i_element += i_element_step - j_element += j_element_step end + + return nothing end - return nothing -end - -function calc_mpi_interface_flux!(surface_flux_values, - mesh::Union{ParallelP4estMesh{2}, - ParallelT8codeMesh{2}}, - nonconservative_terms, - equations, surface_integral, dg::DG, cache) - @unpack local_neighbor_ids, node_indices, local_sides = cache.mpi_interfaces - @unpack contravariant_vectors = cache.elements - index_range = eachnode(dg) - index_end = last(index_range) - - @threaded for interface in eachmpiinterface(dg, cache) - # Get element and side index information on the local element - local_element = local_neighbor_ids[interface] - local_indices = node_indices[interface] - local_direction = indices2direction(local_indices) - local_side = local_sides[interface] - - # Create the local i,j indexing on the local element used to pull normal direction information - i_element_start, i_element_step = index_to_start_step_2d(local_indices[1], - index_range) - j_element_start, j_element_step = index_to_start_step_2d(local_indices[2], - index_range) - - i_element = i_element_start - j_element = j_element_start - - # Initiate the node index to be used in the surface for loop, - # the surface flux storage must be indexed in alignment with the local element indexing - if :i_backward in local_indices - surface_node = index_end - surface_node_step = -1 - else - surface_node = 1 - surface_node_step = 1 - end + function calc_mpi_interface_flux!( + surface_flux_values, + mesh::Union{ + ParallelP4estMesh{2}, + ParallelT8codeMesh{2}, + }, + nonconservative_terms, + equations, surface_integral, dg::DG, cache + ) + @unpack local_neighbor_ids, node_indices, local_sides = cache.mpi_interfaces + @unpack contravariant_vectors = cache.elements + index_range = eachnode(dg) + index_end = last(index_range) + + @threaded for interface in eachmpiinterface(dg, cache) + # Get element and side index information on the local element + local_element = local_neighbor_ids[interface] + local_indices = node_indices[interface] + local_direction = indices2direction(local_indices) + local_side = local_sides[interface] + + # Create the local i,j indexing on the local element used to pull normal direction information + i_element_start, i_element_step = index_to_start_step_2d( + local_indices[1], + index_range + ) + j_element_start, j_element_step = index_to_start_step_2d( + local_indices[2], + index_range + ) + + i_element = i_element_start + j_element = j_element_start + + # Initiate the node index to be used in the surface for loop, + # the surface flux storage must be indexed in alignment with the local element indexing + if :i_backward in local_indices + surface_node = index_end + surface_node_step = -1 + else + surface_node = 1 + surface_node_step = 1 + end - for node in eachnode(dg) - # Get the normal direction on the local element - # Contravariant vectors at interfaces in negative coordinate direction - # are pointing inwards. This is handled by `get_normal_direction`. - normal_direction = get_normal_direction(local_direction, - contravariant_vectors, - i_element, j_element, local_element) - - calc_mpi_interface_flux!(surface_flux_values, mesh, nonconservative_terms, - equations, - surface_integral, dg, cache, - interface, normal_direction, - node, local_side, - surface_node, local_direction, local_element) - - # Increment local element indices to pull the normal direction - i_element += i_element_step - j_element += j_element_step - - # Increment the surface node index along the local element - surface_node += surface_node_step + for node in eachnode(dg) + # Get the normal direction on the local element + # Contravariant vectors at interfaces in negative coordinate direction + # are pointing inwards. This is handled by `get_normal_direction`. + normal_direction = get_normal_direction( + local_direction, + contravariant_vectors, + i_element, j_element, local_element + ) + + calc_mpi_interface_flux!( + surface_flux_values, mesh, nonconservative_terms, + equations, + surface_integral, dg, cache, + interface, normal_direction, + node, local_side, + surface_node, local_direction, local_element + ) + + # Increment local element indices to pull the normal direction + i_element += i_element_step + j_element += j_element_step + + # Increment the surface node index along the local element + surface_node += surface_node_step + end end - end - return nothing -end - -# Inlined version of the interface flux computation for conservation laws -@inline function calc_mpi_interface_flux!(surface_flux_values, - mesh::Union{ParallelP4estMesh{2}, - ParallelT8codeMesh{2}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - interface_index, normal_direction, - interface_node_index, local_side, - surface_node_index, local_direction_index, - local_element_index) - @unpack u = cache.mpi_interfaces - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface_node_index, - interface_index) - - if local_side == 1 - flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) - else # local_side == 2 - flux_ = -surface_flux(u_ll, u_rr, -normal_direction, equations) + return nothing end - for v in eachvariable(equations) - surface_flux_values[v, surface_node_index, local_direction_index, local_element_index] = flux_[v] - end -end - -function prolong2mpimortars!(cache, u, - mesh::Union{ParallelP4estMesh{2}, ParallelT8codeMesh{2}}, - equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DGSEM) - @unpack node_indices = cache.mpi_mortars - index_range = eachnode(dg) - - @threaded for mortar in eachmpimortar(dg, cache) - local_neighbor_ids = cache.mpi_mortars.local_neighbor_ids[mortar] - local_neighbor_positions = cache.mpi_mortars.local_neighbor_positions[mortar] - - # Get start value and step size for indices on both sides to get the correct face - # and orientation - small_indices = node_indices[1, mortar] - i_small_start, i_small_step = index_to_start_step_2d(small_indices[1], - index_range) - j_small_start, j_small_step = index_to_start_step_2d(small_indices[2], - index_range) + # Inlined version of the interface flux computation for conservation laws + @inline function calc_mpi_interface_flux!( + surface_flux_values, + mesh::Union{ + ParallelP4estMesh{2}, + ParallelT8codeMesh{2}, + }, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + interface_index, normal_direction, + interface_node_index, local_side, + surface_node_index, local_direction_index, + local_element_index + ) + @unpack u = cache.mpi_interfaces + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, interface_node_index, + interface_index + ) + + if local_side == 1 + flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) + else # local_side == 2 + flux_ = -surface_flux(u_ll, u_rr, -normal_direction, equations) + end - large_indices = node_indices[2, mortar] - i_large_start, i_large_step = index_to_start_step_2d(large_indices[1], - index_range) - j_large_start, j_large_step = index_to_start_step_2d(large_indices[2], - index_range) + for v in eachvariable(equations) + surface_flux_values[v, surface_node_index, local_direction_index, local_element_index] = flux_[v] + end + end - for (element, position) in zip(local_neighbor_ids, local_neighbor_positions) - if position == 3 # -> large element - # Buffer to copy solution values of the large element in the correct orientation - # before interpolating - u_buffer = cache.u_threaded[Threads.threadid()] - i_large = i_large_start - j_large = j_large_start - for i in eachnode(dg) - for v in eachvariable(equations) - u_buffer[v, i] = u[v, i_large, j_large, element] + function prolong2mpimortars!( + cache, u, + mesh::Union{ParallelP4estMesh{2}, ParallelT8codeMesh{2}}, + equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DGSEM + ) + @unpack node_indices = cache.mpi_mortars + index_range = eachnode(dg) + + @threaded for mortar in eachmpimortar(dg, cache) + local_neighbor_ids = cache.mpi_mortars.local_neighbor_ids[mortar] + local_neighbor_positions = cache.mpi_mortars.local_neighbor_positions[mortar] + + # Get start value and step size for indices on both sides to get the correct face + # and orientation + small_indices = node_indices[1, mortar] + i_small_start, i_small_step = index_to_start_step_2d( + small_indices[1], + index_range + ) + j_small_start, j_small_step = index_to_start_step_2d( + small_indices[2], + index_range + ) + + large_indices = node_indices[2, mortar] + i_large_start, i_large_step = index_to_start_step_2d( + large_indices[1], + index_range + ) + j_large_start, j_large_step = index_to_start_step_2d( + large_indices[2], + index_range + ) + + for (element, position) in zip(local_neighbor_ids, local_neighbor_positions) + if position == 3 # -> large element + # Buffer to copy solution values of the large element in the correct orientation + # before interpolating + u_buffer = cache.u_threaded[Threads.threadid()] + i_large = i_large_start + j_large = j_large_start + for i in eachnode(dg) + for v in eachvariable(equations) + u_buffer[v, i] = u[v, i_large, j_large, element] + end + + i_large += i_large_step + j_large += j_large_step end - i_large += i_large_step - j_large += j_large_step + # Interpolate large element face data from buffer to small face locations + multiply_dimensionwise!( + view(cache.mpi_mortars.u, 2, :, 1, :, mortar), + mortar_l2.forward_lower, + u_buffer + ) + multiply_dimensionwise!( + view(cache.mpi_mortars.u, 2, :, 2, :, mortar), + mortar_l2.forward_upper, + u_buffer + ) + else # position in (1, 2) -> small element + # Copy solution data from the small elements + i_small = i_small_start + j_small = j_small_start + for i in eachnode(dg) + for v in eachvariable(equations) + cache.mpi_mortars.u[1, v, position, i, mortar] = u[ + v, i_small, + j_small, + element, + ] + end + i_small += i_small_step + j_small += j_small_step + end end + end + end - # Interpolate large element face data from buffer to small face locations - multiply_dimensionwise!(view(cache.mpi_mortars.u, 2, :, 1, :, mortar), - mortar_l2.forward_lower, - u_buffer) - multiply_dimensionwise!(view(cache.mpi_mortars.u, 2, :, 2, :, mortar), - mortar_l2.forward_upper, - u_buffer) - else # position in (1, 2) -> small element - # Copy solution data from the small elements + return nothing + end + + function calc_mpi_mortar_flux!( + surface_flux_values, + mesh::Union{ParallelP4estMesh{2}, ParallelT8codeMesh{2}}, + nonconservative_terms, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack local_neighbor_ids, local_neighbor_positions, node_indices = cache.mpi_mortars + @unpack contravariant_vectors = cache.elements + @unpack fstar_upper_threaded, fstar_lower_threaded = cache + index_range = eachnode(dg) + + @threaded for mortar in eachmpimortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar = ( + fstar_lower_threaded[Threads.threadid()], + fstar_upper_threaded[Threads.threadid()], + ) + + # Get index information on the small elements + small_indices = node_indices[1, mortar] + + i_small_start, i_small_step = index_to_start_step_2d( + small_indices[1], + index_range + ) + j_small_start, j_small_step = index_to_start_step_2d( + small_indices[2], + index_range + ) + + for position in 1:2 i_small = i_small_start j_small = j_small_start - for i in eachnode(dg) - for v in eachvariable(equations) - cache.mpi_mortars.u[1, v, position, i, mortar] = u[v, i_small, - j_small, - element] - end + for node in eachnode(dg) + # Get the normal direction on the small element. + normal_direction = get_normal_direction( + cache.mpi_mortars, node, + position, mortar + ) + + calc_mpi_mortar_flux!( + fstar, mesh, nonconservative_terms, equations, + surface_integral, dg, cache, + mortar, position, normal_direction, + node + ) + i_small += i_small_step j_small += j_small_step end end - end - end - - return nothing -end - -function calc_mpi_mortar_flux!(surface_flux_values, - mesh::Union{ParallelP4estMesh{2}, ParallelT8codeMesh{2}}, - nonconservative_terms, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack local_neighbor_ids, local_neighbor_positions, node_indices = cache.mpi_mortars - @unpack contravariant_vectors = cache.elements - @unpack fstar_upper_threaded, fstar_lower_threaded = cache - index_range = eachnode(dg) - - @threaded for mortar in eachmpimortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar = (fstar_lower_threaded[Threads.threadid()], - fstar_upper_threaded[Threads.threadid()]) - - # Get index information on the small elements - small_indices = node_indices[1, mortar] - - i_small_start, i_small_step = index_to_start_step_2d(small_indices[1], - index_range) - j_small_start, j_small_step = index_to_start_step_2d(small_indices[2], - index_range) - for position in 1:2 - i_small = i_small_start - j_small = j_small_start - for node in eachnode(dg) - # Get the normal direction on the small element. - normal_direction = get_normal_direction(cache.mpi_mortars, node, - position, mortar) - - calc_mpi_mortar_flux!(fstar, mesh, nonconservative_terms, equations, - surface_integral, dg, cache, - mortar, position, normal_direction, - node) + # Buffer to interpolate flux values of the large element to before + # copying in the correct orientation + u_buffer = cache.u_threaded[Threads.threadid()] - i_small += i_small_step - j_small += j_small_step - end + mpi_mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, + mortar, fstar, u_buffer + ) end - # Buffer to interpolate flux values of the large element to before - # copying in the correct orientation - u_buffer = cache.u_threaded[Threads.threadid()] + return nothing + end - mpi_mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, - mortar, fstar, u_buffer) + # Inlined version of the mortar flux computation on small elements for conservation laws + @inline function calc_mpi_mortar_flux!( + fstar, + mesh::Union{ + ParallelP4estMesh{2}, + ParallelT8codeMesh{2}, + }, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + mortar_index, position_index, normal_direction, + node_index + ) + @unpack u = cache.mpi_mortars + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, position_index, node_index, + mortar_index + ) + + flux = surface_flux(u_ll, u_rr, normal_direction, equations) + + # Copy flux to buffer + set_node_vars!(fstar[position_index], flux, equations, dg, node_index) end - return nothing -end - -# Inlined version of the mortar flux computation on small elements for conservation laws -@inline function calc_mpi_mortar_flux!(fstar, - mesh::Union{ParallelP4estMesh{2}, - ParallelT8codeMesh{2}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - mortar_index, position_index, normal_direction, - node_index) - @unpack u = cache.mpi_mortars - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, node_index, - mortar_index) - - flux = surface_flux(u_ll, u_rr, normal_direction, equations) - - # Copy flux to buffer - set_node_vars!(fstar[position_index], flux, equations, dg, node_index) -end - -@inline function mpi_mortar_fluxes_to_elements!(surface_flux_values, - mesh::Union{ParallelP4estMesh{2}, - ParallelT8codeMesh{2}}, - equations, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, mortar, fstar, - u_buffer) - @unpack local_neighbor_ids, local_neighbor_positions, node_indices = cache.mpi_mortars - - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) - large_indices = node_indices[2, mortar] - large_direction = indices2direction(large_indices) - - for (element, position) in zip(local_neighbor_ids[mortar], - local_neighbor_positions[mortar]) - if position == 3 # -> large element - # Project small fluxes to large element. - multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_upper, fstar[2], - mortar_l2.reverse_lower, fstar[1]) - # The flux is calculated in the outward direction of the small elements, - # so the sign must be switched to get the flux in outward direction - # of the large element. - # The contravariant vectors of the large element (and therefore the normal - # vectors of the large element as well) are twice as large as the - # contravariant vectors of the small elements. Therefore, the flux needs - # to be scaled by a factor of 2 to obtain the flux of the large element. - u_buffer .*= -2 - # Copy interpolated flux values from buffer to large element face in the - # correct orientation. - # Note that the index of the small sides will always run forward but - # the index of the large side might need to run backwards for flipped sides. - if :i_backward in large_indices - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, end + 1 - i, large_direction, element] = u_buffer[v, - i] + @inline function mpi_mortar_fluxes_to_elements!( + surface_flux_values, + mesh::Union{ + ParallelP4estMesh{2}, + ParallelT8codeMesh{2}, + }, + equations, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, mortar, fstar, + u_buffer + ) + @unpack local_neighbor_ids, local_neighbor_positions, node_indices = cache.mpi_mortars + + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) + large_indices = node_indices[2, mortar] + large_direction = indices2direction(large_indices) + + for (element, position) in zip( + local_neighbor_ids[mortar], + local_neighbor_positions[mortar] + ) + if position == 3 # -> large element + # Project small fluxes to large element. + multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_upper, fstar[2], + mortar_l2.reverse_lower, fstar[1] + ) + # The flux is calculated in the outward direction of the small elements, + # so the sign must be switched to get the flux in outward direction + # of the large element. + # The contravariant vectors of the large element (and therefore the normal + # vectors of the large element as well) are twice as large as the + # contravariant vectors of the small elements. Therefore, the flux needs + # to be scaled by a factor of 2 to obtain the flux of the large element. + u_buffer .*= -2 + # Copy interpolated flux values from buffer to large element face in the + # correct orientation. + # Note that the index of the small sides will always run forward but + # the index of the large side might need to run backwards for flipped sides. + if :i_backward in large_indices + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, end + 1 - i, large_direction, element] = u_buffer[ + v, + i, + ] + end + end + else + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i, large_direction, element] = u_buffer[ + v, + i, + ] + end end end - else + else # position in (1, 2) -> small element + # Copy solution small to small for i in eachnode(dg) for v in eachvariable(equations) - surface_flux_values[v, i, large_direction, element] = u_buffer[v, - i] + surface_flux_values[v, i, small_direction, element] = fstar[position][ + v, + i, + ] end end end - else # position in (1, 2) -> small element - # Copy solution small to small - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i, small_direction, element] = fstar[position][v, - i] - end - end end - end - return nothing -end + return nothing + end end # muladd diff --git a/src/solvers/dgsem_p4est/dg_3d.jl b/src/solvers/dgsem_p4est/dg_3d.jl index ece4840b74..fe7e14c862 100644 --- a/src/solvers/dgsem_p4est/dg_3d.jl +++ b/src/solvers/dgsem_p4est/dg_3d.jl @@ -3,783 +3,967 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# The methods below are specialized on the mortar type -# and called from the basic `create_cache` method at the top. -function create_cache(mesh::Union{P4estMesh{3}, T8codeMesh{3}}, equations, - mortar_l2::LobattoLegendreMortarL2, uEltype) - # TODO: Taal compare performance of different types - fstar_threaded = [Array{uEltype, 4}(undef, nvariables(equations), nnodes(mortar_l2), - nnodes(mortar_l2), 4) - for _ in 1:Threads.nthreads()] - - fstar_tmp_threaded = [Array{uEltype, 3}(undef, nvariables(equations), - nnodes(mortar_l2), nnodes(mortar_l2)) - for _ in 1:Threads.nthreads()] - u_threaded = [Array{uEltype, 3}(undef, nvariables(equations), nnodes(mortar_l2), - nnodes(mortar_l2)) - for _ in 1:Threads.nthreads()] - - (; fstar_threaded, fstar_tmp_threaded, u_threaded) -end - -# index_to_start_step_3d(index::Symbol, index_range) -# -# Given a symbolic `index` and an `indexrange` (usually `eachnode(dg)`), -# return `index_start, index_step_i, index_step_j`, i.e., a tuple containing -# - `index_start`, an index value to begin a loop -# - `index_step_i`, an index step to update during an `i` loop -# - `index_step_j`, an index step to update during a `j` loop -# The resulting indices translate surface indices to volume indices. -# -# !!! warning -# This assumes that loops using the return values are written as -# -# i_volume_start, i_volume_step_i, i_volume_step_j = index_to_start_step_3d(symbolic_index_i, index_range) -# j_volume_start, j_volume_step_i, j_volume_step_j = index_to_start_step_3d(symbolic_index_j, index_range) -# k_volume_start, k_volume_step_i, k_volume_step_j = index_to_start_step_3d(symbolic_index_k, index_range) -# -# i_volume, j_volume, k_volume = i_volume_start, j_volume_start, k_volume_start -# for j_surface in index_range -# for i_surface in index_range -# # do stuff with `(i_surface, j_surface)` and `(i_volume, j_volume, k_volume)` -# -# i_volume += i_volume_step_i -# j_volume += j_volume_step_i -# k_volume += k_volume_step_i -# end -# i_volume += i_volume_step_j -# j_volume += j_volume_step_j -# k_volume += k_volume_step_j -# end -@inline function index_to_start_step_3d(index::Symbol, index_range) - index_begin = first(index_range) - index_end = last(index_range) - - if index === :begin - return index_begin, 0, 0 - elseif index === :end - return index_end, 0, 0 - elseif index === :i_forward - return index_begin, 1, index_begin - index_end - 1 - elseif index === :i_backward - return index_end, -1, index_end + 1 - index_begin - elseif index === :j_forward - return index_begin, 0, 1 - else # if index === :j_backward - return index_end, 0, -1 + #! format: noindent + + # The methods below are specialized on the mortar type + # and called from the basic `create_cache` method at the top. + function create_cache( + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, equations, + mortar_l2::LobattoLegendreMortarL2, uEltype + ) + # TODO: Taal compare performance of different types + fstar_threaded = [ + Array{uEltype, 4}( + undef, nvariables(equations), nnodes(mortar_l2), + nnodes(mortar_l2), 4 + ) + for _ in 1:Threads.nthreads() + ] + + fstar_tmp_threaded = [ + Array{uEltype, 3}( + undef, nvariables(equations), + nnodes(mortar_l2), nnodes(mortar_l2) + ) + for _ in 1:Threads.nthreads() + ] + u_threaded = [ + Array{uEltype, 3}( + undef, nvariables(equations), nnodes(mortar_l2), + nnodes(mortar_l2) + ) + for _ in 1:Threads.nthreads() + ] + + (; fstar_threaded, fstar_tmp_threaded, u_threaded) end -end - -# Extract the two varying indices from a symbolic index tuple. -# For example, `surface_indices((:i_forward, :end, :j_forward)) == (:i_forward, :j_forward)`. -@inline function surface_indices(indices::NTuple{3, Symbol}) - i1, i2, i3 = indices - index = i1 - (index === :begin || index === :end) && return (i2, i3) - - index = i2 - (index === :begin || index === :end) && return (i1, i3) - - # i3 in (:begin, :end) - return (i1, i2) -end - -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache, u, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - equations, surface_integral, dg::DG) - @unpack interfaces = cache - index_range = eachnode(dg) - - @threaded for interface in eachinterface(dg, cache) - # Copy solution data from the primary element using "delayed indexing" with - # a start value and two step sizes to get the correct face and orientation. - # Note that in the current implementation, the interface will be - # "aligned at the primary element", i.e., the indices of the primary side - # will always run forwards. - primary_element = interfaces.neighbor_ids[1, interface] - primary_indices = interfaces.node_indices[1, interface] - - i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1], - index_range) - j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2], - index_range) - k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - k_primary = k_primary_start - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - interfaces.u[1, v, i, j, interface] = u[v, i_primary, j_primary, - k_primary, primary_element] + + # index_to_start_step_3d(index::Symbol, index_range) + # + # Given a symbolic `index` and an `indexrange` (usually `eachnode(dg)`), + # return `index_start, index_step_i, index_step_j`, i.e., a tuple containing + # - `index_start`, an index value to begin a loop + # - `index_step_i`, an index step to update during an `i` loop + # - `index_step_j`, an index step to update during a `j` loop + # The resulting indices translate surface indices to volume indices. + # + # !!! warning + # This assumes that loops using the return values are written as + # + # i_volume_start, i_volume_step_i, i_volume_step_j = index_to_start_step_3d(symbolic_index_i, index_range) + # j_volume_start, j_volume_step_i, j_volume_step_j = index_to_start_step_3d(symbolic_index_j, index_range) + # k_volume_start, k_volume_step_i, k_volume_step_j = index_to_start_step_3d(symbolic_index_k, index_range) + # + # i_volume, j_volume, k_volume = i_volume_start, j_volume_start, k_volume_start + # for j_surface in index_range + # for i_surface in index_range + # # do stuff with `(i_surface, j_surface)` and `(i_volume, j_volume, k_volume)` + # + # i_volume += i_volume_step_i + # j_volume += j_volume_step_i + # k_volume += k_volume_step_i + # end + # i_volume += i_volume_step_j + # j_volume += j_volume_step_j + # k_volume += k_volume_step_j + # end + @inline function index_to_start_step_3d(index::Symbol, index_range) + index_begin = first(index_range) + index_end = last(index_range) + + if index === :begin + return index_begin, 0, 0 + elseif index === :end + return index_end, 0, 0 + elseif index === :i_forward + return index_begin, 1, index_begin - index_end - 1 + elseif index === :i_backward + return index_end, -1, index_end + 1 - index_begin + elseif index === :j_forward + return index_begin, 0, 1 + else # if index === :j_backward + return index_end, 0, -1 + end + end + + # Extract the two varying indices from a symbolic index tuple. + # For example, `surface_indices((:i_forward, :end, :j_forward)) == (:i_forward, :j_forward)`. + @inline function surface_indices(indices::NTuple{3, Symbol}) + i1, i2, i3 = indices + index = i1 + (index === :begin || index === :end) && return (i2, i3) + + index = i2 + (index === :begin || index === :end) && return (i1, i3) + + # i3 in (:begin, :end) + return (i1, i2) + end + + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache, u, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + equations, surface_integral, dg::DG + ) + @unpack interfaces = cache + index_range = eachnode(dg) + + @threaded for interface in eachinterface(dg, cache) + # Copy solution data from the primary element using "delayed indexing" with + # a start value and two step sizes to get the correct face and orientation. + # Note that in the current implementation, the interface will be + # "aligned at the primary element", i.e., the indices of the primary side + # will always run forwards. + primary_element = interfaces.neighbor_ids[1, interface] + primary_indices = interfaces.node_indices[1, interface] + + i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d( + primary_indices[2], + index_range + ) + k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d( + primary_indices[3], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + k_primary = k_primary_start + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + interfaces.u[1, v, i, j, interface] = u[ + v, i_primary, j_primary, + k_primary, primary_element, + ] + end + i_primary += i_primary_step_i + j_primary += j_primary_step_i + k_primary += k_primary_step_i end - i_primary += i_primary_step_i - j_primary += j_primary_step_i - k_primary += k_primary_step_i + i_primary += i_primary_step_j + j_primary += j_primary_step_j + k_primary += k_primary_step_j end - i_primary += i_primary_step_j - j_primary += j_primary_step_j - k_primary += k_primary_step_j - end - # Copy solution data from the secondary element using "delayed indexing" with - # a start value and two step sizes to get the correct face and orientation. - secondary_element = interfaces.neighbor_ids[2, interface] - secondary_indices = interfaces.node_indices[2, interface] - - i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_indices[1], - index_range) - j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_indices[2], - index_range) - k_secondary_start, k_secondary_step_i, k_secondary_step_j = index_to_start_step_3d(secondary_indices[3], - index_range) - - i_secondary = i_secondary_start - j_secondary = j_secondary_start - k_secondary = k_secondary_start - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - interfaces.u[2, v, i, j, interface] = u[v, i_secondary, j_secondary, - k_secondary, - secondary_element] + # Copy solution data from the secondary element using "delayed indexing" with + # a start value and two step sizes to get the correct face and orientation. + secondary_element = interfaces.neighbor_ids[2, interface] + secondary_indices = interfaces.node_indices[2, interface] + + i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d( + secondary_indices[1], + index_range + ) + j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d( + secondary_indices[2], + index_range + ) + k_secondary_start, k_secondary_step_i, k_secondary_step_j = index_to_start_step_3d( + secondary_indices[3], + index_range + ) + + i_secondary = i_secondary_start + j_secondary = j_secondary_start + k_secondary = k_secondary_start + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + interfaces.u[2, v, i, j, interface] = u[ + v, i_secondary, j_secondary, + k_secondary, + secondary_element, + ] + end + i_secondary += i_secondary_step_i + j_secondary += j_secondary_step_i + k_secondary += k_secondary_step_i end - i_secondary += i_secondary_step_i - j_secondary += j_secondary_step_i - k_secondary += k_secondary_step_i + i_secondary += i_secondary_step_j + j_secondary += j_secondary_step_j + k_secondary += k_secondary_step_j end - i_secondary += i_secondary_step_j - j_secondary += j_secondary_step_j - k_secondary += k_secondary_step_j end + + return nothing end - return nothing -end - -function calc_interface_flux!(surface_flux_values, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - nonconservative_terms, - equations, surface_integral, dg::DG, cache) - @unpack neighbor_ids, node_indices = cache.interfaces - @unpack contravariant_vectors = cache.elements - index_range = eachnode(dg) - - @threaded for interface in eachinterface(dg, cache) - # Get element and side information on the primary element - primary_element = neighbor_ids[1, interface] - primary_indices = node_indices[1, interface] - primary_direction = indices2direction(primary_indices) - - i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1], - index_range) - j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2], - index_range) - k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - k_primary = k_primary_start - - # Get element and side information on the secondary element - secondary_element = neighbor_ids[2, interface] - secondary_indices = node_indices[2, interface] - secondary_direction = indices2direction(secondary_indices) - secondary_surface_indices = surface_indices(secondary_indices) - - # Get the surface indexing on the secondary element. - # Note that the indices of the primary side will always run forward but - # the secondary indices might need to run backwards for flipped sides. - i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[1], - index_range) - j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[2], - index_range) - i_secondary = i_secondary_start - j_secondary = j_secondary_start - - for j in eachnode(dg) - for i in eachnode(dg) - # Get the normal direction from the primary element. - # Note, contravariant vectors at interfaces in negative coordinate direction - # are pointing inwards. This is handled by `get_normal_direction`. - normal_direction = get_normal_direction(primary_direction, - contravariant_vectors, - i_primary, j_primary, k_primary, - primary_element) - - calc_interface_flux!(surface_flux_values, mesh, nonconservative_terms, - equations, - surface_integral, dg, cache, - interface, normal_direction, - i, j, primary_direction, primary_element, - i_secondary, j_secondary, secondary_direction, - secondary_element) + function calc_interface_flux!( + surface_flux_values, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + nonconservative_terms, + equations, surface_integral, dg::DG, cache + ) + @unpack neighbor_ids, node_indices = cache.interfaces + @unpack contravariant_vectors = cache.elements + index_range = eachnode(dg) + + @threaded for interface in eachinterface(dg, cache) + # Get element and side information on the primary element + primary_element = neighbor_ids[1, interface] + primary_indices = node_indices[1, interface] + primary_direction = indices2direction(primary_indices) + + i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d( + primary_indices[2], + index_range + ) + k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d( + primary_indices[3], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + k_primary = k_primary_start + + # Get element and side information on the secondary element + secondary_element = neighbor_ids[2, interface] + secondary_indices = node_indices[2, interface] + secondary_direction = indices2direction(secondary_indices) + secondary_surface_indices = surface_indices(secondary_indices) + + # Get the surface indexing on the secondary element. + # Note that the indices of the primary side will always run forward but + # the secondary indices might need to run backwards for flipped sides. + i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d( + secondary_surface_indices[1], + index_range + ) + j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d( + secondary_surface_indices[2], + index_range + ) + i_secondary = i_secondary_start + j_secondary = j_secondary_start + for j in eachnode(dg) + for i in eachnode(dg) + # Get the normal direction from the primary element. + # Note, contravariant vectors at interfaces in negative coordinate direction + # are pointing inwards. This is handled by `get_normal_direction`. + normal_direction = get_normal_direction( + primary_direction, + contravariant_vectors, + i_primary, j_primary, k_primary, + primary_element + ) + + calc_interface_flux!( + surface_flux_values, mesh, nonconservative_terms, + equations, + surface_integral, dg, cache, + interface, normal_direction, + i, j, primary_direction, primary_element, + i_secondary, j_secondary, secondary_direction, + secondary_element + ) + + # Increment the primary element indices + i_primary += i_primary_step_i + j_primary += j_primary_step_i + k_primary += k_primary_step_i + # Increment the secondary element surface indices + i_secondary += i_secondary_step_i + j_secondary += j_secondary_step_i + end # Increment the primary element indices - i_primary += i_primary_step_i - j_primary += j_primary_step_i - k_primary += k_primary_step_i + i_primary += i_primary_step_j + j_primary += j_primary_step_j + k_primary += k_primary_step_j # Increment the secondary element surface indices - i_secondary += i_secondary_step_i - j_secondary += j_secondary_step_i + i_secondary += i_secondary_step_j + j_secondary += j_secondary_step_j end - # Increment the primary element indices - i_primary += i_primary_step_j - j_primary += j_primary_step_j - k_primary += k_primary_step_j - # Increment the secondary element surface indices - i_secondary += i_secondary_step_j - j_secondary += j_secondary_step_j end + + return nothing end - return nothing -end - -# Inlined function for interface flux computation for conservative flux terms -@inline function calc_interface_flux!(surface_flux_values, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - interface_index, normal_direction, - primary_i_node_index, primary_j_node_index, - primary_direction_index, primary_element_index, - secondary_i_node_index, secondary_j_node_index, - secondary_direction_index, - secondary_element_index) - @unpack u = cache.interfaces - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, primary_i_node_index, - primary_j_node_index, interface_index) - - flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) - - for v in eachvariable(equations) - surface_flux_values[v, primary_i_node_index, primary_j_node_index, - primary_direction_index, primary_element_index] = flux_[v] - surface_flux_values[v, secondary_i_node_index, secondary_j_node_index, - secondary_direction_index, secondary_element_index] = -flux_[v] + # Inlined function for interface flux computation for conservative flux terms + @inline function calc_interface_flux!( + surface_flux_values, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + interface_index, normal_direction, + primary_i_node_index, primary_j_node_index, + primary_direction_index, primary_element_index, + secondary_i_node_index, secondary_j_node_index, + secondary_direction_index, + secondary_element_index + ) + @unpack u = cache.interfaces + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, primary_i_node_index, + primary_j_node_index, interface_index + ) + + flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) + + for v in eachvariable(equations) + surface_flux_values[ + v, primary_i_node_index, primary_j_node_index, + primary_direction_index, primary_element_index, + ] = flux_[v] + surface_flux_values[ + v, secondary_i_node_index, secondary_j_node_index, + secondary_direction_index, secondary_element_index, + ] = -flux_[v] + end end -end - -# Inlined function for interface flux computation for flux + nonconservative terms -@inline function calc_interface_flux!(surface_flux_values, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache, - interface_index, normal_direction, - primary_i_node_index, primary_j_node_index, - primary_direction_index, primary_element_index, - secondary_i_node_index, secondary_j_node_index, - secondary_direction_index, - secondary_element_index) - @unpack u = cache.interfaces - surface_flux, nonconservative_flux = surface_integral.surface_flux - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, primary_i_node_index, - primary_j_node_index, interface_index) - - flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) - - # Compute both nonconservative fluxes - # In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. - # However, both are the same at watertight interfaces, so we pass the - # `normal_direction` twice. - noncons_primary = nonconservative_flux(u_ll, u_rr, normal_direction, - normal_direction, equations) - noncons_secondary = nonconservative_flux(u_rr, u_ll, normal_direction, - normal_direction, equations) - - # Store the flux with nonconservative terms on the primary and secondary elements - for v in eachvariable(equations) - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - surface_flux_values[v, primary_i_node_index, primary_j_node_index, - primary_direction_index, primary_element_index] = flux_[v] + - 0.5f0 * noncons_primary[v] - surface_flux_values[v, secondary_i_node_index, secondary_j_node_index, - secondary_direction_index, secondary_element_index] = -(flux_[v] + - 0.5f0 * - noncons_secondary[v]) + + # Inlined function for interface flux computation for flux + nonconservative terms + @inline function calc_interface_flux!( + surface_flux_values, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache, + interface_index, normal_direction, + primary_i_node_index, primary_j_node_index, + primary_direction_index, primary_element_index, + secondary_i_node_index, secondary_j_node_index, + secondary_direction_index, + secondary_element_index + ) + @unpack u = cache.interfaces + surface_flux, nonconservative_flux = surface_integral.surface_flux + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, primary_i_node_index, + primary_j_node_index, interface_index + ) + + flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) + + # Compute both nonconservative fluxes + # In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. + # However, both are the same at watertight interfaces, so we pass the + # `normal_direction` twice. + noncons_primary = nonconservative_flux( + u_ll, u_rr, normal_direction, + normal_direction, equations + ) + noncons_secondary = nonconservative_flux( + u_rr, u_ll, normal_direction, + normal_direction, equations + ) + + # Store the flux with nonconservative terms on the primary and secondary elements + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[ + v, primary_i_node_index, primary_j_node_index, + primary_direction_index, primary_element_index, + ] = flux_[v] + + 0.5f0 * noncons_primary[v] + surface_flux_values[ + v, secondary_i_node_index, secondary_j_node_index, + secondary_direction_index, secondary_element_index, + ] = -( + flux_[v] + + 0.5f0 * + noncons_secondary[v] + ) + end end -end - -function prolong2boundaries!(cache, u, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - equations, surface_integral, dg::DG) - @unpack boundaries = cache - index_range = eachnode(dg) - - @threaded for boundary in eachboundary(dg, cache) - # Copy solution data from the element using "delayed indexing" with - # a start value and two step sizes to get the correct face and orientation. - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - - i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1], - index_range) - j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2], - index_range) - k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3], - index_range) - - i_node = i_node_start - j_node = j_node_start - k_node = k_node_start - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - boundaries.u[v, i, j, boundary] = u[v, i_node, j_node, k_node, - element] + + function prolong2boundaries!( + cache, u, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + equations, surface_integral, dg::DG + ) + @unpack boundaries = cache + index_range = eachnode(dg) + + @threaded for boundary in eachboundary(dg, cache) + # Copy solution data from the element using "delayed indexing" with + # a start value and two step sizes to get the correct face and orientation. + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + + i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d( + node_indices[1], + index_range + ) + j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d( + node_indices[2], + index_range + ) + k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d( + node_indices[3], + index_range + ) + + i_node = i_node_start + j_node = j_node_start + k_node = k_node_start + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + boundaries.u[v, i, j, boundary] = u[ + v, i_node, j_node, k_node, + element, + ] + end + i_node += i_node_step_i + j_node += j_node_step_i + k_node += k_node_step_i end - i_node += i_node_step_i - j_node += j_node_step_i - k_node += k_node_step_i + i_node += i_node_step_j + j_node += j_node_step_j + k_node += k_node_step_j end - i_node += i_node_step_j - j_node += j_node_step_j - k_node += k_node_step_j end - end - - return nothing -end - -function calc_boundary_flux!(cache, t, boundary_condition, boundary_indexing, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - equations, surface_integral, dg::DG) - @unpack boundaries = cache - @unpack surface_flux_values, node_coordinates, contravariant_vectors = cache.elements - @unpack surface_flux = surface_integral - index_range = eachnode(dg) - - @threaded for local_index in eachindex(boundary_indexing) - # Use the local index to get the global boundary index from the - # pre-sorted list - boundary = boundary_indexing[local_index] - - # Get information on the adjacent element, compute the surface fluxes, - # and store them - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - direction = indices2direction(node_indices) - - i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1], - index_range) - j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2], - index_range) - k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3], - index_range) - - i_node = i_node_start - j_node = j_node_start - k_node = k_node_start - for j in eachnode(dg) - for i in eachnode(dg) - # Extract solution data from boundary container - u_inner = get_node_vars(boundaries.u, equations, dg, i, j, boundary) - - # Outward-pointing normal direction (not normalized) - normal_direction = get_normal_direction(direction, - contravariant_vectors, - i_node, j_node, k_node, element) - # Coordinates at boundary node - x = get_node_coords(node_coordinates, equations, dg, - i_node, j_node, k_node, element) + return nothing + end - flux_ = boundary_condition(u_inner, normal_direction, x, t, - surface_flux, equations) + function calc_boundary_flux!( + cache, t, boundary_condition, boundary_indexing, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + equations, surface_integral, dg::DG + ) + @unpack boundaries = cache + @unpack surface_flux_values, node_coordinates, contravariant_vectors = cache.elements + @unpack surface_flux = surface_integral + index_range = eachnode(dg) + + @threaded for local_index in eachindex(boundary_indexing) + # Use the local index to get the global boundary index from the + # pre-sorted list + boundary = boundary_indexing[local_index] + + # Get information on the adjacent element, compute the surface fluxes, + # and store them + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d( + node_indices[1], + index_range + ) + j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d( + node_indices[2], + index_range + ) + k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d( + node_indices[3], + index_range + ) + + i_node = i_node_start + j_node = j_node_start + k_node = k_node_start + for j in eachnode(dg) + for i in eachnode(dg) + # Extract solution data from boundary container + u_inner = get_node_vars(boundaries.u, equations, dg, i, j, boundary) + + # Outward-pointing normal direction (not normalized) + normal_direction = get_normal_direction( + direction, + contravariant_vectors, + i_node, j_node, k_node, element + ) + + # Coordinates at boundary node + x = get_node_coords( + node_coordinates, equations, dg, + i_node, j_node, k_node, element + ) + + flux_ = boundary_condition( + u_inner, normal_direction, x, t, + surface_flux, equations + ) + + # Copy flux to element storage in the correct orientation + for v in eachvariable(equations) + surface_flux_values[v, i, j, direction, element] = flux_[v] + end - # Copy flux to element storage in the correct orientation - for v in eachvariable(equations) - surface_flux_values[v, i, j, direction, element] = flux_[v] + i_node += i_node_step_i + j_node += j_node_step_i + k_node += k_node_step_i end - - i_node += i_node_step_i - j_node += j_node_step_i - k_node += k_node_step_i + i_node += i_node_step_j + j_node += j_node_step_j + k_node += k_node_step_j end - i_node += i_node_step_j - j_node += j_node_step_j - k_node += k_node_step_j end end -end - -function prolong2mortars!(cache, u, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DGSEM) - @unpack fstar_tmp_threaded = cache - @unpack neighbor_ids, node_indices = cache.mortars - index_range = eachnode(dg) - - @threaded for mortar in eachmortar(dg, cache) - # Copy solution data from the small elements using "delayed indexing" with - # a start value and two step sizes to get the correct face and orientation. - small_indices = node_indices[1, mortar] - i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1], - index_range) - j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2], - index_range) - k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3], - index_range) + function prolong2mortars!( + cache, u, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DGSEM + ) + @unpack fstar_tmp_threaded = cache + @unpack neighbor_ids, node_indices = cache.mortars + index_range = eachnode(dg) + + @threaded for mortar in eachmortar(dg, cache) + # Copy solution data from the small elements using "delayed indexing" with + # a start value and two step sizes to get the correct face and orientation. + small_indices = node_indices[1, mortar] + + i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d( + small_indices[1], + index_range + ) + j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d( + small_indices[2], + index_range + ) + k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d( + small_indices[3], + index_range + ) + + for position in 1:4 + i_small = i_small_start + j_small = j_small_start + k_small = k_small_start + element = neighbor_ids[position, mortar] + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u[1, v, position, i, j, mortar] = u[ + v, i_small, + j_small, + k_small, + element, + ] + end + i_small += i_small_step_i + j_small += j_small_step_i + k_small += k_small_step_i + end + i_small += i_small_step_j + j_small += j_small_step_j + k_small += k_small_step_j + end + end - for position in 1:4 - i_small = i_small_start - j_small = j_small_start - k_small = k_small_start - element = neighbor_ids[position, mortar] + # Buffer to copy solution values of the large element in the correct orientation + # before interpolating + u_buffer = cache.u_threaded[Threads.threadid()] + # temporary buffer for projections + fstar_tmp = fstar_tmp_threaded[Threads.threadid()] + + # Copy solution of large element face to buffer in the + # correct orientation + large_indices = node_indices[2, mortar] + + i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d( + large_indices[1], + index_range + ) + j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d( + large_indices[2], + index_range + ) + k_large_start, k_large_step_i, k_large_step_j = index_to_start_step_3d( + large_indices[3], + index_range + ) + + i_large = i_large_start + j_large = j_large_start + k_large = k_large_start + element = neighbor_ids[5, mortar] for j in eachnode(dg) for i in eachnode(dg) for v in eachvariable(equations) - cache.mortars.u[1, v, position, i, j, mortar] = u[v, i_small, - j_small, - k_small, - element] + u_buffer[v, i, j] = u[v, i_large, j_large, k_large, element] end - i_small += i_small_step_i - j_small += j_small_step_i - k_small += k_small_step_i + i_large += i_large_step_i + j_large += j_large_step_i + k_large += k_large_step_i end - i_small += i_small_step_j - j_small += j_small_step_j - k_small += k_small_step_j + i_large += i_large_step_j + j_large += j_large_step_j + k_large += k_large_step_j end - end - # Buffer to copy solution values of the large element in the correct orientation - # before interpolating - u_buffer = cache.u_threaded[Threads.threadid()] - # temporary buffer for projections - fstar_tmp = fstar_tmp_threaded[Threads.threadid()] - - # Copy solution of large element face to buffer in the - # correct orientation - large_indices = node_indices[2, mortar] + # Interpolate large element face data from buffer to small face locations + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 1, :, :, mortar), + mortar_l2.forward_lower, + mortar_l2.forward_lower, + u_buffer, + fstar_tmp + ) + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 2, :, :, mortar), + mortar_l2.forward_upper, + mortar_l2.forward_lower, + u_buffer, + fstar_tmp + ) + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 3, :, :, mortar), + mortar_l2.forward_lower, + mortar_l2.forward_upper, + u_buffer, + fstar_tmp + ) + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 4, :, :, mortar), + mortar_l2.forward_upper, + mortar_l2.forward_upper, + u_buffer, + fstar_tmp + ) + end - i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_indices[1], - index_range) - j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_indices[2], - index_range) - k_large_start, k_large_step_i, k_large_step_j = index_to_start_step_3d(large_indices[3], - index_range) + return nothing + end - i_large = i_large_start - j_large = j_large_start - k_large = k_large_start - element = neighbor_ids[5, mortar] - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - u_buffer[v, i, j] = u[v, i_large, j_large, k_large, element] + function calc_mortar_flux!( + surface_flux_values, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + nonconservative_terms, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack neighbor_ids, node_indices = cache.mortars + @unpack contravariant_vectors = cache.elements + @unpack fstar_threaded, fstar_tmp_threaded = cache + index_range = eachnode(dg) + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar = fstar_threaded[Threads.threadid()] + fstar_tmp = fstar_tmp_threaded[Threads.threadid()] + + # Get index information on the small elements + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) + + i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d( + small_indices[1], + index_range + ) + j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d( + small_indices[2], + index_range + ) + k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d( + small_indices[3], + index_range + ) + + for position in 1:4 + i_small = i_small_start + j_small = j_small_start + k_small = k_small_start + element = neighbor_ids[position, mortar] + for j in eachnode(dg) + for i in eachnode(dg) + # Get the normal direction on the small element. + # Note, contravariant vectors at interfaces in negative coordinate direction + # are pointing inwards. This is handled by `get_normal_direction`. + normal_direction = get_normal_direction( + small_direction, + contravariant_vectors, + i_small, j_small, k_small, + element + ) + + calc_mortar_flux!( + fstar, mesh, nonconservative_terms, equations, + surface_integral, dg, cache, + mortar, position, normal_direction, + i, j + ) + + i_small += i_small_step_i + j_small += j_small_step_i + k_small += k_small_step_i + end + i_small += i_small_step_j + j_small += j_small_step_j + k_small += k_small_step_j end - i_large += i_large_step_i - j_large += j_large_step_i - k_large += k_large_step_i end - i_large += i_large_step_j - j_large += j_large_step_j - k_large += k_large_step_j + + # Buffer to interpolate flux values of the large element to before + # copying in the correct orientation + u_buffer = cache.u_threaded[Threads.threadid()] + + # in calc_interface_flux!, the interface flux is computed once over each + # interface using the normal from the "primary" element. The result is then + # passed back to the "secondary" element, flipping the sign to account for the + # change in the normal direction. For mortars, this sign flip occurs in + # "mortar_fluxes_to_elements!" instead. + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, + mortar, fstar, u_buffer, fstar_tmp + ) end - # Interpolate large element face data from buffer to small face locations - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 1, :, :, mortar), - mortar_l2.forward_lower, - mortar_l2.forward_lower, - u_buffer, - fstar_tmp) - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 2, :, :, mortar), - mortar_l2.forward_upper, - mortar_l2.forward_lower, - u_buffer, - fstar_tmp) - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 3, :, :, mortar), - mortar_l2.forward_lower, - mortar_l2.forward_upper, - u_buffer, - fstar_tmp) - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 4, :, :, mortar), - mortar_l2.forward_upper, - mortar_l2.forward_upper, - u_buffer, - fstar_tmp) + return nothing + end + + # Inlined version of the mortar flux computation on small elements for conservation fluxes + @inline function calc_mortar_flux!( + fstar, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + mortar_index, position_index, normal_direction, + i_node_index, j_node_index + ) + @unpack u = cache.mortars + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, position_index, i_node_index, + j_node_index, mortar_index + ) + + flux = surface_flux(u_ll, u_rr, normal_direction, equations) + + # Copy flux to buffer + set_node_vars!( + fstar, flux, equations, dg, i_node_index, j_node_index, + position_index + ) + end + + # Inlined version of the mortar flux computation on small elements for conservation fluxes + # with nonconservative terms + @inline function calc_mortar_flux!( + fstar, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache, + mortar_index, position_index, normal_direction, + i_node_index, j_node_index + ) + @unpack u = cache.mortars + surface_flux, nonconservative_flux = surface_integral.surface_flux + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, position_index, i_node_index, + j_node_index, mortar_index + ) + + # Compute conservative flux + flux = surface_flux(u_ll, u_rr, normal_direction, equations) + + # Compute nonconservative flux and add it to the flux scaled by a factor of 0.5 based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + noncons = nonconservative_flux( + u_ll, u_rr, normal_direction, normal_direction, + equations + ) + flux_plus_noncons = flux + 0.5f0 * noncons + + # Copy to buffer + set_node_vars!( + fstar, flux_plus_noncons, equations, dg, i_node_index, j_node_index, + position_index + ) end - return nothing -end - -function calc_mortar_flux!(surface_flux_values, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - nonconservative_terms, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack neighbor_ids, node_indices = cache.mortars - @unpack contravariant_vectors = cache.elements - @unpack fstar_threaded, fstar_tmp_threaded = cache - index_range = eachnode(dg) - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar = fstar_threaded[Threads.threadid()] - fstar_tmp = fstar_tmp_threaded[Threads.threadid()] - - # Get index information on the small elements + @inline function mortar_fluxes_to_elements!( + surface_flux_values, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + equations, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, mortar, fstar, u_buffer, + fstar_tmp + ) + @unpack neighbor_ids, node_indices = cache.mortars + index_range = eachnode(dg) + + # Copy solution small to small small_indices = node_indices[1, mortar] small_direction = indices2direction(small_indices) - i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1], - index_range) - j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2], - index_range) - k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3], - index_range) - for position in 1:4 - i_small = i_small_start - j_small = j_small_start - k_small = k_small_start element = neighbor_ids[position, mortar] - for j in eachnode(dg) - for i in eachnode(dg) - # Get the normal direction on the small element. - # Note, contravariant vectors at interfaces in negative coordinate direction - # are pointing inwards. This is handled by `get_normal_direction`. - normal_direction = get_normal_direction(small_direction, - contravariant_vectors, - i_small, j_small, k_small, - element) - - calc_mortar_flux!(fstar, mesh, nonconservative_terms, equations, - surface_integral, dg, cache, - mortar, position, normal_direction, - i, j) - - i_small += i_small_step_i - j_small += j_small_step_i - k_small += k_small_step_i + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i, j, small_direction, element] = fstar[ + v, i, j, + position, + ] end - i_small += i_small_step_j - j_small += j_small_step_j - k_small += k_small_step_j end end - # Buffer to interpolate flux values of the large element to before - # copying in the correct orientation - u_buffer = cache.u_threaded[Threads.threadid()] - - # in calc_interface_flux!, the interface flux is computed once over each - # interface using the normal from the "primary" element. The result is then - # passed back to the "secondary" element, flipping the sign to account for the - # change in the normal direction. For mortars, this sign flip occurs in - # "mortar_fluxes_to_elements!" instead. - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, - mortar, fstar, u_buffer, fstar_tmp) - end - - return nothing -end - -# Inlined version of the mortar flux computation on small elements for conservation fluxes -@inline function calc_mortar_flux!(fstar, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - mortar_index, position_index, normal_direction, - i_node_index, j_node_index) - @unpack u = cache.mortars - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, i_node_index, - j_node_index, mortar_index) - - flux = surface_flux(u_ll, u_rr, normal_direction, equations) - - # Copy flux to buffer - set_node_vars!(fstar, flux, equations, dg, i_node_index, j_node_index, - position_index) -end - -# Inlined version of the mortar flux computation on small elements for conservation fluxes -# with nonconservative terms -@inline function calc_mortar_flux!(fstar, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache, - mortar_index, position_index, normal_direction, - i_node_index, j_node_index) - @unpack u = cache.mortars - surface_flux, nonconservative_flux = surface_integral.surface_flux - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, i_node_index, - j_node_index, mortar_index) - - # Compute conservative flux - flux = surface_flux(u_ll, u_rr, normal_direction, equations) - - # Compute nonconservative flux and add it to the flux scaled by a factor of 0.5 based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - noncons = nonconservative_flux(u_ll, u_rr, normal_direction, normal_direction, - equations) - flux_plus_noncons = flux + 0.5f0 * noncons - - # Copy to buffer - set_node_vars!(fstar, flux_plus_noncons, equations, dg, i_node_index, j_node_index, - position_index) -end - -@inline function mortar_fluxes_to_elements!(surface_flux_values, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - equations, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, mortar, fstar, u_buffer, - fstar_tmp) - @unpack neighbor_ids, node_indices = cache.mortars - index_range = eachnode(dg) - - # Copy solution small to small - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) - - for position in 1:4 - element = neighbor_ids[position, mortar] - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i, j, small_direction, element] = fstar[v, i, j, - position] + # Project small fluxes to large element. + multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_lower, mortar_l2.reverse_lower, + view(fstar, .., 1), + fstar_tmp + ) + add_multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_upper, mortar_l2.reverse_lower, + view(fstar, .., 2), + fstar_tmp + ) + add_multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_lower, mortar_l2.reverse_upper, + view(fstar, .., 3), + fstar_tmp + ) + add_multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_upper, mortar_l2.reverse_upper, + view(fstar, .., 4), + fstar_tmp + ) + + # The flux is calculated in the outward direction of the small elements, + # so the sign must be switched to get the flux in outward direction + # of the large element. + # The contravariant vectors of the large element (and therefore the normal + # vectors of the large element as well) are four times as large as the + # contravariant vectors of the small elements. Therefore, the flux needs + # to be scaled by a factor of 4 to obtain the flux of the large element. + u_buffer .*= -4 + + # Copy interpolated flux values from buffer to large element face in the + # correct orientation. + # Note that the index of the small sides will always run forward but + # the index of the large side might need to run backwards for flipped sides. + large_element = neighbor_ids[5, mortar] + large_indices = node_indices[2, mortar] + large_direction = indices2direction(large_indices) + large_surface_indices = surface_indices(large_indices) + + i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d( + large_surface_indices[1], + index_range + ) + j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d( + large_surface_indices[2], + index_range + ) + + # Note that the indices of the small sides will always run forward but + # the large indices might need to run backwards for flipped sides. + i_large = i_large_start + j_large = j_large_start + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i_large, j_large, large_direction, large_element] = u_buffer[ + v, + i, + j, + ] + end + i_large += i_large_step_i + j_large += j_large_step_i end + i_large += i_large_step_j + j_large += j_large_step_j end - end - # Project small fluxes to large element. - multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_lower, mortar_l2.reverse_lower, - view(fstar, .., 1), - fstar_tmp) - add_multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_upper, mortar_l2.reverse_lower, - view(fstar, .., 2), - fstar_tmp) - add_multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_lower, mortar_l2.reverse_upper, - view(fstar, .., 3), - fstar_tmp) - add_multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_upper, mortar_l2.reverse_upper, - view(fstar, .., 4), - fstar_tmp) - - # The flux is calculated in the outward direction of the small elements, - # so the sign must be switched to get the flux in outward direction - # of the large element. - # The contravariant vectors of the large element (and therefore the normal - # vectors of the large element as well) are four times as large as the - # contravariant vectors of the small elements. Therefore, the flux needs - # to be scaled by a factor of 4 to obtain the flux of the large element. - u_buffer .*= -4 - - # Copy interpolated flux values from buffer to large element face in the - # correct orientation. - # Note that the index of the small sides will always run forward but - # the index of the large side might need to run backwards for flipped sides. - large_element = neighbor_ids[5, mortar] - large_indices = node_indices[2, mortar] - large_direction = indices2direction(large_indices) - large_surface_indices = surface_indices(large_indices) - - i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_surface_indices[1], - index_range) - j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_surface_indices[2], - index_range) - - # Note that the indices of the small sides will always run forward but - # the large indices might need to run backwards for flipped sides. - i_large = i_large_start - j_large = j_large_start - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i_large, j_large, large_direction, large_element] = u_buffer[v, - i, - j] - end - i_large += i_large_step_i - j_large += j_large_step_i - end - i_large += i_large_step_j - j_large += j_large_step_j + return nothing end - return nothing -end - -function calc_surface_integral!(du, u, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - equations, - surface_integral::SurfaceIntegralWeakForm, - dg::DGSEM, cache) - @unpack boundary_interpolation = dg.basis - @unpack surface_flux_values = cache.elements - - # Note that all fluxes have been computed with outward-pointing normal vectors. - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` to let `@muladd` turn these - # into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] - @threaded for element in eachelement(dg, cache) - for m in eachnode(dg), l in eachnode(dg) - for v in eachvariable(equations) - # surface at -x - du[v, 1, l, m, element] = (du[v, 1, l, m, element] + - surface_flux_values[v, l, m, 1, element] * - factor_1) - - # surface at +x - du[v, nnodes(dg), l, m, element] = (du[v, nnodes(dg), l, m, element] + - surface_flux_values[v, l, m, 2, - element] * - factor_2) - - # surface at -y - du[v, l, 1, m, element] = (du[v, l, 1, m, element] + - surface_flux_values[v, l, m, 3, element] * - factor_1) - - # surface at +y - du[v, l, nnodes(dg), m, element] = (du[v, l, nnodes(dg), m, element] + - surface_flux_values[v, l, m, 4, - element] * - factor_2) - - # surface at -z - du[v, l, m, 1, element] = (du[v, l, m, 1, element] + - surface_flux_values[v, l, m, 5, element] * - factor_1) - - # surface at +z - du[v, l, m, nnodes(dg), element] = (du[v, l, m, nnodes(dg), element] + - surface_flux_values[v, l, m, 6, - element] * - factor_2) + function calc_surface_integral!( + du, u, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + equations, + surface_integral::SurfaceIntegralWeakForm, + dg::DGSEM, cache + ) + @unpack boundary_interpolation = dg.basis + @unpack surface_flux_values = cache.elements + + # Note that all fluxes have been computed with outward-pointing normal vectors. + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for m in eachnode(dg), l in eachnode(dg) + for v in eachvariable(equations) + # surface at -x + du[v, 1, l, m, element] = ( + du[v, 1, l, m, element] + + surface_flux_values[v, l, m, 1, element] * + factor_1 + ) + + # surface at +x + du[v, nnodes(dg), l, m, element] = ( + du[v, nnodes(dg), l, m, element] + + surface_flux_values[ + v, l, m, 2, + element, + ] * + factor_2 + ) + + # surface at -y + du[v, l, 1, m, element] = ( + du[v, l, 1, m, element] + + surface_flux_values[v, l, m, 3, element] * + factor_1 + ) + + # surface at +y + du[v, l, nnodes(dg), m, element] = ( + du[v, l, nnodes(dg), m, element] + + surface_flux_values[ + v, l, m, 4, + element, + ] * + factor_2 + ) + + # surface at -z + du[v, l, m, 1, element] = ( + du[v, l, m, 1, element] + + surface_flux_values[v, l, m, 5, element] * + factor_1 + ) + + # surface at +z + du[v, l, m, nnodes(dg), element] = ( + du[v, l, m, nnodes(dg), element] + + surface_flux_values[ + v, l, m, 6, + element, + ] * + factor_2 + ) + end end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_p4est/dg_3d_parabolic.jl b/src/solvers/dgsem_p4est/dg_3d_parabolic.jl index 3f286ca01f..0dc9638759 100644 --- a/src/solvers/dgsem_p4est/dg_3d_parabolic.jl +++ b/src/solvers/dgsem_p4est/dg_3d_parabolic.jl @@ -3,1022 +3,1312 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This method is called when a SemidiscretizationHyperbolicParabolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache_parabolic(mesh::P4estMesh{3}, - equations_hyperbolic::AbstractEquations, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, parabolic_scheme, RealT, uEltype) - balance!(mesh) - - elements = init_elements(mesh, equations_hyperbolic, dg.basis, uEltype) - interfaces = init_interfaces(mesh, equations_hyperbolic, dg.basis, elements) - boundaries = init_boundaries(mesh, equations_hyperbolic, dg.basis, elements) - - viscous_container = init_viscous_container_3d(nvariables(equations_hyperbolic), - nnodes(dg.basis), nelements(elements), - uEltype) - - cache = (; elements, interfaces, boundaries, viscous_container) - - return cache -end - -function calc_gradient!(gradients, u_transformed, t, - mesh::P4estMesh{3}, equations_parabolic, - boundary_conditions_parabolic, dg::DG, - cache, cache_parabolic) - gradients_x, gradients_y, gradients_z = gradients - - # Reset du - @trixi_timeit timer() "reset gradients" begin - reset_du!(gradients_x, dg, cache) - reset_du!(gradients_y, dg, cache) - reset_du!(gradients_z, dg, cache) + #! format: noindent + + # This method is called when a SemidiscretizationHyperbolicParabolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache_parabolic( + mesh::P4estMesh{3}, + equations_hyperbolic::AbstractEquations, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, parabolic_scheme, RealT, uEltype + ) + balance!(mesh) + + elements = init_elements(mesh, equations_hyperbolic, dg.basis, uEltype) + interfaces = init_interfaces(mesh, equations_hyperbolic, dg.basis, elements) + boundaries = init_boundaries(mesh, equations_hyperbolic, dg.basis, elements) + + viscous_container = init_viscous_container_3d( + nvariables(equations_hyperbolic), + nnodes(dg.basis), nelements(elements), + uEltype + ) + + cache = (; elements, interfaces, boundaries, viscous_container) + + return cache end - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - (; derivative_dhat) = dg.basis - (; contravariant_vectors) = cache.elements - - @threaded for element in eachelement(dg, cache) + function calc_gradient!( + gradients, u_transformed, t, + mesh::P4estMesh{3}, equations_parabolic, + boundary_conditions_parabolic, dg::DG, + cache, cache_parabolic + ) + gradients_x, gradients_y, gradients_z = gradients + + # Reset du + @trixi_timeit timer() "reset gradients" begin + reset_du!(gradients_x, dg, cache) + reset_du!(gradients_y, dg, cache) + reset_du!(gradients_z, dg, cache) + end - # Calculate gradients with respect to reference coordinates in one element - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, j, k, - element) + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + (; derivative_dhat) = dg.basis + (; contravariant_vectors) = cache.elements + + @threaded for element in eachelement(dg, cache) + + # Calculate gradients with respect to reference coordinates in one element + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars( + u_transformed, equations_parabolic, dg, i, j, k, + element + ) + + for ii in eachnode(dg) + multiply_add_to_node_vars!( + gradients_x, derivative_dhat[ii, i], + u_node, equations_parabolic, dg, ii, j, + k, element + ) + end - for ii in eachnode(dg) - multiply_add_to_node_vars!(gradients_x, derivative_dhat[ii, i], - u_node, equations_parabolic, dg, ii, j, - k, element) - end + for jj in eachnode(dg) + multiply_add_to_node_vars!( + gradients_y, derivative_dhat[jj, j], + u_node, equations_parabolic, dg, i, jj, + k, element + ) + end - for jj in eachnode(dg) - multiply_add_to_node_vars!(gradients_y, derivative_dhat[jj, j], - u_node, equations_parabolic, dg, i, jj, - k, element) + for kk in eachnode(dg) + multiply_add_to_node_vars!( + gradients_z, derivative_dhat[kk, k], + u_node, equations_parabolic, dg, i, j, + kk, element + ) + end end - for kk in eachnode(dg) - multiply_add_to_node_vars!(gradients_z, derivative_dhat[kk, k], - u_node, equations_parabolic, dg, i, j, - kk, element) + # now that the reference coordinate gradients are computed, transform them node-by-node to physical gradients + # using the contravariant vectors + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + Ja11, Ja12, Ja13 = get_contravariant_vector( + 1, contravariant_vectors, + i, j, k, element + ) + Ja21, Ja22, Ja23 = get_contravariant_vector( + 2, contravariant_vectors, + i, j, k, element + ) + Ja31, Ja32, Ja33 = get_contravariant_vector( + 3, contravariant_vectors, + i, j, k, element + ) + + gradients_reference_1 = get_node_vars( + gradients_x, equations_parabolic, + dg, + i, j, k, element + ) + gradients_reference_2 = get_node_vars( + gradients_y, equations_parabolic, + dg, + i, j, k, element + ) + gradients_reference_3 = get_node_vars( + gradients_z, equations_parabolic, + dg, + i, j, k, element + ) + + # note that the contravariant vectors are transposed compared with computations of flux + # divergences in `calc_volume_integral!`. See + # https://github.com/trixi-framework/Trixi.jl/pull/1490#discussion_r1213345190 + # for a more detailed discussion. + gradient_x_node = Ja11 * gradients_reference_1 + + Ja21 * gradients_reference_2 + + Ja31 * gradients_reference_3 + gradient_y_node = Ja12 * gradients_reference_1 + + Ja22 * gradients_reference_2 + + Ja32 * gradients_reference_3 + gradient_z_node = Ja13 * gradients_reference_1 + + Ja23 * gradients_reference_2 + + Ja33 * gradients_reference_3 + + set_node_vars!( + gradients_x, gradient_x_node, equations_parabolic, dg, + i, j, k, element + ) + set_node_vars!( + gradients_y, gradient_y_node, equations_parabolic, dg, + i, j, k, element + ) + set_node_vars!( + gradients_z, gradient_z_node, equations_parabolic, dg, + i, j, k, element + ) end end - - # now that the reference coordinate gradients are computed, transform them node-by-node to physical gradients - # using the contravariant vectors - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors, - i, j, k, element) - Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors, - i, j, k, element) - Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors, - i, j, k, element) - - gradients_reference_1 = get_node_vars(gradients_x, equations_parabolic, - dg, - i, j, k, element) - gradients_reference_2 = get_node_vars(gradients_y, equations_parabolic, - dg, - i, j, k, element) - gradients_reference_3 = get_node_vars(gradients_z, equations_parabolic, - dg, - i, j, k, element) - - # note that the contravariant vectors are transposed compared with computations of flux - # divergences in `calc_volume_integral!`. See - # https://github.com/trixi-framework/Trixi.jl/pull/1490#discussion_r1213345190 - # for a more detailed discussion. - gradient_x_node = Ja11 * gradients_reference_1 + - Ja21 * gradients_reference_2 + - Ja31 * gradients_reference_3 - gradient_y_node = Ja12 * gradients_reference_1 + - Ja22 * gradients_reference_2 + - Ja32 * gradients_reference_3 - gradient_z_node = Ja13 * gradients_reference_1 + - Ja23 * gradients_reference_2 + - Ja33 * gradients_reference_3 - - set_node_vars!(gradients_x, gradient_x_node, equations_parabolic, dg, - i, j, k, element) - set_node_vars!(gradients_y, gradient_y_node, equations_parabolic, dg, - i, j, k, element) - set_node_vars!(gradients_z, gradient_z_node, equations_parabolic, dg, - i, j, k, element) - end end - end - - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache_parabolic, u_transformed, mesh, - equations_parabolic, dg.surface_integral, dg) - end - # Calculate interface fluxes for the gradient. This reuses P4est `calc_interface_flux!` along with a - # specialization for AbstractEquationsParabolic. - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache_parabolic.elements.surface_flux_values, - mesh, False(), # False() = no nonconservative terms - equations_parabolic, dg.surface_integral, dg, - cache_parabolic) - end + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache_parabolic, u_transformed, mesh, + equations_parabolic, dg.surface_integral, dg + ) + end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache_parabolic, u_transformed, mesh, - equations_parabolic, dg.surface_integral, dg) - end + # Calculate interface fluxes for the gradient. This reuses P4est `calc_interface_flux!` along with a + # specialization for AbstractEquationsParabolic. + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache_parabolic.elements.surface_flux_values, + mesh, False(), # False() = no nonconservative terms + equations_parabolic, dg.surface_integral, dg, + cache_parabolic + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux_gradients!(cache_parabolic, t, boundary_conditions_parabolic, - mesh, equations_parabolic, dg.surface_integral, - dg) - end + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache_parabolic, u_transformed, mesh, + equations_parabolic, dg.surface_integral, dg + ) + end - # Prolong solution to mortars. These should reuse the hyperbolic version of `prolong2mortars` - # !!! NOTE: we reuse the hyperbolic cache here, since it contains both `mortars` and `u_threaded`. - # !!! should we have a separate mortars/u_threaded in cache_parabolic? - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u_transformed, mesh, equations_parabolic, - dg.mortar, dg.surface_integral, dg) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux_gradients!( + cache_parabolic, t, boundary_conditions_parabolic, + mesh, equations_parabolic, dg.surface_integral, + dg + ) + end - # Calculate mortar fluxes. These should reuse the hyperbolic version of `calc_mortar_flux`, - # along with a specialization on `calc_mortar_flux!` and `mortar_fluxes_to_elements!` for - # AbstractEquationsParabolic. - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(cache_parabolic.elements.surface_flux_values, - mesh, False(), # False() = no nonconservative terms - equations_parabolic, - dg.mortar, dg.surface_integral, dg, cache) - end + # Prolong solution to mortars. These should reuse the hyperbolic version of `prolong2mortars` + # !!! NOTE: we reuse the hyperbolic cache here, since it contains both `mortars` and `u_threaded`. + # !!! should we have a separate mortars/u_threaded in cache_parabolic? + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!( + cache, u_transformed, mesh, equations_parabolic, + dg.mortar, dg.surface_integral, dg + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - (; boundary_interpolation) = dg.basis - (; surface_flux_values) = cache_parabolic.elements - (; contravariant_vectors) = cache.elements + # Calculate mortar fluxes. These should reuse the hyperbolic version of `calc_mortar_flux`, + # along with a specialization on `calc_mortar_flux!` and `mortar_fluxes_to_elements!` for + # AbstractEquationsParabolic. + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!( + cache_parabolic.elements.surface_flux_values, + mesh, False(), # False() = no nonconservative terms + equations_parabolic, + dg.mortar, dg.surface_integral, dg, cache + ) + end - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` to let `@muladd` turn these - # into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] - @threaded for element in eachelement(dg, cache) - for l in eachnode(dg), m in eachnode(dg) - for v in eachvariable(equations_parabolic) - for dim in 1:3 - grad = gradients[dim] - # surface at -x - normal_direction = get_normal_direction(1, - contravariant_vectors, - 1, l, m, element) - grad[v, 1, l, m, element] = (grad[v, 1, l, m, element] + - surface_flux_values[v, l, m, 1, - element] * - factor_1 * normal_direction[dim]) - - # surface at +x - normal_direction = get_normal_direction(2, - contravariant_vectors, - nnodes(dg), l, m, - element) - grad[v, nnodes(dg), l, m, element] = (grad[v, nnodes(dg), l, m, - element] + - surface_flux_values[v, l, - m, - 2, - element] * - factor_2 * - normal_direction[dim]) - - # surface at -y - normal_direction = get_normal_direction(3, - contravariant_vectors, - l, m, 1, element) - grad[v, l, 1, m, element] = (grad[v, l, 1, m, element] + - surface_flux_values[v, l, m, 3, - element] * - factor_1 * normal_direction[dim]) - - # surface at +y - normal_direction = get_normal_direction(4, - contravariant_vectors, - l, nnodes(dg), m, - element) - grad[v, l, nnodes(dg), m, element] = (grad[v, l, nnodes(dg), m, - element] + - surface_flux_values[v, l, - m, - 4, - element] * - factor_2 * - normal_direction[dim]) - - # surface at -z - normal_direction = get_normal_direction(5, - contravariant_vectors, - l, m, 1, element) - grad[v, l, m, 1, element] = (grad[v, l, m, 1, element] + - surface_flux_values[v, l, m, 5, - element] * - factor_1 * normal_direction[dim]) - - # surface at +z - normal_direction = get_normal_direction(6, - contravariant_vectors, - l, m, nnodes(dg), - element) - grad[v, l, m, nnodes(dg), element] = (grad[v, l, m, nnodes(dg), - element] + - surface_flux_values[v, l, - m, - 6, - element] * - factor_2 * - normal_direction[dim]) + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + (; boundary_interpolation) = dg.basis + (; surface_flux_values) = cache_parabolic.elements + (; contravariant_vectors) = cache.elements + + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg), m in eachnode(dg) + for v in eachvariable(equations_parabolic) + for dim in 1:3 + grad = gradients[dim] + # surface at -x + normal_direction = get_normal_direction( + 1, + contravariant_vectors, + 1, l, m, element + ) + grad[v, 1, l, m, element] = ( + grad[v, 1, l, m, element] + + surface_flux_values[ + v, l, m, 1, + element, + ] * + factor_1 * normal_direction[dim] + ) + + # surface at +x + normal_direction = get_normal_direction( + 2, + contravariant_vectors, + nnodes(dg), l, m, + element + ) + grad[v, nnodes(dg), l, m, element] = ( + grad[ + v, nnodes(dg), l, m, + element, + ] + + surface_flux_values[ + v, l, + m, + 2, + element, + ] * + factor_2 * + normal_direction[dim] + ) + + # surface at -y + normal_direction = get_normal_direction( + 3, + contravariant_vectors, + l, m, 1, element + ) + grad[v, l, 1, m, element] = ( + grad[v, l, 1, m, element] + + surface_flux_values[ + v, l, m, 3, + element, + ] * + factor_1 * normal_direction[dim] + ) + + # surface at +y + normal_direction = get_normal_direction( + 4, + contravariant_vectors, + l, nnodes(dg), m, + element + ) + grad[v, l, nnodes(dg), m, element] = ( + grad[ + v, l, nnodes(dg), m, + element, + ] + + surface_flux_values[ + v, l, + m, + 4, + element, + ] * + factor_2 * + normal_direction[dim] + ) + + # surface at -z + normal_direction = get_normal_direction( + 5, + contravariant_vectors, + l, m, 1, element + ) + grad[v, l, m, 1, element] = ( + grad[v, l, m, 1, element] + + surface_flux_values[ + v, l, m, 5, + element, + ] * + factor_1 * normal_direction[dim] + ) + + # surface at +z + normal_direction = get_normal_direction( + 6, + contravariant_vectors, + l, m, nnodes(dg), + element + ) + grad[v, l, m, nnodes(dg), element] = ( + grad[ + v, l, m, nnodes(dg), + element, + ] + + surface_flux_values[ + v, l, + m, + 6, + element, + ] * + factor_2 * + normal_direction[dim] + ) + end end end end end - end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" begin - apply_jacobian_parabolic!(gradients_x, mesh, equations_parabolic, dg, - cache_parabolic) - apply_jacobian_parabolic!(gradients_y, mesh, equations_parabolic, dg, - cache_parabolic) - apply_jacobian_parabolic!(gradients_z, mesh, equations_parabolic, dg, - cache_parabolic) + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" begin + apply_jacobian_parabolic!( + gradients_x, mesh, equations_parabolic, dg, + cache_parabolic + ) + apply_jacobian_parabolic!( + gradients_y, mesh, equations_parabolic, dg, + cache_parabolic + ) + apply_jacobian_parabolic!( + gradients_z, mesh, equations_parabolic, dg, + cache_parabolic + ) + end + + return nothing end - return nothing -end - -# This version is called during `calc_gradients!` and must be specialized because the flux -# in the gradient is {u} which doesn't depend on normals. Thus, you don't need to scale by -# 2 and flip the sign when storing the mortar fluxes back into surface_flux_values -@inline function mortar_fluxes_to_elements!(surface_flux_values, - mesh::P4estMesh{3}, - equations::AbstractEquationsParabolic, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, mortar, fstar, u_buffer, - fstar_tmp) - @unpack neighbor_ids, node_indices = cache.mortars - index_range = eachnode(dg) - # Copy solution small to small - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) - - for position in 1:4 # Loop over small elements - element = neighbor_ids[position, mortar] - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i, j, small_direction, element] = fstar[v, i, j, - position] + # This version is called during `calc_gradients!` and must be specialized because the flux + # in the gradient is {u} which doesn't depend on normals. Thus, you don't need to scale by + # 2 and flip the sign when storing the mortar fluxes back into surface_flux_values + @inline function mortar_fluxes_to_elements!( + surface_flux_values, + mesh::P4estMesh{3}, + equations::AbstractEquationsParabolic, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, mortar, fstar, u_buffer, + fstar_tmp + ) + @unpack neighbor_ids, node_indices = cache.mortars + index_range = eachnode(dg) + # Copy solution small to small + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) + + for position in 1:4 # Loop over small elements + element = neighbor_ids[position, mortar] + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i, j, small_direction, element] = fstar[ + v, i, j, + position, + ] + end end end - end - # Project small fluxes to large element. - multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_lower, mortar_l2.reverse_lower, - view(fstar, .., 1), - fstar_tmp) - add_multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_upper, mortar_l2.reverse_lower, - view(fstar, .., 2), - fstar_tmp) - add_multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_lower, mortar_l2.reverse_upper, - view(fstar, .., 3), - fstar_tmp) - add_multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_upper, mortar_l2.reverse_upper, - view(fstar, .., 4), - fstar_tmp) - - # The flux is calculated in the outward direction of the small elements, - # so the sign must be switched to get the flux in outward direction - # of the large element. - # The contravariant vectors of the large element (and therefore the normal - # vectors of the large element as well) are twice as large as the - # contravariant vectors of the small elements. Therefore, the flux needs - # to be scaled by a factor of 2 to obtain the flux of the large element. - # u_buffer .*= 0.5 - - # Copy interpolated flux values from buffer to large element face in the - # correct orientation. - # Note that the index of the small sides will always run forward but - # the index of the large side might need to run backwards for flipped sides. - large_element = neighbor_ids[5, mortar] - large_indices = node_indices[2, mortar] - large_direction = indices2direction(large_indices) - large_surface_indices = surface_indices(large_indices) - - i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_surface_indices[1], - index_range) - j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_surface_indices[2], - index_range) - - # Note that the indices of the small sides will always run forward but - # the large indices might need to run backwards for flipped sides. - i_large = i_large_start - j_large = j_large_start - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i_large, j_large, large_direction, large_element] = u_buffer[v, - i, - j] + # Project small fluxes to large element. + multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_lower, mortar_l2.reverse_lower, + view(fstar, .., 1), + fstar_tmp + ) + add_multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_upper, mortar_l2.reverse_lower, + view(fstar, .., 2), + fstar_tmp + ) + add_multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_lower, mortar_l2.reverse_upper, + view(fstar, .., 3), + fstar_tmp + ) + add_multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_upper, mortar_l2.reverse_upper, + view(fstar, .., 4), + fstar_tmp + ) + + # The flux is calculated in the outward direction of the small elements, + # so the sign must be switched to get the flux in outward direction + # of the large element. + # The contravariant vectors of the large element (and therefore the normal + # vectors of the large element as well) are twice as large as the + # contravariant vectors of the small elements. Therefore, the flux needs + # to be scaled by a factor of 2 to obtain the flux of the large element. + # u_buffer .*= 0.5 + + # Copy interpolated flux values from buffer to large element face in the + # correct orientation. + # Note that the index of the small sides will always run forward but + # the index of the large side might need to run backwards for flipped sides. + large_element = neighbor_ids[5, mortar] + large_indices = node_indices[2, mortar] + large_direction = indices2direction(large_indices) + large_surface_indices = surface_indices(large_indices) + + i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d( + large_surface_indices[1], + index_range + ) + j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d( + large_surface_indices[2], + index_range + ) + + # Note that the indices of the small sides will always run forward but + # the large indices might need to run backwards for flipped sides. + i_large = i_large_start + j_large = j_large_start + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i_large, j_large, large_direction, large_element] = u_buffer[ + v, + i, + j, + ] + end + i_large += i_large_step_i + j_large += j_large_step_i end - i_large += i_large_step_i - j_large += j_large_step_i + i_large += i_large_step_j + j_large += j_large_step_j end - i_large += i_large_step_j - j_large += j_large_step_j + + return nothing end - return nothing -end - -# This version is used for parabolic gradient computations -@inline function calc_interface_flux!(surface_flux_values, mesh::P4estMesh{3}, - nonconservative_terms::False, - equations::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - interface_index, normal_direction, - primary_i_node_index, primary_j_node_index, - primary_direction_index, primary_element_index, - secondary_i_node_index, secondary_j_node_index, - secondary_direction_index, - secondary_element_index) - @unpack u = cache.interfaces - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, primary_i_node_index, - primary_j_node_index, - interface_index) - - flux_ = 0.5f0 * (u_ll + u_rr) # we assume that the gradient computations utilize a central flux - - # Note that we don't flip the sign on the secondondary flux. This is because for parabolic terms, - # the normals are not embedded in `flux_` for the parabolic gradient computations. - for v in eachvariable(equations) - surface_flux_values[v, primary_i_node_index, primary_j_node_index, primary_direction_index, primary_element_index] = flux_[v] - surface_flux_values[v, secondary_i_node_index, secondary_j_node_index, secondary_direction_index, secondary_element_index] = flux_[v] + # This version is used for parabolic gradient computations + @inline function calc_interface_flux!( + surface_flux_values, mesh::P4estMesh{3}, + nonconservative_terms::False, + equations::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + interface_index, normal_direction, + primary_i_node_index, primary_j_node_index, + primary_direction_index, primary_element_index, + secondary_i_node_index, secondary_j_node_index, + secondary_direction_index, + secondary_element_index + ) + @unpack u = cache.interfaces + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, primary_i_node_index, + primary_j_node_index, + interface_index + ) + + flux_ = 0.5f0 * (u_ll + u_rr) # we assume that the gradient computations utilize a central flux + + # Note that we don't flip the sign on the secondondary flux. This is because for parabolic terms, + # the normals are not embedded in `flux_` for the parabolic gradient computations. + for v in eachvariable(equations) + surface_flux_values[v, primary_i_node_index, primary_j_node_index, primary_direction_index, primary_element_index] = flux_[v] + surface_flux_values[v, secondary_i_node_index, secondary_j_node_index, secondary_direction_index, secondary_element_index] = flux_[v] + end end -end - -# This is the version used when calculating the divergence of the viscous fluxes -function calc_volume_integral!(du, flux_viscous, - mesh::P4estMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - dg::DGSEM, cache) - (; derivative_dhat) = dg.basis - (; contravariant_vectors) = cache.elements - flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous - - @threaded for element in eachelement(dg, cache) - # Calculate volume terms in one element - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - flux1 = get_node_vars(flux_viscous_x, equations_parabolic, dg, i, j, k, - element) - flux2 = get_node_vars(flux_viscous_y, equations_parabolic, dg, i, j, k, - element) - flux3 = get_node_vars(flux_viscous_z, equations_parabolic, dg, i, j, k, - element) - - # Compute the contravariant flux by taking the scalar product of the - # first contravariant vector Ja^1 and the flux vector - Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors, i, j, - k, - element) - contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 + Ja13 * flux3 - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[ii, i], - contravariant_flux1, - equations_parabolic, dg, ii, j, k, element) - end - # Compute the contravariant flux by taking the scalar product of the - # second contravariant vector Ja^2 and the flux vector - Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors, i, j, - k, - element) - contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 + Ja23 * flux3 - for jj in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[jj, j], - contravariant_flux2, - equations_parabolic, dg, i, jj, k, element) - end + # This is the version used when calculating the divergence of the viscous fluxes + function calc_volume_integral!( + du, flux_viscous, + mesh::P4estMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + dg::DGSEM, cache + ) + (; derivative_dhat) = dg.basis + (; contravariant_vectors) = cache.elements + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for element in eachelement(dg, cache) + # Calculate volume terms in one element + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + flux1 = get_node_vars( + flux_viscous_x, equations_parabolic, dg, i, j, k, + element + ) + flux2 = get_node_vars( + flux_viscous_y, equations_parabolic, dg, i, j, k, + element + ) + flux3 = get_node_vars( + flux_viscous_z, equations_parabolic, dg, i, j, k, + element + ) + + # Compute the contravariant flux by taking the scalar product of the + # first contravariant vector Ja^1 and the flux vector + Ja11, Ja12, Ja13 = get_contravariant_vector( + 1, contravariant_vectors, i, j, + k, + element + ) + contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 + Ja13 * flux3 + for ii in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[ii, i], + contravariant_flux1, + equations_parabolic, dg, ii, j, k, element + ) + end - # Compute the contravariant flux by taking the scalar product of the - # second contravariant vector Ja^2 and the flux vector - Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors, i, j, - k, - element) - contravariant_flux3 = Ja31 * flux1 + Ja32 * flux2 + Ja33 * flux3 - for kk in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[kk, k], - contravariant_flux3, - equations_parabolic, dg, i, j, kk, element) + # Compute the contravariant flux by taking the scalar product of the + # second contravariant vector Ja^2 and the flux vector + Ja21, Ja22, Ja23 = get_contravariant_vector( + 2, contravariant_vectors, i, j, + k, + element + ) + contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 + Ja23 * flux3 + for jj in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[jj, j], + contravariant_flux2, + equations_parabolic, dg, i, jj, k, element + ) + end + + # Compute the contravariant flux by taking the scalar product of the + # second contravariant vector Ja^2 and the flux vector + Ja31, Ja32, Ja33 = get_contravariant_vector( + 3, contravariant_vectors, i, j, + k, + element + ) + contravariant_flux3 = Ja31 * flux1 + Ja32 * flux2 + Ja33 * flux3 + for kk in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[kk, k], + contravariant_flux3, + equations_parabolic, dg, i, j, kk, element + ) + end end end + + return nothing end - return nothing -end - -# This is the version used when calculating the divergence of the viscous fluxes -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache_parabolic, flux_viscous, - mesh::P4estMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - (; interfaces) = cache_parabolic - (; contravariant_vectors) = cache_parabolic.elements - index_range = eachnode(dg) - flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous - - @threaded for interface in eachinterface(dg, cache) - # Copy solution data from the primary element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - # Note that in the current implementation, the interface will be - # "aligned at the primary element", i.e., the index of the primary side - # will always run forwards. - primary_element = interfaces.neighbor_ids[1, interface] - primary_indices = interfaces.node_indices[1, interface] - primary_direction = indices2direction(primary_indices) - - i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1], - index_range) - j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2], - index_range) - k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - k_primary = k_primary_start + # This is the version used when calculating the divergence of the viscous fluxes + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache_parabolic, flux_viscous, + mesh::P4estMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + (; interfaces) = cache_parabolic + (; contravariant_vectors) = cache_parabolic.elements + index_range = eachnode(dg) + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for interface in eachinterface(dg, cache) + # Copy solution data from the primary element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + # Note that in the current implementation, the interface will be + # "aligned at the primary element", i.e., the index of the primary side + # will always run forwards. + primary_element = interfaces.neighbor_ids[1, interface] + primary_indices = interfaces.node_indices[1, interface] + primary_direction = indices2direction(primary_indices) + + i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d( + primary_indices[2], + index_range + ) + k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d( + primary_indices[3], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + k_primary = k_primary_start - for j in eachnode(dg) - for i in eachnode(dg) - # this is the outward normal direction on the primary element - normal_direction = get_normal_direction(primary_direction, - contravariant_vectors, - i_primary, j_primary, k_primary, - primary_element) - - for v in eachvariable(equations_parabolic) - # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! - flux_viscous = SVector(flux_viscous_x[v, i_primary, j_primary, - k_primary, - primary_element], - flux_viscous_y[v, i_primary, j_primary, - k_primary, - primary_element], - flux_viscous_z[v, i_primary, j_primary, - k_primary, - primary_element]) - - interfaces.u[1, v, i, j, interface] = dot(flux_viscous, - normal_direction) + for j in eachnode(dg) + for i in eachnode(dg) + # this is the outward normal direction on the primary element + normal_direction = get_normal_direction( + primary_direction, + contravariant_vectors, + i_primary, j_primary, k_primary, + primary_element + ) + + for v in eachvariable(equations_parabolic) + # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! + flux_viscous = SVector( + flux_viscous_x[ + v, i_primary, j_primary, + k_primary, + primary_element, + ], + flux_viscous_y[ + v, i_primary, j_primary, + k_primary, + primary_element, + ], + flux_viscous_z[ + v, i_primary, j_primary, + k_primary, + primary_element, + ] + ) + + interfaces.u[1, v, i, j, interface] = dot( + flux_viscous, + normal_direction + ) + end + i_primary += i_primary_step_i + j_primary += j_primary_step_i + k_primary += k_primary_step_i end - i_primary += i_primary_step_i - j_primary += j_primary_step_i - k_primary += k_primary_step_i + i_primary += i_primary_step_j + j_primary += j_primary_step_j + k_primary += k_primary_step_j end - i_primary += i_primary_step_j - j_primary += j_primary_step_j - k_primary += k_primary_step_j - end - # Copy solution data from the secondary element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - secondary_element = interfaces.neighbor_ids[2, interface] - secondary_indices = interfaces.node_indices[2, interface] - secondary_direction = indices2direction(secondary_indices) - - i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_indices[1], - index_range) - j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_indices[2], - index_range) - k_secondary_start, k_secondary_step_i, k_secondary_step_j = index_to_start_step_3d(secondary_indices[3], - index_range) - - i_secondary = i_secondary_start - j_secondary = j_secondary_start - k_secondary = k_secondary_start - for j in eachnode(dg) - for i in eachnode(dg) - # This is the outward normal direction on the secondary element. - # Here, we assume that normal_direction on the secondary element is - # the negative of normal_direction on the primary element. - normal_direction = get_normal_direction(secondary_direction, - contravariant_vectors, - i_secondary, j_secondary, - k_secondary, - secondary_element) - - for v in eachvariable(equations_parabolic) - # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! - flux_viscous = SVector(flux_viscous_x[v, i_secondary, j_secondary, - k_secondary, - secondary_element], - flux_viscous_y[v, i_secondary, j_secondary, - k_secondary, - secondary_element], - flux_viscous_z[v, i_secondary, j_secondary, - k_secondary, - secondary_element]) - # store the normal flux with respect to the primary normal direction - interfaces.u[2, v, i, j, interface] = -dot(flux_viscous, - normal_direction) + # Copy solution data from the secondary element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + secondary_element = interfaces.neighbor_ids[2, interface] + secondary_indices = interfaces.node_indices[2, interface] + secondary_direction = indices2direction(secondary_indices) + + i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d( + secondary_indices[1], + index_range + ) + j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d( + secondary_indices[2], + index_range + ) + k_secondary_start, k_secondary_step_i, k_secondary_step_j = index_to_start_step_3d( + secondary_indices[3], + index_range + ) + + i_secondary = i_secondary_start + j_secondary = j_secondary_start + k_secondary = k_secondary_start + for j in eachnode(dg) + for i in eachnode(dg) + # This is the outward normal direction on the secondary element. + # Here, we assume that normal_direction on the secondary element is + # the negative of normal_direction on the primary element. + normal_direction = get_normal_direction( + secondary_direction, + contravariant_vectors, + i_secondary, j_secondary, + k_secondary, + secondary_element + ) + + for v in eachvariable(equations_parabolic) + # OBS! `interfaces.u` stores the interpolated *fluxes* and *not the solution*! + flux_viscous = SVector( + flux_viscous_x[ + v, i_secondary, j_secondary, + k_secondary, + secondary_element, + ], + flux_viscous_y[ + v, i_secondary, j_secondary, + k_secondary, + secondary_element, + ], + flux_viscous_z[ + v, i_secondary, j_secondary, + k_secondary, + secondary_element, + ] + ) + # store the normal flux with respect to the primary normal direction + interfaces.u[2, v, i, j, interface] = -dot( + flux_viscous, + normal_direction + ) + end + i_secondary += i_secondary_step_i + j_secondary += j_secondary_step_i + k_secondary += k_secondary_step_i end - i_secondary += i_secondary_step_i - j_secondary += j_secondary_step_i - k_secondary += k_secondary_step_i + i_secondary += i_secondary_step_j + j_secondary += j_secondary_step_j + k_secondary += k_secondary_step_j end - i_secondary += i_secondary_step_j - j_secondary += j_secondary_step_j - k_secondary += k_secondary_step_j end + + return nothing end - return nothing -end - -# This version is used for divergence flux computations -function calc_interface_flux!(surface_flux_values, - mesh::P4estMesh{3}, equations_parabolic, - dg::DG, cache_parabolic) - (; neighbor_ids, node_indices) = cache_parabolic.interfaces - index_range = eachnode(dg) - - @threaded for interface in eachinterface(dg, cache_parabolic) - # Get element and side index information on the primary element - primary_element = neighbor_ids[1, interface] - primary_indices = node_indices[1, interface] - primary_direction_index = indices2direction(primary_indices) - - i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d(primary_indices[1], - index_range) - j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d(primary_indices[2], - index_range) - k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d(primary_indices[3], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - k_primary = k_primary_start - - # Get element and side index information on the secondary element - secondary_element = neighbor_ids[2, interface] - secondary_indices = node_indices[2, interface] - secondary_direction_index = indices2direction(secondary_indices) - secondary_surface_indices = surface_indices(secondary_indices) - - # Initiate the secondary index to be used in the surface for loop. - # This index on the primary side will always run forward but - # the secondary index might need to run backwards for flipped sides. - # Get the surface indexing on the secondary element. - # Note that the indices of the primary side will always run forward but - # the secondary indices might need to run backwards for flipped sides. - i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[1], - index_range) - j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d(secondary_surface_indices[2], - index_range) - i_secondary = i_secondary_start - j_secondary = j_secondary_start + # This version is used for divergence flux computations + function calc_interface_flux!( + surface_flux_values, + mesh::P4estMesh{3}, equations_parabolic, + dg::DG, cache_parabolic + ) + (; neighbor_ids, node_indices) = cache_parabolic.interfaces + index_range = eachnode(dg) + + @threaded for interface in eachinterface(dg, cache_parabolic) + # Get element and side index information on the primary element + primary_element = neighbor_ids[1, interface] + primary_indices = node_indices[1, interface] + primary_direction_index = indices2direction(primary_indices) + + i_primary_start, i_primary_step_i, i_primary_step_j = index_to_start_step_3d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step_i, j_primary_step_j = index_to_start_step_3d( + primary_indices[2], + index_range + ) + k_primary_start, k_primary_step_i, k_primary_step_j = index_to_start_step_3d( + primary_indices[3], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + k_primary = k_primary_start + + # Get element and side index information on the secondary element + secondary_element = neighbor_ids[2, interface] + secondary_indices = node_indices[2, interface] + secondary_direction_index = indices2direction(secondary_indices) + secondary_surface_indices = surface_indices(secondary_indices) + + # Initiate the secondary index to be used in the surface for loop. + # This index on the primary side will always run forward but + # the secondary index might need to run backwards for flipped sides. + # Get the surface indexing on the secondary element. + # Note that the indices of the primary side will always run forward but + # the secondary indices might need to run backwards for flipped sides. + i_secondary_start, i_secondary_step_i, i_secondary_step_j = index_to_start_step_3d( + secondary_surface_indices[1], + index_range + ) + j_secondary_start, j_secondary_step_i, j_secondary_step_j = index_to_start_step_3d( + secondary_surface_indices[2], + index_range + ) + i_secondary = i_secondary_start + j_secondary = j_secondary_start - for j in eachnode(dg) - for i in eachnode(dg) - # We prolong the viscous flux dotted with respect the outward normal on the - # primary element. We assume a BR-1 type of flux. - viscous_flux_normal_ll, viscous_flux_normal_rr = get_surface_node_vars(cache_parabolic.interfaces.u, - equations_parabolic, - dg, - i, - j, - interface) - - flux = 0.5f0 * (viscous_flux_normal_ll + viscous_flux_normal_rr) - - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, j, primary_direction_index, primary_element] = flux[v] - surface_flux_values[v, i_secondary, j_secondary, secondary_direction_index, secondary_element] = -flux[v] - end + for j in eachnode(dg) + for i in eachnode(dg) + # We prolong the viscous flux dotted with respect the outward normal on the + # primary element. We assume a BR-1 type of flux. + viscous_flux_normal_ll, viscous_flux_normal_rr = get_surface_node_vars( + cache_parabolic.interfaces.u, + equations_parabolic, + dg, + i, + j, + interface + ) + + flux = 0.5f0 * (viscous_flux_normal_ll + viscous_flux_normal_rr) + + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, j, primary_direction_index, primary_element] = flux[v] + surface_flux_values[v, i_secondary, j_secondary, secondary_direction_index, secondary_element] = -flux[v] + end + # Increment the primary element indices + i_primary += i_primary_step_i + j_primary += j_primary_step_i + k_primary += k_primary_step_i + # Increment the secondary element surface indices + i_secondary += i_secondary_step_i + j_secondary += j_secondary_step_i + end # Increment the primary element indices - i_primary += i_primary_step_i - j_primary += j_primary_step_i - k_primary += k_primary_step_i + i_primary += i_primary_step_j + j_primary += j_primary_step_j + k_primary += k_primary_step_j # Increment the secondary element surface indices - i_secondary += i_secondary_step_i - j_secondary += j_secondary_step_i + i_secondary += i_secondary_step_j + j_secondary += j_secondary_step_j end - # Increment the primary element indices - i_primary += i_primary_step_j - j_primary += j_primary_step_j - k_primary += k_primary_step_j - # Increment the secondary element surface indices - i_secondary += i_secondary_step_j - j_secondary += j_secondary_step_j end - end - - return nothing -end - -function prolong2mortars_divergence!(cache, flux_viscous, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DGSEM) - @unpack neighbor_ids, node_indices = cache.mortars - @unpack fstar_tmp_threaded = cache - @unpack contravariant_vectors = cache.elements - index_range = eachnode(dg) - flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous - - @threaded for mortar in eachmortar(dg, cache) - # Copy solution data from the small elements using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - small_indices = node_indices[1, mortar] - direction_index = indices2direction(small_indices) + return nothing + end - i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1], - index_range) - j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2], - index_range) - k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3], - index_range) + function prolong2mortars_divergence!( + cache, flux_viscous, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DGSEM + ) + @unpack neighbor_ids, node_indices = cache.mortars + @unpack fstar_tmp_threaded = cache + @unpack contravariant_vectors = cache.elements + index_range = eachnode(dg) + + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for mortar in eachmortar(dg, cache) + # Copy solution data from the small elements using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + small_indices = node_indices[1, mortar] + direction_index = indices2direction(small_indices) + + i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d( + small_indices[1], + index_range + ) + j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d( + small_indices[2], + index_range + ) + k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d( + small_indices[3], + index_range + ) + + for position in 1:4 # Loop over small elements + i_small = i_small_start + j_small = j_small_start + k_small = k_small_start + element = neighbor_ids[position, mortar] + for j in eachnode(dg) + for i in eachnode(dg) + normal_direction = get_normal_direction( + direction_index, + contravariant_vectors, + i_small, j_small, k_small, + element + ) + + for v in eachvariable(equations) + flux_viscous = SVector( + flux_viscous_x[ + v, i_small, j_small, + k_small, + element, + ], + flux_viscous_y[ + v, i_small, j_small, + k_small, + element, + ], + flux_viscous_z[ + v, i_small, j_small, + k_small, + element, + ] + ) + + cache.mortars.u[1, v, position, i, j, mortar] = dot( + flux_viscous, + normal_direction + ) + end + i_small += i_small_step_i + j_small += j_small_step_i + k_small += k_small_step_i + end + i_small += i_small_step_j + j_small += j_small_step_j + k_small += k_small_step_j + end + end - for position in 1:4 # Loop over small elements - i_small = i_small_start - j_small = j_small_start - k_small = k_small_start - element = neighbor_ids[position, mortar] + # Buffer to copy solution values of the large element in the correct orientation + # before interpolating + u_buffer = cache.u_threaded[Threads.threadid()] + + # temporary buffer for projections + fstar_tmp = fstar_tmp_threaded[Threads.threadid()] + + # Copy solution of large element face to buffer in the + # correct orientation + large_indices = node_indices[2, mortar] + + i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d( + large_indices[1], + index_range + ) + j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d( + large_indices[2], + index_range + ) + k_large_start, k_large_step_i, k_large_step_j = index_to_start_step_3d( + large_indices[3], + index_range + ) + + i_large = i_large_start + j_large = j_large_start + k_large = k_large_start + element = neighbor_ids[5, mortar] # Large element for j in eachnode(dg) for i in eachnode(dg) - normal_direction = get_normal_direction(direction_index, - contravariant_vectors, - i_small, j_small, k_small, - element) + normal_direction = get_normal_direction( + direction_index, + contravariant_vectors, + i_large, j_large, k_large, + element + ) for v in eachvariable(equations) - flux_viscous = SVector(flux_viscous_x[v, i_small, j_small, - k_small, - element], - flux_viscous_y[v, i_small, j_small, - k_small, - element], - flux_viscous_z[v, i_small, j_small, - k_small, - element]) - - cache.mortars.u[1, v, position, i, j, mortar] = dot(flux_viscous, - normal_direction) + flux_viscous = SVector( + flux_viscous_x[ + v, i_large, j_large, k_large, + element, + ], + flux_viscous_y[ + v, i_large, j_large, k_large, + element, + ], + flux_viscous_z[ + v, i_large, j_large, k_large, + element, + ] + ) + + # We prolong the viscous flux dotted with respect the outward normal + # on the small element. We scale by -1/2 here because the normal + # direction on the large element is negative 2x that of the small + # element (these normal directions are "scaled" by the surface Jacobian) + u_buffer[v, i, j] = -0.5f0 * dot(flux_viscous, normal_direction) end - i_small += i_small_step_i - j_small += j_small_step_i - k_small += k_small_step_i + i_large += i_large_step_i + j_large += j_large_step_i + k_large += k_large_step_i end - i_small += i_small_step_j - j_small += j_small_step_j - k_small += k_small_step_j + i_large += i_large_step_j + j_large += j_large_step_j + k_large += k_large_step_j end - end - # Buffer to copy solution values of the large element in the correct orientation - # before interpolating - u_buffer = cache.u_threaded[Threads.threadid()] - - # temporary buffer for projections - fstar_tmp = fstar_tmp_threaded[Threads.threadid()] - - # Copy solution of large element face to buffer in the - # correct orientation - large_indices = node_indices[2, mortar] - - i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_indices[1], - index_range) - j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_indices[2], - index_range) - k_large_start, k_large_step_i, k_large_step_j = index_to_start_step_3d(large_indices[3], - index_range) + # Interpolate large element face data from buffer to small face locations + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 1, :, :, mortar), + mortar_l2.forward_lower, + mortar_l2.forward_lower, + u_buffer, + fstar_tmp + ) + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 2, :, :, mortar), + mortar_l2.forward_upper, + mortar_l2.forward_lower, + u_buffer, + fstar_tmp + ) + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 3, :, :, mortar), + mortar_l2.forward_lower, + mortar_l2.forward_upper, + u_buffer, + fstar_tmp + ) + multiply_dimensionwise!( + view(cache.mortars.u, 2, :, 4, :, :, mortar), + mortar_l2.forward_upper, + mortar_l2.forward_upper, + u_buffer, + fstar_tmp + ) + end - i_large = i_large_start - j_large = j_large_start - k_large = k_large_start - element = neighbor_ids[5, mortar] # Large element - for j in eachnode(dg) - for i in eachnode(dg) - normal_direction = get_normal_direction(direction_index, - contravariant_vectors, - i_large, j_large, k_large, - element) + return nothing + end - for v in eachvariable(equations) - flux_viscous = SVector(flux_viscous_x[v, i_large, j_large, k_large, - element], - flux_viscous_y[v, i_large, j_large, k_large, - element], - flux_viscous_z[v, i_large, j_large, k_large, - element]) - - # We prolong the viscous flux dotted with respect the outward normal - # on the small element. We scale by -1/2 here because the normal - # direction on the large element is negative 2x that of the small - # element (these normal directions are "scaled" by the surface Jacobian) - u_buffer[v, i, j] = -0.5f0 * dot(flux_viscous, normal_direction) + # We specialize `calc_mortar_flux!` for the divergence part of + # the parabolic terms. + function calc_mortar_flux_divergence!( + surface_flux_values, + mesh::Union{P4estMesh{3}, T8codeMesh{3}}, + equations::AbstractEquationsParabolic, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack neighbor_ids, node_indices = cache.mortars + @unpack contravariant_vectors = cache.elements + @unpack fstar_threaded, fstar_tmp_threaded = cache + index_range = eachnode(dg) + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar = fstar_threaded[Threads.threadid()] + fstar_tmp = fstar_tmp_threaded[Threads.threadid()] + + # Get index information on the small elements + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) + + i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d( + small_indices[1], + index_range + ) + j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d( + small_indices[2], + index_range + ) + k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d( + small_indices[3], + index_range + ) + + for position in 1:4 # Loop over small elements + i_small = i_small_start + j_small = j_small_start + k_small = k_small_start + element = neighbor_ids[position, mortar] + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + viscous_flux_normal_ll = cache.mortars.u[ + 1, v, position, i, j, + mortar, + ] + viscous_flux_normal_rr = cache.mortars.u[ + 2, v, position, i, j, + mortar, + ] + + # TODO: parabolic; only BR1 at the moment + fstar[v, i, j, position] = 0.5f0 * ( + viscous_flux_normal_ll + + viscous_flux_normal_rr + ) + end + + i_small += i_small_step_i + j_small += j_small_step_i + k_small += k_small_step_i + end + i_small += i_small_step_j + j_small += j_small_step_j + k_small += k_small_step_j end - i_large += i_large_step_i - j_large += j_large_step_i - k_large += k_large_step_i end - i_large += i_large_step_j - j_large += j_large_step_j - k_large += k_large_step_j + + # Buffer to interpolate flux values of the large element to before + # copying in the correct orientation + u_buffer = cache.u_threaded[Threads.threadid()] + + # this reuses the hyperbolic version of `mortar_fluxes_to_elements!` + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, + mortar, fstar, u_buffer, fstar_tmp + ) end - # Interpolate large element face data from buffer to small face locations - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 1, :, :, mortar), - mortar_l2.forward_lower, - mortar_l2.forward_lower, - u_buffer, - fstar_tmp) - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 2, :, :, mortar), - mortar_l2.forward_upper, - mortar_l2.forward_lower, - u_buffer, - fstar_tmp) - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 3, :, :, mortar), - mortar_l2.forward_lower, - mortar_l2.forward_upper, - u_buffer, - fstar_tmp) - multiply_dimensionwise!(view(cache.mortars.u, 2, :, 4, :, :, mortar), - mortar_l2.forward_upper, - mortar_l2.forward_upper, - u_buffer, - fstar_tmp) + return nothing end - return nothing -end - -# We specialize `calc_mortar_flux!` for the divergence part of -# the parabolic terms. -function calc_mortar_flux_divergence!(surface_flux_values, - mesh::Union{P4estMesh{3}, T8codeMesh{3}}, - equations::AbstractEquationsParabolic, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack neighbor_ids, node_indices = cache.mortars - @unpack contravariant_vectors = cache.elements - @unpack fstar_threaded, fstar_tmp_threaded = cache - index_range = eachnode(dg) - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar = fstar_threaded[Threads.threadid()] - fstar_tmp = fstar_tmp_threaded[Threads.threadid()] - - # Get index information on the small elements - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) + # NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms. + # Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for + # hyperbolic terms with conserved terms only, i.e., no nonconservative terms. + @inline function calc_mortar_flux!( + fstar, + mesh::P4estMesh{3}, + nonconservative_terms::False, + equations::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + mortar_index, position_index, normal_direction, + i_node_index, j_node_index + ) + @unpack u = cache.mortars + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, position_index, i_node_index, + j_node_index, mortar_index + ) + + # TODO: parabolic; only BR1 at the moment + flux_ = 0.5f0 * (u_ll + u_rr) + # Copy flux to buffer + set_node_vars!( + fstar, flux_, equations, dg, i_node_index, j_node_index, + position_index + ) + end - i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1], - index_range) - j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2], - index_range) - k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3], - index_range) + # TODO: parabolic, finish implementing `calc_boundary_flux_gradients!` and `calc_boundary_flux_divergence!` + function prolong2boundaries!( + cache_parabolic, flux_viscous, + mesh::P4estMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + (; boundaries) = cache_parabolic + (; contravariant_vectors) = cache_parabolic.elements + index_range = eachnode(dg) + + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for boundary in eachboundary(dg, cache_parabolic) + # Copy solution data from the element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d( + node_indices[1], + index_range + ) + j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d( + node_indices[2], + index_range + ) + k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d( + node_indices[3], + index_range + ) + + i_node = i_node_start + j_node = j_node_start + k_node = k_node_start - for position in 1:4 # Loop over small elements - i_small = i_small_start - j_small = j_small_start - k_small = k_small_start - element = neighbor_ids[position, mortar] for j in eachnode(dg) for i in eachnode(dg) - for v in eachvariable(equations) - viscous_flux_normal_ll = cache.mortars.u[1, v, position, i, j, - mortar] - viscous_flux_normal_rr = cache.mortars.u[2, v, position, i, j, - mortar] - - # TODO: parabolic; only BR1 at the moment - fstar[v, i, j, position] = 0.5f0 * (viscous_flux_normal_ll + - viscous_flux_normal_rr) + # this is the outward normal direction on the primary element + normal_direction = get_normal_direction( + direction, + contravariant_vectors, + i_node, j_node, k_node, element + ) + + for v in eachvariable(equations_parabolic) + flux_viscous = SVector( + flux_viscous_x[ + v, i_node, j_node, k_node, + element, + ], + flux_viscous_y[ + v, i_node, j_node, k_node, + element, + ], + flux_viscous_z[ + v, i_node, j_node, k_node, + element, + ] + ) + + boundaries.u[v, i, j, boundary] = dot( + flux_viscous, + normal_direction + ) end - - i_small += i_small_step_i - j_small += j_small_step_i - k_small += k_small_step_i + i_node += i_node_step_i + j_node += j_node_step_i + k_node += k_node_step_i end - i_small += i_small_step_j - j_small += j_small_step_j - k_small += k_small_step_j + i_node += i_node_step_j + j_node += j_node_step_j + k_node += k_node_step_j end end - - # Buffer to interpolate flux values of the large element to before - # copying in the correct orientation - u_buffer = cache.u_threaded[Threads.threadid()] - - # this reuses the hyperbolic version of `mortar_fluxes_to_elements!` - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, - mortar, fstar, u_buffer, fstar_tmp) + return nothing end - return nothing -end - -# NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms. -# Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for -# hyperbolic terms with conserved terms only, i.e., no nonconservative terms. -@inline function calc_mortar_flux!(fstar, - mesh::P4estMesh{3}, - nonconservative_terms::False, - equations::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - mortar_index, position_index, normal_direction, - i_node_index, j_node_index) - @unpack u = cache.mortars - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, i_node_index, - j_node_index, mortar_index) - - # TODO: parabolic; only BR1 at the moment - flux_ = 0.5f0 * (u_ll + u_rr) - # Copy flux to buffer - set_node_vars!(fstar, flux_, equations, dg, i_node_index, j_node_index, - position_index) -end - -# TODO: parabolic, finish implementing `calc_boundary_flux_gradients!` and `calc_boundary_flux_divergence!` -function prolong2boundaries!(cache_parabolic, flux_viscous, - mesh::P4estMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - (; boundaries) = cache_parabolic - (; contravariant_vectors) = cache_parabolic.elements - index_range = eachnode(dg) - - flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous - - @threaded for boundary in eachboundary(dg, cache_parabolic) - # Copy solution data from the element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - direction = indices2direction(node_indices) - - i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1], - index_range) - j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2], - index_range) - k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3], - index_range) - - i_node = i_node_start - j_node = j_node_start - k_node = k_node_start + function calc_boundary_flux!( + cache, t, + boundary_condition_parabolic, # works with Dict types + boundary_condition_indices, + operator_type, mesh::P4estMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + (; boundaries) = cache + (; node_coordinates, surface_flux_values) = cache.elements + (; contravariant_vectors) = cache.elements + index_range = eachnode(dg) + + @threaded for local_index in eachindex(boundary_condition_indices) + # Use the local index to get the global boundary index from the pre-sorted list + boundary_index = boundary_condition_indices[local_index] + + # Get information on the adjacent element, compute the surface fluxes, + # and store them + element = boundaries.neighbor_ids[boundary_index] + node_indices = boundaries.node_indices[boundary_index] + direction_index = indices2direction(node_indices) + + i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d( + node_indices[1], + index_range + ) + j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d( + node_indices[2], + index_range + ) + k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d( + node_indices[3], + index_range + ) + + i_node = i_node_start + j_node = j_node_start + k_node = k_node_start - for j in eachnode(dg) - for i in eachnode(dg) - # this is the outward normal direction on the primary element - normal_direction = get_normal_direction(direction, - contravariant_vectors, - i_node, j_node, k_node, element) - - for v in eachvariable(equations_parabolic) - flux_viscous = SVector(flux_viscous_x[v, i_node, j_node, k_node, - element], - flux_viscous_y[v, i_node, j_node, k_node, - element], - flux_viscous_z[v, i_node, j_node, k_node, - element]) - - boundaries.u[v, i, j, boundary] = dot(flux_viscous, - normal_direction) - end - i_node += i_node_step_i - j_node += j_node_step_i - k_node += k_node_step_i - end - i_node += i_node_step_j - j_node += j_node_step_j - k_node += k_node_step_j - end - end - return nothing -end - -function calc_boundary_flux!(cache, t, - boundary_condition_parabolic, # works with Dict types - boundary_condition_indices, - operator_type, mesh::P4estMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - (; boundaries) = cache - (; node_coordinates, surface_flux_values) = cache.elements - (; contravariant_vectors) = cache.elements - index_range = eachnode(dg) - - @threaded for local_index in eachindex(boundary_condition_indices) - # Use the local index to get the global boundary index from the pre-sorted list - boundary_index = boundary_condition_indices[local_index] - - # Get information on the adjacent element, compute the surface fluxes, - # and store them - element = boundaries.neighbor_ids[boundary_index] - node_indices = boundaries.node_indices[boundary_index] - direction_index = indices2direction(node_indices) - - i_node_start, i_node_step_i, i_node_step_j = index_to_start_step_3d(node_indices[1], - index_range) - j_node_start, j_node_step_i, j_node_step_j = index_to_start_step_3d(node_indices[2], - index_range) - k_node_start, k_node_step_i, k_node_step_j = index_to_start_step_3d(node_indices[3], - index_range) - - i_node = i_node_start - j_node = j_node_start - k_node = k_node_start + for j in eachnode(dg) + for i in eachnode(dg) + # Extract solution data from boundary container + u_inner = get_node_vars( + boundaries.u, equations_parabolic, dg, i, j, + boundary_index + ) + + # Outward-pointing normal direction (not normalized) + normal_direction = get_normal_direction( + direction_index, + contravariant_vectors, + i_node, j_node, k_node, element + ) + + # TODO: revisit if we want more general boundary treatments. + # This assumes the gradient numerical flux at the boundary is the gradient variable, + # which is consistent with BR1, LDG. + flux_inner = u_inner + + # Coordinates at boundary node + x = get_node_coords( + node_coordinates, equations_parabolic, dg, i_node, + j_node, k_node, + element + ) + + flux_ = boundary_condition_parabolic( + flux_inner, u_inner, + normal_direction, + x, t, operator_type, + equations_parabolic + ) + + # Copy flux to element storage in the correct orientation + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, j, direction_index, element] = flux_[v] + end - for j in eachnode(dg) - for i in eachnode(dg) - # Extract solution data from boundary container - u_inner = get_node_vars(boundaries.u, equations_parabolic, dg, i, j, - boundary_index) - - # Outward-pointing normal direction (not normalized) - normal_direction = get_normal_direction(direction_index, - contravariant_vectors, - i_node, j_node, k_node, element) - - # TODO: revisit if we want more general boundary treatments. - # This assumes the gradient numerical flux at the boundary is the gradient variable, - # which is consistent with BR1, LDG. - flux_inner = u_inner - - # Coordinates at boundary node - x = get_node_coords(node_coordinates, equations_parabolic, dg, i_node, - j_node, k_node, - element) - - flux_ = boundary_condition_parabolic(flux_inner, u_inner, - normal_direction, - x, t, operator_type, - equations_parabolic) - - # Copy flux to element storage in the correct orientation - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, j, direction_index, element] = flux_[v] + i_node += i_node_step_i + j_node += j_node_step_i + k_node += k_node_step_i end - - i_node += i_node_step_i - j_node += j_node_step_i - k_node += k_node_step_i + i_node += i_node_step_j + j_node += j_node_step_j + k_node += k_node_step_j end - i_node += i_node_step_j - j_node += j_node_step_j - k_node += k_node_step_j end end -end -function apply_jacobian_parabolic!(du, mesh::P4estMesh{3}, - equations::AbstractEquationsParabolic, - dg::DG, cache) - @unpack inverse_jacobian = cache.elements + function apply_jacobian_parabolic!( + du, mesh::P4estMesh{3}, + equations::AbstractEquationsParabolic, + dg::DG, cache + ) + @unpack inverse_jacobian = cache.elements - @threaded for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - factor = inverse_jacobian[i, j, k, element] + @threaded for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + factor = inverse_jacobian[i, j, k, element] - for v in eachvariable(equations) - du[v, i, j, k, element] *= factor + for v in eachvariable(equations) + du[v, i, j, k, element] *= factor + end end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_p4est/dg_3d_parallel.jl b/src/solvers/dgsem_p4est/dg_3d_parallel.jl index e504e06d2c..1e94009efe 100644 --- a/src/solvers/dgsem_p4est/dg_3d_parallel.jl +++ b/src/solvers/dgsem_p4est/dg_3d_parallel.jl @@ -3,549 +3,681 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function rhs!(du, u, t, - mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, equations, - initial_condition, boundary_conditions, source_terms::Source, - dg::DG, cache) where {Source} - # Start to receive MPI data - @trixi_timeit timer() "start MPI receive" start_mpi_receive!(cache.mpi_cache) - - # Prolong solution to MPI interfaces - @trixi_timeit timer() "prolong2mpiinterfaces" begin - prolong2mpiinterfaces!(cache, u, mesh, equations, dg.surface_integral, dg) - end + #! format: noindent + + function rhs!( + du, u, t, + mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, equations, + initial_condition, boundary_conditions, source_terms::Source, + dg::DG, cache + ) where {Source} + # Start to receive MPI data + @trixi_timeit timer() "start MPI receive" start_mpi_receive!(cache.mpi_cache) + + # Prolong solution to MPI interfaces + @trixi_timeit timer() "prolong2mpiinterfaces" begin + prolong2mpiinterfaces!(cache, u, mesh, equations, dg.surface_integral, dg) + end - # Prolong solution to MPI mortars - @trixi_timeit timer() "prolong2mpimortars" begin - prolong2mpimortars!(cache, u, mesh, equations, - dg.mortar, dg.surface_integral, dg) - end + # Prolong solution to MPI mortars + @trixi_timeit timer() "prolong2mpimortars" begin + prolong2mpimortars!( + cache, u, mesh, equations, + dg.mortar, dg.surface_integral, dg + ) + end - # Start to send MPI data - @trixi_timeit timer() "start MPI send" begin - start_mpi_send!(cache.mpi_cache, mesh, equations, dg, cache) - end + # Start to send MPI data + @trixi_timeit timer() "start MPI send" begin + start_mpi_send!(cache.mpi_cache, mesh, equations, dg, cache) + end - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) - end + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache, u, mesh, equations, dg.surface_integral, dg) - end + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!(cache, u, mesh, equations, dg.surface_integral, dg) + end - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg, cache) - end + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg, cache + ) + end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache, u, mesh, equations, dg.surface_integral, dg) - end + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!(cache, u, mesh, equations, dg.surface_integral, dg) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations, - dg.surface_integral, dg) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, t, boundary_conditions, mesh, equations, + dg.surface_integral, dg + ) + end - # Prolong solution to mortars - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u, mesh, equations, - dg.mortar, dg.surface_integral, dg) - end + # Prolong solution to mortars + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!( + cache, u, mesh, equations, + dg.mortar, dg.surface_integral, dg + ) + end - # Calculate mortar fluxes - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.mortar, dg.surface_integral, dg, cache) - end + # Calculate mortar fluxes + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.mortar, dg.surface_integral, dg, cache + ) + end - # Finish to receive MPI data - @trixi_timeit timer() "finish MPI receive" begin - finish_mpi_receive!(cache.mpi_cache, mesh, equations, dg, cache) - end + # Finish to receive MPI data + @trixi_timeit timer() "finish MPI receive" begin + finish_mpi_receive!(cache.mpi_cache, mesh, equations, dg, cache) + end - # Calculate MPI interface fluxes - @trixi_timeit timer() "MPI interface flux" begin - calc_mpi_interface_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg, cache) - end + # Calculate MPI interface fluxes + @trixi_timeit timer() "MPI interface flux" begin + calc_mpi_interface_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg, cache + ) + end - # Calculate MPI mortar fluxes - @trixi_timeit timer() "MPI mortar flux" begin - calc_mpi_mortar_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.mortar, dg.surface_integral, dg, cache) - end + # Calculate MPI mortar fluxes + @trixi_timeit timer() "MPI mortar flux" begin + calc_mpi_mortar_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.mortar, dg.surface_integral, dg, cache + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, dg.surface_integral, dg, cache) - end + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!(du, u, mesh, equations, dg.surface_integral, dg, cache) + end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + + # Calculate source terms + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, equations, dg, cache) + end - # Calculate source terms - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, equations, dg, cache) + # Finish to send MPI data + @trixi_timeit timer() "finish MPI send" finish_mpi_send!(cache.mpi_cache) + + return nothing end - # Finish to send MPI data - @trixi_timeit timer() "finish MPI send" finish_mpi_send!(cache.mpi_cache) - - return nothing -end - -function prolong2mpiinterfaces!(cache, u, - mesh::Union{ParallelP4estMesh{3}, - ParallelT8codeMesh{3}}, - equations, surface_integral, dg::DG) - @unpack mpi_interfaces = cache - index_range = eachnode(dg) - - @threaded for interface in eachmpiinterface(dg, cache) - # Copy solution data from the local element using "delayed indexing" with - # a start value and a step size to get the correct face and orientation. - # Note that in the current implementation, the interface will be - # "aligned at the primary element", i.e., the index of the primary side - # will always run forwards. - local_side = mpi_interfaces.local_sides[interface] - local_element = mpi_interfaces.local_neighbor_ids[interface] - local_indices = mpi_interfaces.node_indices[interface] - - i_element_start, i_element_step_i, i_element_step_j = index_to_start_step_3d(local_indices[1], - index_range) - j_element_start, j_element_step_i, j_element_step_j = index_to_start_step_3d(local_indices[2], - index_range) - k_element_start, k_element_step_i, k_element_step_j = index_to_start_step_3d(local_indices[3], - index_range) - - i_element = i_element_start - j_element = j_element_start - k_element = k_element_start - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - mpi_interfaces.u[local_side, v, i, j, interface] = u[v, i_element, - j_element, - k_element, - local_element] + function prolong2mpiinterfaces!( + cache, u, + mesh::Union{ + ParallelP4estMesh{3}, + ParallelT8codeMesh{3}, + }, + equations, surface_integral, dg::DG + ) + @unpack mpi_interfaces = cache + index_range = eachnode(dg) + + @threaded for interface in eachmpiinterface(dg, cache) + # Copy solution data from the local element using "delayed indexing" with + # a start value and a step size to get the correct face and orientation. + # Note that in the current implementation, the interface will be + # "aligned at the primary element", i.e., the index of the primary side + # will always run forwards. + local_side = mpi_interfaces.local_sides[interface] + local_element = mpi_interfaces.local_neighbor_ids[interface] + local_indices = mpi_interfaces.node_indices[interface] + + i_element_start, i_element_step_i, i_element_step_j = index_to_start_step_3d( + local_indices[1], + index_range + ) + j_element_start, j_element_step_i, j_element_step_j = index_to_start_step_3d( + local_indices[2], + index_range + ) + k_element_start, k_element_step_i, k_element_step_j = index_to_start_step_3d( + local_indices[3], + index_range + ) + + i_element = i_element_start + j_element = j_element_start + k_element = k_element_start + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + mpi_interfaces.u[local_side, v, i, j, interface] = u[ + v, i_element, + j_element, + k_element, + local_element, + ] + end + i_element += i_element_step_i + j_element += j_element_step_i + k_element += k_element_step_i end - i_element += i_element_step_i - j_element += j_element_step_i - k_element += k_element_step_i + i_element += i_element_step_j + j_element += j_element_step_j + k_element += k_element_step_j end - i_element += i_element_step_j - j_element += j_element_step_j - k_element += k_element_step_j end + + return nothing end - return nothing -end - -function calc_mpi_interface_flux!(surface_flux_values, - mesh::Union{ParallelP4estMesh{3}, - ParallelT8codeMesh{3}}, - nonconservative_terms, - equations, surface_integral, dg::DG, cache) - @unpack local_neighbor_ids, node_indices, local_sides = cache.mpi_interfaces - @unpack contravariant_vectors = cache.elements - index_range = eachnode(dg) - - @threaded for interface in eachmpiinterface(dg, cache) - # Get element and side index information on the local element - local_element = local_neighbor_ids[interface] - local_indices = node_indices[interface] - local_direction = indices2direction(local_indices) - local_side = local_sides[interface] - - # Create the local i,j,k indexing on the local element used to pull normal direction information - i_element_start, i_element_step_i, i_element_step_j = index_to_start_step_3d(local_indices[1], - index_range) - j_element_start, j_element_step_i, j_element_step_j = index_to_start_step_3d(local_indices[2], - index_range) - k_element_start, k_element_step_i, k_element_step_j = index_to_start_step_3d(local_indices[3], - index_range) - - i_element = i_element_start - j_element = j_element_start - k_element = k_element_start - - # Initiate the node indices to be used in the surface for loop, - # the surface flux storage must be indexed in alignment with the local element indexing - local_surface_indices = surface_indices(local_indices) - i_surface_start, i_surface_step_i, i_surface_step_j = index_to_start_step_3d(local_surface_indices[1], - index_range) - j_surface_start, j_surface_step_i, j_surface_step_j = index_to_start_step_3d(local_surface_indices[2], - index_range) - i_surface = i_surface_start - j_surface = j_surface_start - - for j in eachnode(dg) - for i in eachnode(dg) - # Get the normal direction on the local element - # Contravariant vectors at interfaces in negative coordinate direction - # are pointing inwards. This is handled by `get_normal_direction`. - normal_direction = get_normal_direction(local_direction, - contravariant_vectors, - i_element, j_element, k_element, - local_element) - - calc_mpi_interface_flux!(surface_flux_values, mesh, - nonconservative_terms, equations, - surface_integral, dg, cache, - interface, normal_direction, - i, j, local_side, - i_surface, j_surface, local_direction, - local_element) + function calc_mpi_interface_flux!( + surface_flux_values, + mesh::Union{ + ParallelP4estMesh{3}, + ParallelT8codeMesh{3}, + }, + nonconservative_terms, + equations, surface_integral, dg::DG, cache + ) + @unpack local_neighbor_ids, node_indices, local_sides = cache.mpi_interfaces + @unpack contravariant_vectors = cache.elements + index_range = eachnode(dg) + + @threaded for interface in eachmpiinterface(dg, cache) + # Get element and side index information on the local element + local_element = local_neighbor_ids[interface] + local_indices = node_indices[interface] + local_direction = indices2direction(local_indices) + local_side = local_sides[interface] + + # Create the local i,j,k indexing on the local element used to pull normal direction information + i_element_start, i_element_step_i, i_element_step_j = index_to_start_step_3d( + local_indices[1], + index_range + ) + j_element_start, j_element_step_i, j_element_step_j = index_to_start_step_3d( + local_indices[2], + index_range + ) + k_element_start, k_element_step_i, k_element_step_j = index_to_start_step_3d( + local_indices[3], + index_range + ) + + i_element = i_element_start + j_element = j_element_start + k_element = k_element_start + + # Initiate the node indices to be used in the surface for loop, + # the surface flux storage must be indexed in alignment with the local element indexing + local_surface_indices = surface_indices(local_indices) + i_surface_start, i_surface_step_i, i_surface_step_j = index_to_start_step_3d( + local_surface_indices[1], + index_range + ) + j_surface_start, j_surface_step_i, j_surface_step_j = index_to_start_step_3d( + local_surface_indices[2], + index_range + ) + i_surface = i_surface_start + j_surface = j_surface_start + for j in eachnode(dg) + for i in eachnode(dg) + # Get the normal direction on the local element + # Contravariant vectors at interfaces in negative coordinate direction + # are pointing inwards. This is handled by `get_normal_direction`. + normal_direction = get_normal_direction( + local_direction, + contravariant_vectors, + i_element, j_element, k_element, + local_element + ) + + calc_mpi_interface_flux!( + surface_flux_values, mesh, + nonconservative_terms, equations, + surface_integral, dg, cache, + interface, normal_direction, + i, j, local_side, + i_surface, j_surface, local_direction, + local_element + ) + + # Increment local element indices to pull the normal direction + i_element += i_element_step_i + j_element += j_element_step_i + k_element += k_element_step_i + # Increment the surface node indices along the local element + i_surface += i_surface_step_i + j_surface += j_surface_step_i + end # Increment local element indices to pull the normal direction - i_element += i_element_step_i - j_element += j_element_step_i - k_element += k_element_step_i + i_element += i_element_step_j + j_element += j_element_step_j + k_element += k_element_step_j # Increment the surface node indices along the local element - i_surface += i_surface_step_i - j_surface += j_surface_step_i + i_surface += i_surface_step_j + j_surface += j_surface_step_j end - # Increment local element indices to pull the normal direction - i_element += i_element_step_j - j_element += j_element_step_j - k_element += k_element_step_j - # Increment the surface node indices along the local element - i_surface += i_surface_step_j - j_surface += j_surface_step_j end - end - return nothing -end - -# Inlined version of the interface flux computation for conservation laws -@inline function calc_mpi_interface_flux!(surface_flux_values, - mesh::Union{ParallelP4estMesh{3}, - ParallelT8codeMesh{3}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - interface_index, normal_direction, - interface_i_node_index, - interface_j_node_index, local_side, - surface_i_node_index, surface_j_node_index, - local_direction_index, local_element_index) - @unpack u = cache.mpi_interfaces - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, - interface_i_node_index, interface_j_node_index, - interface_index) - - if local_side == 1 - flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) - else # local_side == 2 - flux_ = -surface_flux(u_ll, u_rr, -normal_direction, equations) + return nothing end - for v in eachvariable(equations) - surface_flux_values[v, surface_i_node_index, surface_j_node_index, - local_direction_index, local_element_index] = flux_[v] - end -end - -function prolong2mpimortars!(cache, u, - mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, - equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DGSEM) - @unpack node_indices = cache.mpi_mortars - index_range = eachnode(dg) - - @threaded for mortar in eachmpimortar(dg, cache) - local_neighbor_ids = cache.mpi_mortars.local_neighbor_ids[mortar] - local_neighbor_positions = cache.mpi_mortars.local_neighbor_positions[mortar] - - # Get start value and step size for indices on both sides to get the correct face - # and orientation - small_indices = node_indices[1, mortar] - i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1], - index_range) - j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2], - index_range) - k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3], - index_range) + # Inlined version of the interface flux computation for conservation laws + @inline function calc_mpi_interface_flux!( + surface_flux_values, + mesh::Union{ + ParallelP4estMesh{3}, + ParallelT8codeMesh{3}, + }, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + interface_index, normal_direction, + interface_i_node_index, + interface_j_node_index, local_side, + surface_i_node_index, surface_j_node_index, + local_direction_index, local_element_index + ) + @unpack u = cache.mpi_interfaces + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, + interface_i_node_index, interface_j_node_index, + interface_index + ) + + if local_side == 1 + flux_ = surface_flux(u_ll, u_rr, normal_direction, equations) + else # local_side == 2 + flux_ = -surface_flux(u_ll, u_rr, -normal_direction, equations) + end - large_indices = node_indices[2, mortar] - i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_indices[1], - index_range) - j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_indices[2], - index_range) - k_large_start, k_large_step_i, k_large_step_j = index_to_start_step_3d(large_indices[3], - index_range) - - for (element, position) in zip(local_neighbor_ids, local_neighbor_positions) - if position == 5 # -> large element - # Buffer to copy solution values of the large element in the correct orientation - # before interpolating - u_buffer = cache.u_threaded[Threads.threadid()] - # temporary buffer for projections - fstar_tmp = cache.fstar_tmp_threaded[Threads.threadid()] + for v in eachvariable(equations) + surface_flux_values[ + v, surface_i_node_index, surface_j_node_index, + local_direction_index, local_element_index, + ] = flux_[v] + end + end - i_large = i_large_start - j_large = j_large_start - k_large = k_large_start - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - u_buffer[v, i, j] = u[v, i_large, j_large, k_large, element] + function prolong2mpimortars!( + cache, u, + mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, + equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DGSEM + ) + @unpack node_indices = cache.mpi_mortars + index_range = eachnode(dg) + + @threaded for mortar in eachmpimortar(dg, cache) + local_neighbor_ids = cache.mpi_mortars.local_neighbor_ids[mortar] + local_neighbor_positions = cache.mpi_mortars.local_neighbor_positions[mortar] + + # Get start value and step size for indices on both sides to get the correct face + # and orientation + small_indices = node_indices[1, mortar] + i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d( + small_indices[1], + index_range + ) + j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d( + small_indices[2], + index_range + ) + k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d( + small_indices[3], + index_range + ) + + large_indices = node_indices[2, mortar] + i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d( + large_indices[1], + index_range + ) + j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d( + large_indices[2], + index_range + ) + k_large_start, k_large_step_i, k_large_step_j = index_to_start_step_3d( + large_indices[3], + index_range + ) + + for (element, position) in zip(local_neighbor_ids, local_neighbor_positions) + if position == 5 # -> large element + # Buffer to copy solution values of the large element in the correct orientation + # before interpolating + u_buffer = cache.u_threaded[Threads.threadid()] + # temporary buffer for projections + fstar_tmp = cache.fstar_tmp_threaded[Threads.threadid()] + + i_large = i_large_start + j_large = j_large_start + k_large = k_large_start + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + u_buffer[v, i, j] = u[v, i_large, j_large, k_large, element] + end + + i_large += i_large_step_i + j_large += j_large_step_i + k_large += k_large_step_i end + i_large += i_large_step_j + j_large += j_large_step_j + k_large += k_large_step_j + end - i_large += i_large_step_i - j_large += j_large_step_i - k_large += k_large_step_i + # Interpolate large element face data from buffer to small face locations + multiply_dimensionwise!( + view( + cache.mpi_mortars.u, 2, :, 1, :, :, + mortar + ), + mortar_l2.forward_lower, + mortar_l2.forward_lower, + u_buffer, + fstar_tmp + ) + multiply_dimensionwise!( + view( + cache.mpi_mortars.u, 2, :, 2, :, :, + mortar + ), + mortar_l2.forward_upper, + mortar_l2.forward_lower, + u_buffer, + fstar_tmp + ) + multiply_dimensionwise!( + view( + cache.mpi_mortars.u, 2, :, 3, :, :, + mortar + ), + mortar_l2.forward_lower, + mortar_l2.forward_upper, + u_buffer, + fstar_tmp + ) + multiply_dimensionwise!( + view( + cache.mpi_mortars.u, 2, :, 4, :, :, + mortar + ), + mortar_l2.forward_upper, + mortar_l2.forward_upper, + u_buffer, + fstar_tmp + ) + else # position in (1, 2, 3, 4) -> small element + # Copy solution data from the small elements + i_small = i_small_start + j_small = j_small_start + k_small = k_small_start + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + cache.mpi_mortars.u[1, v, position, i, j, mortar] = u[ + v, + i_small, + j_small, + k_small, + element, + ] + end + i_small += i_small_step_i + j_small += j_small_step_i + k_small += k_small_step_i + end + i_small += i_small_step_j + j_small += j_small_step_j + k_small += k_small_step_j end - i_large += i_large_step_j - j_large += j_large_step_j - k_large += k_large_step_j end + end + end - # Interpolate large element face data from buffer to small face locations - multiply_dimensionwise!(view(cache.mpi_mortars.u, 2, :, 1, :, :, - mortar), - mortar_l2.forward_lower, - mortar_l2.forward_lower, - u_buffer, - fstar_tmp) - multiply_dimensionwise!(view(cache.mpi_mortars.u, 2, :, 2, :, :, - mortar), - mortar_l2.forward_upper, - mortar_l2.forward_lower, - u_buffer, - fstar_tmp) - multiply_dimensionwise!(view(cache.mpi_mortars.u, 2, :, 3, :, :, - mortar), - mortar_l2.forward_lower, - mortar_l2.forward_upper, - u_buffer, - fstar_tmp) - multiply_dimensionwise!(view(cache.mpi_mortars.u, 2, :, 4, :, :, - mortar), - mortar_l2.forward_upper, - mortar_l2.forward_upper, - u_buffer, - fstar_tmp) - else # position in (1, 2, 3, 4) -> small element - # Copy solution data from the small elements + return nothing + end + + function calc_mpi_mortar_flux!( + surface_flux_values, + mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, + nonconservative_terms, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack local_neighbor_ids, local_neighbor_positions, node_indices = cache.mpi_mortars + @unpack contravariant_vectors = cache.elements + @unpack fstar_threaded, fstar_tmp_threaded = cache + index_range = eachnode(dg) + + @threaded for mortar in eachmpimortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar = fstar_threaded[Threads.threadid()] + fstar_tmp = fstar_tmp_threaded[Threads.threadid()] + + # Get index information on the small elements + small_indices = node_indices[1, mortar] + + i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d( + small_indices[1], + index_range + ) + j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d( + small_indices[2], + index_range + ) + k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d( + small_indices[3], + index_range + ) + + for position in 1:4 i_small = i_small_start j_small = j_small_start k_small = k_small_start for j in eachnode(dg) for i in eachnode(dg) - for v in eachvariable(equations) - cache.mpi_mortars.u[1, v, position, i, j, mortar] = u[v, - i_small, - j_small, - k_small, - element] - end + # Get the normal direction on the small element. + normal_direction = get_normal_direction( + cache.mpi_mortars, i, j, + position, mortar + ) + + calc_mpi_mortar_flux!( + fstar, mesh, nonconservative_terms, equations, + surface_integral, dg, cache, + mortar, position, normal_direction, + i, j + ) + i_small += i_small_step_i j_small += j_small_step_i k_small += k_small_step_i end - i_small += i_small_step_j - j_small += j_small_step_j - k_small += k_small_step_j end + i_small += i_small_step_j + j_small += j_small_step_j + k_small += k_small_step_j end - end - end - return nothing -end - -function calc_mpi_mortar_flux!(surface_flux_values, - mesh::Union{ParallelP4estMesh{3}, ParallelT8codeMesh{3}}, - nonconservative_terms, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack local_neighbor_ids, local_neighbor_positions, node_indices = cache.mpi_mortars - @unpack contravariant_vectors = cache.elements - @unpack fstar_threaded, fstar_tmp_threaded = cache - index_range = eachnode(dg) - - @threaded for mortar in eachmpimortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar = fstar_threaded[Threads.threadid()] - fstar_tmp = fstar_tmp_threaded[Threads.threadid()] - - # Get index information on the small elements - small_indices = node_indices[1, mortar] + # Buffer to interpolate flux values of the large element to before + # copying in the correct orientation + u_buffer = cache.u_threaded[Threads.threadid()] - i_small_start, i_small_step_i, i_small_step_j = index_to_start_step_3d(small_indices[1], - index_range) - j_small_start, j_small_step_i, j_small_step_j = index_to_start_step_3d(small_indices[2], - index_range) - k_small_start, k_small_step_i, k_small_step_j = index_to_start_step_3d(small_indices[3], - index_range) - - for position in 1:4 - i_small = i_small_start - j_small = j_small_start - k_small = k_small_start - for j in eachnode(dg) - for i in eachnode(dg) - # Get the normal direction on the small element. - normal_direction = get_normal_direction(cache.mpi_mortars, i, j, - position, mortar) - - calc_mpi_mortar_flux!(fstar, mesh, nonconservative_terms, equations, - surface_integral, dg, cache, - mortar, position, normal_direction, - i, j) - - i_small += i_small_step_i - j_small += j_small_step_i - k_small += k_small_step_i - end - end - i_small += i_small_step_j - j_small += j_small_step_j - k_small += k_small_step_j + mpi_mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, + mortar, fstar, u_buffer, fstar_tmp + ) end - # Buffer to interpolate flux values of the large element to before - # copying in the correct orientation - u_buffer = cache.u_threaded[Threads.threadid()] + return nothing + end - mpi_mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, - mortar, fstar, u_buffer, fstar_tmp) + # Inlined version of the mortar flux computation on small elements for conservation laws + @inline function calc_mpi_mortar_flux!( + fstar, + mesh::Union{ + ParallelP4estMesh{3}, + ParallelT8codeMesh{3}, + }, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + mortar_index, position_index, normal_direction, + i_node_index, j_node_index + ) + @unpack u = cache.mpi_mortars + @unpack surface_flux = surface_integral + + u_ll, u_rr = get_surface_node_vars( + u, equations, dg, position_index, i_node_index, + j_node_index, mortar_index + ) + + flux = surface_flux(u_ll, u_rr, normal_direction, equations) + + # Copy flux to buffer + set_node_vars!( + fstar, flux, equations, dg, i_node_index, j_node_index, + position_index + ) end - return nothing -end - -# Inlined version of the mortar flux computation on small elements for conservation laws -@inline function calc_mpi_mortar_flux!(fstar, - mesh::Union{ParallelP4estMesh{3}, - ParallelT8codeMesh{3}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - mortar_index, position_index, normal_direction, - i_node_index, j_node_index) - @unpack u = cache.mpi_mortars - @unpack surface_flux = surface_integral - - u_ll, u_rr = get_surface_node_vars(u, equations, dg, position_index, i_node_index, - j_node_index, mortar_index) - - flux = surface_flux(u_ll, u_rr, normal_direction, equations) - - # Copy flux to buffer - set_node_vars!(fstar, flux, equations, dg, i_node_index, j_node_index, - position_index) -end - -@inline function mpi_mortar_fluxes_to_elements!(surface_flux_values, - mesh::Union{ParallelP4estMesh{3}, - ParallelT8codeMesh{3}}, - equations, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, mortar, fstar, - u_buffer, fstar_tmp) - @unpack local_neighbor_ids, local_neighbor_positions, node_indices = cache.mpi_mortars - index_range = eachnode(dg) - - small_indices = node_indices[1, mortar] - small_direction = indices2direction(small_indices) - large_indices = node_indices[2, mortar] - large_direction = indices2direction(large_indices) - large_surface_indices = surface_indices(large_indices) - - i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d(large_surface_indices[1], - index_range) - j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d(large_surface_indices[2], - index_range) - - for (element, position) in zip(local_neighbor_ids[mortar], - local_neighbor_positions[mortar]) - if position == 5 # -> large element - # Project small fluxes to large element. - multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_lower, mortar_l2.reverse_lower, - view(fstar, .., 1), - fstar_tmp) - add_multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_upper, - mortar_l2.reverse_lower, - view(fstar, .., 2), - fstar_tmp) - add_multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_lower, - mortar_l2.reverse_upper, - view(fstar, .., 3), - fstar_tmp) - add_multiply_dimensionwise!(u_buffer, - mortar_l2.reverse_upper, - mortar_l2.reverse_upper, - view(fstar, .., 4), - fstar_tmp) - # The flux is calculated in the outward direction of the small elements, - # so the sign must be switched to get the flux in outward direction - # of the large element. - # The contravariant vectors of the large element (and therefore the normal - # vectors of the large element as well) are four times as large as the - # contravariant vectors of the small elements. Therefore, the flux needs - # to be scaled by a factor of 4 to obtain the flux of the large element. - u_buffer .*= -4 - # Copy interpolated flux values from buffer to large element face in the - # correct orientation. - # Note that the index of the small sides will always run forward but - # the index of the large side might need to run backwards for flipped sides. - i_large = i_large_start - j_large = j_large_start - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i_large, j_large, large_direction, element] = u_buffer[v, - i, - j] + @inline function mpi_mortar_fluxes_to_elements!( + surface_flux_values, + mesh::Union{ + ParallelP4estMesh{3}, + ParallelT8codeMesh{3}, + }, + equations, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, mortar, fstar, + u_buffer, fstar_tmp + ) + @unpack local_neighbor_ids, local_neighbor_positions, node_indices = cache.mpi_mortars + index_range = eachnode(dg) + + small_indices = node_indices[1, mortar] + small_direction = indices2direction(small_indices) + large_indices = node_indices[2, mortar] + large_direction = indices2direction(large_indices) + large_surface_indices = surface_indices(large_indices) + + i_large_start, i_large_step_i, i_large_step_j = index_to_start_step_3d( + large_surface_indices[1], + index_range + ) + j_large_start, j_large_step_i, j_large_step_j = index_to_start_step_3d( + large_surface_indices[2], + index_range + ) + + for (element, position) in zip( + local_neighbor_ids[mortar], + local_neighbor_positions[mortar] + ) + if position == 5 # -> large element + # Project small fluxes to large element. + multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_lower, mortar_l2.reverse_lower, + view(fstar, .., 1), + fstar_tmp + ) + add_multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_upper, + mortar_l2.reverse_lower, + view(fstar, .., 2), + fstar_tmp + ) + add_multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_lower, + mortar_l2.reverse_upper, + view(fstar, .., 3), + fstar_tmp + ) + add_multiply_dimensionwise!( + u_buffer, + mortar_l2.reverse_upper, + mortar_l2.reverse_upper, + view(fstar, .., 4), + fstar_tmp + ) + # The flux is calculated in the outward direction of the small elements, + # so the sign must be switched to get the flux in outward direction + # of the large element. + # The contravariant vectors of the large element (and therefore the normal + # vectors of the large element as well) are four times as large as the + # contravariant vectors of the small elements. Therefore, the flux needs + # to be scaled by a factor of 4 to obtain the flux of the large element. + u_buffer .*= -4 + # Copy interpolated flux values from buffer to large element face in the + # correct orientation. + # Note that the index of the small sides will always run forward but + # the index of the large side might need to run backwards for flipped sides. + i_large = i_large_start + j_large = j_large_start + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i_large, j_large, large_direction, element] = u_buffer[ + v, + i, + j, + ] + end + i_large += i_large_step_i + j_large += j_large_step_i end - i_large += i_large_step_i - j_large += j_large_step_i + i_large += i_large_step_j + j_large += j_large_step_j end - i_large += i_large_step_j - j_large += j_large_step_j - end - else # position in (1, 2, 3, 4) -> small element - # Copy solution small to small - for j in eachnode(dg) - for i in eachnode(dg) - for v in eachvariable(equations) - surface_flux_values[v, i, j, small_direction, element] = fstar[v, - i, - j, - position] + else # position in (1, 2, 3, 4) -> small element + # Copy solution small to small + for j in eachnode(dg) + for i in eachnode(dg) + for v in eachvariable(equations) + surface_flux_values[v, i, j, small_direction, element] = fstar[ + v, + i, + j, + position, + ] + end end end end end - end - return nothing -end + return nothing + end end # muladd diff --git a/src/solvers/dgsem_p4est/dg_parallel.jl b/src/solvers/dgsem_p4est/dg_parallel.jl index 0aee0b5652..493c9293d9 100644 --- a/src/solvers/dgsem_p4est/dg_parallel.jl +++ b/src/solvers/dgsem_p4est/dg_parallel.jl @@ -3,584 +3,712 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -mutable struct P4estMPICache{uEltype} - mpi_neighbor_ranks::Vector{Int} - mpi_neighbor_interfaces::Vector{Vector{Int}} - mpi_neighbor_mortars::Vector{Vector{Int}} - mpi_send_buffers::Vector{Vector{uEltype}} - mpi_recv_buffers::Vector{Vector{uEltype}} - mpi_send_requests::Vector{MPI.Request} - mpi_recv_requests::Vector{MPI.Request} - n_elements_by_rank::OffsetArray{Int, 1, Array{Int, 1}} - n_elements_global::Int - first_element_global_id::Int -end - -function P4estMPICache(uEltype) - # MPI communication "just works" for bitstypes only - if !isbitstype(uEltype) - throw(ArgumentError("P4estMPICache only supports bitstypes, $uEltype is not a bitstype.")) + #! format: noindent + + mutable struct P4estMPICache{uEltype} + mpi_neighbor_ranks::Vector{Int} + mpi_neighbor_interfaces::Vector{Vector{Int}} + mpi_neighbor_mortars::Vector{Vector{Int}} + mpi_send_buffers::Vector{Vector{uEltype}} + mpi_recv_buffers::Vector{Vector{uEltype}} + mpi_send_requests::Vector{MPI.Request} + mpi_recv_requests::Vector{MPI.Request} + n_elements_by_rank::OffsetArray{Int, 1, Array{Int, 1}} + n_elements_global::Int + first_element_global_id::Int end - mpi_neighbor_ranks = Vector{Int}(undef, 0) - mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, 0) - mpi_neighbor_mortars = Vector{Vector{Int}}(undef, 0) - mpi_send_buffers = Vector{Vector{uEltype}}(undef, 0) - mpi_recv_buffers = Vector{Vector{uEltype}}(undef, 0) - mpi_send_requests = Vector{MPI.Request}(undef, 0) - mpi_recv_requests = Vector{MPI.Request}(undef, 0) - n_elements_by_rank = OffsetArray(Vector{Int}(undef, 0), 0:-1) - n_elements_global = 0 - first_element_global_id = 0 - - P4estMPICache{uEltype}(mpi_neighbor_ranks, mpi_neighbor_interfaces, - mpi_neighbor_mortars, - mpi_send_buffers, mpi_recv_buffers, - mpi_send_requests, mpi_recv_requests, - n_elements_by_rank, n_elements_global, - first_element_global_id) -end - -@inline Base.eltype(::P4estMPICache{uEltype}) where {uEltype} = uEltype - -function start_mpi_send!(mpi_cache::P4estMPICache, mesh, equations, dg, cache) - data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1) - n_small_elements = 2^(ndims(mesh) - 1) - - for rank in 1:length(mpi_cache.mpi_neighbor_ranks) - send_buffer = mpi_cache.mpi_send_buffers[rank] - - for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[rank]) - first = (index - 1) * data_size + 1 - last = (index - 1) * data_size + data_size - local_side = cache.mpi_interfaces.local_sides[interface] - @views send_buffer[first:last] .= vec(cache.mpi_interfaces.u[local_side, .., - interface]) + function P4estMPICache(uEltype) + # MPI communication "just works" for bitstypes only + if !isbitstype(uEltype) + throw(ArgumentError("P4estMPICache only supports bitstypes, $uEltype is not a bitstype.")) end - # Set send_buffer corresponding to mortar data to NaN and overwrite the parts where local - # data exists - interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[rank]) * - data_size - mortars_data_size = length(mpi_cache.mpi_neighbor_mortars[rank]) * - n_small_elements * 2 * data_size - # `NaN |> eltype(...)` ensures that the NaN's are of the appropriate floating point type - send_buffer[(interfaces_data_size + 1):(interfaces_data_size + mortars_data_size)] .= NaN |> - eltype(mpi_cache) - - for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[rank]) - index_base = interfaces_data_size + - (index - 1) * n_small_elements * 2 * data_size - indices = buffer_mortar_indices(mesh, index_base, data_size) - - for position in cache.mpi_mortars.local_neighbor_positions[mortar] - first, last = indices[position] - if position > n_small_elements # large element - @views send_buffer[first:last] .= vec(cache.mpi_mortars.u[2, :, :, - .., - mortar]) - else # small element - @views send_buffer[first:last] .= vec(cache.mpi_mortars.u[1, :, - position, - .., - mortar]) + mpi_neighbor_ranks = Vector{Int}(undef, 0) + mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, 0) + mpi_neighbor_mortars = Vector{Vector{Int}}(undef, 0) + mpi_send_buffers = Vector{Vector{uEltype}}(undef, 0) + mpi_recv_buffers = Vector{Vector{uEltype}}(undef, 0) + mpi_send_requests = Vector{MPI.Request}(undef, 0) + mpi_recv_requests = Vector{MPI.Request}(undef, 0) + n_elements_by_rank = OffsetArray(Vector{Int}(undef, 0), 0:-1) + n_elements_global = 0 + first_element_global_id = 0 + + P4estMPICache{uEltype}( + mpi_neighbor_ranks, mpi_neighbor_interfaces, + mpi_neighbor_mortars, + mpi_send_buffers, mpi_recv_buffers, + mpi_send_requests, mpi_recv_requests, + n_elements_by_rank, n_elements_global, + first_element_global_id + ) + end + + @inline Base.eltype(::P4estMPICache{uEltype}) where {uEltype} = uEltype + + function start_mpi_send!(mpi_cache::P4estMPICache, mesh, equations, dg, cache) + data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1) + n_small_elements = 2^(ndims(mesh) - 1) + + for rank in 1:length(mpi_cache.mpi_neighbor_ranks) + send_buffer = mpi_cache.mpi_send_buffers[rank] + + for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[rank]) + first = (index - 1) * data_size + 1 + last = (index - 1) * data_size + data_size + local_side = cache.mpi_interfaces.local_sides[interface] + @views send_buffer[first:last] .= vec( + cache.mpi_interfaces.u[ + local_side, .., + interface, + ] + ) + end + + # Set send_buffer corresponding to mortar data to NaN and overwrite the parts where local + # data exists + interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[rank]) * + data_size + mortars_data_size = length(mpi_cache.mpi_neighbor_mortars[rank]) * + n_small_elements * 2 * data_size + # `NaN |> eltype(...)` ensures that the NaN's are of the appropriate floating point type + send_buffer[(interfaces_data_size + 1):(interfaces_data_size + mortars_data_size)] .= NaN |> + eltype(mpi_cache) + + for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[rank]) + index_base = interfaces_data_size + + (index - 1) * n_small_elements * 2 * data_size + indices = buffer_mortar_indices(mesh, index_base, data_size) + + for position in cache.mpi_mortars.local_neighbor_positions[mortar] + first, last = indices[position] + if position > n_small_elements # large element + @views send_buffer[first:last] .= vec( + cache.mpi_mortars.u[ + 2, :, :, + .., + mortar, + ] + ) + else # small element + @views send_buffer[first:last] .= vec( + cache.mpi_mortars.u[ + 1, :, + position, + .., + mortar, + ] + ) + end end end end - end - # Start sending - for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks) - mpi_cache.mpi_send_requests[index] = MPI.Isend(mpi_cache.mpi_send_buffers[index], - rank, mpi_rank(), mpi_comm()) + # Start sending + for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks) + mpi_cache.mpi_send_requests[index] = MPI.Isend( + mpi_cache.mpi_send_buffers[index], + rank, mpi_rank(), mpi_comm() + ) + end + + return nothing end - return nothing -end + function start_mpi_receive!(mpi_cache::P4estMPICache) + for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks) + mpi_cache.mpi_recv_requests[index] = MPI.Irecv!( + mpi_cache.mpi_recv_buffers[index], + rank, rank, mpi_comm() + ) + end -function start_mpi_receive!(mpi_cache::P4estMPICache) - for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks) - mpi_cache.mpi_recv_requests[index] = MPI.Irecv!(mpi_cache.mpi_recv_buffers[index], - rank, rank, mpi_comm()) + return nothing end - return nothing -end - -function finish_mpi_send!(mpi_cache::P4estMPICache) - MPI.Waitall(mpi_cache.mpi_send_requests, MPI.Status) -end + function finish_mpi_send!(mpi_cache::P4estMPICache) + MPI.Waitall(mpi_cache.mpi_send_requests, MPI.Status) + end -function finish_mpi_receive!(mpi_cache::P4estMPICache, mesh, equations, dg, cache) - data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1) - n_small_elements = 2^(ndims(mesh) - 1) - n_positions = n_small_elements + 1 + function finish_mpi_receive!(mpi_cache::P4estMPICache, mesh, equations, dg, cache) + data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1) + n_small_elements = 2^(ndims(mesh) - 1) + n_positions = n_small_elements + 1 - # Start receiving and unpack received data until all communication is finished - data = MPI.Waitany(mpi_cache.mpi_recv_requests) - while data !== nothing - recv_buffer = mpi_cache.mpi_recv_buffers[data] + # Start receiving and unpack received data until all communication is finished + data = MPI.Waitany(mpi_cache.mpi_recv_requests) + while data !== nothing + recv_buffer = mpi_cache.mpi_recv_buffers[data] - for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[data]) - first = (index - 1) * data_size + 1 - last = (index - 1) * data_size + data_size + for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[data]) + first = (index - 1) * data_size + 1 + last = (index - 1) * data_size + data_size - if cache.mpi_interfaces.local_sides[interface] == 1 # local element on primary side - @views vec(cache.mpi_interfaces.u[2, .., interface]) .= recv_buffer[first:last] - else # local element at secondary side - @views vec(cache.mpi_interfaces.u[1, .., interface]) .= recv_buffer[first:last] + if cache.mpi_interfaces.local_sides[interface] == 1 # local element on primary side + @views vec(cache.mpi_interfaces.u[2, .., interface]) .= recv_buffer[first:last] + else # local element at secondary side + @views vec(cache.mpi_interfaces.u[1, .., interface]) .= recv_buffer[first:last] + end end - end - interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[data]) * - data_size - for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[data]) - index_base = interfaces_data_size + - (index - 1) * n_small_elements * 2 * data_size - indices = buffer_mortar_indices(mesh, index_base, data_size) - - for position in 1:n_positions - # Skip if received data for `position` is NaN as no real data has been sent for the - # corresponding element - if isnan(recv_buffer[Base.first(indices[position])]) - continue - end + interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[data]) * + data_size + for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[data]) + index_base = interfaces_data_size + + (index - 1) * n_small_elements * 2 * data_size + indices = buffer_mortar_indices(mesh, index_base, data_size) - first, last = indices[position] - if position == n_positions # large element - @views vec(cache.mpi_mortars.u[2, :, :, .., mortar]) .= recv_buffer[first:last] - else # small element - @views vec(cache.mpi_mortars.u[1, :, position, .., mortar]) .= recv_buffer[first:last] + for position in 1:n_positions + # Skip if received data for `position` is NaN as no real data has been sent for the + # corresponding element + if isnan(recv_buffer[Base.first(indices[position])]) + continue + end + + first, last = indices[position] + if position == n_positions # large element + @views vec(cache.mpi_mortars.u[2, :, :, .., mortar]) .= recv_buffer[first:last] + else # small element + @views vec(cache.mpi_mortars.u[1, :, position, .., mortar]) .= recv_buffer[first:last] + end end end + + data = MPI.Waitany(mpi_cache.mpi_recv_requests) end - data = MPI.Waitany(mpi_cache.mpi_recv_requests) + return nothing end - return nothing -end - -# Return a tuple `indices` where indices[position] is a `(first, last)` tuple for accessing the -# data corresponding to the `position` part of a mortar in an MPI buffer. The mortar data must begin -# at `index_base`+1 in the MPI buffer. `data_size` is the data size associated with each small -# position (i.e. position 1 or 2). The data corresponding to the large side (i.e. position 3) has -# size `2 * data_size`. -@inline function buffer_mortar_indices(mesh::Union{ParallelP4estMesh{2}, - ParallelT8codeMesh{2}}, index_base, - data_size) - return ( + # Return a tuple `indices` where indices[position] is a `(first, last)` tuple for accessing the + # data corresponding to the `position` part of a mortar in an MPI buffer. The mortar data must begin + # at `index_base`+1 in the MPI buffer. `data_size` is the data size associated with each small + # position (i.e. position 1 or 2). The data corresponding to the large side (i.e. position 3) has + # size `2 * data_size`. + @inline function buffer_mortar_indices( + mesh::Union{ + ParallelP4estMesh{2}, + ParallelT8codeMesh{2}, + }, index_base, + data_size + ) + return ( # first, last for local element in position 1 (small element) - (index_base + 1, - index_base + 1 * data_size), + ( + index_base + 1, + index_base + 1 * data_size, + ), # first, last for local element in position 2 (small element) - (index_base + 1 * data_size + 1, - index_base + 2 * data_size), + ( + index_base + 1 * data_size + 1, + index_base + 2 * data_size, + ), # first, last for local element in position 3 (large element) - (index_base + 2 * data_size + 1, - index_base + 4 * data_size)) -end - -# Return a tuple `indices` where indices[position] is a `(first, last)` tuple for accessing the -# data corresponding to the `position` part of a mortar in an MPI buffer. The mortar data must begin -# at `index_base`+1 in the MPI buffer. `data_size` is the data size associated with each small -# position (i.e. position 1 to 4). The data corresponding to the large side (i.e. position 5) has -# size `4 * data_size`. -@inline function buffer_mortar_indices(mesh::Union{ParallelP4estMesh{3}, - ParallelT8codeMesh{3}}, index_base, - data_size) - return ( + ( + index_base + 2 * data_size + 1, + index_base + 4 * data_size, + ), + ) + end + + # Return a tuple `indices` where indices[position] is a `(first, last)` tuple for accessing the + # data corresponding to the `position` part of a mortar in an MPI buffer. The mortar data must begin + # at `index_base`+1 in the MPI buffer. `data_size` is the data size associated with each small + # position (i.e. position 1 to 4). The data corresponding to the large side (i.e. position 5) has + # size `4 * data_size`. + @inline function buffer_mortar_indices( + mesh::Union{ + ParallelP4estMesh{3}, + ParallelT8codeMesh{3}, + }, index_base, + data_size + ) + return ( # first, last for local element in position 1 (small element) - (index_base + 1, - index_base + 1 * data_size), + ( + index_base + 1, + index_base + 1 * data_size, + ), # first, last for local element in position 2 (small element) - (index_base + 1 * data_size + 1, - index_base + 2 * data_size), + ( + index_base + 1 * data_size + 1, + index_base + 2 * data_size, + ), # first, last for local element in position 3 (small element) - (index_base + 2 * data_size + 1, - index_base + 3 * data_size), + ( + index_base + 2 * data_size + 1, + index_base + 3 * data_size, + ), # first, last for local element in position 4 (small element) - (index_base + 3 * data_size + 1, - index_base + 4 * data_size), + ( + index_base + 3 * data_size + 1, + index_base + 4 * data_size, + ), # first, last for local element in position 5 (large element) - (index_base + 4 * data_size + 1, - index_base + 8 * data_size)) -end - -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::ParallelP4estMesh, equations::AbstractEquations, dg::DG, - ::Any, ::Type{uEltype}) where {uEltype <: Real} - # Make sure to balance and partition the p4est and create a new ghost layer before creating any - # containers in case someone has tampered with the p4est after creating the mesh - balance!(mesh) - partition!(mesh) - update_ghost_layer!(mesh) - - elements = init_elements(mesh, equations, dg.basis, uEltype) - - mpi_interfaces = init_mpi_interfaces(mesh, equations, dg.basis, elements) - mpi_mortars = init_mpi_mortars(mesh, equations, dg.basis, elements) - mpi_cache = init_mpi_cache(mesh, mpi_interfaces, mpi_mortars, - nvariables(equations), nnodes(dg), uEltype) - - exchange_normal_directions!(mpi_mortars, mpi_cache, mesh, nnodes(dg)) - - interfaces = init_interfaces(mesh, equations, dg.basis, elements) - boundaries = init_boundaries(mesh, equations, dg.basis, elements) - mortars = init_mortars(mesh, equations, dg.basis, elements) - - cache = (; elements, interfaces, mpi_interfaces, boundaries, mortars, mpi_mortars, - mpi_cache) - - # Add specialized parts of the cache required to compute the volume integral etc. - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) - - return cache -end - -function init_mpi_cache(mesh::ParallelP4estMesh, mpi_interfaces, mpi_mortars, nvars, - nnodes, uEltype) - mpi_cache = P4estMPICache(uEltype) - init_mpi_cache!(mpi_cache, mesh, mpi_interfaces, mpi_mortars, nvars, nnodes, - uEltype) - - return mpi_cache -end - -function init_mpi_cache!(mpi_cache::P4estMPICache, mesh::ParallelP4estMesh, - mpi_interfaces, mpi_mortars, nvars, n_nodes, uEltype) - mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars = init_mpi_neighbor_connectivity(mpi_interfaces, - mpi_mortars, - mesh) - - mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests = init_mpi_data_structures(mpi_neighbor_interfaces, - mpi_neighbor_mortars, - ndims(mesh), - nvars, - n_nodes, - uEltype) - - # Determine local and total number of elements - n_elements_global = Int(mesh.p4est.global_num_quadrants[]) - n_elements_by_rank = vcat(Int.(unsafe_wrap(Array, mesh.p4est.global_first_quadrant, - mpi_nranks())), - n_elements_global) |> diff # diff sufficient due to 0-based quad indices - n_elements_by_rank = OffsetArray(n_elements_by_rank, 0:(mpi_nranks() - 1)) - # Account for 1-based indexing in Julia - first_element_global_id = Int(mesh.p4est.global_first_quadrant[mpi_rank() + 1]) + 1 - @assert n_elements_global==sum(n_elements_by_rank) "error in total number of elements" - - # TODO reuse existing structures - @pack! mpi_cache = mpi_neighbor_ranks, mpi_neighbor_interfaces, - mpi_neighbor_mortars, - mpi_send_buffers, mpi_recv_buffers, - mpi_send_requests, mpi_recv_requests, - n_elements_by_rank, n_elements_global, - first_element_global_id -end - -function init_mpi_neighbor_connectivity(mpi_interfaces, mpi_mortars, - mesh::ParallelP4estMesh) - # Let p4est iterate over all interfaces and call init_neighbor_rank_connectivity_iter_face - # to collect connectivity information - iter_face_c = cfunction(init_neighbor_rank_connectivity_iter_face, Val(ndims(mesh))) - user_data = InitNeighborRankConnectivityIterFaceUserData(mpi_interfaces, - mpi_mortars, mesh) - - iterate_p4est(mesh.p4est, user_data; ghost_layer = mesh.ghost, - iter_face_c = iter_face_c) - - # Build proper connectivity data structures from information gathered by iterating over p4est - @unpack global_interface_ids, neighbor_ranks_interface, global_mortar_ids, neighbor_ranks_mortar = user_data - - mpi_neighbor_ranks = vcat(neighbor_ranks_interface, neighbor_ranks_mortar...) |> - sort |> unique - - p = sortperm(global_interface_ids) - neighbor_ranks_interface .= neighbor_ranks_interface[p] - interface_ids = collect(1:nmpiinterfaces(mpi_interfaces))[p] - - p = sortperm(global_mortar_ids) - neighbor_ranks_mortar .= neighbor_ranks_mortar[p] - mortar_ids = collect(1:nmpimortars(mpi_mortars))[p] - - # For each neighbor rank, init connectivity data structures - mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) - mpi_neighbor_mortars = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) - for (index, rank) in enumerate(mpi_neighbor_ranks) - mpi_neighbor_interfaces[index] = interface_ids[findall(==(rank), - neighbor_ranks_interface)] - mpi_neighbor_mortars[index] = mortar_ids[findall(x -> (rank in x), - neighbor_ranks_mortar)] + ( + index_base + 4 * data_size + 1, + index_base + 8 * data_size, + ), + ) end - # Check that all interfaces were counted exactly once - @assert mapreduce(length, +, mpi_neighbor_interfaces; init = 0) == + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::ParallelP4estMesh, equations::AbstractEquations, dg::DG, + ::Any, ::Type{uEltype} + ) where {uEltype <: Real} + # Make sure to balance and partition the p4est and create a new ghost layer before creating any + # containers in case someone has tampered with the p4est after creating the mesh + balance!(mesh) + partition!(mesh) + update_ghost_layer!(mesh) + + elements = init_elements(mesh, equations, dg.basis, uEltype) + + mpi_interfaces = init_mpi_interfaces(mesh, equations, dg.basis, elements) + mpi_mortars = init_mpi_mortars(mesh, equations, dg.basis, elements) + mpi_cache = init_mpi_cache( + mesh, mpi_interfaces, mpi_mortars, + nvariables(equations), nnodes(dg), uEltype + ) + + exchange_normal_directions!(mpi_mortars, mpi_cache, mesh, nnodes(dg)) + + interfaces = init_interfaces(mesh, equations, dg.basis, elements) + boundaries = init_boundaries(mesh, equations, dg.basis, elements) + mortars = init_mortars(mesh, equations, dg.basis, elements) + + cache = (; + elements, interfaces, mpi_interfaces, boundaries, mortars, mpi_mortars, + mpi_cache, + ) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) + + return cache + end + + function init_mpi_cache( + mesh::ParallelP4estMesh, mpi_interfaces, mpi_mortars, nvars, + nnodes, uEltype + ) + mpi_cache = P4estMPICache(uEltype) + init_mpi_cache!( + mpi_cache, mesh, mpi_interfaces, mpi_mortars, nvars, nnodes, + uEltype + ) + + return mpi_cache + end + + function init_mpi_cache!( + mpi_cache::P4estMPICache, mesh::ParallelP4estMesh, + mpi_interfaces, mpi_mortars, nvars, n_nodes, uEltype + ) + mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars = init_mpi_neighbor_connectivity( + mpi_interfaces, + mpi_mortars, + mesh + ) + + mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests = init_mpi_data_structures( + mpi_neighbor_interfaces, + mpi_neighbor_mortars, + ndims(mesh), + nvars, + n_nodes, + uEltype + ) + + # Determine local and total number of elements + n_elements_global = Int(mesh.p4est.global_num_quadrants[]) + n_elements_by_rank = vcat( + Int.( + unsafe_wrap( + Array, mesh.p4est.global_first_quadrant, + mpi_nranks() + ) + ), + n_elements_global + ) |> diff # diff sufficient due to 0-based quad indices + n_elements_by_rank = OffsetArray(n_elements_by_rank, 0:(mpi_nranks() - 1)) + # Account for 1-based indexing in Julia + first_element_global_id = Int(mesh.p4est.global_first_quadrant[mpi_rank() + 1]) + 1 + @assert n_elements_global == sum(n_elements_by_rank) "error in total number of elements" + + # TODO reuse existing structures + @pack! mpi_cache = mpi_neighbor_ranks, mpi_neighbor_interfaces, + mpi_neighbor_mortars, + mpi_send_buffers, mpi_recv_buffers, + mpi_send_requests, mpi_recv_requests, + n_elements_by_rank, n_elements_global, + first_element_global_id + end + + function init_mpi_neighbor_connectivity( + mpi_interfaces, mpi_mortars, + mesh::ParallelP4estMesh + ) + # Let p4est iterate over all interfaces and call init_neighbor_rank_connectivity_iter_face + # to collect connectivity information + iter_face_c = cfunction(init_neighbor_rank_connectivity_iter_face, Val(ndims(mesh))) + user_data = InitNeighborRankConnectivityIterFaceUserData( + mpi_interfaces, + mpi_mortars, mesh + ) + + iterate_p4est( + mesh.p4est, user_data; ghost_layer = mesh.ghost, + iter_face_c = iter_face_c + ) + + # Build proper connectivity data structures from information gathered by iterating over p4est + @unpack global_interface_ids, neighbor_ranks_interface, global_mortar_ids, neighbor_ranks_mortar = user_data + + mpi_neighbor_ranks = vcat(neighbor_ranks_interface, neighbor_ranks_mortar...) |> + sort |> unique + + p = sortperm(global_interface_ids) + neighbor_ranks_interface .= neighbor_ranks_interface[p] + interface_ids = collect(1:nmpiinterfaces(mpi_interfaces))[p] + + p = sortperm(global_mortar_ids) + neighbor_ranks_mortar .= neighbor_ranks_mortar[p] + mortar_ids = collect(1:nmpimortars(mpi_mortars))[p] + + # For each neighbor rank, init connectivity data structures + mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) + mpi_neighbor_mortars = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) + for (index, rank) in enumerate(mpi_neighbor_ranks) + mpi_neighbor_interfaces[index] = interface_ids[ + findall( + ==(rank), + neighbor_ranks_interface + ), + ] + mpi_neighbor_mortars[index] = mortar_ids[ + findall( + x -> (rank in x), + neighbor_ranks_mortar + ), + ] + end + + # Check that all interfaces were counted exactly once + @assert mapreduce(length, +, mpi_neighbor_interfaces; init = 0) == nmpiinterfaces(mpi_interfaces) - return mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars -end - -mutable struct InitNeighborRankConnectivityIterFaceUserData{MPIInterfaces, MPIMortars, - Mesh} - interfaces::MPIInterfaces - interface_id::Int - global_interface_ids::Vector{Int} - neighbor_ranks_interface::Vector{Int} - mortars::MPIMortars - mortar_id::Int - global_mortar_ids::Vector{Int} - neighbor_ranks_mortar::Vector{Vector{Int}} - mesh::Mesh -end - -function InitNeighborRankConnectivityIterFaceUserData(mpi_interfaces, mpi_mortars, mesh) - global_interface_ids = fill(-1, nmpiinterfaces(mpi_interfaces)) - neighbor_ranks_interface = fill(-1, nmpiinterfaces(mpi_interfaces)) - global_mortar_ids = fill(-1, nmpimortars(mpi_mortars)) - neighbor_ranks_mortar = Vector{Vector{Int}}(undef, nmpimortars(mpi_mortars)) - - return InitNeighborRankConnectivityIterFaceUserData{typeof(mpi_interfaces), - typeof(mpi_mortars), - typeof(mesh)}(mpi_interfaces, 1, - global_interface_ids, - neighbor_ranks_interface, - mpi_mortars, 1, - global_mortar_ids, - neighbor_ranks_mortar, - mesh) -end - -function init_neighbor_rank_connectivity_iter_face(info, user_data) - data = unsafe_pointer_to_objref(Ptr{InitNeighborRankConnectivityIterFaceUserData}(user_data)) - - # Function barrier because the unpacked user_data above is not type-stable - init_neighbor_rank_connectivity_iter_face_inner(info, data) -end - -# 2D -function cfunction(::typeof(init_neighbor_rank_connectivity_iter_face), ::Val{2}) - @cfunction(init_neighbor_rank_connectivity_iter_face, Cvoid, - (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid})) -end -# 3D -function cfunction(::typeof(init_neighbor_rank_connectivity_iter_face), ::Val{3}) - @cfunction(init_neighbor_rank_connectivity_iter_face, Cvoid, - (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid})) -end - -# Function barrier for type stability -function init_neighbor_rank_connectivity_iter_face_inner(info, user_data) - @unpack interfaces, interface_id, global_interface_ids, neighbor_ranks_interface, - mortars, mortar_id, global_mortar_ids, neighbor_ranks_mortar, mesh = user_data - - info_pw = PointerWrapper(info) - # Get the global interface/mortar ids and neighbor rank if current face belongs to an MPI - # interface/mortar - if info_pw.sides.elem_count[] == 2 # MPI interfaces/mortars have two neighboring elements - # Extract surface data - sides_pw = (load_pointerwrapper_side(info_pw, 1), - load_pointerwrapper_side(info_pw, 2)) - - if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false # No hanging nodes for MPI interfaces - if sides_pw[1].is.full.is_ghost[] == true - remote_side = 1 - local_side = 2 - elseif sides_pw[2].is.full.is_ghost[] == true - remote_side = 2 - local_side = 1 - else # both sides are on this rank -> skip since it's a regular interface - return nothing - end + return mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars + end - # Sanity check, current face should belong to current MPI interface - local_tree_pw = load_pointerwrapper_tree(mesh.p4est, - sides_pw[local_side].treeid[] + 1) # one-based indexing - local_quad_id = local_tree_pw.quadrants_offset[] + - sides_pw[local_side].is.full.quadid[] - @assert interfaces.local_neighbor_ids[interface_id] == local_quad_id + 1 # one-based indexing - - # Get neighbor ID from ghost layer - proc_offsets = unsafe_wrap(Array, - info_pw.ghost_layer.proc_offsets, - mpi_nranks() + 1) - ghost_id = sides_pw[remote_side].is.full.quadid[] # indexes the ghost layer, 0-based - neighbor_rank = findfirst(r -> proc_offsets[r] <= ghost_id < - proc_offsets[r + 1], - 1:mpi_nranks()) - 1 # MPI ranks are 0-based - neighbor_ranks_interface[interface_id] = neighbor_rank - - # Global interface id is the globally unique quadrant id of the quadrant on the primary - # side (1) multiplied by the number of faces per quadrant plus face - if local_side == 1 - offset = mesh.p4est.global_first_quadrant[mpi_rank() + 1] # one-based indexing - primary_quad_id = offset + local_quad_id - else - offset = mesh.p4est.global_first_quadrant[neighbor_rank + 1] # one-based indexing - primary_quad_id = offset + sides_pw[1].is.full.quad.p.piggy3.local_num[] - end - global_interface_id = 2 * ndims(mesh) * primary_quad_id + sides_pw[1].face[] - global_interface_ids[interface_id] = global_interface_id - - user_data.interface_id += 1 - else # hanging node - if sides_pw[1].is_hanging[] == true - hanging_side = 1 - full_side = 2 - else - hanging_side = 2 - full_side = 1 - end - # Verify before accessing is.full / is.hanging - @assert sides_pw[hanging_side].is_hanging[] == true && - sides_pw[full_side].is_hanging[] == false + mutable struct InitNeighborRankConnectivityIterFaceUserData{ + MPIInterfaces, MPIMortars, + Mesh, + } + interfaces::MPIInterfaces + interface_id::Int + global_interface_ids::Vector{Int} + neighbor_ranks_interface::Vector{Int} + mortars::MPIMortars + mortar_id::Int + global_mortar_ids::Vector{Int} + neighbor_ranks_mortar::Vector{Vector{Int}} + mesh::Mesh + end - # If all quadrants are locally available, this is a regular mortar -> skip - if sides_pw[full_side].is.full.is_ghost[] == false && - all(sides_pw[hanging_side].is.hanging.is_ghost[] .== false) - return nothing - end + function InitNeighborRankConnectivityIterFaceUserData(mpi_interfaces, mpi_mortars, mesh) + global_interface_ids = fill(-1, nmpiinterfaces(mpi_interfaces)) + neighbor_ranks_interface = fill(-1, nmpiinterfaces(mpi_interfaces)) + global_mortar_ids = fill(-1, nmpimortars(mpi_mortars)) + neighbor_ranks_mortar = Vector{Vector{Int}}(undef, nmpimortars(mpi_mortars)) + + return InitNeighborRankConnectivityIterFaceUserData{ + typeof(mpi_interfaces), + typeof(mpi_mortars), + typeof(mesh), + }( + mpi_interfaces, 1, + global_interface_ids, + neighbor_ranks_interface, + mpi_mortars, 1, + global_mortar_ids, + neighbor_ranks_mortar, + mesh + ) + end - trees_pw = (load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1), - load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1)) - - # Find small quads that are remote and determine which rank owns them - remote_small_quad_positions = findall(sides_pw[hanging_side].is.hanging.is_ghost[] .== - true) - proc_offsets = unsafe_wrap(Array, - info_pw.ghost_layer.proc_offsets, - mpi_nranks() + 1) - # indices of small remote quads inside the ghost layer, 0-based - ghost_ids = map(pos -> sides_pw[hanging_side].is.hanging.quadid[][pos], - remote_small_quad_positions) - neighbor_ranks = map(ghost_ids) do ghost_id - return findfirst(r -> proc_offsets[r] <= ghost_id < proc_offsets[r + 1], - 1:mpi_nranks()) - 1 # MPI ranks are 0-based - end - # Determine global quad id of large element to determine global MPI mortar id - # Furthermore, if large element is ghost, add its owner rank to neighbor_ranks - if sides_pw[full_side].is.full.is_ghost[] == true - ghost_id = sides_pw[full_side].is.full.quadid[] - large_quad_owner_rank = findfirst(r -> proc_offsets[r] <= ghost_id < - proc_offsets[r + 1], - 1:mpi_nranks()) - 1 # MPI ranks are 0-based - push!(neighbor_ranks, large_quad_owner_rank) - - offset = mesh.p4est.global_first_quadrant[large_quad_owner_rank + 1] # one-based indexing - large_quad_id = offset + - sides_pw[full_side].is.full.quad.p.piggy3.local_num[] - else - offset = mesh.p4est.global_first_quadrant[mpi_rank() + 1] # one-based indexing - large_quad_id = offset + trees_pw[full_side].quadrants_offset[] + - sides_pw[full_side].is.full.quadid[] - end - neighbor_ranks_mortar[mortar_id] = neighbor_ranks - # Global mortar id is the globally unique quadrant id of the large quadrant multiplied by the - # number of faces per quadrant plus face - global_mortar_ids[mortar_id] = 2 * ndims(mesh) * large_quad_id + - sides_pw[full_side].face[] + function init_neighbor_rank_connectivity_iter_face(info, user_data) + data = unsafe_pointer_to_objref(Ptr{InitNeighborRankConnectivityIterFaceUserData}(user_data)) - user_data.mortar_id += 1 - end + # Function barrier because the unpacked user_data above is not type-stable + init_neighbor_rank_connectivity_iter_face_inner(info, data) end - return nothing -end - -# Exchange normal directions of small elements of the MPI mortars. They are needed on all involved -# MPI ranks to calculate the mortar fluxes. -function exchange_normal_directions!(mpi_mortars, mpi_cache, - mesh::Union{ParallelP4estMesh, ParallelT8codeMesh}, - n_nodes) - RealT = real(mesh) - n_dims = ndims(mesh) - @unpack mpi_neighbor_mortars, mpi_neighbor_ranks = mpi_cache - n_small_elements = 2^(n_dims - 1) - data_size = n_nodes^(n_dims - 1) * n_dims - - # Create buffers and requests - send_buffers = Vector{Vector{RealT}}(undef, length(mpi_neighbor_mortars)) - recv_buffers = Vector{Vector{RealT}}(undef, length(mpi_neighbor_mortars)) - for index in 1:length(mpi_neighbor_mortars) - send_buffers[index] = Vector{RealT}(undef, - length(mpi_neighbor_mortars[index]) * - n_small_elements * data_size) - send_buffers[index] .= NaN |> RealT - recv_buffers[index] = Vector{RealT}(undef, - length(mpi_neighbor_mortars[index]) * - n_small_elements * data_size) - recv_buffers[index] .= NaN |> RealT + # 2D + function cfunction(::typeof(init_neighbor_rank_connectivity_iter_face), ::Val{2}) + @cfunction( + init_neighbor_rank_connectivity_iter_face, Cvoid, + (Ptr{p4est_iter_face_info_t}, Ptr{Cvoid}) + ) end - send_requests = Vector{MPI.Request}(undef, length(mpi_neighbor_mortars)) - recv_requests = Vector{MPI.Request}(undef, length(mpi_neighbor_mortars)) - - # Fill send buffers - for rank in 1:length(mpi_neighbor_ranks) - send_buffer = send_buffers[rank] - - for (index, mortar) in enumerate(mpi_neighbor_mortars[rank]) - index_base = (index - 1) * n_small_elements * data_size - indices = buffer_mortar_indices(mesh, index_base, data_size) - for position in mpi_mortars.local_neighbor_positions[mortar] - if position <= n_small_elements # element is small - first, last = indices[position] - @views send_buffer[first:last] .= vec(mpi_mortars.normal_directions[:, - .., - position, - mortar]) + # 3D + function cfunction(::typeof(init_neighbor_rank_connectivity_iter_face), ::Val{3}) + @cfunction( + init_neighbor_rank_connectivity_iter_face, Cvoid, + (Ptr{p8est_iter_face_info_t}, Ptr{Cvoid}) + ) + end + + # Function barrier for type stability + function init_neighbor_rank_connectivity_iter_face_inner(info, user_data) + @unpack interfaces, interface_id, global_interface_ids, neighbor_ranks_interface, + mortars, mortar_id, global_mortar_ids, neighbor_ranks_mortar, mesh = user_data + + info_pw = PointerWrapper(info) + # Get the global interface/mortar ids and neighbor rank if current face belongs to an MPI + # interface/mortar + if info_pw.sides.elem_count[] == 2 # MPI interfaces/mortars have two neighboring elements + # Extract surface data + sides_pw = ( + load_pointerwrapper_side(info_pw, 1), + load_pointerwrapper_side(info_pw, 2), + ) + + if sides_pw[1].is_hanging[] == false && sides_pw[2].is_hanging[] == false # No hanging nodes for MPI interfaces + if sides_pw[1].is.full.is_ghost[] == true + remote_side = 1 + local_side = 2 + elseif sides_pw[2].is.full.is_ghost[] == true + remote_side = 2 + local_side = 1 + else # both sides are on this rank -> skip since it's a regular interface + return nothing + end + + # Sanity check, current face should belong to current MPI interface + local_tree_pw = load_pointerwrapper_tree( + mesh.p4est, + sides_pw[local_side].treeid[] + 1 + ) # one-based indexing + local_quad_id = local_tree_pw.quadrants_offset[] + + sides_pw[local_side].is.full.quadid[] + @assert interfaces.local_neighbor_ids[interface_id] == local_quad_id + 1 # one-based indexing + + # Get neighbor ID from ghost layer + proc_offsets = unsafe_wrap( + Array, + info_pw.ghost_layer.proc_offsets, + mpi_nranks() + 1 + ) + ghost_id = sides_pw[remote_side].is.full.quadid[] # indexes the ghost layer, 0-based + neighbor_rank = findfirst( + r -> proc_offsets[r] <= ghost_id < + proc_offsets[r + 1], + 1:mpi_nranks() + ) - 1 # MPI ranks are 0-based + neighbor_ranks_interface[interface_id] = neighbor_rank + + # Global interface id is the globally unique quadrant id of the quadrant on the primary + # side (1) multiplied by the number of faces per quadrant plus face + if local_side == 1 + offset = mesh.p4est.global_first_quadrant[mpi_rank() + 1] # one-based indexing + primary_quad_id = offset + local_quad_id + else + offset = mesh.p4est.global_first_quadrant[neighbor_rank + 1] # one-based indexing + primary_quad_id = offset + sides_pw[1].is.full.quad.p.piggy3.local_num[] + end + global_interface_id = 2 * ndims(mesh) * primary_quad_id + sides_pw[1].face[] + global_interface_ids[interface_id] = global_interface_id + + user_data.interface_id += 1 + else # hanging node + if sides_pw[1].is_hanging[] == true + hanging_side = 1 + full_side = 2 + else + hanging_side = 2 + full_side = 1 + end + # Verify before accessing is.full / is.hanging + @assert sides_pw[hanging_side].is_hanging[] == true && + sides_pw[full_side].is_hanging[] == false + + # If all quadrants are locally available, this is a regular mortar -> skip + if sides_pw[full_side].is.full.is_ghost[] == false && + all(sides_pw[hanging_side].is.hanging.is_ghost[] .== false) + return nothing + end + + trees_pw = ( + load_pointerwrapper_tree(mesh.p4est, sides_pw[1].treeid[] + 1), + load_pointerwrapper_tree(mesh.p4est, sides_pw[2].treeid[] + 1), + ) + + # Find small quads that are remote and determine which rank owns them + remote_small_quad_positions = findall( + sides_pw[hanging_side].is.hanging.is_ghost[] .== + true + ) + proc_offsets = unsafe_wrap( + Array, + info_pw.ghost_layer.proc_offsets, + mpi_nranks() + 1 + ) + # indices of small remote quads inside the ghost layer, 0-based + ghost_ids = map( + pos -> sides_pw[hanging_side].is.hanging.quadid[][pos], + remote_small_quad_positions + ) + neighbor_ranks = map(ghost_ids) do ghost_id + return findfirst( + r -> proc_offsets[r] <= ghost_id < proc_offsets[r + 1], + 1:mpi_nranks() + ) - 1 # MPI ranks are 0-based end + # Determine global quad id of large element to determine global MPI mortar id + # Furthermore, if large element is ghost, add its owner rank to neighbor_ranks + if sides_pw[full_side].is.full.is_ghost[] == true + ghost_id = sides_pw[full_side].is.full.quadid[] + large_quad_owner_rank = findfirst( + r -> proc_offsets[r] <= ghost_id < + proc_offsets[r + 1], + 1:mpi_nranks() + ) - 1 # MPI ranks are 0-based + push!(neighbor_ranks, large_quad_owner_rank) + + offset = mesh.p4est.global_first_quadrant[large_quad_owner_rank + 1] # one-based indexing + large_quad_id = offset + + sides_pw[full_side].is.full.quad.p.piggy3.local_num[] + else + offset = mesh.p4est.global_first_quadrant[mpi_rank() + 1] # one-based indexing + large_quad_id = offset + trees_pw[full_side].quadrants_offset[] + + sides_pw[full_side].is.full.quadid[] + end + neighbor_ranks_mortar[mortar_id] = neighbor_ranks + # Global mortar id is the globally unique quadrant id of the large quadrant multiplied by the + # number of faces per quadrant plus face + global_mortar_ids[mortar_id] = 2 * ndims(mesh) * large_quad_id + + sides_pw[full_side].face[] + + user_data.mortar_id += 1 end end - end - # Start data exchange - for (index, rank) in enumerate(mpi_neighbor_ranks) - send_requests[index] = MPI.Isend(send_buffers[index], rank, mpi_rank(), - mpi_comm()) - recv_requests[index] = MPI.Irecv!(recv_buffers[index], rank, rank, mpi_comm()) + return nothing end - # Unpack data from receive buffers - data = MPI.Waitany(recv_requests) - while data !== nothing - recv_buffer = recv_buffers[data] - - for (index, mortar) in enumerate(mpi_neighbor_mortars[data]) - index_base = (index - 1) * n_small_elements * data_size - indices = buffer_mortar_indices(mesh, index_base, data_size) - for position in 1:n_small_elements - # Skip if received data for `position` is NaN as no real data has been sent for the - # corresponding element - if isnan(recv_buffer[Base.first(indices[position])]) - continue + # Exchange normal directions of small elements of the MPI mortars. They are needed on all involved + # MPI ranks to calculate the mortar fluxes. + function exchange_normal_directions!( + mpi_mortars, mpi_cache, + mesh::Union{ParallelP4estMesh, ParallelT8codeMesh}, + n_nodes + ) + RealT = real(mesh) + n_dims = ndims(mesh) + @unpack mpi_neighbor_mortars, mpi_neighbor_ranks = mpi_cache + n_small_elements = 2^(n_dims - 1) + data_size = n_nodes^(n_dims - 1) * n_dims + + # Create buffers and requests + send_buffers = Vector{Vector{RealT}}(undef, length(mpi_neighbor_mortars)) + recv_buffers = Vector{Vector{RealT}}(undef, length(mpi_neighbor_mortars)) + for index in 1:length(mpi_neighbor_mortars) + send_buffers[index] = Vector{RealT}( + undef, + length(mpi_neighbor_mortars[index]) * + n_small_elements * data_size + ) + send_buffers[index] .= NaN |> RealT + recv_buffers[index] = Vector{RealT}( + undef, + length(mpi_neighbor_mortars[index]) * + n_small_elements * data_size + ) + recv_buffers[index] .= NaN |> RealT + end + send_requests = Vector{MPI.Request}(undef, length(mpi_neighbor_mortars)) + recv_requests = Vector{MPI.Request}(undef, length(mpi_neighbor_mortars)) + + # Fill send buffers + for rank in 1:length(mpi_neighbor_ranks) + send_buffer = send_buffers[rank] + + for (index, mortar) in enumerate(mpi_neighbor_mortars[rank]) + index_base = (index - 1) * n_small_elements * data_size + indices = buffer_mortar_indices(mesh, index_base, data_size) + for position in mpi_mortars.local_neighbor_positions[mortar] + if position <= n_small_elements # element is small + first, last = indices[position] + @views send_buffer[first:last] .= vec( + mpi_mortars.normal_directions[ + :, + .., + position, + mortar, + ] + ) + end end - - first, last = indices[position] - @views vec(mpi_mortars.normal_directions[:, .., position, mortar]) .= recv_buffer[first:last] end end + # Start data exchange + for (index, rank) in enumerate(mpi_neighbor_ranks) + send_requests[index] = MPI.Isend( + send_buffers[index], rank, mpi_rank(), + mpi_comm() + ) + recv_requests[index] = MPI.Irecv!(recv_buffers[index], rank, rank, mpi_comm()) + end + + # Unpack data from receive buffers data = MPI.Waitany(recv_requests) - end + while data !== nothing + recv_buffer = recv_buffers[data] + + for (index, mortar) in enumerate(mpi_neighbor_mortars[data]) + index_base = (index - 1) * n_small_elements * data_size + indices = buffer_mortar_indices(mesh, index_base, data_size) + for position in 1:n_small_elements + # Skip if received data for `position` is NaN as no real data has been sent for the + # corresponding element + if isnan(recv_buffer[Base.first(indices[position])]) + continue + end - # Wait for communication to finish - MPI.Waitall(send_requests, MPI.Status) + first, last = indices[position] + @views vec(mpi_mortars.normal_directions[:, .., position, mortar]) .= recv_buffer[first:last] + end + end + + data = MPI.Waitany(recv_requests) + end + + # Wait for communication to finish + MPI.Waitall(send_requests, MPI.Status) - return nothing -end + return nothing + end -# Get normal direction of MPI mortar -@inline function get_normal_direction(mpi_mortars::P4estMPIMortarContainer, indices...) - SVector(ntuple(@inline(dim->mpi_mortars.normal_directions[dim, indices...]), - Val(ndims(mpi_mortars)))) -end + # Get normal direction of MPI mortar + @inline function get_normal_direction(mpi_mortars::P4estMPIMortarContainer, indices...) + SVector( + ntuple( + @inline(dim -> mpi_mortars.normal_directions[dim, indices...]), + Val(ndims(mpi_mortars)) + ) + ) + end -include("dg_2d_parallel.jl") -include("dg_3d_parallel.jl") + include("dg_2d_parallel.jl") + include("dg_3d_parallel.jl") end # muladd diff --git a/src/solvers/dgsem_p4est/subcell_limiters_2d.jl b/src/solvers/dgsem_p4est/subcell_limiters_2d.jl index 95267f387f..8b8463d5f4 100644 --- a/src/solvers/dgsem_p4est/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_p4est/subcell_limiters_2d.jl @@ -3,255 +3,343 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function calc_bounds_twosided_interface!(var_min, var_max, variable, u, t, semi, - mesh::P4estMesh{2}) - _, equations, dg, cache = mesh_equations_solver_cache(semi) - (; boundary_conditions) = semi - - (; neighbor_ids, node_indices) = cache.interfaces - index_range = eachnode(dg) - - # Calc bounds at interfaces and periodic boundaries - for interface in eachinterface(dg, cache) - # Get element and side index information on the primary element - primary_element = neighbor_ids[1, interface] - primary_indices = node_indices[1, interface] - - # Get element and side index information on the secondary element - secondary_element = neighbor_ids[2, interface] - secondary_indices = node_indices[2, interface] - - # Create the local i,j indexing - i_primary_start, i_primary_step = index_to_start_step_2d(primary_indices[1], - index_range) - j_primary_start, j_primary_step = index_to_start_step_2d(primary_indices[2], - index_range) - i_secondary_start, i_secondary_step = index_to_start_step_2d(secondary_indices[1], - index_range) - j_secondary_start, j_secondary_step = index_to_start_step_2d(secondary_indices[2], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - i_secondary = i_secondary_start - j_secondary = j_secondary_start - - for node in eachnode(dg) - var_primary = u[variable, i_primary, j_primary, primary_element] - var_secondary = u[variable, i_secondary, j_secondary, secondary_element] - - var_min[i_primary, j_primary, primary_element] = min(var_min[i_primary, - j_primary, - primary_element], - var_secondary) - var_max[i_primary, j_primary, primary_element] = max(var_max[i_primary, - j_primary, - primary_element], - var_secondary) - - var_min[i_secondary, j_secondary, secondary_element] = min(var_min[i_secondary, - j_secondary, - secondary_element], - var_primary) - var_max[i_secondary, j_secondary, secondary_element] = max(var_max[i_secondary, - j_secondary, - secondary_element], - var_primary) - - # Increment primary element indices - i_primary += i_primary_step - j_primary += j_primary_step - i_secondary += i_secondary_step - j_secondary += j_secondary_step - end - end + #! format: noindent + + function calc_bounds_twosided_interface!( + var_min, var_max, variable, u, t, semi, + mesh::P4estMesh{2} + ) + _, equations, dg, cache = mesh_equations_solver_cache(semi) + (; boundary_conditions) = semi + + (; neighbor_ids, node_indices) = cache.interfaces + index_range = eachnode(dg) + + # Calc bounds at interfaces and periodic boundaries + for interface in eachinterface(dg, cache) + # Get element and side index information on the primary element + primary_element = neighbor_ids[1, interface] + primary_indices = node_indices[1, interface] + + # Get element and side index information on the secondary element + secondary_element = neighbor_ids[2, interface] + secondary_indices = node_indices[2, interface] + + # Create the local i,j indexing + i_primary_start, i_primary_step = index_to_start_step_2d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step = index_to_start_step_2d( + primary_indices[2], + index_range + ) + i_secondary_start, i_secondary_step = index_to_start_step_2d( + secondary_indices[1], + index_range + ) + j_secondary_start, j_secondary_step = index_to_start_step_2d( + secondary_indices[2], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + i_secondary = i_secondary_start + j_secondary = j_secondary_start - # Calc bounds at physical boundaries - calc_bounds_twosided_boundary!(var_min, var_max, variable, u, t, - boundary_conditions, - mesh, equations, dg, cache) - - return nothing -end - -@inline function calc_bounds_twosided_boundary!(var_min, var_max, variable, u, t, - boundary_conditions::BoundaryConditionPeriodic, - mesh::P4estMesh{2}, - equations, dg, cache) - return nothing -end - -@inline function calc_bounds_twosided_boundary!(var_min, var_max, variable, u, t, - boundary_conditions, - mesh::P4estMesh{2}, - equations, dg, cache) - (; boundary_condition_types, boundary_indices) = boundary_conditions - (; contravariant_vectors) = cache.elements - - (; boundaries) = cache - index_range = eachnode(dg) - - foreach_enumerate(boundary_condition_types) do (i, boundary_condition) - for boundary in boundary_indices[i] - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - direction = indices2direction(node_indices) - - i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], - index_range) - j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], - index_range) - - i_node = i_node_start - j_node = j_node_start - for i in eachnode(dg) - normal_direction = get_normal_direction(direction, - contravariant_vectors, - i_node, j_node, element) - - u_inner = get_node_vars(u, equations, dg, i_node, j_node, element) - - u_outer = get_boundary_outer_state(u_inner, t, boundary_condition, - normal_direction, - equations, dg, cache, - i_node, j_node, element) - var_outer = u_outer[variable] - - var_min[i_node, j_node, element] = min(var_min[i_node, j_node, element], - var_outer) - var_max[i_node, j_node, element] = max(var_max[i_node, j_node, element], - var_outer) - - i_node += i_node_step - j_node += j_node_step + for node in eachnode(dg) + var_primary = u[variable, i_primary, j_primary, primary_element] + var_secondary = u[variable, i_secondary, j_secondary, secondary_element] + + var_min[i_primary, j_primary, primary_element] = min( + var_min[ + i_primary, + j_primary, + primary_element, + ], + var_secondary + ) + var_max[i_primary, j_primary, primary_element] = max( + var_max[ + i_primary, + j_primary, + primary_element, + ], + var_secondary + ) + + var_min[i_secondary, j_secondary, secondary_element] = min( + var_min[ + i_secondary, + j_secondary, + secondary_element, + ], + var_primary + ) + var_max[i_secondary, j_secondary, secondary_element] = max( + var_max[ + i_secondary, + j_secondary, + secondary_element, + ], + var_primary + ) + + # Increment primary element indices + i_primary += i_primary_step + j_primary += j_primary_step + i_secondary += i_secondary_step + j_secondary += j_secondary_step end end + + # Calc bounds at physical boundaries + calc_bounds_twosided_boundary!( + var_min, var_max, variable, u, t, + boundary_conditions, + mesh, equations, dg, cache + ) + + return nothing + end + + @inline function calc_bounds_twosided_boundary!( + var_min, var_max, variable, u, t, + boundary_conditions::BoundaryConditionPeriodic, + mesh::P4estMesh{2}, + equations, dg, cache + ) + return nothing end - return nothing -end - -function calc_bounds_onesided_interface!(var_minmax, minmax, variable, u, t, semi, - mesh::P4estMesh{2}) - _, equations, dg, cache = mesh_equations_solver_cache(semi) - (; boundary_conditions) = semi - - (; neighbor_ids, node_indices) = cache.interfaces - index_range = eachnode(dg) - index_end = last(index_range) - - # Calc bounds at interfaces and periodic boundaries - for interface in eachinterface(dg, cache) - # Get element and side index information on the primary element - primary_element = neighbor_ids[1, interface] - primary_indices = node_indices[1, interface] - - # Get element and side index information on the secondary element - secondary_element = neighbor_ids[2, interface] - secondary_indices = node_indices[2, interface] - - # Create the local i,j indexing - i_primary_start, i_primary_step = index_to_start_step_2d(primary_indices[1], - index_range) - j_primary_start, j_primary_step = index_to_start_step_2d(primary_indices[2], - index_range) - i_secondary_start, i_secondary_step = index_to_start_step_2d(secondary_indices[1], - index_range) - j_secondary_start, j_secondary_step = index_to_start_step_2d(secondary_indices[2], - index_range) - - i_primary = i_primary_start - j_primary = j_primary_start - i_secondary = i_secondary_start - j_secondary = j_secondary_start - - for node in eachnode(dg) - var_primary = variable(get_node_vars(u, equations, dg, i_primary, j_primary, - primary_element), equations) - var_secondary = variable(get_node_vars(u, equations, dg, i_secondary, - j_secondary, secondary_element), - equations) - - var_minmax[i_primary, j_primary, primary_element] = minmax(var_minmax[i_primary, - j_primary, - primary_element], - var_secondary) - var_minmax[i_secondary, j_secondary, secondary_element] = minmax(var_minmax[i_secondary, - j_secondary, - secondary_element], - var_primary) - - # Increment primary element indices - i_primary += i_primary_step - j_primary += j_primary_step - i_secondary += i_secondary_step - j_secondary += j_secondary_step + @inline function calc_bounds_twosided_boundary!( + var_min, var_max, variable, u, t, + boundary_conditions, + mesh::P4estMesh{2}, + equations, dg, cache + ) + (; boundary_condition_types, boundary_indices) = boundary_conditions + (; contravariant_vectors) = cache.elements + + (; boundaries) = cache + index_range = eachnode(dg) + + foreach_enumerate(boundary_condition_types) do (i, boundary_condition) + for boundary in boundary_indices[i] + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step = index_to_start_step_2d( + node_indices[1], + index_range + ) + j_node_start, j_node_step = index_to_start_step_2d( + node_indices[2], + index_range + ) + + i_node = i_node_start + j_node = j_node_start + for i in eachnode(dg) + normal_direction = get_normal_direction( + direction, + contravariant_vectors, + i_node, j_node, element + ) + + u_inner = get_node_vars(u, equations, dg, i_node, j_node, element) + + u_outer = get_boundary_outer_state( + u_inner, t, boundary_condition, + normal_direction, + equations, dg, cache, + i_node, j_node, element + ) + var_outer = u_outer[variable] + + var_min[i_node, j_node, element] = min( + var_min[i_node, j_node, element], + var_outer + ) + var_max[i_node, j_node, element] = max( + var_max[i_node, j_node, element], + var_outer + ) + + i_node += i_node_step + j_node += j_node_step + end + end end + + return nothing end - # Calc bounds at physical boundaries - calc_bounds_onesided_boundary!(var_minmax, minmax, variable, u, t, - boundary_conditions, - mesh, equations, dg, cache) - - return nothing -end - -@inline function calc_bounds_onesided_boundary!(var_minmax, minmax, variable, u, t, - boundary_conditions::BoundaryConditionPeriodic, - mesh::P4estMesh{2}, - equations, dg, cache) - return nothing -end - -@inline function calc_bounds_onesided_boundary!(var_minmax, minmax, variable, u, t, - boundary_conditions, - mesh::P4estMesh{2}, - equations, dg, cache) - (; boundary_condition_types, boundary_indices) = boundary_conditions - (; contravariant_vectors) = cache.elements - - (; boundaries) = cache - index_range = eachnode(dg) - - foreach_enumerate(boundary_condition_types) do (i, boundary_condition) - for boundary in boundary_indices[i] - element = boundaries.neighbor_ids[boundary] - node_indices = boundaries.node_indices[boundary] - direction = indices2direction(node_indices) - - i_node_start, i_node_step = index_to_start_step_2d(node_indices[1], - index_range) - j_node_start, j_node_step = index_to_start_step_2d(node_indices[2], - index_range) - - i_node = i_node_start - j_node = j_node_start + function calc_bounds_onesided_interface!( + var_minmax, minmax, variable, u, t, semi, + mesh::P4estMesh{2} + ) + _, equations, dg, cache = mesh_equations_solver_cache(semi) + (; boundary_conditions) = semi + + (; neighbor_ids, node_indices) = cache.interfaces + index_range = eachnode(dg) + index_end = last(index_range) + + # Calc bounds at interfaces and periodic boundaries + for interface in eachinterface(dg, cache) + # Get element and side index information on the primary element + primary_element = neighbor_ids[1, interface] + primary_indices = node_indices[1, interface] + + # Get element and side index information on the secondary element + secondary_element = neighbor_ids[2, interface] + secondary_indices = node_indices[2, interface] + + # Create the local i,j indexing + i_primary_start, i_primary_step = index_to_start_step_2d( + primary_indices[1], + index_range + ) + j_primary_start, j_primary_step = index_to_start_step_2d( + primary_indices[2], + index_range + ) + i_secondary_start, i_secondary_step = index_to_start_step_2d( + secondary_indices[1], + index_range + ) + j_secondary_start, j_secondary_step = index_to_start_step_2d( + secondary_indices[2], + index_range + ) + + i_primary = i_primary_start + j_primary = j_primary_start + i_secondary = i_secondary_start + j_secondary = j_secondary_start + for node in eachnode(dg) - normal_direction = get_normal_direction(direction, - contravariant_vectors, - i_node, j_node, element) + var_primary = variable( + get_node_vars( + u, equations, dg, i_primary, j_primary, + primary_element + ), equations + ) + var_secondary = variable( + get_node_vars( + u, equations, dg, i_secondary, + j_secondary, secondary_element + ), + equations + ) + + var_minmax[i_primary, j_primary, primary_element] = minmax( + var_minmax[ + i_primary, + j_primary, + primary_element, + ], + var_secondary + ) + var_minmax[i_secondary, j_secondary, secondary_element] = minmax( + var_minmax[ + i_secondary, + j_secondary, + secondary_element, + ], + var_primary + ) + + # Increment primary element indices + i_primary += i_primary_step + j_primary += j_primary_step + i_secondary += i_secondary_step + j_secondary += j_secondary_step + end + end - u_inner = get_node_vars(u, equations, dg, i_node, j_node, element) + # Calc bounds at physical boundaries + calc_bounds_onesided_boundary!( + var_minmax, minmax, variable, u, t, + boundary_conditions, + mesh, equations, dg, cache + ) - u_outer = get_boundary_outer_state(u_inner, t, boundary_condition, - normal_direction, - equations, dg, cache, - i_node, j_node, element) - var_outer = variable(u_outer, equations) + return nothing + end - var_minmax[i_node, j_node, element] = minmax(var_minmax[i_node, j_node, - element], - var_outer) + @inline function calc_bounds_onesided_boundary!( + var_minmax, minmax, variable, u, t, + boundary_conditions::BoundaryConditionPeriodic, + mesh::P4estMesh{2}, + equations, dg, cache + ) + return nothing + end - i_node += i_node_step - j_node += j_node_step + @inline function calc_bounds_onesided_boundary!( + var_minmax, minmax, variable, u, t, + boundary_conditions, + mesh::P4estMesh{2}, + equations, dg, cache + ) + (; boundary_condition_types, boundary_indices) = boundary_conditions + (; contravariant_vectors) = cache.elements + + (; boundaries) = cache + index_range = eachnode(dg) + + foreach_enumerate(boundary_condition_types) do (i, boundary_condition) + for boundary in boundary_indices[i] + element = boundaries.neighbor_ids[boundary] + node_indices = boundaries.node_indices[boundary] + direction = indices2direction(node_indices) + + i_node_start, i_node_step = index_to_start_step_2d( + node_indices[1], + index_range + ) + j_node_start, j_node_step = index_to_start_step_2d( + node_indices[2], + index_range + ) + + i_node = i_node_start + j_node = j_node_start + for node in eachnode(dg) + normal_direction = get_normal_direction( + direction, + contravariant_vectors, + i_node, j_node, element + ) + + u_inner = get_node_vars(u, equations, dg, i_node, j_node, element) + + u_outer = get_boundary_outer_state( + u_inner, t, boundary_condition, + normal_direction, + equations, dg, cache, + i_node, j_node, element + ) + var_outer = variable(u_outer, equations) + + var_minmax[i_node, j_node, element] = minmax( + var_minmax[ + i_node, j_node, + element, + ], + var_outer + ) + + i_node += i_node_step + j_node += j_node_step + end end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_structured/containers.jl b/src/solvers/dgsem_structured/containers.jl index 7b0d275c5b..5c2e0aba51 100644 --- a/src/solvers/dgsem_structured/containers.jl +++ b/src/solvers/dgsem_structured/containers.jl @@ -3,68 +3,88 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -struct ElementContainer{NDIMS, RealT <: Real, uEltype <: Real, NDIMSP1, NDIMSP2, - NDIMSP3} - # Physical coordinates at each node - node_coordinates::Array{RealT, NDIMSP2} # [orientation, node_i, node_j, node_k, element] - # ID of neighbor element in negative direction in orientation - left_neighbors::Array{Int, 2} # [orientation, elements] - # Jacobian matrix of the transformation - # [jacobian_i, jacobian_j, node_i, node_j, node_k, element] where jacobian_i is the first index of the Jacobian matrix,... - jacobian_matrix::Array{RealT, NDIMSP3} - # Contravariant vectors, scaled by J, in Kopriva's blue book called Ja^i_n (i index, n dimension) - contravariant_vectors::Array{RealT, NDIMSP3} # [dimension, index, node_i, node_j, node_k, element] - # 1/J where J is the Jacobian determinant (determinant of Jacobian matrix) - inverse_jacobian::Array{RealT, NDIMSP1} # [node_i, node_j, node_k, element] - # Buffer for calculated surface flux - surface_flux_values::Array{uEltype, NDIMSP2} # [variable, i, j, direction, element] -end + struct ElementContainer{ + NDIMS, RealT <: Real, uEltype <: Real, NDIMSP1, NDIMSP2, + NDIMSP3, + } + # Physical coordinates at each node + node_coordinates::Array{RealT, NDIMSP2} # [orientation, node_i, node_j, node_k, element] + # ID of neighbor element in negative direction in orientation + left_neighbors::Array{Int, 2} # [orientation, elements] + # Jacobian matrix of the transformation + # [jacobian_i, jacobian_j, node_i, node_j, node_k, element] where jacobian_i is the first index of the Jacobian matrix,... + jacobian_matrix::Array{RealT, NDIMSP3} + # Contravariant vectors, scaled by J, in Kopriva's blue book called Ja^i_n (i index, n dimension) + contravariant_vectors::Array{RealT, NDIMSP3} # [dimension, index, node_i, node_j, node_k, element] + # 1/J where J is the Jacobian determinant (determinant of Jacobian matrix) + inverse_jacobian::Array{RealT, NDIMSP1} # [node_i, node_j, node_k, element] + # Buffer for calculated surface flux + surface_flux_values::Array{uEltype, NDIMSP2} # [variable, i, j, direction, element] + end -# Create element container and initialize element data -function init_elements(mesh::Union{StructuredMesh{NDIMS, RealT}, - StructuredMeshView{NDIMS, RealT}}, - equations::AbstractEquations, - basis, - ::Type{uEltype}) where {NDIMS, RealT <: Real, uEltype <: Real} - nelements = prod(size(mesh)) - node_coordinates = Array{RealT, NDIMS + 2}(undef, NDIMS, - ntuple(_ -> nnodes(basis), NDIMS)..., - nelements) - left_neighbors = Array{Int, 2}(undef, NDIMS, nelements) - jacobian_matrix = Array{RealT, NDIMS + 3}(undef, NDIMS, NDIMS, - ntuple(_ -> nnodes(basis), NDIMS)..., - nelements) - contravariant_vectors = similar(jacobian_matrix) - inverse_jacobian = Array{RealT, NDIMS + 1}(undef, - ntuple(_ -> nnodes(basis), NDIMS)..., - nelements) - surface_flux_values = Array{uEltype, NDIMS + 2}(undef, nvariables(equations), - ntuple(_ -> nnodes(basis), - NDIMS - 1)..., NDIMS * 2, - nelements) + # Create element container and initialize element data + function init_elements( + mesh::Union{ + StructuredMesh{NDIMS, RealT}, + StructuredMeshView{NDIMS, RealT}, + }, + equations::AbstractEquations, + basis, + ::Type{uEltype} + ) where {NDIMS, RealT <: Real, uEltype <: Real} + nelements = prod(size(mesh)) + node_coordinates = Array{RealT, NDIMS + 2}( + undef, NDIMS, + ntuple(_ -> nnodes(basis), NDIMS)..., + nelements + ) + left_neighbors = Array{Int, 2}(undef, NDIMS, nelements) + jacobian_matrix = Array{RealT, NDIMS + 3}( + undef, NDIMS, NDIMS, + ntuple(_ -> nnodes(basis), NDIMS)..., + nelements + ) + contravariant_vectors = similar(jacobian_matrix) + inverse_jacobian = Array{RealT, NDIMS + 1}( + undef, + ntuple(_ -> nnodes(basis), NDIMS)..., + nelements + ) + surface_flux_values = Array{uEltype, NDIMS + 2}( + undef, nvariables(equations), + ntuple( + _ -> nnodes(basis), + NDIMS - 1 + )..., NDIMS * 2, + nelements + ) - elements = ElementContainer{NDIMS, RealT, uEltype, NDIMS + 1, NDIMS + 2, NDIMS + 3}(node_coordinates, - left_neighbors, - jacobian_matrix, - contravariant_vectors, - inverse_jacobian, - surface_flux_values) + elements = ElementContainer{NDIMS, RealT, uEltype, NDIMS + 1, NDIMS + 2, NDIMS + 3}( + node_coordinates, + left_neighbors, + jacobian_matrix, + contravariant_vectors, + inverse_jacobian, + surface_flux_values + ) - init_elements!(elements, mesh, basis) - return elements -end + init_elements!(elements, mesh, basis) + return elements + end -@inline nelements(elements::ElementContainer) = size(elements.left_neighbors, 2) -@inline Base.ndims(::ElementContainer{NDIMS}) where {NDIMS} = NDIMS + @inline nelements(elements::ElementContainer) = size(elements.left_neighbors, 2) + @inline Base.ndims(::ElementContainer{NDIMS}) where {NDIMS} = NDIMS -function Base.eltype(::ElementContainer{NDIMS, RealT, uEltype}) where {NDIMS, RealT, - uEltype} - uEltype -end + function Base.eltype(::ElementContainer{NDIMS, RealT, uEltype}) where { + NDIMS, RealT, + uEltype, + } + uEltype + end -include("containers_1d.jl") -include("containers_2d.jl") -include("containers_3d.jl") + include("containers_1d.jl") + include("containers_2d.jl") + include("containers_3d.jl") end # @muladd diff --git a/src/solvers/dgsem_structured/containers_1d.jl b/src/solvers/dgsem_structured/containers_1d.jl index 1a1bb183cb..dfb70477b6 100644 --- a/src/solvers/dgsem_structured/containers_1d.jl +++ b/src/solvers/dgsem_structured/containers_1d.jl @@ -3,83 +3,91 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Initialize data structures in element container -function init_elements!(elements, mesh::StructuredMesh{1}, basis::LobattoLegendreBasis) - @unpack node_coordinates, left_neighbors, - jacobian_matrix, contravariant_vectors, inverse_jacobian = elements + # Initialize data structures in element container + function init_elements!(elements, mesh::StructuredMesh{1}, basis::LobattoLegendreBasis) + @unpack node_coordinates, left_neighbors, + jacobian_matrix, contravariant_vectors, inverse_jacobian = elements - # Calculate node coordinates, Jacobian matrix, and inverse Jacobian determinant - for cell_x in 1:size(mesh, 1) - calc_node_coordinates!(node_coordinates, cell_x, mesh.mapping, mesh, basis) + # Calculate node coordinates, Jacobian matrix, and inverse Jacobian determinant + for cell_x in 1:size(mesh, 1) + calc_node_coordinates!(node_coordinates, cell_x, mesh.mapping, mesh, basis) - calc_jacobian_matrix!(jacobian_matrix, cell_x, node_coordinates, basis) + calc_jacobian_matrix!(jacobian_matrix, cell_x, node_coordinates, basis) - calc_inverse_jacobian!(inverse_jacobian, cell_x, jacobian_matrix) - end + calc_inverse_jacobian!(inverse_jacobian, cell_x, jacobian_matrix) + end - # Contravariant vectors don't make sense in 1D, they would be identical to inverse_jacobian - fill!(contravariant_vectors, NaN) + # Contravariant vectors don't make sense in 1D, they would be identical to inverse_jacobian + fill!(contravariant_vectors, NaN) - initialize_left_neighbor_connectivity!(left_neighbors, mesh) + initialize_left_neighbor_connectivity!(left_neighbors, mesh) - return nothing -end + return nothing + end -# Calculate physical coordinates to which every node of the reference element is mapped -# `mesh.mapping` is passed as an additional argument for type stability (function barrier) -function calc_node_coordinates!(node_coordinates, cell_x, mapping, - mesh::StructuredMesh{1}, - basis::LobattoLegendreBasis) - @unpack nodes = basis + # Calculate physical coordinates to which every node of the reference element is mapped + # `mesh.mapping` is passed as an additional argument for type stability (function barrier) + function calc_node_coordinates!( + node_coordinates, cell_x, mapping, + mesh::StructuredMesh{1}, + basis::LobattoLegendreBasis + ) + @unpack nodes = basis + + # Get cell length in reference mesh + dx = 2 / size(mesh, 1) + + # Calculate node coordinates of reference mesh + cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 + + for i in eachnode(basis) + # node_coordinates are the mapped reference node_coordinates + node_coordinates[1, i, cell_x] = mapping(cell_x_offset + dx / 2 * nodes[i])[1] + end + end - # Get cell length in reference mesh - dx = 2 / size(mesh, 1) + # Calculate Jacobian matrix of the mapping from the reference element to the element in the physical domain + function calc_jacobian_matrix!( + jacobian_matrix, element, + node_coordinates::AbstractArray{<:Any, 3}, + basis::LobattoLegendreBasis + ) + @views mul!( + jacobian_matrix[1, 1, :, element], basis.derivative_matrix, + node_coordinates[1, :, element] + ) # x_ξ + + return jacobian_matrix + end - # Calculate node coordinates of reference mesh - cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 + # Calculate inverse Jacobian (determinant of Jacobian matrix of the mapping) in each node + function calc_inverse_jacobian!( + inverse_jacobian::AbstractArray{<:Any, 2}, element, + jacobian_matrix + ) + @views inverse_jacobian[:, element] .= inv.(jacobian_matrix[1, 1, :, element]) - for i in eachnode(basis) - # node_coordinates are the mapped reference node_coordinates - node_coordinates[1, i, cell_x] = mapping(cell_x_offset + dx / 2 * nodes[i])[1] - end -end - -# Calculate Jacobian matrix of the mapping from the reference element to the element in the physical domain -function calc_jacobian_matrix!(jacobian_matrix, element, - node_coordinates::AbstractArray{<:Any, 3}, - basis::LobattoLegendreBasis) - @views mul!(jacobian_matrix[1, 1, :, element], basis.derivative_matrix, - node_coordinates[1, :, element]) # x_ξ - - return jacobian_matrix -end - -# Calculate inverse Jacobian (determinant of Jacobian matrix of the mapping) in each node -function calc_inverse_jacobian!(inverse_jacobian::AbstractArray{<:Any, 2}, element, - jacobian_matrix) - @views inverse_jacobian[:, element] .= inv.(jacobian_matrix[1, 1, :, element]) - - return inverse_jacobian -end - -# Save id of left neighbor of every element -function initialize_left_neighbor_connectivity!(left_neighbors, mesh::StructuredMesh{1}) - # Neighbors in x-direction - # Inner elements - for cell_x in 2:size(mesh, 1) - left_neighbors[1, cell_x] = cell_x - 1 + return inverse_jacobian end - if isperiodic(mesh) - # Periodic boundary - left_neighbors[1, 1] = size(mesh, 1) - else - # Use boundary conditions - left_neighbors[1, 1] = 0 + # Save id of left neighbor of every element + function initialize_left_neighbor_connectivity!(left_neighbors, mesh::StructuredMesh{1}) + # Neighbors in x-direction + # Inner elements + for cell_x in 2:size(mesh, 1) + left_neighbors[1, cell_x] = cell_x - 1 + end + + if isperiodic(mesh) + # Periodic boundary + left_neighbors[1, 1] = size(mesh, 1) + else + # Use boundary conditions + left_neighbors[1, 1] = 0 + end + + return left_neighbors end - - return left_neighbors -end end # @muladd diff --git a/src/solvers/dgsem_structured/containers_2d.jl b/src/solvers/dgsem_structured/containers_2d.jl index 8a0722fc5d..3e5b823d77 100644 --- a/src/solvers/dgsem_structured/containers_2d.jl +++ b/src/solvers/dgsem_structured/containers_2d.jl @@ -3,190 +3,218 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Initialize data structures in element container -function init_elements!(elements, mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, - basis::LobattoLegendreBasis) - @unpack node_coordinates, left_neighbors, - jacobian_matrix, contravariant_vectors, inverse_jacobian = elements + # Initialize data structures in element container + function init_elements!( + elements, mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, + basis::LobattoLegendreBasis + ) + @unpack node_coordinates, left_neighbors, + jacobian_matrix, contravariant_vectors, inverse_jacobian = elements - linear_indices = LinearIndices(size(mesh)) + linear_indices = LinearIndices(size(mesh)) - # Calculate node coordinates, Jacobian matrix, and inverse Jacobian determinant - for cell_y in 1:size(mesh, 2), cell_x in 1:size(mesh, 1) - element = linear_indices[cell_x, cell_y] - - calc_node_coordinates!(node_coordinates, element, cell_x, cell_y, mesh.mapping, - mesh, basis) - - calc_jacobian_matrix!(jacobian_matrix, element, node_coordinates, basis) - - calc_contravariant_vectors!(contravariant_vectors, element, jacobian_matrix) - - calc_inverse_jacobian!(inverse_jacobian, element, jacobian_matrix) - end + # Calculate node coordinates, Jacobian matrix, and inverse Jacobian determinant + for cell_y in 1:size(mesh, 2), cell_x in 1:size(mesh, 1) + element = linear_indices[cell_x, cell_y] - initialize_left_neighbor_connectivity!(left_neighbors, mesh, linear_indices) + calc_node_coordinates!( + node_coordinates, element, cell_x, cell_y, mesh.mapping, + mesh, basis + ) - return nothing -end + calc_jacobian_matrix!(jacobian_matrix, element, node_coordinates, basis) -# Calculate physical coordinates to which every node of the reference element is mapped -# `mesh.mapping` is passed as an additional argument for type stability (function barrier) -function calc_node_coordinates!(node_coordinates, element, - cell_x, cell_y, mapping, - mesh::StructuredMesh{2}, - basis::LobattoLegendreBasis) - @unpack nodes = basis + calc_contravariant_vectors!(contravariant_vectors, element, jacobian_matrix) - # Get cell length in reference mesh - dx = 2 / size(mesh, 1) - dy = 2 / size(mesh, 2) + calc_inverse_jacobian!(inverse_jacobian, element, jacobian_matrix) + end - # Calculate node coordinates of reference mesh - cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 - cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 + initialize_left_neighbor_connectivity!(left_neighbors, mesh, linear_indices) - for j in eachnode(basis), i in eachnode(basis) - # node_coordinates are the mapped reference node_coordinates - node_coordinates[:, i, j, element] .= mapping(cell_x_offset + dx / 2 * nodes[i], - cell_y_offset + dy / 2 * nodes[j]) + return nothing end -end - -# Calculate Jacobian matrix of the mapping from the reference element to the element in the physical domain -function calc_jacobian_matrix!(jacobian_matrix, element, - node_coordinates::AbstractArray{<:Any, 4}, - basis::LobattoLegendreBasis) - @unpack derivative_matrix = basis - - # The code below is equivalent to the following matrix multiplications, which - # seem to end up calling generic linear algebra code from Julia. Thus, the - # optimized code below using `@turbo` is much faster. - # jacobian_matrix[1, 1, :, :, element] = derivative_matrix * node_coordinates[1, :, :, element] # x_ξ - # jacobian_matrix[2, 1, :, :, element] = derivative_matrix * node_coordinates[2, :, :, element] # y_ξ - # jacobian_matrix[1, 2, :, :, element] = node_coordinates[1, :, :, element] * derivative_matrix' # x_η - # jacobian_matrix[2, 2, :, :, element] = node_coordinates[2, :, :, element] * derivative_matrix' # y_η - - # x_ξ, y_ξ - @turbo for xy in indices((jacobian_matrix, node_coordinates), (1, 1)) - for j in indices((jacobian_matrix, node_coordinates), (4, 3)), - i in indices((jacobian_matrix, derivative_matrix), (3, 1)) - - result = zero(eltype(jacobian_matrix)) - for ii in indices((node_coordinates, derivative_matrix), (2, 2)) - result += derivative_matrix[i, ii] * - node_coordinates[xy, ii, j, element] - end - jacobian_matrix[xy, 1, i, j, element] = result + + # Calculate physical coordinates to which every node of the reference element is mapped + # `mesh.mapping` is passed as an additional argument for type stability (function barrier) + function calc_node_coordinates!( + node_coordinates, element, + cell_x, cell_y, mapping, + mesh::StructuredMesh{2}, + basis::LobattoLegendreBasis + ) + @unpack nodes = basis + + # Get cell length in reference mesh + dx = 2 / size(mesh, 1) + dy = 2 / size(mesh, 2) + + # Calculate node coordinates of reference mesh + cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 + cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 + + for j in eachnode(basis), i in eachnode(basis) + # node_coordinates are the mapped reference node_coordinates + node_coordinates[:, i, j, element] .= mapping( + cell_x_offset + dx / 2 * nodes[i], + cell_y_offset + dy / 2 * nodes[j] + ) end end - # x_η, y_η - @turbo for xy in indices((jacobian_matrix, node_coordinates), (1, 1)) - for j in indices((jacobian_matrix, derivative_matrix), (4, 1)), - i in indices((jacobian_matrix, node_coordinates), (3, 2)) + # Calculate Jacobian matrix of the mapping from the reference element to the element in the physical domain + function calc_jacobian_matrix!( + jacobian_matrix, element, + node_coordinates::AbstractArray{<:Any, 4}, + basis::LobattoLegendreBasis + ) + @unpack derivative_matrix = basis + + # The code below is equivalent to the following matrix multiplications, which + # seem to end up calling generic linear algebra code from Julia. Thus, the + # optimized code below using `@turbo` is much faster. + # jacobian_matrix[1, 1, :, :, element] = derivative_matrix * node_coordinates[1, :, :, element] # x_ξ + # jacobian_matrix[2, 1, :, :, element] = derivative_matrix * node_coordinates[2, :, :, element] # y_ξ + # jacobian_matrix[1, 2, :, :, element] = node_coordinates[1, :, :, element] * derivative_matrix' # x_η + # jacobian_matrix[2, 2, :, :, element] = node_coordinates[2, :, :, element] * derivative_matrix' # y_η + + # x_ξ, y_ξ + @turbo for xy in indices((jacobian_matrix, node_coordinates), (1, 1)) + for j in indices((jacobian_matrix, node_coordinates), (4, 3)), + i in indices((jacobian_matrix, derivative_matrix), (3, 1)) + + result = zero(eltype(jacobian_matrix)) + for ii in indices((node_coordinates, derivative_matrix), (2, 2)) + result += derivative_matrix[i, ii] * + node_coordinates[xy, ii, j, element] + end + jacobian_matrix[xy, 1, i, j, element] = result + end + end - result = zero(eltype(jacobian_matrix)) - for jj in indices((node_coordinates, derivative_matrix), (3, 2)) - result += derivative_matrix[j, jj] * - node_coordinates[xy, i, jj, element] + # x_η, y_η + @turbo for xy in indices((jacobian_matrix, node_coordinates), (1, 1)) + for j in indices((jacobian_matrix, derivative_matrix), (4, 1)), + i in indices((jacobian_matrix, node_coordinates), (3, 2)) + + result = zero(eltype(jacobian_matrix)) + for jj in indices((node_coordinates, derivative_matrix), (3, 2)) + result += derivative_matrix[j, jj] * + node_coordinates[xy, i, jj, element] + end + jacobian_matrix[xy, 2, i, j, element] = result end - jacobian_matrix[xy, 2, i, j, element] = result end - end - return jacobian_matrix -end - -# Calculate contravarant vectors, multiplied by the Jacobian determinant J of the transformation mapping. -# Those are called Ja^i in Kopriva's blue book. -function calc_contravariant_vectors!(contravariant_vectors::AbstractArray{<:Any, 5}, - element, jacobian_matrix) - # The code below is equivalent to the following using broadcasting but much faster. - # # First contravariant vector Ja^1 - # contravariant_vectors[1, 1, :, :, element] = jacobian_matrix[2, 2, :, :, element] - # contravariant_vectors[2, 1, :, :, element] = -jacobian_matrix[1, 2, :, :, element] - # # Second contravariant vector Ja^2 - # contravariant_vectors[1, 2, :, :, element] = -jacobian_matrix[2, 1, :, :, element] - # contravariant_vectors[2, 2, :, :, element] = jacobian_matrix[1, 1, :, :, element] - - @turbo for j in indices((contravariant_vectors, jacobian_matrix), (4, 4)), - i in indices((contravariant_vectors, jacobian_matrix), (3, 3)) - # First contravariant vector Ja^1 - contravariant_vectors[1, 1, i, j, element] = jacobian_matrix[2, 2, i, j, - element] - contravariant_vectors[2, 1, i, j, element] = -jacobian_matrix[1, 2, i, j, - element] - - # Second contravariant vector Ja^2 - contravariant_vectors[1, 2, i, j, element] = -jacobian_matrix[2, 1, i, j, - element] - contravariant_vectors[2, 2, i, j, element] = jacobian_matrix[1, 1, i, j, - element] + return jacobian_matrix end - return contravariant_vectors -end - -# Calculate inverse Jacobian (determinant of Jacobian matrix of the mapping) in each node -function calc_inverse_jacobian!(inverse_jacobian::AbstractArray{<:Any, 3}, element, - jacobian_matrix) - # The code below is equivalent to the following high-level code but much faster. - # inverse_jacobian[i, j, element] = inv(det(jacobian_matrix[:, :, i, j, element]) - - @turbo for j in indices((inverse_jacobian, jacobian_matrix), (2, 4)), - i in indices((inverse_jacobian, jacobian_matrix), (1, 3)) + # Calculate contravarant vectors, multiplied by the Jacobian determinant J of the transformation mapping. + # Those are called Ja^i in Kopriva's blue book. + function calc_contravariant_vectors!( + contravariant_vectors::AbstractArray{<:Any, 5}, + element, jacobian_matrix + ) + # The code below is equivalent to the following using broadcasting but much faster. + # # First contravariant vector Ja^1 + # contravariant_vectors[1, 1, :, :, element] = jacobian_matrix[2, 2, :, :, element] + # contravariant_vectors[2, 1, :, :, element] = -jacobian_matrix[1, 2, :, :, element] + # # Second contravariant vector Ja^2 + # contravariant_vectors[1, 2, :, :, element] = -jacobian_matrix[2, 1, :, :, element] + # contravariant_vectors[2, 2, :, :, element] = jacobian_matrix[1, 1, :, :, element] + + @turbo for j in indices((contravariant_vectors, jacobian_matrix), (4, 4)), + i in indices((contravariant_vectors, jacobian_matrix), (3, 3)) + # First contravariant vector Ja^1 + contravariant_vectors[1, 1, i, j, element] = jacobian_matrix[ + 2, 2, i, j, + element, + ] + contravariant_vectors[2, 1, i, j, element] = -jacobian_matrix[ + 1, 2, i, j, + element, + ] + + # Second contravariant vector Ja^2 + contravariant_vectors[1, 2, i, j, element] = -jacobian_matrix[ + 2, 1, i, j, + element, + ] + contravariant_vectors[2, 2, i, j, element] = jacobian_matrix[ + 1, 1, i, j, + element, + ] + end - inverse_jacobian[i, j, element] = inv(jacobian_matrix[1, 1, i, j, element] * - jacobian_matrix[2, 2, i, j, element] - - jacobian_matrix[1, 2, i, j, element] * - jacobian_matrix[2, 1, i, j, element]) + return contravariant_vectors end - return inverse_jacobian -end - -# Save id of left neighbor of every element -function initialize_left_neighbor_connectivity!(left_neighbors, - mesh::Union{StructuredMesh{2}, - StructuredMeshView{2}}, - linear_indices) - # Neighbors in x-direction - for cell_y in 1:size(mesh, 2) - # Inner elements - for cell_x in 2:size(mesh, 1) - element = linear_indices[cell_x, cell_y] - left_neighbors[1, element] = linear_indices[cell_x - 1, cell_y] + # Calculate inverse Jacobian (determinant of Jacobian matrix of the mapping) in each node + function calc_inverse_jacobian!( + inverse_jacobian::AbstractArray{<:Any, 3}, element, + jacobian_matrix + ) + # The code below is equivalent to the following high-level code but much faster. + # inverse_jacobian[i, j, element] = inv(det(jacobian_matrix[:, :, i, j, element]) + + @turbo for j in indices((inverse_jacobian, jacobian_matrix), (2, 4)), + i in indices((inverse_jacobian, jacobian_matrix), (1, 3)) + + inverse_jacobian[i, j, element] = inv( + jacobian_matrix[1, 1, i, j, element] * + jacobian_matrix[2, 2, i, j, element] - + jacobian_matrix[1, 2, i, j, element] * + jacobian_matrix[2, 1, i, j, element] + ) end - if isperiodic(mesh, 1) - # Periodic boundary - left_neighbors[1, linear_indices[1, cell_y]] = linear_indices[end, cell_y] - else - # Use boundary conditions - left_neighbors[1, linear_indices[1, cell_y]] = 0 - end + return inverse_jacobian end - # Neighbors in y-direction - for cell_x in 1:size(mesh, 1) - # Inner elements - for cell_y in 2:size(mesh, 2) - element = linear_indices[cell_x, cell_y] - left_neighbors[2, element] = linear_indices[cell_x, cell_y - 1] + # Save id of left neighbor of every element + function initialize_left_neighbor_connectivity!( + left_neighbors, + mesh::Union{ + StructuredMesh{2}, + StructuredMeshView{2}, + }, + linear_indices + ) + # Neighbors in x-direction + for cell_y in 1:size(mesh, 2) + # Inner elements + for cell_x in 2:size(mesh, 1) + element = linear_indices[cell_x, cell_y] + left_neighbors[1, element] = linear_indices[cell_x - 1, cell_y] + end + + if isperiodic(mesh, 1) + # Periodic boundary + left_neighbors[1, linear_indices[1, cell_y]] = linear_indices[end, cell_y] + else + # Use boundary conditions + left_neighbors[1, linear_indices[1, cell_y]] = 0 + end end - if isperiodic(mesh, 2) - # Periodic boundary - left_neighbors[2, linear_indices[cell_x, 1]] = linear_indices[cell_x, end] - else - # Use boundary conditions - left_neighbors[2, linear_indices[cell_x, 1]] = 0 + # Neighbors in y-direction + for cell_x in 1:size(mesh, 1) + # Inner elements + for cell_y in 2:size(mesh, 2) + element = linear_indices[cell_x, cell_y] + left_neighbors[2, element] = linear_indices[cell_x, cell_y - 1] + end + + if isperiodic(mesh, 2) + # Periodic boundary + left_neighbors[2, linear_indices[cell_x, 1]] = linear_indices[cell_x, end] + else + # Use boundary conditions + left_neighbors[2, linear_indices[cell_x, 1]] = 0 + end end - end - return left_neighbors -end + return left_neighbors + end end # @muladd diff --git a/src/solvers/dgsem_structured/containers_3d.jl b/src/solvers/dgsem_structured/containers_3d.jl index 75cc98bf2b..671fbe9c77 100644 --- a/src/solvers/dgsem_structured/containers_3d.jl +++ b/src/solvers/dgsem_structured/containers_3d.jl @@ -3,342 +3,402 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Initialize data structures in element container -function init_elements!(elements, mesh::StructuredMesh{3}, basis::LobattoLegendreBasis) - @unpack node_coordinates, left_neighbors, - jacobian_matrix, contravariant_vectors, inverse_jacobian = elements + # Initialize data structures in element container + function init_elements!(elements, mesh::StructuredMesh{3}, basis::LobattoLegendreBasis) + @unpack node_coordinates, left_neighbors, + jacobian_matrix, contravariant_vectors, inverse_jacobian = elements - linear_indices = LinearIndices(size(mesh)) + linear_indices = LinearIndices(size(mesh)) - # Calculate node coordinates, Jacobian matrix, and inverse Jacobian determinant - for cell_z in 1:size(mesh, 3), cell_y in 1:size(mesh, 2), cell_x in 1:size(mesh, 1) - element = linear_indices[cell_x, cell_y, cell_z] - - calc_node_coordinates!(node_coordinates, element, cell_x, cell_y, cell_z, - mesh.mapping, mesh, basis) + # Calculate node coordinates, Jacobian matrix, and inverse Jacobian determinant + for cell_z in 1:size(mesh, 3), cell_y in 1:size(mesh, 2), cell_x in 1:size(mesh, 1) + element = linear_indices[cell_x, cell_y, cell_z] - calc_jacobian_matrix!(jacobian_matrix, element, node_coordinates, basis) + calc_node_coordinates!( + node_coordinates, element, cell_x, cell_y, cell_z, + mesh.mapping, mesh, basis + ) - calc_contravariant_vectors!(contravariant_vectors, element, jacobian_matrix, - node_coordinates, basis) + calc_jacobian_matrix!(jacobian_matrix, element, node_coordinates, basis) - calc_inverse_jacobian!(inverse_jacobian, element, jacobian_matrix, basis) - end + calc_contravariant_vectors!( + contravariant_vectors, element, jacobian_matrix, + node_coordinates, basis + ) - initialize_left_neighbor_connectivity!(left_neighbors, mesh, linear_indices) - - return nothing -end - -# Calculate physical coordinates to which every node of the reference element is mapped -# `mesh.mapping` is passed as an additional argument for type stability (function barrier) -function calc_node_coordinates!(node_coordinates, element, - cell_x, cell_y, cell_z, - mapping, mesh::StructuredMesh{3}, - basis::LobattoLegendreBasis) - @unpack nodes = basis - - # Get cell length in reference mesh - dx = 2 / size(mesh, 1) - dy = 2 / size(mesh, 2) - dz = 2 / size(mesh, 3) - - # Calculate node coordinates of reference mesh - cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 - cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 - cell_z_offset = -1 + (cell_z - 1) * dz + dz / 2 - - for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) - # node_coordinates are the mapped reference node_coordinates - node_coordinates[:, i, j, k, element] .= mapping(cell_x_offset + - dx / 2 * nodes[i], - cell_y_offset + - dy / 2 * nodes[j], - cell_z_offset + - dz / 2 * nodes[k]) - end -end - -# Calculate Jacobian matrix of the mapping from the reference element to the element in the physical domain -function calc_jacobian_matrix!(jacobian_matrix::AbstractArray{<:Any, 6}, element, - node_coordinates, basis) - # The code below is equivalent to the following matrix multiplications but much faster. - # - # for dim in 1:3, j in eachnode(basis), i in eachnode(basis) - # # ∂/∂ξ - # jacobian_matrix[dim, 1, :, i, j, element] = basis.derivative_matrix * node_coordinates[dim, :, i, j, element] - # # ∂/∂η - # jacobian_matrix[dim, 2, i, :, j, element] = basis.derivative_matrix * node_coordinates[dim, i, :, j, element] - # # ∂/∂ζ - # jacobian_matrix[dim, 3, i, j, :, element] = basis.derivative_matrix * node_coordinates[dim, i, j, :, element] - # end - - @turbo for dim in 1:3, k in eachnode(basis), j in eachnode(basis), - i in eachnode(basis) - - result = zero(eltype(jacobian_matrix)) - - for ii in eachnode(basis) - result += basis.derivative_matrix[i, ii] * - node_coordinates[dim, ii, j, k, element] + calc_inverse_jacobian!(inverse_jacobian, element, jacobian_matrix, basis) end - jacobian_matrix[dim, 1, i, j, k, element] = result - end - - @turbo for dim in 1:3, k in eachnode(basis), j in eachnode(basis), - i in eachnode(basis) - - result = zero(eltype(jacobian_matrix)) + initialize_left_neighbor_connectivity!(left_neighbors, mesh, linear_indices) - for ii in eachnode(basis) - result += basis.derivative_matrix[j, ii] * - node_coordinates[dim, i, ii, k, element] - end - - jacobian_matrix[dim, 2, i, j, k, element] = result + return nothing end - @turbo for dim in 1:3, k in eachnode(basis), j in eachnode(basis), - i in eachnode(basis) - - result = zero(eltype(jacobian_matrix)) - - for ii in eachnode(basis) - result += basis.derivative_matrix[k, ii] * - node_coordinates[dim, i, j, ii, element] + # Calculate physical coordinates to which every node of the reference element is mapped + # `mesh.mapping` is passed as an additional argument for type stability (function barrier) + function calc_node_coordinates!( + node_coordinates, element, + cell_x, cell_y, cell_z, + mapping, mesh::StructuredMesh{3}, + basis::LobattoLegendreBasis + ) + @unpack nodes = basis + + # Get cell length in reference mesh + dx = 2 / size(mesh, 1) + dy = 2 / size(mesh, 2) + dz = 2 / size(mesh, 3) + + # Calculate node coordinates of reference mesh + cell_x_offset = -1 + (cell_x - 1) * dx + dx / 2 + cell_y_offset = -1 + (cell_y - 1) * dy + dy / 2 + cell_z_offset = -1 + (cell_z - 1) * dz + dz / 2 + + for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) + # node_coordinates are the mapped reference node_coordinates + node_coordinates[:, i, j, k, element] .= mapping( + cell_x_offset + + dx / 2 * nodes[i], + cell_y_offset + + dy / 2 * nodes[j], + cell_z_offset + + dz / 2 * nodes[k] + ) end - - jacobian_matrix[dim, 3, i, j, k, element] = result end - return jacobian_matrix -end - -# Calculate contravariant vectors, multiplied by the Jacobian determinant J of the transformation mapping, -# using the invariant curl form. -# These are called Ja^i in Kopriva's blue book. -function calc_contravariant_vectors!(contravariant_vectors::AbstractArray{<:Any, 6}, - element, - jacobian_matrix, node_coordinates, - basis::LobattoLegendreBasis) - @unpack derivative_matrix = basis - - # The general form is - # Jaⁱₙ = 0.5 * ( ∇ × (Xₘ ∇ Xₗ - Xₗ ∇ Xₘ) )ᵢ where (n, m, l) cyclic and ∇ = (∂/∂ξ, ∂/∂η, ∂/∂ζ)ᵀ - - for n in 1:3 - # (n, m, l) cyclic - m = (n % 3) + 1 - l = ((n + 1) % 3) + 1 - - # Calculate Ja¹ₙ = 0.5 * [ (Xₘ Xₗ_ζ - Xₗ Xₘ_ζ)_η - (Xₘ Xₗ_η - Xₗ Xₘ_η)_ζ ] - # For each of these, the first and second summand are computed in separate loops - # for performance reasons. - - # First summand 0.5 * (Xₘ Xₗ_ζ - Xₗ Xₘ_ζ)_η - @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) - result = zero(eltype(contravariant_vectors)) + # Calculate Jacobian matrix of the mapping from the reference element to the element in the physical domain + function calc_jacobian_matrix!( + jacobian_matrix::AbstractArray{<:Any, 6}, element, + node_coordinates, basis + ) + # The code below is equivalent to the following matrix multiplications but much faster. + # + # for dim in 1:3, j in eachnode(basis), i in eachnode(basis) + # # ∂/∂ξ + # jacobian_matrix[dim, 1, :, i, j, element] = basis.derivative_matrix * node_coordinates[dim, :, i, j, element] + # # ∂/∂η + # jacobian_matrix[dim, 2, i, :, j, element] = basis.derivative_matrix * node_coordinates[dim, i, :, j, element] + # # ∂/∂ζ + # jacobian_matrix[dim, 3, i, j, :, element] = basis.derivative_matrix * node_coordinates[dim, i, j, :, element] + # end + + @turbo for dim in 1:3, k in eachnode(basis), j in eachnode(basis), + i in eachnode(basis) + + result = zero(eltype(jacobian_matrix)) for ii in eachnode(basis) - # Multiply derivative_matrix to j-dimension to differentiate wrt η - result += 0.5f0 * derivative_matrix[j, ii] * - (node_coordinates[m, i, ii, k, element] * - jacobian_matrix[l, 3, i, ii, k, element] - - node_coordinates[l, i, ii, k, element] * - jacobian_matrix[m, 3, i, ii, k, element]) + result += basis.derivative_matrix[i, ii] * + node_coordinates[dim, ii, j, k, element] end - contravariant_vectors[n, 1, i, j, k, element] = result + jacobian_matrix[dim, 1, i, j, k, element] = result end - # Second summand -0.5 * (Xₘ Xₗ_η - Xₗ Xₘ_η)_ζ - @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) - result = zero(eltype(contravariant_vectors)) + @turbo for dim in 1:3, k in eachnode(basis), j in eachnode(basis), + i in eachnode(basis) + + result = zero(eltype(jacobian_matrix)) for ii in eachnode(basis) - # Multiply derivative_matrix to k-dimension to differentiate wrt ζ - result += 0.5f0 * derivative_matrix[k, ii] * - (node_coordinates[m, i, j, ii, element] * - jacobian_matrix[l, 2, i, j, ii, element] - - node_coordinates[l, i, j, ii, element] * - jacobian_matrix[m, 2, i, j, ii, element]) + result += basis.derivative_matrix[j, ii] * + node_coordinates[dim, i, ii, k, element] end - contravariant_vectors[n, 1, i, j, k, element] -= result + jacobian_matrix[dim, 2, i, j, k, element] = result end - # Calculate Ja²ₙ = 0.5 * [ (Xₘ Xₗ_ξ - Xₗ Xₘ_ξ)_ζ - (Xₘ Xₗ_ζ - Xₗ Xₘ_ζ)_ξ ] + @turbo for dim in 1:3, k in eachnode(basis), j in eachnode(basis), + i in eachnode(basis) - # First summand 0.5 * (Xₘ Xₗ_ξ - Xₗ Xₘ_ξ)_ζ - @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) - result = zero(eltype(contravariant_vectors)) + result = zero(eltype(jacobian_matrix)) for ii in eachnode(basis) - # Multiply derivative_matrix to k-dimension to differentiate wrt ζ - result += 0.5f0 * derivative_matrix[k, ii] * - (node_coordinates[m, i, j, ii, element] * - jacobian_matrix[l, 1, i, j, ii, element] - - node_coordinates[l, i, j, ii, element] * - jacobian_matrix[m, 1, i, j, ii, element]) + result += basis.derivative_matrix[k, ii] * + node_coordinates[dim, i, j, ii, element] end - contravariant_vectors[n, 2, i, j, k, element] = result + jacobian_matrix[dim, 3, i, j, k, element] = result end - # Second summand -0.5 * (Xₘ Xₗ_ζ - Xₗ Xₘ_ζ)_ξ - @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) - result = zero(eltype(contravariant_vectors)) + return jacobian_matrix + end - for ii in eachnode(basis) - # Multiply derivative_matrix to i-dimension to differentiate wrt ξ - result += 0.5f0 * derivative_matrix[i, ii] * - (node_coordinates[m, ii, j, k, element] * - jacobian_matrix[l, 3, ii, j, k, element] - - node_coordinates[l, ii, j, k, element] * - jacobian_matrix[m, 3, ii, j, k, element]) + # Calculate contravariant vectors, multiplied by the Jacobian determinant J of the transformation mapping, + # using the invariant curl form. + # These are called Ja^i in Kopriva's blue book. + function calc_contravariant_vectors!( + contravariant_vectors::AbstractArray{<:Any, 6}, + element, + jacobian_matrix, node_coordinates, + basis::LobattoLegendreBasis + ) + @unpack derivative_matrix = basis + + # The general form is + # Jaⁱₙ = 0.5 * ( ∇ × (Xₘ ∇ Xₗ - Xₗ ∇ Xₘ) )ᵢ where (n, m, l) cyclic and ∇ = (∂/∂ξ, ∂/∂η, ∂/∂ζ)ᵀ + + for n in 1:3 + # (n, m, l) cyclic + m = (n % 3) + 1 + l = ((n + 1) % 3) + 1 + + # Calculate Ja¹ₙ = 0.5 * [ (Xₘ Xₗ_ζ - Xₗ Xₘ_ζ)_η - (Xₘ Xₗ_η - Xₗ Xₘ_η)_ζ ] + # For each of these, the first and second summand are computed in separate loops + # for performance reasons. + + # First summand 0.5 * (Xₘ Xₗ_ζ - Xₗ Xₘ_ζ)_η + @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) + result = zero(eltype(contravariant_vectors)) + + for ii in eachnode(basis) + # Multiply derivative_matrix to j-dimension to differentiate wrt η + result += 0.5f0 * derivative_matrix[j, ii] * + ( + node_coordinates[m, i, ii, k, element] * + jacobian_matrix[l, 3, i, ii, k, element] - + node_coordinates[l, i, ii, k, element] * + jacobian_matrix[m, 3, i, ii, k, element] + ) + end + + contravariant_vectors[n, 1, i, j, k, element] = result end - contravariant_vectors[n, 2, i, j, k, element] -= result - end + # Second summand -0.5 * (Xₘ Xₗ_η - Xₗ Xₘ_η)_ζ + @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) + result = zero(eltype(contravariant_vectors)) + + for ii in eachnode(basis) + # Multiply derivative_matrix to k-dimension to differentiate wrt ζ + result += 0.5f0 * derivative_matrix[k, ii] * + ( + node_coordinates[m, i, j, ii, element] * + jacobian_matrix[l, 2, i, j, ii, element] - + node_coordinates[l, i, j, ii, element] * + jacobian_matrix[m, 2, i, j, ii, element] + ) + end + + contravariant_vectors[n, 1, i, j, k, element] -= result + end - # Calculate Ja³ₙ = 0.5 * [ (Xₘ Xₗ_η - Xₗ Xₘ_η)_ξ - (Xₘ Xₗ_ξ - Xₗ Xₘ_ξ)_η ] + # Calculate Ja²ₙ = 0.5 * [ (Xₘ Xₗ_ξ - Xₗ Xₘ_ξ)_ζ - (Xₘ Xₗ_ζ - Xₗ Xₘ_ζ)_ξ ] - # First summand 0.5 * (Xₘ Xₗ_η - Xₗ Xₘ_η)_ξ - @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) - result = zero(eltype(contravariant_vectors)) + # First summand 0.5 * (Xₘ Xₗ_ξ - Xₗ Xₘ_ξ)_ζ + @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) + result = zero(eltype(contravariant_vectors)) - for ii in eachnode(basis) - # Multiply derivative_matrix to i-dimension to differentiate wrt ξ - result += 0.5f0 * derivative_matrix[i, ii] * - (node_coordinates[m, ii, j, k, element] * - jacobian_matrix[l, 2, ii, j, k, element] - - node_coordinates[l, ii, j, k, element] * - jacobian_matrix[m, 2, ii, j, k, element]) + for ii in eachnode(basis) + # Multiply derivative_matrix to k-dimension to differentiate wrt ζ + result += 0.5f0 * derivative_matrix[k, ii] * + ( + node_coordinates[m, i, j, ii, element] * + jacobian_matrix[l, 1, i, j, ii, element] - + node_coordinates[l, i, j, ii, element] * + jacobian_matrix[m, 1, i, j, ii, element] + ) + end + + contravariant_vectors[n, 2, i, j, k, element] = result end - contravariant_vectors[n, 3, i, j, k, element] = result - end + # Second summand -0.5 * (Xₘ Xₗ_ζ - Xₗ Xₘ_ζ)_ξ + @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) + result = zero(eltype(contravariant_vectors)) + + for ii in eachnode(basis) + # Multiply derivative_matrix to i-dimension to differentiate wrt ξ + result += 0.5f0 * derivative_matrix[i, ii] * + ( + node_coordinates[m, ii, j, k, element] * + jacobian_matrix[l, 3, ii, j, k, element] - + node_coordinates[l, ii, j, k, element] * + jacobian_matrix[m, 3, ii, j, k, element] + ) + end + + contravariant_vectors[n, 2, i, j, k, element] -= result + end - # Second summand -0.5 * (Xₘ Xₗ_ξ - Xₗ Xₘ_ξ)_η - @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) - result = zero(eltype(contravariant_vectors)) + # Calculate Ja³ₙ = 0.5 * [ (Xₘ Xₗ_η - Xₗ Xₘ_η)_ξ - (Xₘ Xₗ_ξ - Xₗ Xₘ_ξ)_η ] - for ii in eachnode(basis) - # Multiply derivative_matrix to j-dimension to differentiate wrt η - result += 0.5f0 * derivative_matrix[j, ii] * - (node_coordinates[m, i, ii, k, element] * - jacobian_matrix[l, 1, i, ii, k, element] - - node_coordinates[l, i, ii, k, element] * - jacobian_matrix[m, 1, i, ii, k, element]) + # First summand 0.5 * (Xₘ Xₗ_η - Xₗ Xₘ_η)_ξ + @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) + result = zero(eltype(contravariant_vectors)) + + for ii in eachnode(basis) + # Multiply derivative_matrix to i-dimension to differentiate wrt ξ + result += 0.5f0 * derivative_matrix[i, ii] * + ( + node_coordinates[m, ii, j, k, element] * + jacobian_matrix[l, 2, ii, j, k, element] - + node_coordinates[l, ii, j, k, element] * + jacobian_matrix[m, 2, ii, j, k, element] + ) + end + + contravariant_vectors[n, 3, i, j, k, element] = result end - contravariant_vectors[n, 3, i, j, k, element] -= result + # Second summand -0.5 * (Xₘ Xₗ_ξ - Xₗ Xₘ_ξ)_η + @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) + result = zero(eltype(contravariant_vectors)) + + for ii in eachnode(basis) + # Multiply derivative_matrix to j-dimension to differentiate wrt η + result += 0.5f0 * derivative_matrix[j, ii] * + ( + node_coordinates[m, i, ii, k, element] * + jacobian_matrix[l, 1, i, ii, k, element] - + node_coordinates[l, i, ii, k, element] * + jacobian_matrix[m, 1, i, ii, k, element] + ) + end + + contravariant_vectors[n, 3, i, j, k, element] -= result + end end - end - return contravariant_vectors -end - -# Calculate inverse Jacobian (determinant of Jacobian matrix of the mapping) in each node -function calc_inverse_jacobian!(inverse_jacobian::AbstractArray{<:Any, 4}, element, - jacobian_matrix, basis) - @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) - # Calculate Determinant by using Sarrus formula (about 100 times faster than LinearAlgebra.det()) - inverse_jacobian[i, j, k, element] = inv(jacobian_matrix[1, 1, i, j, k, - element] * - jacobian_matrix[2, 2, i, j, k, - element] * - jacobian_matrix[3, 3, i, j, k, element] + - jacobian_matrix[1, 2, i, j, k, - element] * - jacobian_matrix[2, 3, i, j, k, - element] * - jacobian_matrix[3, 1, i, j, k, element] + - jacobian_matrix[1, 3, i, j, k, - element] * - jacobian_matrix[2, 1, i, j, k, - element] * - jacobian_matrix[3, 2, i, j, k, element] - - jacobian_matrix[3, 1, i, j, k, - element] * - jacobian_matrix[2, 2, i, j, k, - element] * - jacobian_matrix[1, 3, i, j, k, element] - - jacobian_matrix[3, 2, i, j, k, - element] * - jacobian_matrix[2, 3, i, j, k, - element] * - jacobian_matrix[1, 1, i, j, k, element] - - jacobian_matrix[3, 3, i, j, k, - element] * - jacobian_matrix[2, 1, i, j, k, - element] * - jacobian_matrix[1, 2, i, j, k, element]) + return contravariant_vectors end - return inverse_jacobian -end - -# Save id of left neighbor of every element -function initialize_left_neighbor_connectivity!(left_neighbors, mesh::StructuredMesh{3}, - linear_indices) - # Neighbors in x-direction - for cell_z in 1:size(mesh, 3), cell_y in 1:size(mesh, 2) - # Inner elements - for cell_x in 2:size(mesh, 1) - element = linear_indices[cell_x, cell_y, cell_z] - left_neighbors[1, element] = linear_indices[cell_x - 1, cell_y, cell_z] + # Calculate inverse Jacobian (determinant of Jacobian matrix of the mapping) in each node + function calc_inverse_jacobian!( + inverse_jacobian::AbstractArray{<:Any, 4}, element, + jacobian_matrix, basis + ) + @turbo for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) + # Calculate Determinant by using Sarrus formula (about 100 times faster than LinearAlgebra.det()) + inverse_jacobian[i, j, k, element] = inv( + jacobian_matrix[ + 1, 1, i, j, k, + element, + ] * + jacobian_matrix[ + 2, 2, i, j, k, + element, + ] * + jacobian_matrix[3, 3, i, j, k, element] + + jacobian_matrix[ + 1, 2, i, j, k, + element, + ] * + jacobian_matrix[ + 2, 3, i, j, k, + element, + ] * + jacobian_matrix[3, 1, i, j, k, element] + + jacobian_matrix[ + 1, 3, i, j, k, + element, + ] * + jacobian_matrix[ + 2, 1, i, j, k, + element, + ] * + jacobian_matrix[3, 2, i, j, k, element] - + jacobian_matrix[ + 3, 1, i, j, k, + element, + ] * + jacobian_matrix[ + 2, 2, i, j, k, + element, + ] * + jacobian_matrix[1, 3, i, j, k, element] - + jacobian_matrix[ + 3, 2, i, j, k, + element, + ] * + jacobian_matrix[ + 2, 3, i, j, k, + element, + ] * + jacobian_matrix[1, 1, i, j, k, element] - + jacobian_matrix[ + 3, 3, i, j, k, + element, + ] * + jacobian_matrix[ + 2, 1, i, j, k, + element, + ] * + jacobian_matrix[1, 2, i, j, k, element] + ) end - if isperiodic(mesh, 1) - # Periodic boundary - left_neighbors[1, linear_indices[1, cell_y, cell_z]] = linear_indices[end, - cell_y, - cell_z] - else - left_neighbors[1, linear_indices[1, cell_y, cell_z]] = 0 - end + return inverse_jacobian end - # Neighbors in y-direction - for cell_z in 1:size(mesh, 3), cell_x in 1:size(mesh, 1) - # Inner elements - for cell_y in 2:size(mesh, 2) - element = linear_indices[cell_x, cell_y, cell_z] - left_neighbors[2, element] = linear_indices[cell_x, cell_y - 1, cell_z] - end + # Save id of left neighbor of every element + function initialize_left_neighbor_connectivity!( + left_neighbors, mesh::StructuredMesh{3}, + linear_indices + ) + # Neighbors in x-direction + for cell_z in 1:size(mesh, 3), cell_y in 1:size(mesh, 2) + # Inner elements + for cell_x in 2:size(mesh, 1) + element = linear_indices[cell_x, cell_y, cell_z] + left_neighbors[1, element] = linear_indices[cell_x - 1, cell_y, cell_z] + end - if isperiodic(mesh, 2) - # Periodic boundary - left_neighbors[2, linear_indices[cell_x, 1, cell_z]] = linear_indices[cell_x, - end, - cell_z] - else - left_neighbors[2, linear_indices[cell_x, 1, cell_z]] = 0 + if isperiodic(mesh, 1) + # Periodic boundary + left_neighbors[1, linear_indices[1, cell_y, cell_z]] = linear_indices[ + end, + cell_y, + cell_z, + ] + else + left_neighbors[1, linear_indices[1, cell_y, cell_z]] = 0 + end end - end - # Neighbors in z-direction - for cell_y in 1:size(mesh, 2), cell_x in 1:size(mesh, 1) - # Inner elements - for cell_z in 2:size(mesh, 3) - element = linear_indices[cell_x, cell_y, cell_z] - left_neighbors[3, element] = linear_indices[cell_x, cell_y, cell_z - 1] + # Neighbors in y-direction + for cell_z in 1:size(mesh, 3), cell_x in 1:size(mesh, 1) + # Inner elements + for cell_y in 2:size(mesh, 2) + element = linear_indices[cell_x, cell_y, cell_z] + left_neighbors[2, element] = linear_indices[cell_x, cell_y - 1, cell_z] + end + + if isperiodic(mesh, 2) + # Periodic boundary + left_neighbors[2, linear_indices[cell_x, 1, cell_z]] = linear_indices[ + cell_x, + end, + cell_z, + ] + else + left_neighbors[2, linear_indices[cell_x, 1, cell_z]] = 0 + end end - if isperiodic(mesh, 3) - # Periodic boundary - left_neighbors[3, linear_indices[cell_x, cell_y, 1]] = linear_indices[cell_x, - cell_y, - end] - else - left_neighbors[3, linear_indices[cell_x, cell_y, 1]] = 0 + # Neighbors in z-direction + for cell_y in 1:size(mesh, 2), cell_x in 1:size(mesh, 1) + # Inner elements + for cell_z in 2:size(mesh, 3) + element = linear_indices[cell_x, cell_y, cell_z] + left_neighbors[3, element] = linear_indices[cell_x, cell_y, cell_z - 1] + end + + if isperiodic(mesh, 3) + # Periodic boundary + left_neighbors[3, linear_indices[cell_x, cell_y, 1]] = linear_indices[ + cell_x, + cell_y, + end, + ] + else + left_neighbors[3, linear_indices[cell_x, cell_y, 1]] = 0 + end end - end - return left_neighbors -end + return left_neighbors + end end # @muladd diff --git a/src/solvers/dgsem_structured/dg.jl b/src/solvers/dgsem_structured/dg.jl index 5617ae90e3..ab5bb059da 100644 --- a/src/solvers/dgsem_structured/dg.jl +++ b/src/solvers/dgsem_structured/dg.jl @@ -3,100 +3,122 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::Union{StructuredMesh, StructuredMeshView}, - equations::AbstractEquations, dg::DG, ::Any, - ::Type{uEltype}) where {uEltype <: Real} - elements = init_elements(mesh, equations, dg.basis, uEltype) - - cache = (; elements) - - # Add specialized parts of the cache required to compute the volume integral etc. - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - - return cache -end - -# Extract contravariant vector Ja^i (i = index) as SVector -@inline function get_contravariant_vector(index, contravariant_vectors, indices...) - SVector(ntuple(@inline(dim->contravariant_vectors[dim, index, indices...]), - Val(ndims(contravariant_vectors) - 3))) -end - -@inline function calc_boundary_flux_by_direction!(surface_flux_values, u, t, - orientation, - boundary_condition::BoundaryConditionPeriodic, - mesh::Union{StructuredMesh, - StructuredMeshView}, - equations, - surface_integral, dg::DG, cache, - direction, node_indices, - surface_node_indices, element) - @assert isperiodic(mesh, orientation) -end - -@inline function calc_boundary_flux_by_direction!(surface_flux_values, u, t, - orientation, - boundary_condition, - mesh::Union{StructuredMesh, - StructuredMeshView}, - equations, - surface_integral, dg::DG, cache, - direction, node_indices, - surface_node_indices, element) - @unpack node_coordinates, contravariant_vectors, inverse_jacobian = cache.elements - @unpack surface_flux = surface_integral - - u_inner = get_node_vars(u, equations, dg, node_indices..., element) - x = get_node_coords(node_coordinates, equations, dg, node_indices..., element) - - # If the mapping is orientation-reversing, the contravariant vectors' orientation - # is reversed as well. The normal vector must be oriented in the direction - # from `left_element` to `right_element`, or the numerical flux will be computed - # incorrectly (downwind direction). - sign_jacobian = sign(inverse_jacobian[node_indices..., element]) - - # Contravariant vector Ja^i is the normal vector - normal = sign_jacobian * - get_contravariant_vector(orientation, contravariant_vectors, - node_indices..., element) - - # If the mapping is orientation-reversing, the normal vector will be reversed (see above). - # However, the flux now has the wrong sign, since we need the physical flux in normal direction. - flux = sign_jacobian * - boundary_condition(u_inner, normal, direction, x, t, surface_flux, equations) - - for v in eachvariable(equations) - surface_flux_values[v, surface_node_indices..., direction, element] = flux[v] + #! format: noindent + + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::Union{StructuredMesh, StructuredMeshView}, + equations::AbstractEquations, dg::DG, ::Any, + ::Type{uEltype} + ) where {uEltype <: Real} + elements = init_elements(mesh, equations, dg.basis, uEltype) + + cache = (; elements) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + + return cache end -end - -@inline function get_inverse_jacobian(inverse_jacobian, - mesh::Union{StructuredMesh, StructuredMeshView, - UnstructuredMesh2D, P4estMesh, - T8codeMesh}, - indices...) - return inverse_jacobian[indices...] -end - -include("containers.jl") -include("dg_1d.jl") -include("dg_2d.jl") -include("dg_3d.jl") - -include("indicators_1d.jl") -include("indicators_2d.jl") -include("indicators_3d.jl") - -include("subcell_limiters_2d.jl") -include("dg_2d_subcell_limiters.jl") - -# Specialized implementations used to improve performance -include("dg_2d_compressible_euler.jl") -include("dg_3d_compressible_euler.jl") + + # Extract contravariant vector Ja^i (i = index) as SVector + @inline function get_contravariant_vector(index, contravariant_vectors, indices...) + SVector( + ntuple( + @inline(dim -> contravariant_vectors[dim, index, indices...]), + Val(ndims(contravariant_vectors) - 3) + ) + ) + end + + @inline function calc_boundary_flux_by_direction!( + surface_flux_values, u, t, + orientation, + boundary_condition::BoundaryConditionPeriodic, + mesh::Union{ + StructuredMesh, + StructuredMeshView, + }, + equations, + surface_integral, dg::DG, cache, + direction, node_indices, + surface_node_indices, element + ) + @assert isperiodic(mesh, orientation) + end + + @inline function calc_boundary_flux_by_direction!( + surface_flux_values, u, t, + orientation, + boundary_condition, + mesh::Union{ + StructuredMesh, + StructuredMeshView, + }, + equations, + surface_integral, dg::DG, cache, + direction, node_indices, + surface_node_indices, element + ) + @unpack node_coordinates, contravariant_vectors, inverse_jacobian = cache.elements + @unpack surface_flux = surface_integral + + u_inner = get_node_vars(u, equations, dg, node_indices..., element) + x = get_node_coords(node_coordinates, equations, dg, node_indices..., element) + + # If the mapping is orientation-reversing, the contravariant vectors' orientation + # is reversed as well. The normal vector must be oriented in the direction + # from `left_element` to `right_element`, or the numerical flux will be computed + # incorrectly (downwind direction). + sign_jacobian = sign(inverse_jacobian[node_indices..., element]) + + # Contravariant vector Ja^i is the normal vector + normal = sign_jacobian * + get_contravariant_vector( + orientation, contravariant_vectors, + node_indices..., element + ) + + # If the mapping is orientation-reversing, the normal vector will be reversed (see above). + # However, the flux now has the wrong sign, since we need the physical flux in normal direction. + flux = sign_jacobian * + boundary_condition(u_inner, normal, direction, x, t, surface_flux, equations) + + for v in eachvariable(equations) + surface_flux_values[v, surface_node_indices..., direction, element] = flux[v] + end + end + + @inline function get_inverse_jacobian( + inverse_jacobian, + mesh::Union{ + StructuredMesh, StructuredMeshView, + UnstructuredMesh2D, P4estMesh, + T8codeMesh, + }, + indices... + ) + return inverse_jacobian[indices...] + end + + include("containers.jl") + include("dg_1d.jl") + include("dg_2d.jl") + include("dg_3d.jl") + + include("indicators_1d.jl") + include("indicators_2d.jl") + include("indicators_3d.jl") + + include("subcell_limiters_2d.jl") + include("dg_2d_subcell_limiters.jl") + + # Specialized implementations used to improve performance + include("dg_2d_compressible_euler.jl") + include("dg_3d_compressible_euler.jl") end # @muladd diff --git a/src/solvers/dgsem_structured/dg_1d.jl b/src/solvers/dgsem_structured/dg_1d.jl index 3d63cc5af3..ba374ad9b8 100644 --- a/src/solvers/dgsem_structured/dg_1d.jl +++ b/src/solvers/dgsem_structured/dg_1d.jl @@ -3,114 +3,134 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function rhs!(du, u, t, - mesh::StructuredMesh{1}, equations, - initial_condition, boundary_conditions, source_terms::Source, - dg::DG, cache) where {Source} - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) - end + #! format: noindent + + function rhs!( + du, u, t, + mesh::StructuredMesh{1}, equations, + initial_condition, boundary_conditions, source_terms::Source, + dg::DG, cache + ) where {Source} + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - # Calculate interface and boundary fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache, u, mesh, equations, dg.surface_integral, dg) - end + # Calculate interface and boundary fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!(cache, u, mesh, equations, dg.surface_integral, dg) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, u, t, boundary_conditions, mesh, equations, - dg.surface_integral, dg) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, u, t, boundary_conditions, mesh, equations, + dg.surface_integral, dg + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) - end + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) - # Calculate source terms - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, equations, dg, cache) - end + # Calculate source terms + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, equations, dg, cache) + end - return nothing -end + return nothing + end -function calc_interface_flux!(cache, u, mesh::StructuredMesh{1}, - equations, surface_integral, dg::DG) - @unpack surface_flux = surface_integral + function calc_interface_flux!( + cache, u, mesh::StructuredMesh{1}, + equations, surface_integral, dg::DG + ) + @unpack surface_flux = surface_integral - @threaded for element in eachelement(dg, cache) - left_element = cache.elements.left_neighbors[1, element] + @threaded for element in eachelement(dg, cache) + left_element = cache.elements.left_neighbors[1, element] - if left_element > 0 # left_element = 0 at boundaries - u_ll = get_node_vars(u, equations, dg, nnodes(dg), left_element) - u_rr = get_node_vars(u, equations, dg, 1, element) + if left_element > 0 # left_element = 0 at boundaries + u_ll = get_node_vars(u, equations, dg, nnodes(dg), left_element) + u_rr = get_node_vars(u, equations, dg, 1, element) - f1 = surface_flux(u_ll, u_rr, 1, equations) + f1 = surface_flux(u_ll, u_rr, 1, equations) - for v in eachvariable(equations) - cache.elements.surface_flux_values[v, 2, left_element] = f1[v] - cache.elements.surface_flux_values[v, 1, element] = f1[v] + for v in eachvariable(equations) + cache.elements.surface_flux_values[v, 2, left_element] = f1[v] + cache.elements.surface_flux_values[v, 1, element] = f1[v] + end end end - end - return nothing -end + return nothing + end -# TODO: Taal dimension agnostic -function calc_boundary_flux!(cache, u, t, boundary_condition::BoundaryConditionPeriodic, - mesh::StructuredMesh{1}, equations, surface_integral, - dg::DG) - @assert isperiodic(mesh) -end + # TODO: Taal dimension agnostic + function calc_boundary_flux!( + cache, u, t, boundary_condition::BoundaryConditionPeriodic, + mesh::StructuredMesh{1}, equations, surface_integral, + dg::DG + ) + @assert isperiodic(mesh) + end -function calc_boundary_flux!(cache, u, t, boundary_conditions::NamedTuple, - mesh::StructuredMesh{1}, equations, surface_integral, - dg::DG) - @unpack surface_flux = surface_integral - @unpack surface_flux_values, node_coordinates = cache.elements + function calc_boundary_flux!( + cache, u, t, boundary_conditions::NamedTuple, + mesh::StructuredMesh{1}, equations, surface_integral, + dg::DG + ) + @unpack surface_flux = surface_integral + @unpack surface_flux_values, node_coordinates = cache.elements - orientation = 1 + orientation = 1 - # Negative x-direction - direction = 1 + # Negative x-direction + direction = 1 - u_rr = get_node_vars(u, equations, dg, 1, 1) - x = get_node_coords(node_coordinates, equations, dg, 1, 1) + u_rr = get_node_vars(u, equations, dg, 1, 1) + x = get_node_coords(node_coordinates, equations, dg, 1, 1) - flux = boundary_conditions[direction](u_rr, orientation, direction, x, t, - surface_flux, equations) + flux = boundary_conditions[direction]( + u_rr, orientation, direction, x, t, + surface_flux, equations + ) - for v in eachvariable(equations) - surface_flux_values[v, direction, 1] = flux[v] - end + for v in eachvariable(equations) + surface_flux_values[v, direction, 1] = flux[v] + end - # Positive x-direction - direction = 2 + # Positive x-direction + direction = 2 - u_rr = get_node_vars(u, equations, dg, nnodes(dg), nelements(dg, cache)) - x = get_node_coords(node_coordinates, equations, dg, nnodes(dg), - nelements(dg, cache)) + u_rr = get_node_vars(u, equations, dg, nnodes(dg), nelements(dg, cache)) + x = get_node_coords( + node_coordinates, equations, dg, nnodes(dg), + nelements(dg, cache) + ) - flux = boundary_conditions[direction](u_rr, orientation, direction, x, t, - surface_flux, equations) + flux = boundary_conditions[direction]( + u_rr, orientation, direction, x, t, + surface_flux, equations + ) - # Copy flux to left and right element storage - for v in eachvariable(equations) - surface_flux_values[v, direction, nelements(dg, cache)] = flux[v] + # Copy flux to left and right element storage + for v in eachvariable(equations) + surface_flux_values[v, direction, nelements(dg, cache)] = flux[v] + end end -end end # @muladd diff --git a/src/solvers/dgsem_structured/dg_2d.jl b/src/solvers/dgsem_structured/dg_2d.jl index 6023c9e619..bb83696644 100644 --- a/src/solvers/dgsem_structured/dg_2d.jl +++ b/src/solvers/dgsem_structured/dg_2d.jl @@ -3,642 +3,766 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function rhs!(du, u, t, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, equations, - initial_condition, boundary_conditions, source_terms::Source, - dg::DG, cache) where {Source} - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) - end + #! format: noindent + + function rhs!( + du, u, t, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, equations, + initial_condition, boundary_conditions, source_terms::Source, + dg::DG, cache + ) where {Source} + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache, u, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg) - end + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache, u, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, u, t, boundary_conditions, mesh, equations, - dg.surface_integral, dg) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, u, t, boundary_conditions, mesh, equations, + dg.surface_integral, dg + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) - end + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) - # Calculate source terms - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, equations, dg, cache) - end + # Calculate source terms + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, equations, dg, cache) + end - return nothing -end + return nothing + end -#= + #= `weak_form_kernel!` is only implemented for conserved terms as non-conservative terms should always be discretized in conjunction with a flux-splitting scheme, see `flux_differencing_kernel!`. This treatment is required to achieve, e.g., entropy-stability or well-balancedness. See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-1765644064 =# -@inline function weak_form_kernel!(du, u, - element, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, - UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - nonconservative_terms::False, equations, - dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_dhat = dg.basis - @unpack contravariant_vectors = cache.elements - - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - flux1 = flux(u_node, 1, equations) - flux2 = flux(u_node, 2, equations) - - # Compute the contravariant flux by taking the scalar product of the - # first contravariant vector Ja^1 and the flux vector - Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, element) - contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[ii, i], - contravariant_flux1, equations, dg, ii, j, - element) - end + @inline function weak_form_kernel!( + du, u, + element, + mesh::Union{ + StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + nonconservative_terms::False, equations, + dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_dhat = dg.basis + @unpack contravariant_vectors = cache.elements - # Compute the contravariant flux by taking the scalar product of the - # second contravariant vector Ja^2 and the flux vector - Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, element) - contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 - for jj in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[jj, j], - contravariant_flux2, equations, dg, i, jj, - element) - end - end + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) - return nothing -end - -@inline function flux_differencing_kernel!(du, u, - element, - mesh::Union{StructuredMesh{2}, - StructuredMeshView{2}, - UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - nonconservative_terms::False, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - @unpack derivative_split = dg.basis - @unpack contravariant_vectors = cache.elements - - # Calculate volume integral in one element - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - # pull the contravariant vectors in each coordinate direction - Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, element) - Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, element) - - # All diagonal entries of `derivative_split` are zero. Thus, we can skip - # the computation of the diagonal terms. In addition, we use the symmetry - # of the `volume_flux` to save half of the possible two-point flux - # computations. - - # x direction - for ii in (i + 1):nnodes(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, element) - # pull the contravariant vectors and compute the average - Ja1_node_ii = get_contravariant_vector(1, contravariant_vectors, ii, j, - element) - Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) - # compute the contravariant sharp flux in the direction of the - # averaged contravariant vector - fluxtilde1 = volume_flux(u_node, u_node_ii, Ja1_avg, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[i, ii], fluxtilde1, - equations, dg, i, j, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[ii, i], fluxtilde1, - equations, dg, ii, j, element) - end + flux1 = flux(u_node, 1, equations) + flux2 = flux(u_node, 2, equations) - # y direction - for jj in (j + 1):nnodes(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, element) - # pull the contravariant vectors and compute the average - Ja2_node_jj = get_contravariant_vector(2, contravariant_vectors, i, jj, - element) - Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) - # compute the contravariant sharp flux in the direction of the - # averaged contravariant vector - fluxtilde2 = volume_flux(u_node, u_node_jj, Ja2_avg, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[j, jj], fluxtilde2, - equations, dg, i, j, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[jj, j], fluxtilde2, - equations, dg, i, jj, element) - end - end -end - -@inline function flux_differencing_kernel!(du, u, - element, - mesh::Union{StructuredMesh{2}, - StructuredMeshView{2}, - UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - nonconservative_terms::True, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - @unpack derivative_split = dg.basis - @unpack contravariant_vectors = cache.elements - symmetric_flux, nonconservative_flux = volume_flux - - # Apply the symmetric flux as usual - flux_differencing_kernel!(du, u, element, mesh, False(), equations, symmetric_flux, - dg, cache, alpha) - - # Calculate the remaining volume terms using the nonsymmetric generalized flux - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - # pull the contravariant vectors in each coordinate direction - Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, element) - Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, element) - - # The diagonal terms are zero since the diagonal of `derivative_split` - # is zero. We ignore this for now. - # In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. - # Thus, we need to pass both to the nonconservative flux. - - # x direction - integral_contribution = zero(u_node) - for ii in eachnode(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, element) - # pull the contravariant vectors and compute the average - Ja1_node_ii = get_contravariant_vector(1, contravariant_vectors, ii, j, - element) - Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) - # Compute the contravariant nonconservative flux. - fluxtilde1 = nonconservative_flux(u_node, u_node_ii, Ja1_node, Ja1_avg, - equations) - integral_contribution = integral_contribution + - derivative_split[i, ii] * fluxtilde1 - end + # Compute the contravariant flux by taking the scalar product of the + # first contravariant vector Ja^1 and the flux vector + Ja11, Ja12 = get_contravariant_vector(1, contravariant_vectors, i, j, element) + contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 + for ii in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[ii, i], + contravariant_flux1, equations, dg, ii, j, + element + ) + end - # y direction - for jj in eachnode(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, element) - # pull the contravariant vectors and compute the average - Ja2_node_jj = get_contravariant_vector(2, contravariant_vectors, i, jj, - element) - Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) - # compute the contravariant nonconservative flux in the direction of the - # averaged contravariant vector - fluxtilde2 = nonconservative_flux(u_node, u_node_jj, Ja2_node, Ja2_avg, - equations) - integral_contribution = integral_contribution + - derivative_split[j, jj] * fluxtilde2 + # Compute the contravariant flux by taking the scalar product of the + # second contravariant vector Ja^2 and the flux vector + Ja21, Ja22 = get_contravariant_vector(2, contravariant_vectors, i, j, element) + contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 + for jj in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[jj, j], + contravariant_flux2, equations, dg, i, jj, + element + ) + end end - # The factor 0.5 cancels the factor 2 in the flux differencing form - multiply_add_to_node_vars!(du, alpha * 0.5f0, integral_contribution, equations, - dg, i, j, element) + return nothing end -end - -# Computing the normal vector for the FV method on curvilinear subcells. -# To fulfill free-stream preservation we use the explicit formula B.53 in Appendix B.4 -# by Hennemann, Rueda-Ramirez, Hindenlang, Gassner (2020) -# "A provably entropy stable subcell shock capturing approach for high order split form DG for the compressible Euler equations" -# [arXiv: 2008.12044v2](https://arxiv.org/pdf/2008.12044) -@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, - UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms::False, equations, - volume_flux_fv, dg::DGSEM, element, cache) - @unpack contravariant_vectors = cache.elements - @unpack weights, derivative_matrix = dg.basis - - # Performance improvement if the metric terms of the subcell FV method are only computed - # once at the beginning of the simulation, instead of at every Runge-Kutta stage - fstar1_L[:, 1, :] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1, :] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_R)) - - for j in eachnode(dg) - normal_direction = get_contravariant_vector(1, contravariant_vectors, 1, j, - element) - - for i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, j, element) - u_rr = get_node_vars(u, equations, dg, i, j, element) - - for m in 1:nnodes(dg) - normal_direction += weights[i - 1] * derivative_matrix[i - 1, m] * - get_contravariant_vector(1, contravariant_vectors, - m, j, element) - end - # Compute the contravariant flux - contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + @inline function flux_differencing_kernel!( + du, u, + element, + mesh::Union{ + StructuredMesh{2}, + StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + nonconservative_terms::False, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + @unpack derivative_split = dg.basis + @unpack contravariant_vectors = cache.elements + + # Calculate volume integral in one element + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # pull the contravariant vectors in each coordinate direction + Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, element) + Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, element) + + # All diagonal entries of `derivative_split` are zero. Thus, we can skip + # the computation of the diagonal terms. In addition, we use the symmetry + # of the `volume_flux` to save half of the possible two-point flux + # computations. + + # x direction + for ii in (i + 1):nnodes(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, element) + # pull the contravariant vectors and compute the average + Ja1_node_ii = get_contravariant_vector( + 1, contravariant_vectors, ii, j, + element + ) + Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) + # compute the contravariant sharp flux in the direction of the + # averaged contravariant vector + fluxtilde1 = volume_flux(u_node, u_node_ii, Ja1_avg, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[i, ii], fluxtilde1, + equations, dg, i, j, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[ii, i], fluxtilde1, + equations, dg, ii, j, element + ) + end - set_node_vars!(fstar1_L, contravariant_flux, equations, dg, i, j) - set_node_vars!(fstar1_R, contravariant_flux, equations, dg, i, j) + # y direction + for jj in (j + 1):nnodes(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, element) + # pull the contravariant vectors and compute the average + Ja2_node_jj = get_contravariant_vector( + 2, contravariant_vectors, i, jj, + element + ) + Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) + # compute the contravariant sharp flux in the direction of the + # averaged contravariant vector + fluxtilde2 = volume_flux(u_node, u_node_jj, Ja2_avg, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[j, jj], fluxtilde2, + equations, dg, i, j, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[jj, j], fluxtilde2, + equations, dg, i, jj, element + ) + end end end - fstar2_L[:, :, 1] .= zero(eltype(fstar2_L)) - fstar2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_L)) - fstar2_R[:, :, 1] .= zero(eltype(fstar2_R)) - fstar2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_R)) - - for i in eachnode(dg) - normal_direction = get_contravariant_vector(2, contravariant_vectors, i, 1, - element) - - for j in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i, j - 1, element) - u_rr = get_node_vars(u, equations, dg, i, j, element) - - for m in 1:nnodes(dg) - normal_direction += weights[j - 1] * derivative_matrix[j - 1, m] * - get_contravariant_vector(2, contravariant_vectors, - i, m, element) + @inline function flux_differencing_kernel!( + du, u, + element, + mesh::Union{ + StructuredMesh{2}, + StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + nonconservative_terms::True, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + @unpack derivative_split = dg.basis + @unpack contravariant_vectors = cache.elements + symmetric_flux, nonconservative_flux = volume_flux + + # Apply the symmetric flux as usual + flux_differencing_kernel!( + du, u, element, mesh, False(), equations, symmetric_flux, + dg, cache, alpha + ) + + # Calculate the remaining volume terms using the nonsymmetric generalized flux + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # pull the contravariant vectors in each coordinate direction + Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, element) + Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, element) + + # The diagonal terms are zero since the diagonal of `derivative_split` + # is zero. We ignore this for now. + # In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. + # Thus, we need to pass both to the nonconservative flux. + + # x direction + integral_contribution = zero(u_node) + for ii in eachnode(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, element) + # pull the contravariant vectors and compute the average + Ja1_node_ii = get_contravariant_vector( + 1, contravariant_vectors, ii, j, + element + ) + Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) + # Compute the contravariant nonconservative flux. + fluxtilde1 = nonconservative_flux( + u_node, u_node_ii, Ja1_node, Ja1_avg, + equations + ) + integral_contribution = integral_contribution + + derivative_split[i, ii] * fluxtilde1 end - # Compute the contravariant flux by taking the scalar product of the - # normal vector and the flux vector - contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + # y direction + for jj in eachnode(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, element) + # pull the contravariant vectors and compute the average + Ja2_node_jj = get_contravariant_vector( + 2, contravariant_vectors, i, jj, + element + ) + Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) + # compute the contravariant nonconservative flux in the direction of the + # averaged contravariant vector + fluxtilde2 = nonconservative_flux( + u_node, u_node_jj, Ja2_node, Ja2_avg, + equations + ) + integral_contribution = integral_contribution + + derivative_split[j, jj] * fluxtilde2 + end - set_node_vars!(fstar2_L, contravariant_flux, equations, dg, i, j) - set_node_vars!(fstar2_R, contravariant_flux, equations, dg, i, j) + # The factor 0.5 cancels the factor 2 in the flux differencing form + multiply_add_to_node_vars!( + du, alpha * 0.5f0, integral_contribution, equations, + dg, i, j, element + ) end end - return nothing -end - -# Calculate the finite volume fluxes inside curvilinear elements (**with non-conservative terms**). -@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, - u::AbstractArray{<:Any, 4}, - mesh::Union{StructuredMesh{2}, StructuredMesh{2}, - UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms::True, equations, - volume_flux_fv, dg::DGSEM, element, cache) - @unpack contravariant_vectors = cache.elements - @unpack weights, derivative_matrix = dg.basis - - volume_flux, nonconservative_flux = volume_flux_fv - - # Performance improvement if the metric terms of the subcell FV method are only computed - # once at the beginning of the simulation, instead of at every Runge-Kutta stage - fstar1_L[:, 1, :] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1, :] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_R)) - - for j in eachnode(dg) - normal_direction = get_contravariant_vector(1, contravariant_vectors, 1, j, - element) - for i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, j, element) - u_rr = get_node_vars(u, equations, dg, i, j, element) - - for m in eachnode(dg) - normal_direction += weights[i - 1] * derivative_matrix[i - 1, m] * - get_contravariant_vector(1, contravariant_vectors, - m, j, element) - end + # Computing the normal vector for the FV method on curvilinear subcells. + # To fulfill free-stream preservation we use the explicit formula B.53 in Appendix B.4 + # by Hennemann, Rueda-Ramirez, Hindenlang, Gassner (2020) + # "A provably entropy stable subcell shock capturing approach for high order split form DG for the compressible Euler equations" + # [arXiv: 2008.12044v2](https://arxiv.org/pdf/2008.12044) + @inline function calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, + mesh::Union{ + StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}, + }, + nonconservative_terms::False, equations, + volume_flux_fv, dg::DGSEM, element, cache + ) + @unpack contravariant_vectors = cache.elements + @unpack weights, derivative_matrix = dg.basis + + # Performance improvement if the metric terms of the subcell FV method are only computed + # once at the beginning of the simulation, instead of at every Runge-Kutta stage + fstar1_L[:, 1, :] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1, :] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_R)) - # Compute the conservative part of the contravariant flux - ftilde1 = volume_flux(u_ll, u_rr, normal_direction, equations) - - # Compute and add in the nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - ftilde1_L = ftilde1 + - 0.5f0 * nonconservative_flux(u_ll, u_rr, normal_direction, - normal_direction, equations) - ftilde1_R = ftilde1 + - 0.5f0 * nonconservative_flux(u_rr, u_ll, normal_direction, - normal_direction, equations) - - set_node_vars!(fstar1_L, ftilde1_L, equations, dg, i, j) - set_node_vars!(fstar1_R, ftilde1_R, equations, dg, i, j) + for j in eachnode(dg) + normal_direction = get_contravariant_vector( + 1, contravariant_vectors, 1, j, + element + ) + + for i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, j, element) + u_rr = get_node_vars(u, equations, dg, i, j, element) + + for m in 1:nnodes(dg) + normal_direction += weights[i - 1] * derivative_matrix[i - 1, m] * + get_contravariant_vector( + 1, contravariant_vectors, + m, j, element + ) + end + + # Compute the contravariant flux + contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + + set_node_vars!(fstar1_L, contravariant_flux, equations, dg, i, j) + set_node_vars!(fstar1_R, contravariant_flux, equations, dg, i, j) + end end - end - # Fluxes in y - fstar2_L[:, :, 1] .= zero(eltype(fstar2_L)) - fstar2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_L)) - fstar2_R[:, :, 1] .= zero(eltype(fstar2_R)) - fstar2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_R)) - - # Compute inner fluxes - for i in eachnode(dg) - normal_direction = get_contravariant_vector(2, contravariant_vectors, i, 1, - element) - - for j in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i, j - 1, element) - u_rr = get_node_vars(u, equations, dg, i, j, element) - - for m in eachnode(dg) - normal_direction += weights[j - 1] * derivative_matrix[j - 1, m] * - get_contravariant_vector(2, contravariant_vectors, - i, m, element) - end + fstar2_L[:, :, 1] .= zero(eltype(fstar2_L)) + fstar2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_L)) + fstar2_R[:, :, 1] .= zero(eltype(fstar2_R)) + fstar2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_R)) - # Compute the conservative part of the contravariant flux - ftilde2 = volume_flux(u_ll, u_rr, normal_direction, equations) - - # Compute and add in the nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - ftilde2_L = ftilde2 + - 0.5f0 * nonconservative_flux(u_ll, u_rr, normal_direction, - normal_direction, equations) - ftilde2_R = ftilde2 + - 0.5f0 * nonconservative_flux(u_rr, u_ll, normal_direction, - normal_direction, equations) - - set_node_vars!(fstar2_L, ftilde2_L, equations, dg, i, j) - set_node_vars!(fstar2_R, ftilde2_R, equations, dg, i, j) + for i in eachnode(dg) + normal_direction = get_contravariant_vector( + 2, contravariant_vectors, i, 1, + element + ) + + for j in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i, j - 1, element) + u_rr = get_node_vars(u, equations, dg, i, j, element) + + for m in 1:nnodes(dg) + normal_direction += weights[j - 1] * derivative_matrix[j - 1, m] * + get_contravariant_vector( + 2, contravariant_vectors, + i, m, element + ) + end + + # Compute the contravariant flux by taking the scalar product of the + # normal vector and the flux vector + contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + + set_node_vars!(fstar2_L, contravariant_flux, equations, dg, i, j) + set_node_vars!(fstar2_R, contravariant_flux, equations, dg, i, j) + end end - end - return nothing -end - -function calc_interface_flux!(cache, u, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, - nonconservative_terms, # can be True/False - equations, surface_integral, dg::DG) - @unpack elements = cache - - @threaded for element in eachelement(dg, cache) - # Interfaces in negative directions - # Faster version of "for orientation in (1, 2)" - - # Interfaces in x-direction (`orientation` = 1) - calc_interface_flux!(elements.surface_flux_values, - elements.left_neighbors[1, element], - element, 1, u, mesh, - nonconservative_terms, equations, - surface_integral, dg, cache) - - # Interfaces in y-direction (`orientation` = 2) - calc_interface_flux!(elements.surface_flux_values, - elements.left_neighbors[2, element], - element, 2, u, mesh, - nonconservative_terms, equations, - surface_integral, dg, cache) - end - - return nothing -end - -@inline function calc_interface_flux!(surface_flux_values, left_element, right_element, - orientation, u, - mesh::Union{StructuredMesh{2}, - StructuredMeshView{2}}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache) - # This is slow for LSA, but for some reason faster for Euler (see #519) - if left_element <= 0 # left_element = 0 at boundaries return nothing end - @unpack surface_flux = surface_integral - @unpack contravariant_vectors, inverse_jacobian = cache.elements - - right_direction = 2 * orientation - left_direction = right_direction - 1 - - for i in eachnode(dg) - if orientation == 1 - u_ll = get_node_vars(u, equations, dg, nnodes(dg), i, left_element) - u_rr = get_node_vars(u, equations, dg, 1, i, right_element) - - # If the mapping is orientation-reversing, the contravariant vectors' orientation - # is reversed as well. The normal vector must be oriented in the direction - # from `left_element` to `right_element`, or the numerical flux will be computed - # incorrectly (downwind direction). - sign_jacobian = sign(inverse_jacobian[1, i, right_element]) - - # First contravariant vector Ja^1 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(1, contravariant_vectors, - 1, i, right_element) - else # orientation == 2 - u_ll = get_node_vars(u, equations, dg, i, nnodes(dg), left_element) - u_rr = get_node_vars(u, equations, dg, i, 1, right_element) - - # See above - sign_jacobian = sign(inverse_jacobian[i, 1, right_element]) - - # Second contravariant vector Ja^2 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(2, contravariant_vectors, - i, 1, right_element) + # Calculate the finite volume fluxes inside curvilinear elements (**with non-conservative terms**). + @inline function calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, + u::AbstractArray{<:Any, 4}, + mesh::Union{ + StructuredMesh{2}, StructuredMesh{2}, + UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}, + }, + nonconservative_terms::True, equations, + volume_flux_fv, dg::DGSEM, element, cache + ) + @unpack contravariant_vectors = cache.elements + @unpack weights, derivative_matrix = dg.basis + + volume_flux, nonconservative_flux = volume_flux_fv + + # Performance improvement if the metric terms of the subcell FV method are only computed + # once at the beginning of the simulation, instead of at every Runge-Kutta stage + fstar1_L[:, 1, :] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1, :] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_R)) + + for j in eachnode(dg) + normal_direction = get_contravariant_vector( + 1, contravariant_vectors, 1, j, + element + ) + for i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, j, element) + u_rr = get_node_vars(u, equations, dg, i, j, element) + + for m in eachnode(dg) + normal_direction += weights[i - 1] * derivative_matrix[i - 1, m] * + get_contravariant_vector( + 1, contravariant_vectors, + m, j, element + ) + end + + # Compute the conservative part of the contravariant flux + ftilde1 = volume_flux(u_ll, u_rr, normal_direction, equations) + + # Compute and add in the nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + ftilde1_L = ftilde1 + + 0.5f0 * nonconservative_flux( + u_ll, u_rr, normal_direction, + normal_direction, equations + ) + ftilde1_R = ftilde1 + + 0.5f0 * nonconservative_flux( + u_rr, u_ll, normal_direction, + normal_direction, equations + ) + + set_node_vars!(fstar1_L, ftilde1_L, equations, dg, i, j) + set_node_vars!(fstar1_R, ftilde1_R, equations, dg, i, j) + end end - # If the mapping is orientation-reversing, the normal vector will be reversed (see above). - # However, the flux now has the wrong sign, since we need the physical flux in normal direction. - flux = sign_jacobian * surface_flux(u_ll, u_rr, normal_direction, equations) + # Fluxes in y + fstar2_L[:, :, 1] .= zero(eltype(fstar2_L)) + fstar2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_L)) + fstar2_R[:, :, 1] .= zero(eltype(fstar2_R)) + fstar2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_R)) - for v in eachvariable(equations) - surface_flux_values[v, i, right_direction, left_element] = flux[v] - surface_flux_values[v, i, left_direction, right_element] = flux[v] + # Compute inner fluxes + for i in eachnode(dg) + normal_direction = get_contravariant_vector( + 2, contravariant_vectors, i, 1, + element + ) + + for j in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i, j - 1, element) + u_rr = get_node_vars(u, equations, dg, i, j, element) + + for m in eachnode(dg) + normal_direction += weights[j - 1] * derivative_matrix[j - 1, m] * + get_contravariant_vector( + 2, contravariant_vectors, + i, m, element + ) + end + + # Compute the conservative part of the contravariant flux + ftilde2 = volume_flux(u_ll, u_rr, normal_direction, equations) + + # Compute and add in the nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + ftilde2_L = ftilde2 + + 0.5f0 * nonconservative_flux( + u_ll, u_rr, normal_direction, + normal_direction, equations + ) + ftilde2_R = ftilde2 + + 0.5f0 * nonconservative_flux( + u_rr, u_ll, normal_direction, + normal_direction, equations + ) + + set_node_vars!(fstar2_L, ftilde2_L, equations, dg, i, j) + set_node_vars!(fstar2_R, ftilde2_R, equations, dg, i, j) + end end - end - return nothing -end - -@inline function calc_interface_flux!(surface_flux_values, left_element, right_element, - orientation, u, - mesh::Union{StructuredMesh{2}, - StructuredMeshView{2}}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache) - # See comment on `calc_interface_flux!` with `nonconservative_terms::False` - if left_element <= 0 # left_element = 0 at boundaries return nothing end - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack contravariant_vectors, inverse_jacobian = cache.elements - - right_direction = 2 * orientation - left_direction = right_direction - 1 - - for i in eachnode(dg) - if orientation == 1 - u_ll = get_node_vars(u, equations, dg, nnodes(dg), i, left_element) - u_rr = get_node_vars(u, equations, dg, 1, i, right_element) - - # If the mapping is orientation-reversing, the contravariant vectors' orientation - # is reversed as well. The normal vector must be oriented in the direction - # from `left_element` to `right_element`, or the numerical flux will be computed - # incorrectly (downwind direction). - sign_jacobian = sign(inverse_jacobian[1, i, right_element]) - - # First contravariant vector Ja^1 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(1, contravariant_vectors, - 1, i, right_element) - else # orientation == 2 - u_ll = get_node_vars(u, equations, dg, i, nnodes(dg), left_element) - u_rr = get_node_vars(u, equations, dg, i, 1, right_element) - - # See above - sign_jacobian = sign(inverse_jacobian[i, 1, right_element]) - - # Second contravariant vector Ja^2 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(2, contravariant_vectors, - i, 1, right_element) + function calc_interface_flux!( + cache, u, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, + nonconservative_terms, # can be True/False + equations, surface_integral, dg::DG + ) + @unpack elements = cache + + @threaded for element in eachelement(dg, cache) + # Interfaces in negative directions + # Faster version of "for orientation in (1, 2)" + + # Interfaces in x-direction (`orientation` = 1) + calc_interface_flux!( + elements.surface_flux_values, + elements.left_neighbors[1, element], + element, 1, u, mesh, + nonconservative_terms, equations, + surface_integral, dg, cache + ) + + # Interfaces in y-direction (`orientation` = 2) + calc_interface_flux!( + elements.surface_flux_values, + elements.left_neighbors[2, element], + element, 2, u, mesh, + nonconservative_terms, equations, + surface_integral, dg, cache + ) end - # If the mapping is orientation-reversing, the normal vector will be reversed (see above). - # However, the flux now has the wrong sign, since we need the physical flux in normal direction. - flux = sign_jacobian * surface_flux(u_ll, u_rr, normal_direction, equations) - - # Compute both nonconservative fluxes - # In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. - # However, both are the same at watertight interfaces, so we pass the - # `normal_direction` twice. - # Scale with sign_jacobian to ensure that the normal_direction matches that - # from the flux above - noncons_left = sign_jacobian * - nonconservative_flux(u_ll, u_rr, normal_direction, - normal_direction, equations) - noncons_right = sign_jacobian * - nonconservative_flux(u_rr, u_ll, normal_direction, - normal_direction, equations) - - for v in eachvariable(equations) - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - surface_flux_values[v, i, right_direction, left_element] = flux[v] + - 0.5f0 * - noncons_left[v] - surface_flux_values[v, i, left_direction, right_element] = flux[v] + - 0.5f0 * - noncons_right[v] - end + return nothing end - return nothing -end + @inline function calc_interface_flux!( + surface_flux_values, left_element, right_element, + orientation, u, + mesh::Union{ + StructuredMesh{2}, + StructuredMeshView{2}, + }, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache + ) + # This is slow for LSA, but for some reason faster for Euler (see #519) + if left_element <= 0 # left_element = 0 at boundaries + return nothing + end + + @unpack surface_flux = surface_integral + @unpack contravariant_vectors, inverse_jacobian = cache.elements -# TODO: Taal dimension agnostic -function calc_boundary_flux!(cache, u, t, boundary_condition::BoundaryConditionPeriodic, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, - equations, surface_integral, dg::DG) - @assert isperiodic(mesh) -end + right_direction = 2 * orientation + left_direction = right_direction - 1 -function calc_boundary_flux!(cache, u, t, boundary_conditions::NamedTuple, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, - equations, surface_integral, - dg::DG) - @unpack surface_flux_values = cache.elements - linear_indices = LinearIndices(size(mesh)) + for i in eachnode(dg) + if orientation == 1 + u_ll = get_node_vars(u, equations, dg, nnodes(dg), i, left_element) + u_rr = get_node_vars(u, equations, dg, 1, i, right_element) + + # If the mapping is orientation-reversing, the contravariant vectors' orientation + # is reversed as well. The normal vector must be oriented in the direction + # from `left_element` to `right_element`, or the numerical flux will be computed + # incorrectly (downwind direction). + sign_jacobian = sign(inverse_jacobian[1, i, right_element]) + + # First contravariant vector Ja^1 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 1, contravariant_vectors, + 1, i, right_element + ) + else # orientation == 2 + u_ll = get_node_vars(u, equations, dg, i, nnodes(dg), left_element) + u_rr = get_node_vars(u, equations, dg, i, 1, right_element) + + # See above + sign_jacobian = sign(inverse_jacobian[i, 1, right_element]) + + # Second contravariant vector Ja^2 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 2, contravariant_vectors, + i, 1, right_element + ) + end - for cell_y in axes(mesh, 2) - # Negative x-direction - direction = 1 - element = linear_indices[begin, cell_y] + # If the mapping is orientation-reversing, the normal vector will be reversed (see above). + # However, the flux now has the wrong sign, since we need the physical flux in normal direction. + flux = sign_jacobian * surface_flux(u_ll, u_rr, normal_direction, equations) - for j in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 1, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (1, j), (j,), element) + for v in eachvariable(equations) + surface_flux_values[v, i, right_direction, left_element] = flux[v] + surface_flux_values[v, i, left_direction, right_element] = flux[v] + end end - # Positive x-direction - direction = 2 - element = linear_indices[end, cell_y] + return nothing + end - for j in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 1, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (nnodes(dg), j), (j,), element) + @inline function calc_interface_flux!( + surface_flux_values, left_element, right_element, + orientation, u, + mesh::Union{ + StructuredMesh{2}, + StructuredMeshView{2}, + }, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache + ) + # See comment on `calc_interface_flux!` with `nonconservative_terms::False` + if left_element <= 0 # left_element = 0 at boundaries + return nothing end - end - for cell_x in axes(mesh, 1) - # Negative y-direction - direction = 3 - element = linear_indices[cell_x, begin] + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack contravariant_vectors, inverse_jacobian = cache.elements + + right_direction = 2 * orientation + left_direction = right_direction - 1 for i in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 2, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (i, 1), (i,), element) - end + if orientation == 1 + u_ll = get_node_vars(u, equations, dg, nnodes(dg), i, left_element) + u_rr = get_node_vars(u, equations, dg, 1, i, right_element) + + # If the mapping is orientation-reversing, the contravariant vectors' orientation + # is reversed as well. The normal vector must be oriented in the direction + # from `left_element` to `right_element`, or the numerical flux will be computed + # incorrectly (downwind direction). + sign_jacobian = sign(inverse_jacobian[1, i, right_element]) + + # First contravariant vector Ja^1 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 1, contravariant_vectors, + 1, i, right_element + ) + else # orientation == 2 + u_ll = get_node_vars(u, equations, dg, i, nnodes(dg), left_element) + u_rr = get_node_vars(u, equations, dg, i, 1, right_element) + + # See above + sign_jacobian = sign(inverse_jacobian[i, 1, right_element]) + + # Second contravariant vector Ja^2 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 2, contravariant_vectors, + i, 1, right_element + ) + end - # Positive y-direction - direction = 4 - element = linear_indices[cell_x, end] + # If the mapping is orientation-reversing, the normal vector will be reversed (see above). + # However, the flux now has the wrong sign, since we need the physical flux in normal direction. + flux = sign_jacobian * surface_flux(u_ll, u_rr, normal_direction, equations) + + # Compute both nonconservative fluxes + # In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. + # However, both are the same at watertight interfaces, so we pass the + # `normal_direction` twice. + # Scale with sign_jacobian to ensure that the normal_direction matches that + # from the flux above + noncons_left = sign_jacobian * + nonconservative_flux( + u_ll, u_rr, normal_direction, + normal_direction, equations + ) + noncons_right = sign_jacobian * + nonconservative_flux( + u_rr, u_ll, normal_direction, + normal_direction, equations + ) - for i in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 2, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (i, nnodes(dg)), (i,), element) + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[v, i, right_direction, left_element] = flux[v] + + 0.5f0 * + noncons_left[v] + surface_flux_values[v, i, left_direction, right_element] = flux[v] + + 0.5f0 * + noncons_right[v] + end end + + return nothing end -end -function apply_jacobian!(du, - mesh::Union{StructuredMesh{2}, StructuredMeshView{2}, - UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}}, - equations, dg::DG, cache) - @unpack inverse_jacobian = cache.elements + # TODO: Taal dimension agnostic + function calc_boundary_flux!( + cache, u, t, boundary_condition::BoundaryConditionPeriodic, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, + equations, surface_integral, dg::DG + ) + @assert isperiodic(mesh) + end - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - factor = -inverse_jacobian[i, j, element] + function calc_boundary_flux!( + cache, u, t, boundary_conditions::NamedTuple, + mesh::Union{StructuredMesh{2}, StructuredMeshView{2}}, + equations, surface_integral, + dg::DG + ) + @unpack surface_flux_values = cache.elements + linear_indices = LinearIndices(size(mesh)) + + for cell_y in axes(mesh, 2) + # Negative x-direction + direction = 1 + element = linear_indices[begin, cell_y] + + for j in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 1, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (1, j), (j,), element + ) + end - for v in eachvariable(equations) - du[v, i, j, element] *= factor + # Positive x-direction + direction = 2 + element = linear_indices[end, cell_y] + + for j in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 1, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (nnodes(dg), j), (j,), element + ) + end + end + + for cell_x in axes(mesh, 1) + # Negative y-direction + direction = 3 + element = linear_indices[cell_x, begin] + + for i in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 2, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (i, 1), (i,), element + ) + end + + # Positive y-direction + direction = 4 + element = linear_indices[cell_x, end] + + for i in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 2, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (i, nnodes(dg)), (i,), element + ) end end end - return nothing -end + function apply_jacobian!( + du, + mesh::Union{ + StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, T8codeMesh{2}, + }, + equations, dg::DG, cache + ) + @unpack inverse_jacobian = cache.elements + + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + factor = -inverse_jacobian[i, j, element] + + for v in eachvariable(equations) + du[v, i, j, element] *= factor + end + end + end + + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_structured/dg_2d_compressible_euler.jl b/src/solvers/dgsem_structured/dg_2d_compressible_euler.jl index 43f70da475..66cb309f34 100644 --- a/src/solvers/dgsem_structured/dg_2d_compressible_euler.jl +++ b/src/solvers/dgsem_structured/dg_2d_compressible_euler.jl @@ -1,4 +1,3 @@ - # From here on, this file contains specializations of DG methods on the # curved 3D meshes `StructuredMesh{3}, P4estMesh{3}` to the compressible # Euler equations. @@ -17,29 +16,43 @@ # We specialize on `PtrArray` since these will be returned by `Trixi.wrap_array` # if LoopVectorization.jl can handle the array types. This ensures that `@turbo` # works efficiently here. -@inline function flux_differencing_kernel!(_du::PtrArray, u_cons::PtrArray, - element, - mesh::Union{StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}}, - nonconservative_terms::False, - equations::CompressibleEulerEquations2D, - volume_flux::typeof(flux_shima_etal_turbo), - dg::DGSEM, cache, alpha) +@inline function flux_differencing_kernel!( + _du::PtrArray, u_cons::PtrArray, + element, + mesh::Union{ + StructuredMesh{2}, + UnstructuredMesh2D, P4estMesh{2}, + }, + nonconservative_terms::False, + equations::CompressibleEulerEquations2D, + volume_flux::typeof(flux_shima_etal_turbo), + dg::DGSEM, cache, alpha + ) @unpack derivative_split = dg.basis @unpack contravariant_vectors = cache.elements # Create a temporary array that will be used to store the RHS with permuted # indices `[i, j, v]` to allow using SIMD instructions. # `StrideArray`s with purely static dimensions do not allocate on the heap. - du = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., - StaticInt(nvariables(equations)))) + du = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., + StaticInt(nvariables(equations)), + ) + ) # Convert conserved to primitive variables on the given `element`. - u_prim = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), - ndims(mesh))..., - StaticInt(nvariables(equations)))) + u_prim = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple( + _ -> StaticInt(nnodes(dg)), + ndims(mesh) + )..., + StaticInt(nvariables(equations)), + ) + ) @turbo for j in eachnode(dg), i in eachnode(dg) rho = u_cons[1, i, j, element] @@ -61,28 +74,40 @@ # At first, we create new temporary arrays with permuted memory layout to # allow using SIMD instructions along the first dimension (which is contiguous # in memory). - du_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - u_prim_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) + du_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + u_prim_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) u_prim_permuted[j, i, v] = u_prim[i, j, v] end fill!(du_permuted, zero(eltype(du_permuted))) # We must also permute the contravariant vectors. - contravariant_vectors_x = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_x = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for j in eachnode(dg), i in eachnode(dg) contravariant_vectors_x[j, i, 1] = contravariant_vectors[1, 1, i, j, element] @@ -104,10 +129,14 @@ v2_rr = u_prim_permuted[j, ii, 3] p_rr = u_prim_permuted[j, ii, 4] - normal_direction_1 = 0.5 * (contravariant_vectors_x[j, i, 1] + - contravariant_vectors_x[j, ii, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_x[j, i, 2] + - contravariant_vectors_x[j, ii, 2]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_x[j, i, 1] + + contravariant_vectors_x[j, ii, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_x[j, i, 2] + + contravariant_vectors_x[j, ii, 2] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 @@ -124,9 +153,11 @@ f1 = rho_avg * v_dot_n_avg f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 - f4 = (f1 * velocity_square_avg + - p_avg * v_dot_n_avg * equations.inv_gamma_minus_one - + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f4 = ( + f1 * velocity_square_avg + + p_avg * v_dot_n_avg * equations.inv_gamma_minus_one + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_i = alpha * derivative_split[i, ii] @@ -144,18 +175,22 @@ end @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) du[i, j, v] = du_permuted[j, i, v] end # y direction # We must also permute the contravariant vectors. - contravariant_vectors_y = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_y = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for j in eachnode(dg), i in eachnode(dg) contravariant_vectors_y[i, j, 1] = contravariant_vectors[1, 2, i, j, element] @@ -175,10 +210,14 @@ v2_rr = u_prim[i, jj, 3] p_rr = u_prim[i, jj, 4] - normal_direction_1 = 0.5 * (contravariant_vectors_y[i, j, 1] + - contravariant_vectors_y[i, jj, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_y[i, j, 2] + - contravariant_vectors_y[i, jj, 2]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_y[i, j, 1] + + contravariant_vectors_y[i, jj, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_y[i, j, 2] + + contravariant_vectors_y[i, jj, 2] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 @@ -195,9 +234,11 @@ f1 = rho_avg * v_dot_n_avg f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 - f4 = (f1 * velocity_square_avg + - p_avg * v_dot_n_avg * equations.inv_gamma_minus_one - + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f4 = ( + f1 * velocity_square_avg + + p_avg * v_dot_n_avg * equations.inv_gamma_minus_one + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_j = alpha * derivative_split[j, jj] @@ -217,39 +258,53 @@ # Finally, we add the temporary RHS computed here to the global RHS in the # given `element`. @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) _du[v, i, j, element] += du[i, j, v] end end -@inline function flux_differencing_kernel!(_du::PtrArray, u_cons::PtrArray, - element, - mesh::Union{StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}}, - nonconservative_terms::False, - equations::CompressibleEulerEquations2D, - volume_flux::typeof(flux_ranocha_turbo), - dg::DGSEM, cache, alpha) +@inline function flux_differencing_kernel!( + _du::PtrArray, u_cons::PtrArray, + element, + mesh::Union{ + StructuredMesh{2}, + UnstructuredMesh2D, P4estMesh{2}, + }, + nonconservative_terms::False, + equations::CompressibleEulerEquations2D, + volume_flux::typeof(flux_ranocha_turbo), + dg::DGSEM, cache, alpha + ) @unpack derivative_split = dg.basis @unpack contravariant_vectors = cache.elements # Create a temporary array that will be used to store the RHS with permuted # indices `[i, j, v]` to allow using SIMD instructions. # `StrideArray`s with purely static dimensions do not allocate on the heap. - du = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., - StaticInt(nvariables(equations)))) + du = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., + StaticInt(nvariables(equations)), + ) + ) # Convert conserved to primitive variables on the given `element`. In addition # to the usual primitive variables, we also compute logarithms of the density # and pressure to increase the performance of the required logarithmic mean # values. - u_prim = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), - ndims(mesh))..., - StaticInt(nvariables(equations) + 2))) # We also compute "+ 2" logs + u_prim = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple( + _ -> StaticInt(nnodes(dg)), + ndims(mesh) + )..., + StaticInt(nvariables(equations) + 2), + ) + ) # We also compute "+ 2" logs @turbo for j in eachnode(dg), i in eachnode(dg) rho = u_cons[1, i, j, element] @@ -273,28 +328,40 @@ end # At first, we create new temporary arrays with permuted memory layout to # allow using SIMD instructions along the first dimension (which is contiguous # in memory). - du_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - u_prim_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations) + 2))) + du_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + u_prim_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations) + 2), + ) + ) @turbo for v in indices(u_prim, 3), # v in eachvariable(equations) misses +2 logs - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) u_prim_permuted[j, i, v] = u_prim[i, j, v] end fill!(du_permuted, zero(eltype(du_permuted))) # We must also permute the contravariant vectors. - contravariant_vectors_x = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_x = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for j in eachnode(dg), i in eachnode(dg) contravariant_vectors_x[j, i, 1] = contravariant_vectors[1, 1, i, j, element] @@ -320,10 +387,14 @@ end log_rho_rr = u_prim_permuted[j, ii, 5] log_p_rr = u_prim_permuted[j, ii, 6] - normal_direction_1 = 0.5 * (contravariant_vectors_x[j, i, 1] + - contravariant_vectors_x[j, ii, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_x[j, i, 2] + - contravariant_vectors_x[j, ii, 2]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_x[j, i, 1] + + contravariant_vectors_x[j, ii, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_x[j, i, 2] + + contravariant_vectors_x[j, ii, 2] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 @@ -368,10 +439,12 @@ end f1 = rho_mean * 0.5 * (v_dot_n_ll + v_dot_n_rr) f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 - f4 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f4 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_i = alpha * derivative_split[i, ii] @@ -389,18 +462,22 @@ end end @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) du[i, j, v] = du_permuted[j, i, v] end # y direction # We must also permute the contravariant vectors. - contravariant_vectors_y = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_y = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) contravariant_vectors_y[i, j, 1] = contravariant_vectors[1, 2, i, j, element] @@ -424,10 +501,14 @@ end log_rho_rr = u_prim[i, jj, 5] log_p_rr = u_prim[i, jj, 6] - normal_direction_1 = 0.5 * (contravariant_vectors_y[i, j, 1] + - contravariant_vectors_y[i, jj, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_y[i, j, 2] + - contravariant_vectors_y[i, jj, 2]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_y[i, j, 1] + + contravariant_vectors_y[i, jj, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_y[i, j, 2] + + contravariant_vectors_y[i, jj, 2] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 @@ -472,10 +553,12 @@ end f1 = rho_mean * 0.5 * (v_dot_n_ll + v_dot_n_rr) f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 - f4 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f4 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_j = alpha * derivative_split[j, jj] @@ -495,8 +578,8 @@ end # Finally, we add the temporary RHS computed here to the global RHS in the # given `element`. @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) _du[v, i, j, element] += du[i, j, v] end diff --git a/src/solvers/dgsem_structured/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_structured/dg_2d_subcell_limiters.jl index b8ea14bf76..7f134dbb32 100644 --- a/src/solvers/dgsem_structured/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_structured/dg_2d_subcell_limiters.jl @@ -3,109 +3,123 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Calculate the DG staggered volume fluxes `fhat` in subcell FV-form inside the element -# (**without non-conservative terms**). -# -# See also `flux_differencing_kernel!`. -@inline function calcflux_fhat!(fhat1_L, fhat1_R, fhat2_L, fhat2_R, u, - mesh::Union{StructuredMesh{2}, P4estMesh{2}}, - nonconservative_terms::False, equations, - volume_flux, dg::DGSEM, element, cache) - (; contravariant_vectors) = cache.elements - (; weights, derivative_split) = dg.basis - (; flux_temp_threaded) = cache - - flux_temp = flux_temp_threaded[Threads.threadid()] - - # The FV-form fluxes are calculated in a recursive manner, i.e.: - # fhat_(0,1) = w_0 * FVol_0, - # fhat_(j,j+1) = fhat_(j-1,j) + w_j * FVol_j, for j=1,...,N-1, - # with the split form volume fluxes FVol_j = -2 * sum_i=0^N D_ji f*_(j,i). - - # To use the symmetry of the `volume_flux`, the split form volume flux is precalculated - # like in `calc_volume_integral!` for the `VolumeIntegralFluxDifferencing` - # and saved in in `flux_temp`. - - # Split form volume flux in orientation 1: x direction - flux_temp .= zero(eltype(flux_temp)) - - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - # pull the contravariant vectors in each coordinate direction - Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, element) # x direction - - # All diagonal entries of `derivative_split` are zero. Thus, we can skip - # the computation of the diagonal terms. In addition, we use the symmetry - # of the `volume_flux` to save half of the possible two-point flux - # computations. - - # x direction - for ii in (i + 1):nnodes(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, element) - # pull the contravariant vectors and compute the average - Ja1_node_ii = get_contravariant_vector(1, contravariant_vectors, ii, j, - element) - Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) - - # compute the contravariant sharp flux in the direction of the averaged contravariant vector - fluxtilde1 = volume_flux(u_node, u_node_ii, Ja1_avg, equations) - multiply_add_to_node_vars!(flux_temp, derivative_split[i, ii], fluxtilde1, - equations, dg, i, j) - multiply_add_to_node_vars!(flux_temp, derivative_split[ii, i], fluxtilde1, - equations, dg, ii, j) + #! format: noindent + + # Calculate the DG staggered volume fluxes `fhat` in subcell FV-form inside the element + # (**without non-conservative terms**). + # + # See also `flux_differencing_kernel!`. + @inline function calcflux_fhat!( + fhat1_L, fhat1_R, fhat2_L, fhat2_R, u, + mesh::Union{StructuredMesh{2}, P4estMesh{2}}, + nonconservative_terms::False, equations, + volume_flux, dg::DGSEM, element, cache + ) + (; contravariant_vectors) = cache.elements + (; weights, derivative_split) = dg.basis + (; flux_temp_threaded) = cache + + flux_temp = flux_temp_threaded[Threads.threadid()] + + # The FV-form fluxes are calculated in a recursive manner, i.e.: + # fhat_(0,1) = w_0 * FVol_0, + # fhat_(j,j+1) = fhat_(j-1,j) + w_j * FVol_j, for j=1,...,N-1, + # with the split form volume fluxes FVol_j = -2 * sum_i=0^N D_ji f*_(j,i). + + # To use the symmetry of the `volume_flux`, the split form volume flux is precalculated + # like in `calc_volume_integral!` for the `VolumeIntegralFluxDifferencing` + # and saved in in `flux_temp`. + + # Split form volume flux in orientation 1: x direction + flux_temp .= zero(eltype(flux_temp)) + + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # pull the contravariant vectors in each coordinate direction + Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, element) # x direction + + # All diagonal entries of `derivative_split` are zero. Thus, we can skip + # the computation of the diagonal terms. In addition, we use the symmetry + # of the `volume_flux` to save half of the possible two-point flux + # computations. + + # x direction + for ii in (i + 1):nnodes(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, element) + # pull the contravariant vectors and compute the average + Ja1_node_ii = get_contravariant_vector( + 1, contravariant_vectors, ii, j, + element + ) + Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) + + # compute the contravariant sharp flux in the direction of the averaged contravariant vector + fluxtilde1 = volume_flux(u_node, u_node_ii, Ja1_avg, equations) + multiply_add_to_node_vars!( + flux_temp, derivative_split[i, ii], fluxtilde1, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + flux_temp, derivative_split[ii, i], fluxtilde1, + equations, dg, ii, j + ) + end end - end - # FV-form flux `fhat` in x direction - fhat1_L[:, 1, :] .= zero(eltype(fhat1_L)) - fhat1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_L)) - fhat1_R[:, 1, :] .= zero(eltype(fhat1_R)) - fhat1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_R)) + # FV-form flux `fhat` in x direction + fhat1_L[:, 1, :] .= zero(eltype(fhat1_L)) + fhat1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_L)) + fhat1_R[:, 1, :] .= zero(eltype(fhat1_R)) + fhat1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_R)) - for j in eachnode(dg), i in 1:(nnodes(dg) - 1), v in eachvariable(equations) - fhat1_L[v, i + 1, j] = fhat1_L[v, i, j] + weights[i] * flux_temp[v, i, j] - fhat1_R[v, i + 1, j] = fhat1_L[v, i + 1, j] - end + for j in eachnode(dg), i in 1:(nnodes(dg) - 1), v in eachvariable(equations) + fhat1_L[v, i + 1, j] = fhat1_L[v, i, j] + weights[i] * flux_temp[v, i, j] + fhat1_R[v, i + 1, j] = fhat1_L[v, i + 1, j] + end - # Split form volume flux in orientation 2: y direction - flux_temp .= zero(eltype(flux_temp)) - - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - # pull the contravariant vectors in each coordinate direction - Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, element) - - # y direction - for jj in (j + 1):nnodes(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, element) - # pull the contravariant vectors and compute the average - Ja2_node_jj = get_contravariant_vector(2, contravariant_vectors, i, jj, - element) - Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) - # compute the contravariant sharp flux in the direction of the averaged contravariant vector - fluxtilde2 = volume_flux(u_node, u_node_jj, Ja2_avg, equations) - multiply_add_to_node_vars!(flux_temp, derivative_split[j, jj], fluxtilde2, - equations, dg, i, j) - multiply_add_to_node_vars!(flux_temp, derivative_split[jj, j], fluxtilde2, - equations, dg, i, jj) + # Split form volume flux in orientation 2: y direction + flux_temp .= zero(eltype(flux_temp)) + + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # pull the contravariant vectors in each coordinate direction + Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, element) + + # y direction + for jj in (j + 1):nnodes(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, element) + # pull the contravariant vectors and compute the average + Ja2_node_jj = get_contravariant_vector( + 2, contravariant_vectors, i, jj, + element + ) + Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) + # compute the contravariant sharp flux in the direction of the averaged contravariant vector + fluxtilde2 = volume_flux(u_node, u_node_jj, Ja2_avg, equations) + multiply_add_to_node_vars!( + flux_temp, derivative_split[j, jj], fluxtilde2, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + flux_temp, derivative_split[jj, j], fluxtilde2, + equations, dg, i, jj + ) + end end - end - # FV-form flux `fhat` in y direction - fhat2_L[:, :, 1] .= zero(eltype(fhat2_L)) - fhat2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_L)) - fhat2_R[:, :, 1] .= zero(eltype(fhat2_R)) - fhat2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_R)) + # FV-form flux `fhat` in y direction + fhat2_L[:, :, 1] .= zero(eltype(fhat2_L)) + fhat2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_L)) + fhat2_R[:, :, 1] .= zero(eltype(fhat2_R)) + fhat2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_R)) - for j in 1:(nnodes(dg) - 1), i in eachnode(dg), v in eachvariable(equations) - fhat2_L[v, i, j + 1] = fhat2_L[v, i, j] + weights[j] * flux_temp[v, i, j] - fhat2_R[v, i, j + 1] = fhat2_L[v, i, j + 1] - end + for j in 1:(nnodes(dg) - 1), i in eachnode(dg), v in eachvariable(equations) + fhat2_L[v, i, j + 1] = fhat2_L[v, i, j] + weights[j] * flux_temp[v, i, j] + fhat2_R[v, i, j + 1] = fhat2_L[v, i, j + 1] + end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_structured/dg_3d.jl b/src/solvers/dgsem_structured/dg_3d.jl index e8430eaa49..dc363eac8a 100644 --- a/src/solvers/dgsem_structured/dg_3d.jl +++ b/src/solvers/dgsem_structured/dg_3d.jl @@ -3,803 +3,961 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function rhs!(du, u, t, - mesh::StructuredMesh{3}, equations, - initial_condition, boundary_conditions, source_terms::Source, - dg::DG, cache) where {Source} - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) - end + #! format: noindent + + function rhs!( + du, u, t, + mesh::StructuredMesh{3}, equations, + initial_condition, boundary_conditions, source_terms::Source, + dg::DG, cache + ) where {Source} + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache, u, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg) - end + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache, u, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, u, t, boundary_conditions, mesh, equations, - dg.surface_integral, dg) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, u, t, boundary_conditions, mesh, equations, + dg.surface_integral, dg + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) - end + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) - # Calculate source terms - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, equations, dg, cache) - end + # Calculate source terms + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, equations, dg, cache) + end - return nothing -end + return nothing + end -#= + #= `weak_form_kernel!` is only implemented for conserved terms as non-conservative terms should always be discretized in conjunction with a flux-splitting scheme, see `flux_differencing_kernel!`. This treatment is required to achieve, e.g., entropy-stability or well-balancedness. See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-1765644064 =# -@inline function weak_form_kernel!(du, u, - element, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms::False, equations, - dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_dhat = dg.basis - @unpack contravariant_vectors = cache.elements - - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - - flux1 = flux(u_node, 1, equations) - flux2 = flux(u_node, 2, equations) - flux3 = flux(u_node, 3, equations) - - # Compute the contravariant flux by taking the scalar product of the - # first contravariant vector Ja^1 and the flux vector - Ja11, Ja12, Ja13 = get_contravariant_vector(1, contravariant_vectors, i, j, k, - element) - contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 + Ja13 * flux3 - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[ii, i], - contravariant_flux1, equations, dg, ii, j, k, - element) - end + @inline function weak_form_kernel!( + du, u, + element, + mesh::Union{ + StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms::False, equations, + dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_dhat = dg.basis + @unpack contravariant_vectors = cache.elements - # Compute the contravariant flux by taking the scalar product of the - # second contravariant vector Ja^2 and the flux vector - Ja21, Ja22, Ja23 = get_contravariant_vector(2, contravariant_vectors, i, j, k, - element) - contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 + Ja23 * flux3 - for jj in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[jj, j], - contravariant_flux2, equations, dg, i, jj, k, - element) - end - - # Compute the contravariant flux by taking the scalar product of the - # third contravariant vector Ja^3 and the flux vector - Ja31, Ja32, Ja33 = get_contravariant_vector(3, contravariant_vectors, i, j, k, - element) - contravariant_flux3 = Ja31 * flux1 + Ja32 * flux2 + Ja33 * flux3 - for kk in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[kk, k], - contravariant_flux3, equations, dg, i, j, kk, - element) - end - end + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + + flux1 = flux(u_node, 1, equations) + flux2 = flux(u_node, 2, equations) + flux3 = flux(u_node, 3, equations) + + # Compute the contravariant flux by taking the scalar product of the + # first contravariant vector Ja^1 and the flux vector + Ja11, Ja12, Ja13 = get_contravariant_vector( + 1, contravariant_vectors, i, j, k, + element + ) + contravariant_flux1 = Ja11 * flux1 + Ja12 * flux2 + Ja13 * flux3 + for ii in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[ii, i], + contravariant_flux1, equations, dg, ii, j, k, + element + ) + end - return nothing -end - -# flux differencing volume integral on curvilinear hexahedral elements. Averaging of the -# mapping terms, stored in `contravariant_vectors`, is peeled apart from the evaluation of -# the physical fluxes in each Cartesian direction -@inline function flux_differencing_kernel!(du, u, - element, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms::False, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_split = dg.basis - @unpack contravariant_vectors = cache.elements - - # Calculate volume integral in one element - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - - # pull the contravariant vectors in each coordinate direction - Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, k, element) - Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, k, element) - Ja3_node = get_contravariant_vector(3, contravariant_vectors, i, j, k, element) - - # All diagonal entries of `derivative_split` are zero. Thus, we can skip - # the computation of the diagonal terms. In addition, we use the symmetry - # of the `volume_flux` to save half of the possible two-point flux - # computations. - - # x direction - for ii in (i + 1):nnodes(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, k, element) - # pull the contravariant vectors and compute the average - Ja1_node_ii = get_contravariant_vector(1, contravariant_vectors, - ii, j, k, element) - Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) - # compute the contravariant sharp flux in the direction of the - # averaged contravariant vector - fluxtilde1 = volume_flux(u_node, u_node_ii, Ja1_avg, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[i, ii], fluxtilde1, - equations, dg, i, j, k, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[ii, i], fluxtilde1, - equations, dg, ii, j, k, element) - end + # Compute the contravariant flux by taking the scalar product of the + # second contravariant vector Ja^2 and the flux vector + Ja21, Ja22, Ja23 = get_contravariant_vector( + 2, contravariant_vectors, i, j, k, + element + ) + contravariant_flux2 = Ja21 * flux1 + Ja22 * flux2 + Ja23 * flux3 + for jj in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[jj, j], + contravariant_flux2, equations, dg, i, jj, k, + element + ) + end - # y direction - for jj in (j + 1):nnodes(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, k, element) - # pull the contravariant vectors and compute the average - Ja2_node_jj = get_contravariant_vector(2, contravariant_vectors, - i, jj, k, element) - Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) - # compute the contravariant sharp flux in the direction of the - # averaged contravariant vector - fluxtilde2 = volume_flux(u_node, u_node_jj, Ja2_avg, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[j, jj], fluxtilde2, - equations, dg, i, j, k, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[jj, j], fluxtilde2, - equations, dg, i, jj, k, element) + # Compute the contravariant flux by taking the scalar product of the + # third contravariant vector Ja^3 and the flux vector + Ja31, Ja32, Ja33 = get_contravariant_vector( + 3, contravariant_vectors, i, j, k, + element + ) + contravariant_flux3 = Ja31 * flux1 + Ja32 * flux2 + Ja33 * flux3 + for kk in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[kk, k], + contravariant_flux3, equations, dg, i, j, kk, + element + ) + end end - # z direction - for kk in (k + 1):nnodes(dg) - u_node_kk = get_node_vars(u, equations, dg, i, j, kk, element) - # pull the contravariant vectors and compute the average - Ja3_node_kk = get_contravariant_vector(3, contravariant_vectors, - i, j, kk, element) - Ja3_avg = 0.5f0 * (Ja3_node + Ja3_node_kk) - # compute the contravariant sharp flux in the direction of the - # averaged contravariant vector - fluxtilde3 = volume_flux(u_node, u_node_kk, Ja3_avg, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[k, kk], fluxtilde3, - equations, dg, i, j, k, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[kk, k], fluxtilde3, - equations, dg, i, j, kk, element) - end + return nothing end -end - -@inline function flux_differencing_kernel!(du, u, - element, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms::True, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - @unpack derivative_split = dg.basis - @unpack contravariant_vectors = cache.elements - symmetric_flux, nonconservative_flux = volume_flux - - # Apply the symmetric flux as usual - flux_differencing_kernel!(du, u, element, mesh, False(), equations, symmetric_flux, - dg, cache, alpha) - - # Calculate the remaining volume terms using the nonsymmetric generalized flux - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - - # pull the contravariant vectors in each coordinate direction - Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, k, element) - Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, k, element) - Ja3_node = get_contravariant_vector(3, contravariant_vectors, i, j, k, element) - - # The diagonal terms are zero since the diagonal of `derivative_split` - # is zero. We ignore this for now. - # In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. - # Thus, we need to pass both to the nonconservative flux. - - # x direction - integral_contribution = zero(u_node) - for ii in eachnode(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, k, element) - # pull the contravariant vectors and compute the average - Ja1_node_ii = get_contravariant_vector(1, contravariant_vectors, ii, j, k, - element) - Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) - # compute the contravariant nonconservative flux in the direction of the - # averaged contravariant vector - fluxtilde1 = nonconservative_flux(u_node, u_node_ii, Ja1_node, Ja1_avg, - equations) - integral_contribution = integral_contribution + - derivative_split[i, ii] * fluxtilde1 - end - - # y direction - for jj in eachnode(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, k, element) - # pull the contravariant vectors and compute the average - Ja2_node_jj = get_contravariant_vector(2, contravariant_vectors, i, jj, k, - element) - Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) - # compute the contravariant nonconservative flux in the direction of the - # averaged contravariant vector - fluxtilde2 = nonconservative_flux(u_node, u_node_jj, Ja2_node, Ja2_avg, - equations) - integral_contribution = integral_contribution + - derivative_split[j, jj] * fluxtilde2 - end - # z direction - for kk in eachnode(dg) - u_node_kk = get_node_vars(u, equations, dg, i, j, kk, element) - # pull the contravariant vectors and compute the average - Ja3_node_kk = get_contravariant_vector(3, contravariant_vectors, i, j, kk, - element) - Ja3_avg = 0.5f0 * (Ja3_node + Ja3_node_kk) - # compute the contravariant nonconservative flux in the direction of the - # averaged contravariant vector - fluxtilde3 = nonconservative_flux(u_node, u_node_kk, Ja3_node, Ja3_avg, - equations) - integral_contribution = integral_contribution + - derivative_split[k, kk] * fluxtilde3 - end - - # The factor 0.5 cancels the factor 2 in the flux differencing form - multiply_add_to_node_vars!(du, alpha * 0.5f0, integral_contribution, equations, - dg, i, j, k, element) - end -end - -# Computing the normal vector for the FV method on curvilinear subcells. -# To fulfill free-stream preservation we use the explicit formula B.53 in Appendix B.4 -# by Hennemann, Rueda-Ramirez, Hindenlang, Gassner (2020) -# "A provably entropy stable subcell shock capturing approach for high order split form DG for the compressible Euler equations" -# [arXiv: 2008.12044v2](https://arxiv.org/pdf/2008.12044) -@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, - fstar3_R, u, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms::False, - equations, volume_flux_fv, dg::DGSEM, element, cache) - @unpack contravariant_vectors = cache.elements - @unpack weights, derivative_matrix = dg.basis - - # Performance improvement if the metric terms of the subcell FV method are only computed - # once at the beginning of the simulation, instead of at every Runge-Kutta stage - fstar1_L[:, 1, :, :] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1, :, :] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_R)) - - for k in eachnode(dg), j in eachnode(dg) - normal_direction = get_contravariant_vector(1, contravariant_vectors, 1, j, k, - element) - - for i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, j, k, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) - - for m in 1:nnodes(dg) - normal_direction += weights[i - 1] * derivative_matrix[i - 1, m] * - get_contravariant_vector(1, contravariant_vectors, - m, j, k, element) + # flux differencing volume integral on curvilinear hexahedral elements. Averaging of the + # mapping terms, stored in `contravariant_vectors`, is peeled apart from the evaluation of + # the physical fluxes in each Cartesian direction + @inline function flux_differencing_kernel!( + du, u, + element, + mesh::Union{ + StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms::False, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_split = dg.basis + @unpack contravariant_vectors = cache.elements + + # Calculate volume integral in one element + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + + # pull the contravariant vectors in each coordinate direction + Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, k, element) + Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, k, element) + Ja3_node = get_contravariant_vector(3, contravariant_vectors, i, j, k, element) + + # All diagonal entries of `derivative_split` are zero. Thus, we can skip + # the computation of the diagonal terms. In addition, we use the symmetry + # of the `volume_flux` to save half of the possible two-point flux + # computations. + + # x direction + for ii in (i + 1):nnodes(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, k, element) + # pull the contravariant vectors and compute the average + Ja1_node_ii = get_contravariant_vector( + 1, contravariant_vectors, + ii, j, k, element + ) + Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) + # compute the contravariant sharp flux in the direction of the + # averaged contravariant vector + fluxtilde1 = volume_flux(u_node, u_node_ii, Ja1_avg, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[i, ii], fluxtilde1, + equations, dg, i, j, k, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[ii, i], fluxtilde1, + equations, dg, ii, j, k, element + ) end - # Compute the contravariant flux - contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + # y direction + for jj in (j + 1):nnodes(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, k, element) + # pull the contravariant vectors and compute the average + Ja2_node_jj = get_contravariant_vector( + 2, contravariant_vectors, + i, jj, k, element + ) + Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) + # compute the contravariant sharp flux in the direction of the + # averaged contravariant vector + fluxtilde2 = volume_flux(u_node, u_node_jj, Ja2_avg, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[j, jj], fluxtilde2, + equations, dg, i, j, k, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[jj, j], fluxtilde2, + equations, dg, i, jj, k, element + ) + end - set_node_vars!(fstar1_L, contravariant_flux, equations, dg, i, j, k) - set_node_vars!(fstar1_R, contravariant_flux, equations, dg, i, j, k) + # z direction + for kk in (k + 1):nnodes(dg) + u_node_kk = get_node_vars(u, equations, dg, i, j, kk, element) + # pull the contravariant vectors and compute the average + Ja3_node_kk = get_contravariant_vector( + 3, contravariant_vectors, + i, j, kk, element + ) + Ja3_avg = 0.5f0 * (Ja3_node + Ja3_node_kk) + # compute the contravariant sharp flux in the direction of the + # averaged contravariant vector + fluxtilde3 = volume_flux(u_node, u_node_kk, Ja3_avg, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[k, kk], fluxtilde3, + equations, dg, i, j, k, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[kk, k], fluxtilde3, + equations, dg, i, j, kk, element + ) + end end end - fstar2_L[:, :, 1, :] .= zero(eltype(fstar2_L)) - fstar2_L[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_L)) - fstar2_R[:, :, 1, :] .= zero(eltype(fstar2_R)) - fstar2_R[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_R)) - - for k in eachnode(dg), i in eachnode(dg) - normal_direction = get_contravariant_vector(2, contravariant_vectors, i, 1, k, - element) - - for j in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i, j - 1, k, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) + @inline function flux_differencing_kernel!( + du, u, + element, + mesh::Union{ + StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms::True, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + @unpack derivative_split = dg.basis + @unpack contravariant_vectors = cache.elements + symmetric_flux, nonconservative_flux = volume_flux + + # Apply the symmetric flux as usual + flux_differencing_kernel!( + du, u, element, mesh, False(), equations, symmetric_flux, + dg, cache, alpha + ) + + # Calculate the remaining volume terms using the nonsymmetric generalized flux + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + + # pull the contravariant vectors in each coordinate direction + Ja1_node = get_contravariant_vector(1, contravariant_vectors, i, j, k, element) + Ja2_node = get_contravariant_vector(2, contravariant_vectors, i, j, k, element) + Ja3_node = get_contravariant_vector(3, contravariant_vectors, i, j, k, element) + + # The diagonal terms are zero since the diagonal of `derivative_split` + # is zero. We ignore this for now. + # In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. + # Thus, we need to pass both to the nonconservative flux. + + # x direction + integral_contribution = zero(u_node) + for ii in eachnode(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, k, element) + # pull the contravariant vectors and compute the average + Ja1_node_ii = get_contravariant_vector( + 1, contravariant_vectors, ii, j, k, + element + ) + Ja1_avg = 0.5f0 * (Ja1_node + Ja1_node_ii) + # compute the contravariant nonconservative flux in the direction of the + # averaged contravariant vector + fluxtilde1 = nonconservative_flux( + u_node, u_node_ii, Ja1_node, Ja1_avg, + equations + ) + integral_contribution = integral_contribution + + derivative_split[i, ii] * fluxtilde1 + end - for m in 1:nnodes(dg) - normal_direction += weights[j - 1] * derivative_matrix[j - 1, m] * - get_contravariant_vector(2, contravariant_vectors, - i, m, k, element) + # y direction + for jj in eachnode(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, k, element) + # pull the contravariant vectors and compute the average + Ja2_node_jj = get_contravariant_vector( + 2, contravariant_vectors, i, jj, k, + element + ) + Ja2_avg = 0.5f0 * (Ja2_node + Ja2_node_jj) + # compute the contravariant nonconservative flux in the direction of the + # averaged contravariant vector + fluxtilde2 = nonconservative_flux( + u_node, u_node_jj, Ja2_node, Ja2_avg, + equations + ) + integral_contribution = integral_contribution + + derivative_split[j, jj] * fluxtilde2 end - # Compute the contravariant flux - contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + # z direction + for kk in eachnode(dg) + u_node_kk = get_node_vars(u, equations, dg, i, j, kk, element) + # pull the contravariant vectors and compute the average + Ja3_node_kk = get_contravariant_vector( + 3, contravariant_vectors, i, j, kk, + element + ) + Ja3_avg = 0.5f0 * (Ja3_node + Ja3_node_kk) + # compute the contravariant nonconservative flux in the direction of the + # averaged contravariant vector + fluxtilde3 = nonconservative_flux( + u_node, u_node_kk, Ja3_node, Ja3_avg, + equations + ) + integral_contribution = integral_contribution + + derivative_split[k, kk] * fluxtilde3 + end - set_node_vars!(fstar2_L, contravariant_flux, equations, dg, i, j, k) - set_node_vars!(fstar2_R, contravariant_flux, equations, dg, i, j, k) + # The factor 0.5 cancels the factor 2 in the flux differencing form + multiply_add_to_node_vars!( + du, alpha * 0.5f0, integral_contribution, equations, + dg, i, j, k, element + ) end end - fstar3_L[:, :, :, 1] .= zero(eltype(fstar3_L)) - fstar3_L[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_L)) - fstar3_R[:, :, :, 1] .= zero(eltype(fstar3_R)) - fstar3_R[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_R)) - - for j in eachnode(dg), i in eachnode(dg) - normal_direction = get_contravariant_vector(3, contravariant_vectors, i, j, 1, - element) + # Computing the normal vector for the FV method on curvilinear subcells. + # To fulfill free-stream preservation we use the explicit formula B.53 in Appendix B.4 + # by Hennemann, Rueda-Ramirez, Hindenlang, Gassner (2020) + # "A provably entropy stable subcell shock capturing approach for high order split form DG for the compressible Euler equations" + # [arXiv: 2008.12044v2](https://arxiv.org/pdf/2008.12044) + @inline function calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, + fstar3_R, u, + mesh::Union{ + StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms::False, + equations, volume_flux_fv, dg::DGSEM, element, cache + ) + @unpack contravariant_vectors = cache.elements + @unpack weights, derivative_matrix = dg.basis + + # Performance improvement if the metric terms of the subcell FV method are only computed + # once at the beginning of the simulation, instead of at every Runge-Kutta stage + fstar1_L[:, 1, :, :] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1, :, :] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_R)) - for k in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i, j, k - 1, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) - - for m in 1:nnodes(dg) - normal_direction += weights[k - 1] * derivative_matrix[k - 1, m] * - get_contravariant_vector(3, contravariant_vectors, - i, j, m, element) + for k in eachnode(dg), j in eachnode(dg) + normal_direction = get_contravariant_vector( + 1, contravariant_vectors, 1, j, k, + element + ) + + for i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, j, k, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + + for m in 1:nnodes(dg) + normal_direction += weights[i - 1] * derivative_matrix[i - 1, m] * + get_contravariant_vector( + 1, contravariant_vectors, + m, j, k, element + ) + end + + # Compute the contravariant flux + contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + + set_node_vars!(fstar1_L, contravariant_flux, equations, dg, i, j, k) + set_node_vars!(fstar1_R, contravariant_flux, equations, dg, i, j, k) end + end - # Compute the contravariant flux - contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + fstar2_L[:, :, 1, :] .= zero(eltype(fstar2_L)) + fstar2_L[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_L)) + fstar2_R[:, :, 1, :] .= zero(eltype(fstar2_R)) + fstar2_R[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_R)) - set_node_vars!(fstar3_L, contravariant_flux, equations, dg, i, j, k) - set_node_vars!(fstar3_R, contravariant_flux, equations, dg, i, j, k) + for k in eachnode(dg), i in eachnode(dg) + normal_direction = get_contravariant_vector( + 2, contravariant_vectors, i, 1, k, + element + ) + + for j in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i, j - 1, k, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + + for m in 1:nnodes(dg) + normal_direction += weights[j - 1] * derivative_matrix[j - 1, m] * + get_contravariant_vector( + 2, contravariant_vectors, + i, m, k, element + ) + end + + # Compute the contravariant flux + contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + + set_node_vars!(fstar2_L, contravariant_flux, equations, dg, i, j, k) + set_node_vars!(fstar2_R, contravariant_flux, equations, dg, i, j, k) + end end - end - return nothing -end - -# # Calculate the finite volume fluxes inside curvilinear elements (**with non-conservative terms**). -@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, - fstar3_R, u, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms::True, - equations, volume_flux_fv, dg::DGSEM, element, cache) - @unpack contravariant_vectors = cache.elements - @unpack weights, derivative_matrix = dg.basis - - volume_flux, nonconservative_flux = volume_flux_fv - - # Performance improvement if the metric terms of the subcell FV method are only computed - # once at the beginning of the simulation, instead of at every Runge-Kutta stage - fstar1_L[:, 1, :, :] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1, :, :] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_R)) - - for k in eachnode(dg), j in eachnode(dg) - normal_direction = get_contravariant_vector(1, contravariant_vectors, 1, j, k, - element) - - for i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, j, k, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) - - for m in eachnode(dg) - normal_direction += weights[i - 1] * derivative_matrix[i - 1, m] * - get_contravariant_vector(1, contravariant_vectors, - m, j, k, element) - end + fstar3_L[:, :, :, 1] .= zero(eltype(fstar3_L)) + fstar3_L[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_L)) + fstar3_R[:, :, :, 1] .= zero(eltype(fstar3_R)) + fstar3_R[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_R)) - # Compute the contravariant conservative flux - ftilde = volume_flux(u_ll, u_rr, normal_direction, equations) - - # Compute and add in the nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - ftilde_L = ftilde + - 0.5f0 * nonconservative_flux(u_ll, u_rr, normal_direction, - normal_direction, equations) - ftilde_R = ftilde + - 0.5f0 * nonconservative_flux(u_rr, u_ll, normal_direction, - normal_direction, equations) - - set_node_vars!(fstar1_L, ftilde_L, equations, dg, i, j, k) - set_node_vars!(fstar1_R, ftilde_R, equations, dg, i, j, k) + for j in eachnode(dg), i in eachnode(dg) + normal_direction = get_contravariant_vector( + 3, contravariant_vectors, i, j, 1, + element + ) + + for k in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i, j, k - 1, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + + for m in 1:nnodes(dg) + normal_direction += weights[k - 1] * derivative_matrix[k - 1, m] * + get_contravariant_vector( + 3, contravariant_vectors, + i, j, m, element + ) + end + + # Compute the contravariant flux + contravariant_flux = volume_flux_fv(u_ll, u_rr, normal_direction, equations) + + set_node_vars!(fstar3_L, contravariant_flux, equations, dg, i, j, k) + set_node_vars!(fstar3_R, contravariant_flux, equations, dg, i, j, k) + end end - end - - fstar2_L[:, :, 1, :] .= zero(eltype(fstar2_L)) - fstar2_L[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_L)) - fstar2_R[:, :, 1, :] .= zero(eltype(fstar2_R)) - fstar2_R[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_R)) - for k in eachnode(dg), i in eachnode(dg) - normal_direction = get_contravariant_vector(2, contravariant_vectors, i, 1, k, - element) + return nothing + end - for j in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i, j - 1, k, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) + # # Calculate the finite volume fluxes inside curvilinear elements (**with non-conservative terms**). + @inline function calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, + fstar3_R, u, + mesh::Union{ + StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms::True, + equations, volume_flux_fv, dg::DGSEM, element, cache + ) + @unpack contravariant_vectors = cache.elements + @unpack weights, derivative_matrix = dg.basis + + volume_flux, nonconservative_flux = volume_flux_fv + + # Performance improvement if the metric terms of the subcell FV method are only computed + # once at the beginning of the simulation, instead of at every Runge-Kutta stage + fstar1_L[:, 1, :, :] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1, :, :] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_R)) - for m in eachnode(dg) - normal_direction += weights[j - 1] * derivative_matrix[j - 1, m] * - get_contravariant_vector(2, contravariant_vectors, - i, m, k, element) + for k in eachnode(dg), j in eachnode(dg) + normal_direction = get_contravariant_vector( + 1, contravariant_vectors, 1, j, k, + element + ) + + for i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, j, k, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + + for m in eachnode(dg) + normal_direction += weights[i - 1] * derivative_matrix[i - 1, m] * + get_contravariant_vector( + 1, contravariant_vectors, + m, j, k, element + ) + end + + # Compute the contravariant conservative flux + ftilde = volume_flux(u_ll, u_rr, normal_direction, equations) + + # Compute and add in the nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + ftilde_L = ftilde + + 0.5f0 * nonconservative_flux( + u_ll, u_rr, normal_direction, + normal_direction, equations + ) + ftilde_R = ftilde + + 0.5f0 * nonconservative_flux( + u_rr, u_ll, normal_direction, + normal_direction, equations + ) + + set_node_vars!(fstar1_L, ftilde_L, equations, dg, i, j, k) + set_node_vars!(fstar1_R, ftilde_R, equations, dg, i, j, k) end - - # Compute the contravariant conservative flux - ftilde = volume_flux(u_ll, u_rr, normal_direction, equations) - - # Compute and add in the nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - ftilde_L = ftilde + - 0.5f0 * nonconservative_flux(u_ll, u_rr, normal_direction, - normal_direction, equations) - ftilde_R = ftilde + - 0.5f0 * nonconservative_flux(u_rr, u_ll, normal_direction, - normal_direction, equations) - - set_node_vars!(fstar2_L, ftilde_L, equations, dg, i, j, k) - set_node_vars!(fstar2_R, ftilde_R, equations, dg, i, j, k) end - end - fstar3_L[:, :, :, 1] .= zero(eltype(fstar3_L)) - fstar3_L[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_L)) - fstar3_R[:, :, :, 1] .= zero(eltype(fstar3_R)) - fstar3_R[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_R)) + fstar2_L[:, :, 1, :] .= zero(eltype(fstar2_L)) + fstar2_L[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_L)) + fstar2_R[:, :, 1, :] .= zero(eltype(fstar2_R)) + fstar2_R[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_R)) - for j in eachnode(dg), i in eachnode(dg) - normal_direction = get_contravariant_vector(3, contravariant_vectors, i, j, 1, - element) + for k in eachnode(dg), i in eachnode(dg) + normal_direction = get_contravariant_vector( + 2, contravariant_vectors, i, 1, k, + element + ) + + for j in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i, j - 1, k, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + + for m in eachnode(dg) + normal_direction += weights[j - 1] * derivative_matrix[j - 1, m] * + get_contravariant_vector( + 2, contravariant_vectors, + i, m, k, element + ) + end + + # Compute the contravariant conservative flux + ftilde = volume_flux(u_ll, u_rr, normal_direction, equations) + + # Compute and add in the nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + ftilde_L = ftilde + + 0.5f0 * nonconservative_flux( + u_ll, u_rr, normal_direction, + normal_direction, equations + ) + ftilde_R = ftilde + + 0.5f0 * nonconservative_flux( + u_rr, u_ll, normal_direction, + normal_direction, equations + ) + + set_node_vars!(fstar2_L, ftilde_L, equations, dg, i, j, k) + set_node_vars!(fstar2_R, ftilde_R, equations, dg, i, j, k) + end + end - for k in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i, j, k - 1, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) + fstar3_L[:, :, :, 1] .= zero(eltype(fstar3_L)) + fstar3_L[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_L)) + fstar3_R[:, :, :, 1] .= zero(eltype(fstar3_R)) + fstar3_R[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_R)) - for m in eachnode(dg) - normal_direction += weights[k - 1] * derivative_matrix[k - 1, m] * - get_contravariant_vector(3, contravariant_vectors, - i, j, m, element) + for j in eachnode(dg), i in eachnode(dg) + normal_direction = get_contravariant_vector( + 3, contravariant_vectors, i, j, 1, + element + ) + + for k in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i, j, k - 1, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + + for m in eachnode(dg) + normal_direction += weights[k - 1] * derivative_matrix[k - 1, m] * + get_contravariant_vector( + 3, contravariant_vectors, + i, j, m, element + ) + end + + # Compute the contravariant conservative flux + ftilde = volume_flux(u_ll, u_rr, normal_direction, equations) + + # Compute and add in the nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + ftilde_L = ftilde + + 0.5f0 * nonconservative_flux( + u_ll, u_rr, normal_direction, + normal_direction, equations + ) + ftilde_R = ftilde + + 0.5f0 * nonconservative_flux( + u_rr, u_ll, normal_direction, + normal_direction, equations + ) + + set_node_vars!(fstar3_L, ftilde_L, equations, dg, i, j, k) + set_node_vars!(fstar3_R, ftilde_R, equations, dg, i, j, k) end - - # Compute the contravariant conservative flux - ftilde = volume_flux(u_ll, u_rr, normal_direction, equations) - - # Compute and add in the nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - ftilde_L = ftilde + - 0.5f0 * nonconservative_flux(u_ll, u_rr, normal_direction, - normal_direction, equations) - ftilde_R = ftilde + - 0.5f0 * nonconservative_flux(u_rr, u_ll, normal_direction, - normal_direction, equations) - - set_node_vars!(fstar3_L, ftilde_L, equations, dg, i, j, k) - set_node_vars!(fstar3_R, ftilde_R, equations, dg, i, j, k) end - end - return nothing -end - -function calc_interface_flux!(cache, u, mesh::StructuredMesh{3}, - nonconservative_terms, # can be True/False - equations, surface_integral, dg::DG) - @unpack elements = cache - - @threaded for element in eachelement(dg, cache) - # Interfaces in negative directions - # Faster version of "for orientation in (1, 2, 3)" - - # Interfaces in x-direction (`orientation` = 1) - calc_interface_flux!(elements.surface_flux_values, - elements.left_neighbors[1, element], - element, 1, u, mesh, - nonconservative_terms, equations, - surface_integral, dg, cache) - - # Interfaces in y-direction (`orientation` = 2) - calc_interface_flux!(elements.surface_flux_values, - elements.left_neighbors[2, element], - element, 2, u, mesh, - nonconservative_terms, equations, - surface_integral, dg, cache) - - # Interfaces in z-direction (`orientation` = 3) - calc_interface_flux!(elements.surface_flux_values, - elements.left_neighbors[3, element], - element, 3, u, mesh, - nonconservative_terms, equations, - surface_integral, dg, cache) + return nothing end - return nothing -end - -@inline function calc_interface_flux!(surface_flux_values, left_element, right_element, - orientation, u, - mesh::StructuredMesh{3}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache) - # This is slow for LSA, but for some reason faster for Euler (see #519) - if left_element <= 0 # left_element = 0 at boundaries - return surface_flux_values + function calc_interface_flux!( + cache, u, mesh::StructuredMesh{3}, + nonconservative_terms, # can be True/False + equations, surface_integral, dg::DG + ) + @unpack elements = cache + + @threaded for element in eachelement(dg, cache) + # Interfaces in negative directions + # Faster version of "for orientation in (1, 2, 3)" + + # Interfaces in x-direction (`orientation` = 1) + calc_interface_flux!( + elements.surface_flux_values, + elements.left_neighbors[1, element], + element, 1, u, mesh, + nonconservative_terms, equations, + surface_integral, dg, cache + ) + + # Interfaces in y-direction (`orientation` = 2) + calc_interface_flux!( + elements.surface_flux_values, + elements.left_neighbors[2, element], + element, 2, u, mesh, + nonconservative_terms, equations, + surface_integral, dg, cache + ) + + # Interfaces in z-direction (`orientation` = 3) + calc_interface_flux!( + elements.surface_flux_values, + elements.left_neighbors[3, element], + element, 3, u, mesh, + nonconservative_terms, equations, + surface_integral, dg, cache + ) + end + + return nothing end - @unpack surface_flux = surface_integral - @unpack contravariant_vectors, inverse_jacobian = cache.elements - - right_direction = 2 * orientation - left_direction = right_direction - 1 - - for j in eachnode(dg), i in eachnode(dg) - if orientation == 1 - u_ll = get_node_vars(u, equations, dg, nnodes(dg), i, j, left_element) - u_rr = get_node_vars(u, equations, dg, 1, i, j, right_element) - - # If the mapping is orientation-reversing, the contravariant vectors' orientation - # is reversed as well. The normal vector must be oriented in the direction - # from `left_element` to `right_element`, or the numerical flux will be computed - # incorrectly (downwind direction). - sign_jacobian = sign(inverse_jacobian[1, i, j, right_element]) - - # First contravariant vector Ja^1 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(1, contravariant_vectors, - 1, i, j, right_element) - elseif orientation == 2 - u_ll = get_node_vars(u, equations, dg, i, nnodes(dg), j, left_element) - u_rr = get_node_vars(u, equations, dg, i, 1, j, right_element) - - # See above - sign_jacobian = sign(inverse_jacobian[i, 1, j, right_element]) - - # Second contravariant vector Ja^2 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(2, contravariant_vectors, - i, 1, j, right_element) - else # orientation == 3 - u_ll = get_node_vars(u, equations, dg, i, j, nnodes(dg), left_element) - u_rr = get_node_vars(u, equations, dg, i, j, 1, right_element) - - # See above - sign_jacobian = sign(inverse_jacobian[i, j, 1, right_element]) - - # Third contravariant vector Ja^3 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(3, contravariant_vectors, - i, j, 1, right_element) + @inline function calc_interface_flux!( + surface_flux_values, left_element, right_element, + orientation, u, + mesh::StructuredMesh{3}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache + ) + # This is slow for LSA, but for some reason faster for Euler (see #519) + if left_element <= 0 # left_element = 0 at boundaries + return surface_flux_values end - # If the mapping is orientation-reversing, the normal vector will be reversed (see above). - # However, the flux now has the wrong sign, since we need the physical flux in normal direction. - flux = sign_jacobian * surface_flux(u_ll, u_rr, normal_direction, equations) + @unpack surface_flux = surface_integral + @unpack contravariant_vectors, inverse_jacobian = cache.elements - for v in eachvariable(equations) - surface_flux_values[v, i, j, right_direction, left_element] = flux[v] - surface_flux_values[v, i, j, left_direction, right_element] = flux[v] - end - end + right_direction = 2 * orientation + left_direction = right_direction - 1 - return nothing -end - -@inline function calc_interface_flux!(surface_flux_values, left_element, right_element, - orientation, u, - mesh::StructuredMesh{3}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache) - # See comment on `calc_interface_flux!` with `nonconservative_terms::False` - if left_element <= 0 # left_element = 0 at boundaries - return surface_flux_values - end + for j in eachnode(dg), i in eachnode(dg) + if orientation == 1 + u_ll = get_node_vars(u, equations, dg, nnodes(dg), i, j, left_element) + u_rr = get_node_vars(u, equations, dg, 1, i, j, right_element) + + # If the mapping is orientation-reversing, the contravariant vectors' orientation + # is reversed as well. The normal vector must be oriented in the direction + # from `left_element` to `right_element`, or the numerical flux will be computed + # incorrectly (downwind direction). + sign_jacobian = sign(inverse_jacobian[1, i, j, right_element]) + + # First contravariant vector Ja^1 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 1, contravariant_vectors, + 1, i, j, right_element + ) + elseif orientation == 2 + u_ll = get_node_vars(u, equations, dg, i, nnodes(dg), j, left_element) + u_rr = get_node_vars(u, equations, dg, i, 1, j, right_element) + + # See above + sign_jacobian = sign(inverse_jacobian[i, 1, j, right_element]) + + # Second contravariant vector Ja^2 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 2, contravariant_vectors, + i, 1, j, right_element + ) + else # orientation == 3 + u_ll = get_node_vars(u, equations, dg, i, j, nnodes(dg), left_element) + u_rr = get_node_vars(u, equations, dg, i, j, 1, right_element) + + # See above + sign_jacobian = sign(inverse_jacobian[i, j, 1, right_element]) + + # Third contravariant vector Ja^3 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 3, contravariant_vectors, + i, j, 1, right_element + ) + end - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack contravariant_vectors, inverse_jacobian = cache.elements - - right_direction = 2 * orientation - left_direction = right_direction - 1 - - for j in eachnode(dg), i in eachnode(dg) - if orientation == 1 - u_ll = get_node_vars(u, equations, dg, nnodes(dg), i, j, left_element) - u_rr = get_node_vars(u, equations, dg, 1, i, j, right_element) - - # If the mapping is orientation-reversing, the contravariant vectors' orientation - # is reversed as well. The normal vector must be oriented in the direction - # from `left_element` to `right_element`, or the numerical flux will be computed - # incorrectly (downwind direction). - sign_jacobian = sign(inverse_jacobian[1, i, j, right_element]) - - # First contravariant vector Ja^1 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(1, contravariant_vectors, - 1, i, j, right_element) - elseif orientation == 2 - u_ll = get_node_vars(u, equations, dg, i, nnodes(dg), j, left_element) - u_rr = get_node_vars(u, equations, dg, i, 1, j, right_element) - - # See above - sign_jacobian = sign(inverse_jacobian[i, 1, j, right_element]) - - # Second contravariant vector Ja^2 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(2, contravariant_vectors, - i, 1, j, right_element) - else # orientation == 3 - u_ll = get_node_vars(u, equations, dg, i, j, nnodes(dg), left_element) - u_rr = get_node_vars(u, equations, dg, i, j, 1, right_element) - - # See above - sign_jacobian = sign(inverse_jacobian[i, j, 1, right_element]) - - # Third contravariant vector Ja^3 as SVector - normal_direction = sign_jacobian * - get_contravariant_vector(3, contravariant_vectors, - i, j, 1, right_element) - end + # If the mapping is orientation-reversing, the normal vector will be reversed (see above). + # However, the flux now has the wrong sign, since we need the physical flux in normal direction. + flux = sign_jacobian * surface_flux(u_ll, u_rr, normal_direction, equations) - # If the mapping is orientation-reversing, the normal vector will be reversed (see above). - # However, the flux now has the wrong sign, since we need the physical flux in normal direction. - flux = sign_jacobian * surface_flux(u_ll, u_rr, normal_direction, equations) - - # Compute both nonconservative fluxes - # In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. - # However, both are the same at watertight interfaces, so we pass the - # `normal_direction` twice. - # Scale with sign_jacobian to ensure that the normal_direction matches that - # from the flux above - noncons_left = sign_jacobian * - nonconservative_flux(u_ll, u_rr, normal_direction, - normal_direction, equations) - noncons_right = sign_jacobian * - nonconservative_flux(u_rr, u_ll, normal_direction, - normal_direction, equations) - - for v in eachvariable(equations) - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - surface_flux_values[v, i, j, right_direction, left_element] = flux[v] + - 0.5f0 * - noncons_left[v] - surface_flux_values[v, i, j, left_direction, right_element] = flux[v] + - 0.5f0 * - noncons_right[v] + for v in eachvariable(equations) + surface_flux_values[v, i, j, right_direction, left_element] = flux[v] + surface_flux_values[v, i, j, left_direction, right_element] = flux[v] + end end - end - return nothing -end + return nothing + end -# TODO: Taal dimension agnostic -function calc_boundary_flux!(cache, u, t, boundary_condition::BoundaryConditionPeriodic, - mesh::StructuredMesh{3}, equations, surface_integral, - dg::DG) - @assert isperiodic(mesh) -end + @inline function calc_interface_flux!( + surface_flux_values, left_element, right_element, + orientation, u, + mesh::StructuredMesh{3}, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache + ) + # See comment on `calc_interface_flux!` with `nonconservative_terms::False` + if left_element <= 0 # left_element = 0 at boundaries + return surface_flux_values + end -function calc_boundary_flux!(cache, u, t, boundary_conditions::NamedTuple, - mesh::StructuredMesh{3}, equations, surface_integral, - dg::DG) - @unpack surface_flux_values = cache.elements - linear_indices = LinearIndices(size(mesh)) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack contravariant_vectors, inverse_jacobian = cache.elements - for cell_z in axes(mesh, 3), cell_y in axes(mesh, 2) - # Negative x-direction - direction = 1 - element = linear_indices[begin, cell_y, cell_z] + right_direction = 2 * orientation + left_direction = right_direction - 1 - for k in eachnode(dg), j in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 1, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (1, j, k), (j, k), element) - end + for j in eachnode(dg), i in eachnode(dg) + if orientation == 1 + u_ll = get_node_vars(u, equations, dg, nnodes(dg), i, j, left_element) + u_rr = get_node_vars(u, equations, dg, 1, i, j, right_element) + + # If the mapping is orientation-reversing, the contravariant vectors' orientation + # is reversed as well. The normal vector must be oriented in the direction + # from `left_element` to `right_element`, or the numerical flux will be computed + # incorrectly (downwind direction). + sign_jacobian = sign(inverse_jacobian[1, i, j, right_element]) + + # First contravariant vector Ja^1 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 1, contravariant_vectors, + 1, i, j, right_element + ) + elseif orientation == 2 + u_ll = get_node_vars(u, equations, dg, i, nnodes(dg), j, left_element) + u_rr = get_node_vars(u, equations, dg, i, 1, j, right_element) + + # See above + sign_jacobian = sign(inverse_jacobian[i, 1, j, right_element]) + + # Second contravariant vector Ja^2 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 2, contravariant_vectors, + i, 1, j, right_element + ) + else # orientation == 3 + u_ll = get_node_vars(u, equations, dg, i, j, nnodes(dg), left_element) + u_rr = get_node_vars(u, equations, dg, i, j, 1, right_element) + + # See above + sign_jacobian = sign(inverse_jacobian[i, j, 1, right_element]) + + # Third contravariant vector Ja^3 as SVector + normal_direction = sign_jacobian * + get_contravariant_vector( + 3, contravariant_vectors, + i, j, 1, right_element + ) + end - # Positive x-direction - direction = 2 - element = linear_indices[end, cell_y, cell_z] + # If the mapping is orientation-reversing, the normal vector will be reversed (see above). + # However, the flux now has the wrong sign, since we need the physical flux in normal direction. + flux = sign_jacobian * surface_flux(u_ll, u_rr, normal_direction, equations) + + # Compute both nonconservative fluxes + # In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. + # However, both are the same at watertight interfaces, so we pass the + # `normal_direction` twice. + # Scale with sign_jacobian to ensure that the normal_direction matches that + # from the flux above + noncons_left = sign_jacobian * + nonconservative_flux( + u_ll, u_rr, normal_direction, + normal_direction, equations + ) + noncons_right = sign_jacobian * + nonconservative_flux( + u_rr, u_ll, normal_direction, + normal_direction, equations + ) - for k in eachnode(dg), j in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 1, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (nnodes(dg), j, k), (j, k), - element) + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[v, i, j, right_direction, left_element] = flux[v] + + 0.5f0 * + noncons_left[v] + surface_flux_values[v, i, j, left_direction, right_element] = flux[v] + + 0.5f0 * + noncons_right[v] + end end - end - for cell_z in axes(mesh, 3), cell_x in axes(mesh, 1) - # Negative y-direction - direction = 3 - element = linear_indices[cell_x, begin, cell_z] + return nothing + end - for k in eachnode(dg), i in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 2, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (i, 1, k), (i, k), element) - end + # TODO: Taal dimension agnostic + function calc_boundary_flux!( + cache, u, t, boundary_condition::BoundaryConditionPeriodic, + mesh::StructuredMesh{3}, equations, surface_integral, + dg::DG + ) + @assert isperiodic(mesh) + end - # Positive y-direction - direction = 4 - element = linear_indices[cell_x, end, cell_z] + function calc_boundary_flux!( + cache, u, t, boundary_conditions::NamedTuple, + mesh::StructuredMesh{3}, equations, surface_integral, + dg::DG + ) + @unpack surface_flux_values = cache.elements + linear_indices = LinearIndices(size(mesh)) + + for cell_z in axes(mesh, 3), cell_y in axes(mesh, 2) + # Negative x-direction + direction = 1 + element = linear_indices[begin, cell_y, cell_z] + + for k in eachnode(dg), j in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 1, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (1, j, k), (j, k), element + ) + end - for k in eachnode(dg), i in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 2, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (i, nnodes(dg), k), (i, k), - element) + # Positive x-direction + direction = 2 + element = linear_indices[end, cell_y, cell_z] + + for k in eachnode(dg), j in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 1, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (nnodes(dg), j, k), (j, k), + element + ) + end end - end - for cell_y in axes(mesh, 2), cell_x in axes(mesh, 1) - # Negative z-direction - direction = 5 - element = linear_indices[cell_x, cell_y, begin] + for cell_z in axes(mesh, 3), cell_x in axes(mesh, 1) + # Negative y-direction + direction = 3 + element = linear_indices[cell_x, begin, cell_z] + + for k in eachnode(dg), i in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 2, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (i, 1, k), (i, k), element + ) + end - for j in eachnode(dg), i in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 3, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (i, j, 1), (i, j), element) + # Positive y-direction + direction = 4 + element = linear_indices[cell_x, end, cell_z] + + for k in eachnode(dg), i in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 2, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (i, nnodes(dg), k), (i, k), + element + ) + end end - # Positive z-direction - direction = 6 - element = linear_indices[cell_x, cell_y, end] + for cell_y in axes(mesh, 2), cell_x in axes(mesh, 1) + # Negative z-direction + direction = 5 + element = linear_indices[cell_x, cell_y, begin] + + for j in eachnode(dg), i in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 3, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (i, j, 1), (i, j), element + ) + end - for j in eachnode(dg), i in eachnode(dg) - calc_boundary_flux_by_direction!(surface_flux_values, u, t, 3, - boundary_conditions[direction], - mesh, equations, surface_integral, dg, - cache, - direction, (i, j, nnodes(dg)), (i, j), - element) + # Positive z-direction + direction = 6 + element = linear_indices[cell_x, cell_y, end] + + for j in eachnode(dg), i in eachnode(dg) + calc_boundary_flux_by_direction!( + surface_flux_values, u, t, 3, + boundary_conditions[direction], + mesh, equations, surface_integral, dg, + cache, + direction, (i, j, nnodes(dg)), (i, j), + element + ) + end end end -end - -function apply_jacobian!(du, - mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, - equations, dg::DG, cache) - @threaded for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - factor = -cache.elements.inverse_jacobian[i, j, k, element] - for v in eachvariable(equations) - du[v, i, j, k, element] *= factor + function apply_jacobian!( + du, + mesh::Union{StructuredMesh{3}, P4estMesh{3}, T8codeMesh{3}}, + equations, dg::DG, cache + ) + @threaded for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + factor = -cache.elements.inverse_jacobian[i, j, k, element] + + for v in eachvariable(equations) + du[v, i, j, k, element] *= factor + end end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_structured/dg_3d_compressible_euler.jl b/src/solvers/dgsem_structured/dg_3d_compressible_euler.jl index 64a3456b94..bd20b41415 100644 --- a/src/solvers/dgsem_structured/dg_3d_compressible_euler.jl +++ b/src/solvers/dgsem_structured/dg_3d_compressible_euler.jl @@ -1,4 +1,3 @@ - # From here on, this file contains specializations of DG methods on the # curved 3D meshes `StructuredMesh{3}, P4estMesh{3}` to the compressible # Euler equations. @@ -17,28 +16,40 @@ # We specialize on `PtrArray` since these will be returned by `Trixi.wrap_array` # if LoopVectorization.jl can handle the array types. This ensures that `@turbo` # works efficiently here. -@inline function flux_differencing_kernel!(_du::PtrArray, u_cons::PtrArray, - element, - mesh::Union{StructuredMesh{3}, P4estMesh{3}}, - nonconservative_terms::False, - equations::CompressibleEulerEquations3D, - volume_flux::typeof(flux_shima_etal_turbo), - dg::DGSEM, cache, alpha) +@inline function flux_differencing_kernel!( + _du::PtrArray, u_cons::PtrArray, + element, + mesh::Union{StructuredMesh{3}, P4estMesh{3}}, + nonconservative_terms::False, + equations::CompressibleEulerEquations3D, + volume_flux::typeof(flux_shima_etal_turbo), + dg::DGSEM, cache, alpha + ) @unpack derivative_split = dg.basis @unpack contravariant_vectors = cache.elements # Create a temporary array that will be used to store the RHS with permuted # indices `[i, j, k, v]` to allow using SIMD instructions. # `StrideArray`s with purely static dimensions do not allocate on the heap. - du = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., - StaticInt(nvariables(equations)))) + du = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., + StaticInt(nvariables(equations)), + ) + ) # Convert conserved to primitive variables on the given `element`. - u_prim = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), - ndims(mesh))..., - StaticInt(nvariables(equations)))) + u_prim = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple( + _ -> StaticInt(nnodes(dg)), + ndims(mesh) + )..., + StaticInt(nvariables(equations)), + ) + ) @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) rho = u_cons[1, i, j, k, element] @@ -64,20 +75,28 @@ # At first, we create new temporary arrays with permuted memory layout to # allow using SIMD instructions along the first dimension (which is contiguous # in memory). - du_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - u_prim_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) + du_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + u_prim_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) u_prim_permuted[jk, i, v] = u_prim[i, j, k, v] @@ -85,10 +104,14 @@ fill!(du_permuted, zero(eltype(du_permuted))) # We must also permute the contravariant vectors. - contravariant_vectors_x = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_x = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) @@ -114,17 +137,23 @@ v3_rr = u_prim_permuted[jk, ii, 4] p_rr = u_prim_permuted[jk, ii, 5] - normal_direction_1 = 0.5 * (contravariant_vectors_x[jk, i, 1] + - contravariant_vectors_x[jk, ii, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_x[jk, i, 2] + - contravariant_vectors_x[jk, ii, 2]) - normal_direction_3 = 0.5 * (contravariant_vectors_x[jk, i, 3] + - contravariant_vectors_x[jk, ii, 3]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_x[jk, i, 1] + + contravariant_vectors_x[jk, ii, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_x[jk, i, 2] + + contravariant_vectors_x[jk, ii, 2] + ) + normal_direction_3 = 0.5 * ( + contravariant_vectors_x[jk, i, 3] + + contravariant_vectors_x[jk, ii, 3] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 + - v3_ll * normal_direction_3 + v3_ll * normal_direction_3 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 + - v3_rr * normal_direction_3 + v3_rr * normal_direction_3 # Compute required mean values rho_avg = 0.5 * (rho_ll + rho_rr) @@ -140,9 +169,11 @@ f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 f4 = f1 * v3_avg + p_avg * normal_direction_3 - f5 = (f1 * velocity_square_avg + - p_avg * v_dot_n_avg * equations.inv_gamma_minus_one - + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f5 = ( + f1 * velocity_square_avg + + p_avg * v_dot_n_avg * equations.inv_gamma_minus_one + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_i = alpha * derivative_split[i, ii] @@ -162,9 +193,9 @@ end @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) du[i, j, k, v] = du_permuted[jk, i, v] @@ -172,11 +203,15 @@ # y direction # We must also permute the contravariant vectors. - contravariant_vectors_y = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_y = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) contravariant_vectors_y[i, j, k, 1] = contravariant_vectors[1, 2, i, j, k, element] @@ -201,17 +236,23 @@ v3_rr = u_prim[i, jj, k, 4] p_rr = u_prim[i, jj, k, 5] - normal_direction_1 = 0.5 * (contravariant_vectors_y[i, j, k, 1] + - contravariant_vectors_y[i, jj, k, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_y[i, j, k, 2] + - contravariant_vectors_y[i, jj, k, 2]) - normal_direction_3 = 0.5 * (contravariant_vectors_y[i, j, k, 3] + - contravariant_vectors_y[i, jj, k, 3]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_y[i, j, k, 1] + + contravariant_vectors_y[i, jj, k, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_y[i, j, k, 2] + + contravariant_vectors_y[i, jj, k, 2] + ) + normal_direction_3 = 0.5 * ( + contravariant_vectors_y[i, j, k, 3] + + contravariant_vectors_y[i, jj, k, 3] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 + - v3_ll * normal_direction_3 + v3_ll * normal_direction_3 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 + - v3_rr * normal_direction_3 + v3_rr * normal_direction_3 # Compute required mean values rho_avg = 0.5 * (rho_ll + rho_rr) @@ -227,9 +268,11 @@ f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 f4 = f1 * v3_avg + p_avg * normal_direction_3 - f5 = (f1 * velocity_square_avg + - p_avg * v_dot_n_avg * equations.inv_gamma_minus_one - + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f5 = ( + f1 * velocity_square_avg + + p_avg * v_dot_n_avg * equations.inv_gamma_minus_one + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_j = alpha * derivative_split[j, jj] @@ -252,28 +295,46 @@ # The memory layout is already optimal for SIMD vectorization in this loop. # We just squeeze the first two dimensions to make the code slightly faster. GC.@preserve u_prim begin - u_prim_reshaped = PtrArray(pointer(u_prim), - (StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - du_reshaped = PtrArray(pointer(du), - (StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) + u_prim_reshaped = PtrArray( + pointer(u_prim), + ( + StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + du_reshaped = PtrArray( + pointer(du), + ( + StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) # We must also permute the contravariant vectors. - contravariant_vectors_z = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_z = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) ij = i + nnodes(dg) * (j - 1) - contravariant_vectors_z[ij, k, 1] = contravariant_vectors[1, 3, i, j, k, - element] - contravariant_vectors_z[ij, k, 2] = contravariant_vectors[2, 3, i, j, k, - element] - contravariant_vectors_z[ij, k, 3] = contravariant_vectors[3, 3, i, j, k, - element] + contravariant_vectors_z[ij, k, 1] = contravariant_vectors[ + 1, 3, i, j, k, + element, + ] + contravariant_vectors_z[ij, k, 2] = contravariant_vectors[ + 2, 3, i, j, k, + element, + ] + contravariant_vectors_z[ij, k, 3] = contravariant_vectors[ + 3, 3, i, j, k, + element, + ] end for k in eachnode(dg), kk in (k + 1):nnodes(dg) @@ -290,17 +351,23 @@ v3_rr = u_prim_reshaped[ij, kk, 4] p_rr = u_prim_reshaped[ij, kk, 5] - normal_direction_1 = 0.5 * (contravariant_vectors_z[ij, k, 1] + - contravariant_vectors_z[ij, kk, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_z[ij, k, 2] + - contravariant_vectors_z[ij, kk, 2]) - normal_direction_3 = 0.5 * (contravariant_vectors_z[ij, k, 3] + - contravariant_vectors_z[ij, kk, 3]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_z[ij, k, 1] + + contravariant_vectors_z[ij, kk, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_z[ij, k, 2] + + contravariant_vectors_z[ij, kk, 2] + ) + normal_direction_3 = 0.5 * ( + contravariant_vectors_z[ij, k, 3] + + contravariant_vectors_z[ij, kk, 3] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 + - v3_ll * normal_direction_3 + v3_ll * normal_direction_3 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 + - v3_rr * normal_direction_3 + v3_rr * normal_direction_3 # Compute required mean values rho_avg = 0.5 * (rho_ll + rho_rr) @@ -316,9 +383,11 @@ f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 f4 = f1 * v3_avg + p_avg * normal_direction_3 - f5 = (f1 * velocity_square_avg + - p_avg * v_dot_n_avg * equations.inv_gamma_minus_one - + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f5 = ( + f1 * velocity_square_avg + + p_avg * v_dot_n_avg * equations.inv_gamma_minus_one + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_k = alpha * derivative_split[k, kk] @@ -341,39 +410,51 @@ # Finally, we add the temporary RHS computed here to the global RHS in the # given `element`. @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) _du[v, i, j, k, element] += du[i, j, k, v] end end -@inline function flux_differencing_kernel!(_du::PtrArray, u_cons::PtrArray, - element, - mesh::Union{StructuredMesh{3}, P4estMesh{3}}, - nonconservative_terms::False, - equations::CompressibleEulerEquations3D, - volume_flux::typeof(flux_ranocha_turbo), - dg::DGSEM, cache, alpha) +@inline function flux_differencing_kernel!( + _du::PtrArray, u_cons::PtrArray, + element, + mesh::Union{StructuredMesh{3}, P4estMesh{3}}, + nonconservative_terms::False, + equations::CompressibleEulerEquations3D, + volume_flux::typeof(flux_ranocha_turbo), + dg::DGSEM, cache, alpha + ) @unpack derivative_split = dg.basis @unpack contravariant_vectors = cache.elements # Create a temporary array that will be used to store the RHS with permuted # indices `[i, j, k, v]` to allow using SIMD instructions. # `StrideArray`s with purely static dimensions do not allocate on the heap. - du = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., - StaticInt(nvariables(equations)))) + du = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., + StaticInt(nvariables(equations)), + ) + ) # Convert conserved to primitive variables on the given `element`. In addition # to the usual primitive variables, we also compute logarithms of the density # and pressure to increase the performance of the required logarithmic mean # values. - u_prim = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), - ndims(mesh))..., - StaticInt(nvariables(equations) + 2))) # We also compute "+ 2" logs + u_prim = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple( + _ -> StaticInt(nnodes(dg)), + ndims(mesh) + )..., + StaticInt(nvariables(equations) + 2), + ) + ) # We also compute "+ 2" logs @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) rho = u_cons[1, i, j, k, element] @@ -401,20 +482,28 @@ end # At first, we create new temporary arrays with permuted memory layout to # allow using SIMD instructions along the first dimension (which is contiguous # in memory). - du_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - u_prim_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations) + 2))) + du_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + u_prim_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations) + 2), + ) + ) @turbo for v in indices(u_prim, 4), # v in eachvariable(equations) misses +2 logs - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) u_prim_permuted[jk, i, v] = u_prim[i, j, k, v] @@ -422,10 +511,14 @@ end fill!(du_permuted, zero(eltype(du_permuted))) # We must also permute the contravariant vectors. - contravariant_vectors_x = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_x = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) @@ -455,17 +548,23 @@ end log_rho_rr = u_prim_permuted[jk, ii, 6] log_p_rr = u_prim_permuted[jk, ii, 7] - normal_direction_1 = 0.5 * (contravariant_vectors_x[jk, i, 1] + - contravariant_vectors_x[jk, ii, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_x[jk, i, 2] + - contravariant_vectors_x[jk, ii, 2]) - normal_direction_3 = 0.5 * (contravariant_vectors_x[jk, i, 3] + - contravariant_vectors_x[jk, ii, 3]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_x[jk, i, 1] + + contravariant_vectors_x[jk, ii, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_x[jk, i, 2] + + contravariant_vectors_x[jk, ii, 2] + ) + normal_direction_3 = 0.5 * ( + contravariant_vectors_x[jk, i, 3] + + contravariant_vectors_x[jk, ii, 3] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 + - v3_ll * normal_direction_3 + v3_ll * normal_direction_3 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 + - v3_rr * normal_direction_3 + v3_rr * normal_direction_3 # Compute required mean values # We inline the logarithmic mean to allow LoopVectorization.jl to optimize @@ -509,10 +608,12 @@ end f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 f4 = f1 * v3_avg + p_avg * normal_direction_3 - f5 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f5 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_i = alpha * derivative_split[i, ii] @@ -532,9 +633,9 @@ end end @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) du[i, j, k, v] = du_permuted[jk, i, v] @@ -542,11 +643,15 @@ end # y direction # We must also permute the contravariant vectors. - contravariant_vectors_y = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_y = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) contravariant_vectors_y[i, j, k, 1] = contravariant_vectors[1, 2, i, j, k, element] @@ -575,17 +680,23 @@ end log_rho_rr = u_prim[i, jj, k, 6] log_p_rr = u_prim[i, jj, k, 7] - normal_direction_1 = 0.5 * (contravariant_vectors_y[i, j, k, 1] + - contravariant_vectors_y[i, jj, k, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_y[i, j, k, 2] + - contravariant_vectors_y[i, jj, k, 2]) - normal_direction_3 = 0.5 * (contravariant_vectors_y[i, j, k, 3] + - contravariant_vectors_y[i, jj, k, 3]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_y[i, j, k, 1] + + contravariant_vectors_y[i, jj, k, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_y[i, j, k, 2] + + contravariant_vectors_y[i, jj, k, 2] + ) + normal_direction_3 = 0.5 * ( + contravariant_vectors_y[i, j, k, 3] + + contravariant_vectors_y[i, jj, k, 3] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 + - v3_ll * normal_direction_3 + v3_ll * normal_direction_3 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 + - v3_rr * normal_direction_3 + v3_rr * normal_direction_3 # Compute required mean values # We inline the logarithmic mean to allow LoopVectorization.jl to optimize @@ -629,10 +740,12 @@ end f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 f4 = f1 * v3_avg + p_avg * normal_direction_3 - f5 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f5 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_j = alpha * derivative_split[j, jj] @@ -655,28 +768,46 @@ end # The memory layout is already optimal for SIMD vectorization in this loop. # We just squeeze the first two dimensions to make the code slightly faster. GC.@preserve u_prim begin - u_prim_reshaped = PtrArray(pointer(u_prim), - (StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations) + 2))) - - du_reshaped = PtrArray(pointer(du), - (StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) + u_prim_reshaped = PtrArray( + pointer(u_prim), + ( + StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations) + 2), + ) + ) + + du_reshaped = PtrArray( + pointer(du), + ( + StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) # We must also permute the contravariant vectors. - contravariant_vectors_z = StrideArray{eltype(contravariant_vectors)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(ndims(mesh)))) + contravariant_vectors_z = StrideArray{eltype(contravariant_vectors)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(ndims(mesh)), + ) + ) @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) ij = i + nnodes(dg) * (j - 1) - contravariant_vectors_z[ij, k, 1] = contravariant_vectors[1, 3, i, j, k, - element] - contravariant_vectors_z[ij, k, 2] = contravariant_vectors[2, 3, i, j, k, - element] - contravariant_vectors_z[ij, k, 3] = contravariant_vectors[3, 3, i, j, k, - element] + contravariant_vectors_z[ij, k, 1] = contravariant_vectors[ + 1, 3, i, j, k, + element, + ] + contravariant_vectors_z[ij, k, 2] = contravariant_vectors[ + 2, 3, i, j, k, + element, + ] + contravariant_vectors_z[ij, k, 3] = contravariant_vectors[ + 3, 3, i, j, k, + element, + ] end for k in eachnode(dg), kk in (k + 1):nnodes(dg) @@ -697,17 +828,23 @@ end log_rho_rr = u_prim_reshaped[ij, kk, 6] log_p_rr = u_prim_reshaped[ij, kk, 7] - normal_direction_1 = 0.5 * (contravariant_vectors_z[ij, k, 1] + - contravariant_vectors_z[ij, kk, 1]) - normal_direction_2 = 0.5 * (contravariant_vectors_z[ij, k, 2] + - contravariant_vectors_z[ij, kk, 2]) - normal_direction_3 = 0.5 * (contravariant_vectors_z[ij, k, 3] + - contravariant_vectors_z[ij, kk, 3]) + normal_direction_1 = 0.5 * ( + contravariant_vectors_z[ij, k, 1] + + contravariant_vectors_z[ij, kk, 1] + ) + normal_direction_2 = 0.5 * ( + contravariant_vectors_z[ij, k, 2] + + contravariant_vectors_z[ij, kk, 2] + ) + normal_direction_3 = 0.5 * ( + contravariant_vectors_z[ij, k, 3] + + contravariant_vectors_z[ij, kk, 3] + ) v_dot_n_ll = v1_ll * normal_direction_1 + v2_ll * normal_direction_2 + - v3_ll * normal_direction_3 + v3_ll * normal_direction_3 v_dot_n_rr = v1_rr * normal_direction_1 + v2_rr * normal_direction_2 + - v3_rr * normal_direction_3 + v3_rr * normal_direction_3 # Compute required mean values # We inline the logarithmic mean to allow LoopVectorization.jl to optimize @@ -739,7 +876,7 @@ end special_path2 = (2 + z2 * (2 / 3 + z2 * (2 / 5 + 2 / 7 * z2))) / x2_plus_y2 regular_path2 = (log_y2 - log_x2) / y2_minus_x2 inv_rho_p_mean = p_ll * p_rr * - ifelse(z2 < 1.0e-4, special_path2, regular_path2) + ifelse(z2 < 1.0e-4, special_path2, regular_path2) v1_avg = 0.5 * (v1_ll + v1_rr) v2_avg = 0.5 * (v2_ll + v2_rr) @@ -752,10 +889,12 @@ end f2 = f1 * v1_avg + p_avg * normal_direction_1 f3 = f1 * v2_avg + p_avg * normal_direction_2 f4 = f1 * v3_avg + p_avg * normal_direction_3 - f5 = (f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) - + - 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll)) + f5 = ( + f1 * + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + + 0.5 * (p_ll * v_dot_n_rr + p_rr * v_dot_n_ll) + ) # Add scaled fluxes to RHS factor_k = alpha * derivative_split[k, kk] @@ -778,9 +917,9 @@ end # Finally, we add the temporary RHS computed here to the global RHS in the # given `element`. @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) _du[v, i, j, k, element] += du[i, j, k, v] end diff --git a/src/solvers/dgsem_structured/indicators_1d.jl b/src/solvers/dgsem_structured/indicators_1d.jl index a6d518699d..a71c9cdf3a 100644 --- a/src/solvers/dgsem_structured/indicators_1d.jl +++ b/src/solvers/dgsem_structured/indicators_1d.jl @@ -3,25 +3,27 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function apply_smoothing!(mesh::StructuredMesh{1}, alpha, alpha_tmp, dg, cache) - # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha - # Copy alpha values such that smoothing is indpedenent of the element access order - alpha_tmp .= alpha + function apply_smoothing!(mesh::StructuredMesh{1}, alpha, alpha_tmp, dg, cache) + # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha + # Copy alpha values such that smoothing is indpedenent of the element access order + alpha_tmp .= alpha - # So far, alpha smoothing doesn't work for non-periodic initial conditions for structured meshes. - @assert isperiodic(mesh) "alpha smoothing for structured meshes works only with periodic initial conditions so far" + # So far, alpha smoothing doesn't work for non-periodic initial conditions for structured meshes. + @assert isperiodic(mesh) "alpha smoothing for structured meshes works only with periodic initial conditions so far" - # Loop over elements, because there is no interface container - for element in eachelement(dg, cache) - # Get neighboring element ids - left = cache.elements.left_neighbors[1, element] + # Loop over elements, because there is no interface container + for element in eachelement(dg, cache) + # Get neighboring element ids + left = cache.elements.left_neighbors[1, element] - # Apply smoothing - alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[element], alpha[left]) - alpha[element] = max(alpha_tmp[element], 0.5f0 * alpha_tmp[left], - alpha[element]) + # Apply smoothing + alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[element], alpha[left]) + alpha[element] = max( + alpha_tmp[element], 0.5f0 * alpha_tmp[left], + alpha[element] + ) + end end -end end # @muladd diff --git a/src/solvers/dgsem_structured/indicators_2d.jl b/src/solvers/dgsem_structured/indicators_2d.jl index 52d6ac2a95..a668052ac7 100644 --- a/src/solvers/dgsem_structured/indicators_2d.jl +++ b/src/solvers/dgsem_structured/indicators_2d.jl @@ -3,30 +3,34 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function apply_smoothing!(mesh::StructuredMesh{2}, alpha, alpha_tmp, dg, cache) - # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha - # Copy alpha values such that smoothing is indpedenent of the element access order - alpha_tmp .= alpha + function apply_smoothing!(mesh::StructuredMesh{2}, alpha, alpha_tmp, dg, cache) + # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha + # Copy alpha values such that smoothing is indpedenent of the element access order + alpha_tmp .= alpha - # So far, alpha smoothing doesn't work for non-periodic initial conditions for structured meshes. - @assert isperiodic(mesh) "alpha smoothing for structured meshes works only with periodic initial conditions so far" + # So far, alpha smoothing doesn't work for non-periodic initial conditions for structured meshes. + @assert isperiodic(mesh) "alpha smoothing for structured meshes works only with periodic initial conditions so far" - # Loop over elements, because there is no interface container - for element in eachelement(dg, cache) - # Get neighboring element ids - left = cache.elements.left_neighbors[1, element] - lower = cache.elements.left_neighbors[2, element] + # Loop over elements, because there is no interface container + for element in eachelement(dg, cache) + # Get neighboring element ids + left = cache.elements.left_neighbors[1, element] + lower = cache.elements.left_neighbors[2, element] - # Apply smoothing - alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[element], alpha[left]) - alpha[element] = max(alpha_tmp[element], 0.5f0 * alpha_tmp[left], - alpha[element]) + # Apply smoothing + alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[element], alpha[left]) + alpha[element] = max( + alpha_tmp[element], 0.5f0 * alpha_tmp[left], + alpha[element] + ) - alpha[lower] = max(alpha_tmp[lower], 0.5f0 * alpha_tmp[element], alpha[lower]) - alpha[element] = max(alpha_tmp[element], 0.5f0 * alpha_tmp[lower], - alpha[element]) + alpha[lower] = max(alpha_tmp[lower], 0.5f0 * alpha_tmp[element], alpha[lower]) + alpha[element] = max( + alpha_tmp[element], 0.5f0 * alpha_tmp[lower], + alpha[element] + ) + end end -end end # @muladd diff --git a/src/solvers/dgsem_structured/indicators_3d.jl b/src/solvers/dgsem_structured/indicators_3d.jl index 8b477da2e8..6c0c8ab434 100644 --- a/src/solvers/dgsem_structured/indicators_3d.jl +++ b/src/solvers/dgsem_structured/indicators_3d.jl @@ -3,35 +3,41 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function apply_smoothing!(mesh::StructuredMesh{3}, alpha, alpha_tmp, dg, cache) - # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha - # Copy alpha values such that smoothing is indpedenent of the element access order - alpha_tmp .= alpha + function apply_smoothing!(mesh::StructuredMesh{3}, alpha, alpha_tmp, dg, cache) + # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha + # Copy alpha values such that smoothing is indpedenent of the element access order + alpha_tmp .= alpha - # So far, alpha smoothing doesn't work for non-periodic initial conditions for structured meshes. - @assert isperiodic(mesh) "alpha smoothing for structured meshes works only with periodic initial conditions so far" + # So far, alpha smoothing doesn't work for non-periodic initial conditions for structured meshes. + @assert isperiodic(mesh) "alpha smoothing for structured meshes works only with periodic initial conditions so far" - # Loop over elements, because there is no interface container - for element in eachelement(dg, cache) - # Get neighboring element ids - left = cache.elements.left_neighbors[1, element] - lower = cache.elements.left_neighbors[2, element] - front = cache.elements.left_neighbors[3, element] + # Loop over elements, because there is no interface container + for element in eachelement(dg, cache) + # Get neighboring element ids + left = cache.elements.left_neighbors[1, element] + lower = cache.elements.left_neighbors[2, element] + front = cache.elements.left_neighbors[3, element] - # Apply smoothing - alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[element], alpha[left]) - alpha[element] = max(alpha_tmp[element], 0.5f0 * alpha_tmp[left], - alpha[element]) + # Apply smoothing + alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[element], alpha[left]) + alpha[element] = max( + alpha_tmp[element], 0.5f0 * alpha_tmp[left], + alpha[element] + ) - alpha[lower] = max(alpha_tmp[lower], 0.5f0 * alpha_tmp[element], alpha[lower]) - alpha[element] = max(alpha_tmp[element], 0.5f0 * alpha_tmp[lower], - alpha[element]) + alpha[lower] = max(alpha_tmp[lower], 0.5f0 * alpha_tmp[element], alpha[lower]) + alpha[element] = max( + alpha_tmp[element], 0.5f0 * alpha_tmp[lower], + alpha[element] + ) - alpha[front] = max(alpha_tmp[front], 0.5f0 * alpha_tmp[element], alpha[front]) - alpha[element] = max(alpha_tmp[element], 0.5f0 * alpha_tmp[front], - alpha[element]) + alpha[front] = max(alpha_tmp[front], 0.5f0 * alpha_tmp[element], alpha[front]) + alpha[element] = max( + alpha_tmp[element], 0.5f0 * alpha_tmp[front], + alpha[element] + ) + end end -end end # @muladd diff --git a/src/solvers/dgsem_structured/subcell_limiters_2d.jl b/src/solvers/dgsem_structured/subcell_limiters_2d.jl index 823c481722..57ca2483b7 100644 --- a/src/solvers/dgsem_structured/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_structured/subcell_limiters_2d.jl @@ -3,248 +3,320 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function calc_bounds_twosided_interface!(var_min, var_max, variable, u, t, semi, - mesh::StructuredMesh{2}) - _, equations, dg, cache = mesh_equations_solver_cache(semi) - (; boundary_conditions) = semi - (; contravariant_vectors) = cache.elements - - # Calc bounds at interfaces and periodic boundaries - for element in eachelement(dg, cache) - # Get neighboring element ids - left = cache.elements.left_neighbors[1, element] - lower = cache.elements.left_neighbors[2, element] - - if left != 0 - for j in eachnode(dg) - var_left = u[variable, nnodes(dg), j, left] - var_element = u[variable, 1, j, element] - - var_min[1, j, element] = min(var_min[1, j, element], var_left) - var_max[1, j, element] = max(var_max[1, j, element], var_left) - - var_min[nnodes(dg), j, left] = min(var_min[nnodes(dg), j, left], - var_element) - var_max[nnodes(dg), j, left] = max(var_max[nnodes(dg), j, left], - var_element) - end - end - if lower != 0 - for i in eachnode(dg) - var_lower = u[variable, i, nnodes(dg), lower] - var_element = u[variable, i, 1, element] - - var_min[i, 1, element] = min(var_min[i, 1, element], var_lower) - var_max[i, 1, element] = max(var_max[i, 1, element], var_lower) - - var_min[i, nnodes(dg), lower] = min(var_min[i, nnodes(dg), lower], - var_element) - var_max[i, nnodes(dg), lower] = max(var_max[i, nnodes(dg), lower], - var_element) + #! format: noindent + + function calc_bounds_twosided_interface!( + var_min, var_max, variable, u, t, semi, + mesh::StructuredMesh{2} + ) + _, equations, dg, cache = mesh_equations_solver_cache(semi) + (; boundary_conditions) = semi + (; contravariant_vectors) = cache.elements + + # Calc bounds at interfaces and periodic boundaries + for element in eachelement(dg, cache) + # Get neighboring element ids + left = cache.elements.left_neighbors[1, element] + lower = cache.elements.left_neighbors[2, element] + + if left != 0 + for j in eachnode(dg) + var_left = u[variable, nnodes(dg), j, left] + var_element = u[variable, 1, j, element] + + var_min[1, j, element] = min(var_min[1, j, element], var_left) + var_max[1, j, element] = max(var_max[1, j, element], var_left) + + var_min[nnodes(dg), j, left] = min( + var_min[nnodes(dg), j, left], + var_element + ) + var_max[nnodes(dg), j, left] = max( + var_max[nnodes(dg), j, left], + var_element + ) + end end - end - end + if lower != 0 + for i in eachnode(dg) + var_lower = u[variable, i, nnodes(dg), lower] + var_element = u[variable, i, 1, element] - # Calc bounds at physical boundaries - if isperiodic(mesh) - return nothing - end - linear_indices = LinearIndices(size(mesh)) - if !isperiodic(mesh, 1) - # - xi direction - for cell_y in axes(mesh, 2) - element = linear_indices[begin, cell_y] - for j in eachnode(dg) - Ja1 = get_contravariant_vector(1, contravariant_vectors, 1, j, element) - u_inner = get_node_vars(u, equations, dg, 1, j, element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[1], Ja1, 1, - equations, dg, cache, - 1, j, element) - var_outer = u_outer[variable] - - var_min[1, j, element] = min(var_min[1, j, element], var_outer) - var_max[1, j, element] = max(var_max[1, j, element], var_outer) + var_min[i, 1, element] = min(var_min[i, 1, element], var_lower) + var_max[i, 1, element] = max(var_max[i, 1, element], var_lower) + + var_min[i, nnodes(dg), lower] = min( + var_min[i, nnodes(dg), lower], + var_element + ) + var_max[i, nnodes(dg), lower] = max( + var_max[i, nnodes(dg), lower], + var_element + ) + end end end - # + xi direction - for cell_y in axes(mesh, 2) - element = linear_indices[end, cell_y] - for j in eachnode(dg) - Ja1 = get_contravariant_vector(1, contravariant_vectors, nnodes(dg), j, - element) - u_inner = get_node_vars(u, equations, dg, nnodes(dg), j, element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[2], Ja1, 2, - equations, dg, cache, - nnodes(dg), j, element) - var_outer = u_outer[variable] - - var_min[nnodes(dg), j, element] = min(var_min[nnodes(dg), j, element], - var_outer) - var_max[nnodes(dg), j, element] = max(var_max[nnodes(dg), j, element], - var_outer) - end + + # Calc bounds at physical boundaries + if isperiodic(mesh) + return nothing end - end - if !isperiodic(mesh, 2) - # - eta direction - for cell_x in axes(mesh, 1) - element = linear_indices[cell_x, begin] - for i in eachnode(dg) - Ja2 = get_contravariant_vector(2, contravariant_vectors, i, 1, element) - u_inner = get_node_vars(u, equations, dg, i, 1, element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[3], Ja2, 3, - equations, dg, cache, - i, 1, element) - var_outer = u_outer[variable] - - var_min[i, 1, element] = min(var_min[i, 1, element], var_outer) - var_max[i, 1, element] = max(var_max[i, 1, element], var_outer) + linear_indices = LinearIndices(size(mesh)) + if !isperiodic(mesh, 1) + # - xi direction + for cell_y in axes(mesh, 2) + element = linear_indices[begin, cell_y] + for j in eachnode(dg) + Ja1 = get_contravariant_vector(1, contravariant_vectors, 1, j, element) + u_inner = get_node_vars(u, equations, dg, 1, j, element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[1], Ja1, 1, + equations, dg, cache, + 1, j, element + ) + var_outer = u_outer[variable] + + var_min[1, j, element] = min(var_min[1, j, element], var_outer) + var_max[1, j, element] = max(var_max[1, j, element], var_outer) + end end - end - # - eta direction - for cell_x in axes(mesh, 1) - element = linear_indices[cell_x, end] - for i in eachnode(dg) - Ja2 = get_contravariant_vector(2, contravariant_vectors, i, nnodes(dg), - element) - u_inner = get_node_vars(u, equations, dg, i, nnodes(dg), element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[4], Ja2, 4, - equations, dg, cache, - i, nnodes(dg), element) - var_outer = u_outer[variable] - - var_min[i, nnodes(dg), element] = min(var_min[i, nnodes(dg), element], - var_outer) - var_max[i, nnodes(dg), element] = max(var_max[i, nnodes(dg), element], - var_outer) + # + xi direction + for cell_y in axes(mesh, 2) + element = linear_indices[end, cell_y] + for j in eachnode(dg) + Ja1 = get_contravariant_vector( + 1, contravariant_vectors, nnodes(dg), j, + element + ) + u_inner = get_node_vars(u, equations, dg, nnodes(dg), j, element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[2], Ja1, 2, + equations, dg, cache, + nnodes(dg), j, element + ) + var_outer = u_outer[variable] + + var_min[nnodes(dg), j, element] = min( + var_min[nnodes(dg), j, element], + var_outer + ) + var_max[nnodes(dg), j, element] = max( + var_max[nnodes(dg), j, element], + var_outer + ) + end end end - end + if !isperiodic(mesh, 2) + # - eta direction + for cell_x in axes(mesh, 1) + element = linear_indices[cell_x, begin] + for i in eachnode(dg) + Ja2 = get_contravariant_vector(2, contravariant_vectors, i, 1, element) + u_inner = get_node_vars(u, equations, dg, i, 1, element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[3], Ja2, 3, + equations, dg, cache, + i, 1, element + ) + var_outer = u_outer[variable] - return nothing -end - -function calc_bounds_onesided_interface!(var_minmax, minmax, variable, u, t, semi, - mesh::StructuredMesh{2}) - _, equations, dg, cache = mesh_equations_solver_cache(semi) - (; boundary_conditions) = semi - (; contravariant_vectors) = cache.elements - - # Calc bounds at interfaces and periodic boundaries - for element in eachelement(dg, cache) - # Get neighboring element ids - left = cache.elements.left_neighbors[1, element] - lower = cache.elements.left_neighbors[2, element] - - if left != 0 - for j in eachnode(dg) - var_left = variable(get_node_vars(u, equations, dg, nnodes(dg), j, - left), equations) - var_element = variable(get_node_vars(u, equations, dg, 1, j, element), - equations) - - var_minmax[1, j, element] = minmax(var_minmax[1, j, element], var_left) - var_minmax[nnodes(dg), j, left] = minmax(var_minmax[nnodes(dg), j, - left], var_element) + var_min[i, 1, element] = min(var_min[i, 1, element], var_outer) + var_max[i, 1, element] = max(var_max[i, 1, element], var_outer) + end end - end - if lower != 0 - for i in eachnode(dg) - var_lower = variable(get_node_vars(u, equations, dg, i, nnodes(dg), - lower), equations) - var_element = variable(get_node_vars(u, equations, dg, i, 1, element), - equations) - - var_minmax[i, 1, element] = minmax(var_minmax[i, 1, element], var_lower) - var_minmax[i, nnodes(dg), lower] = minmax(var_minmax[i, nnodes(dg), - lower], - var_element) + # - eta direction + for cell_x in axes(mesh, 1) + element = linear_indices[cell_x, end] + for i in eachnode(dg) + Ja2 = get_contravariant_vector( + 2, contravariant_vectors, i, nnodes(dg), + element + ) + u_inner = get_node_vars(u, equations, dg, i, nnodes(dg), element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[4], Ja2, 4, + equations, dg, cache, + i, nnodes(dg), element + ) + var_outer = u_outer[variable] + + var_min[i, nnodes(dg), element] = min( + var_min[i, nnodes(dg), element], + var_outer + ) + var_max[i, nnodes(dg), element] = max( + var_max[i, nnodes(dg), element], + var_outer + ) + end end end - end - # Calc bounds at physical boundaries - if isperiodic(mesh) return nothing end - linear_indices = LinearIndices(size(mesh)) - if !isperiodic(mesh, 1) - # - xi direction - for cell_y in axes(mesh, 2) - element = linear_indices[begin, cell_y] - for j in eachnode(dg) - Ja1 = get_contravariant_vector(1, contravariant_vectors, 1, j, element) - u_inner = get_node_vars(u, equations, dg, 1, j, element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[1], Ja1, 1, - equations, dg, cache, - 1, j, element) - var_outer = variable(u_outer, equations) - - var_minmax[1, j, element] = minmax(var_minmax[1, j, element], var_outer) + + function calc_bounds_onesided_interface!( + var_minmax, minmax, variable, u, t, semi, + mesh::StructuredMesh{2} + ) + _, equations, dg, cache = mesh_equations_solver_cache(semi) + (; boundary_conditions) = semi + (; contravariant_vectors) = cache.elements + + # Calc bounds at interfaces and periodic boundaries + for element in eachelement(dg, cache) + # Get neighboring element ids + left = cache.elements.left_neighbors[1, element] + lower = cache.elements.left_neighbors[2, element] + + if left != 0 + for j in eachnode(dg) + var_left = variable( + get_node_vars( + u, equations, dg, nnodes(dg), j, + left + ), equations + ) + var_element = variable( + get_node_vars(u, equations, dg, 1, j, element), + equations + ) + + var_minmax[1, j, element] = minmax(var_minmax[1, j, element], var_left) + var_minmax[nnodes(dg), j, left] = minmax( + var_minmax[ + nnodes(dg), j, + left, + ], var_element + ) + end end - end - # + xi direction - for cell_y in axes(mesh, 2) - element = linear_indices[end, cell_y] - for j in eachnode(dg) - Ja1 = get_contravariant_vector(1, contravariant_vectors, nnodes(dg), j, - element) - u_inner = get_node_vars(u, equations, dg, nnodes(dg), j, element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[2], Ja1, 2, - equations, dg, cache, - nnodes(dg), j, element) - var_outer = variable(u_outer, equations) - - var_minmax[nnodes(dg), j, element] = minmax(var_minmax[nnodes(dg), j, - element], - var_outer) + if lower != 0 + for i in eachnode(dg) + var_lower = variable( + get_node_vars( + u, equations, dg, i, nnodes(dg), + lower + ), equations + ) + var_element = variable( + get_node_vars(u, equations, dg, i, 1, element), + equations + ) + + var_minmax[i, 1, element] = minmax(var_minmax[i, 1, element], var_lower) + var_minmax[i, nnodes(dg), lower] = minmax( + var_minmax[ + i, nnodes(dg), + lower, + ], + var_element + ) + end end end - end - if !isperiodic(mesh, 2) - # - eta direction - for cell_x in axes(mesh, 1) - element = linear_indices[cell_x, begin] - for i in eachnode(dg) - Ja2 = get_contravariant_vector(2, contravariant_vectors, i, 1, element) - u_inner = get_node_vars(u, equations, dg, i, 1, element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[3], Ja2, 3, - equations, dg, cache, - i, 1, element) - var_outer = variable(u_outer, equations) - - var_minmax[i, 1, element] = minmax(var_minmax[i, 1, element], var_outer) + + # Calc bounds at physical boundaries + if isperiodic(mesh) + return nothing + end + linear_indices = LinearIndices(size(mesh)) + if !isperiodic(mesh, 1) + # - xi direction + for cell_y in axes(mesh, 2) + element = linear_indices[begin, cell_y] + for j in eachnode(dg) + Ja1 = get_contravariant_vector(1, contravariant_vectors, 1, j, element) + u_inner = get_node_vars(u, equations, dg, 1, j, element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[1], Ja1, 1, + equations, dg, cache, + 1, j, element + ) + var_outer = variable(u_outer, equations) + + var_minmax[1, j, element] = minmax(var_minmax[1, j, element], var_outer) + end + end + # + xi direction + for cell_y in axes(mesh, 2) + element = linear_indices[end, cell_y] + for j in eachnode(dg) + Ja1 = get_contravariant_vector( + 1, contravariant_vectors, nnodes(dg), j, + element + ) + u_inner = get_node_vars(u, equations, dg, nnodes(dg), j, element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[2], Ja1, 2, + equations, dg, cache, + nnodes(dg), j, element + ) + var_outer = variable(u_outer, equations) + + var_minmax[nnodes(dg), j, element] = minmax( + var_minmax[ + nnodes(dg), j, + element, + ], + var_outer + ) + end end end - # + eta direction - for cell_x in axes(mesh, 1) - element = linear_indices[cell_x, end] - for i in eachnode(dg) - Ja2 = get_contravariant_vector(2, contravariant_vectors, i, nnodes(dg), - element) - u_inner = get_node_vars(u, equations, dg, i, nnodes(dg), element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[4], Ja2, 4, - equations, dg, cache, - i, nnodes(dg), element) - var_outer = variable(u_outer, equations) - - var_minmax[i, nnodes(dg), element] = minmax(var_minmax[i, nnodes(dg), - element], - var_outer) + if !isperiodic(mesh, 2) + # - eta direction + for cell_x in axes(mesh, 1) + element = linear_indices[cell_x, begin] + for i in eachnode(dg) + Ja2 = get_contravariant_vector(2, contravariant_vectors, i, 1, element) + u_inner = get_node_vars(u, equations, dg, i, 1, element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[3], Ja2, 3, + equations, dg, cache, + i, 1, element + ) + var_outer = variable(u_outer, equations) + + var_minmax[i, 1, element] = minmax(var_minmax[i, 1, element], var_outer) + end + end + # + eta direction + for cell_x in axes(mesh, 1) + element = linear_indices[cell_x, end] + for i in eachnode(dg) + Ja2 = get_contravariant_vector( + 2, contravariant_vectors, i, nnodes(dg), + element + ) + u_inner = get_node_vars(u, equations, dg, i, nnodes(dg), element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[4], Ja2, 4, + equations, dg, cache, + i, nnodes(dg), element + ) + var_outer = variable(u_outer, equations) + + var_minmax[i, nnodes(dg), element] = minmax( + var_minmax[ + i, nnodes(dg), + element, + ], + var_outer + ) + end end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_t8code/containers.jl b/src/solvers/dgsem_t8code/containers.jl index d7ff79fbf2..3318849563 100644 --- a/src/solvers/dgsem_t8code/containers.jl +++ b/src/solvers/dgsem_t8code/containers.jl @@ -18,8 +18,10 @@ function reinitialize_containers!(mesh::T8codeMesh, equations, dg::DGSEM, cache) @unpack boundaries = cache resize!(boundaries, mesh.nboundaries) - fill_mesh_info!(mesh, interfaces, mortars, boundaries, - mesh.boundary_names) + fill_mesh_info!( + mesh, interfaces, mortars, boundaries, + mesh.boundary_names + ) return nothing end @@ -39,11 +41,13 @@ end # Compatibility to `dgsem_p4est/containers.jl`. function count_required_surfaces(mesh::T8codeMesh) - return (interfaces = mesh.ninterfaces, - mortars = mesh.nmortars, - boundaries = mesh.nboundaries, - mpi_interfaces = mesh.nmpiinterfaces, - mpi_mortars = mesh.nmpimortars) + return ( + interfaces = mesh.ninterfaces, + mortars = mesh.nmortars, + boundaries = mesh.nboundaries, + mpi_interfaces = mesh.nmpiinterfaces, + mpi_mortars = mesh.nmpimortars, + ) end # Compatibility to `dgsem_p4est/containers.jl`. diff --git a/src/solvers/dgsem_t8code/containers_2d.jl b/src/solvers/dgsem_t8code/containers_2d.jl index ce525bfdf6..d3097c4a83 100644 --- a/src/solvers/dgsem_t8code/containers_2d.jl +++ b/src/solvers/dgsem_t8code/containers_2d.jl @@ -3,76 +3,94 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Interpolate tree_node_coordinates to each quadrant at the specified nodes. -function calc_node_coordinates!(node_coordinates, - mesh::T8codeMesh{2}, - nodes::AbstractVector) - # We use `StrideArray`s here since these buffers are used in performance-critical - # places and the additional information passed to the compiler makes them faster - # than native `Array`s. - tmp1 = StrideArray(undef, real(mesh), - StaticInt(2), static_length(nodes), static_length(mesh.nodes)) - matrix1 = StrideArray(undef, real(mesh), - static_length(nodes), static_length(mesh.nodes)) - matrix2 = similar(matrix1) - baryweights_in = barycentric_weights(mesh.nodes) + # Interpolate tree_node_coordinates to each quadrant at the specified nodes. + function calc_node_coordinates!( + node_coordinates, + mesh::T8codeMesh{2}, + nodes::AbstractVector + ) + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + tmp1 = StrideArray( + undef, real(mesh), + StaticInt(2), static_length(nodes), static_length(mesh.nodes) + ) + matrix1 = StrideArray( + undef, real(mesh), + static_length(nodes), static_length(mesh.nodes) + ) + matrix2 = similar(matrix1) + baryweights_in = barycentric_weights(mesh.nodes) - num_local_trees = t8_forest_get_num_local_trees(mesh.forest) + num_local_trees = t8_forest_get_num_local_trees(mesh.forest) - current_index = 0 - for itree in 0:(num_local_trees - 1) - tree_class = t8_forest_get_tree_class(mesh.forest, itree) - eclass_scheme = t8_forest_get_eclass_scheme(mesh.forest, tree_class) - num_elements_in_tree = t8_forest_get_tree_num_elements(mesh.forest, itree) - global_itree = t8_forest_global_tree_id(mesh.forest, itree) + current_index = 0 + for itree in 0:(num_local_trees - 1) + tree_class = t8_forest_get_tree_class(mesh.forest, itree) + eclass_scheme = t8_forest_get_eclass_scheme(mesh.forest, tree_class) + num_elements_in_tree = t8_forest_get_tree_num_elements(mesh.forest, itree) + global_itree = t8_forest_global_tree_id(mesh.forest, itree) - for ielement in 0:(num_elements_in_tree - 1) - element = t8_forest_get_element_in_tree(mesh.forest, itree, ielement) - element_level = t8_element_level(eclass_scheme, element) + for ielement in 0:(num_elements_in_tree - 1) + element = t8_forest_get_element_in_tree(mesh.forest, itree, ielement) + element_level = t8_element_level(eclass_scheme, element) - # Note, `t8_quad_len` is encoded as an integer (Morton encoding) in - # relation to `t8_quad_root_len`. This line transforms the - # "integer" length to a float in relation to the unit interval [0,1]. - element_length = t8_quad_len(element_level) / t8_quad_root_len + # Note, `t8_quad_len` is encoded as an integer (Morton encoding) in + # relation to `t8_quad_root_len`. This line transforms the + # "integer" length to a float in relation to the unit interval [0,1]. + element_length = t8_quad_len(element_level) / t8_quad_root_len - element_coords = Array{Float64}(undef, 3) - t8_element_vertex_reference_coords(eclass_scheme, element, 0, - pointer(element_coords)) + element_coords = Array{Float64}(undef, 3) + t8_element_vertex_reference_coords( + eclass_scheme, element, 0, + pointer(element_coords) + ) - nodes_out_x = 2 * - (element_length * 1 / 2 * (nodes .+ 1) .+ element_coords[1]) .- - 1 - nodes_out_y = 2 * - (element_length * 1 / 2 * (nodes .+ 1) .+ element_coords[2]) .- - 1 + nodes_out_x = 2 * + (element_length * 1 / 2 * (nodes .+ 1) .+ element_coords[1]) .- + 1 + nodes_out_y = 2 * + (element_length * 1 / 2 * (nodes .+ 1) .+ element_coords[2]) .- + 1 - polynomial_interpolation_matrix!(matrix1, mesh.nodes, nodes_out_x, - baryweights_in) - polynomial_interpolation_matrix!(matrix2, mesh.nodes, nodes_out_y, - baryweights_in) + polynomial_interpolation_matrix!( + matrix1, mesh.nodes, nodes_out_x, + baryweights_in + ) + polynomial_interpolation_matrix!( + matrix2, mesh.nodes, nodes_out_y, + baryweights_in + ) - multiply_dimensionwise!(view(node_coordinates, :, :, :, current_index += 1), - matrix1, matrix2, - view(mesh.tree_node_coordinates, :, :, :, - global_itree + 1), - tmp1) + multiply_dimensionwise!( + view(node_coordinates, :, :, :, current_index += 1), + matrix1, matrix2, + view( + mesh.tree_node_coordinates, :, :, :, + global_itree + 1 + ), + tmp1 + ) + end end - end - return node_coordinates -end + return node_coordinates + end -function init_mortar_neighbor_ids!(mortars::P4estMortarContainer{2}, my_face, - other_face, orientation, neighbor_ielements, - mortar_id) - if orientation == 0 - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[1] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[2] + 1 - else - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[2] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[1] + 1 + function init_mortar_neighbor_ids!( + mortars::P4estMortarContainer{2}, my_face, + other_face, orientation, neighbor_ielements, + mortar_id + ) + if orientation == 0 + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[1] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[2] + 1 + else + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[2] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[1] + 1 + end end -end end # @muladd diff --git a/src/solvers/dgsem_t8code/containers_3d.jl b/src/solvers/dgsem_t8code/containers_3d.jl index 1375782631..7b9b72221f 100644 --- a/src/solvers/dgsem_t8code/containers_3d.jl +++ b/src/solvers/dgsem_t8code/containers_3d.jl @@ -3,234 +3,266 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Interpolate tree_node_coordinates to each quadrant at the specified nodes -function calc_node_coordinates!(node_coordinates, - mesh::T8codeMesh{3}, - nodes::AbstractVector) - # We use `StrideArray`s here since these buffers are used in performance-critical - # places and the additional information passed to the compiler makes them faster - # than native `Array`s. - tmp1 = StrideArray(undef, real(mesh), - StaticInt(3), static_length(nodes), static_length(mesh.nodes), - static_length(mesh.nodes)) - matrix1 = StrideArray(undef, real(mesh), - static_length(nodes), static_length(mesh.nodes)) - matrix2 = similar(matrix1) - matrix3 = similar(matrix1) - baryweights_in = barycentric_weights(mesh.nodes) - - num_local_trees = t8_forest_get_num_local_trees(mesh.forest) - - current_index = 0 - for itree in 0:(num_local_trees - 1) - tree_class = t8_forest_get_tree_class(mesh.forest, itree) - eclass_scheme = t8_forest_get_eclass_scheme(mesh.forest, tree_class) - num_elements_in_tree = t8_forest_get_tree_num_elements(mesh.forest, itree) - global_itree = t8_forest_global_tree_id(mesh.forest, itree) - - for ielement in 0:(num_elements_in_tree - 1) - element = t8_forest_get_element_in_tree(mesh.forest, itree, ielement) - element_level = t8_element_level(eclass_scheme, element) - - # Note, `t8_hex_len` is encoded as an integer (Morton encoding) in - # relation to `t8_hex_root_len`. This line transforms the - # "integer" length to a float in relation to the unit interval [0,1]. - element_length = t8_hex_len(element_level) / t8_hex_root_len - - element_coords = Vector{Float64}(undef, 3) - t8_element_vertex_reference_coords(eclass_scheme, element, 0, - pointer(element_coords)) - - nodes_out_x = (2 * - (element_length * 0.5f0 * (nodes .+ 1) .+ element_coords[1]) .- - 1) - nodes_out_y = (2 * - (element_length * 0.5f0 * (nodes .+ 1) .+ element_coords[2]) .- - 1) - nodes_out_z = (2 * - (element_length * 0.5f0 * (nodes .+ 1) .+ element_coords[3]) .- - 1) - - polynomial_interpolation_matrix!(matrix1, mesh.nodes, nodes_out_x, - baryweights_in) - polynomial_interpolation_matrix!(matrix2, mesh.nodes, nodes_out_y, - baryweights_in) - polynomial_interpolation_matrix!(matrix3, mesh.nodes, nodes_out_z, - baryweights_in) - - multiply_dimensionwise!(view(node_coordinates, :, :, :, :, - current_index += 1), - matrix1, matrix2, matrix3, - view(mesh.tree_node_coordinates, :, :, :, :, - global_itree + 1), - tmp1) + #! format: noindent + + # Interpolate tree_node_coordinates to each quadrant at the specified nodes + function calc_node_coordinates!( + node_coordinates, + mesh::T8codeMesh{3}, + nodes::AbstractVector + ) + # We use `StrideArray`s here since these buffers are used in performance-critical + # places and the additional information passed to the compiler makes them faster + # than native `Array`s. + tmp1 = StrideArray( + undef, real(mesh), + StaticInt(3), static_length(nodes), static_length(mesh.nodes), + static_length(mesh.nodes) + ) + matrix1 = StrideArray( + undef, real(mesh), + static_length(nodes), static_length(mesh.nodes) + ) + matrix2 = similar(matrix1) + matrix3 = similar(matrix1) + baryweights_in = barycentric_weights(mesh.nodes) + + num_local_trees = t8_forest_get_num_local_trees(mesh.forest) + + current_index = 0 + for itree in 0:(num_local_trees - 1) + tree_class = t8_forest_get_tree_class(mesh.forest, itree) + eclass_scheme = t8_forest_get_eclass_scheme(mesh.forest, tree_class) + num_elements_in_tree = t8_forest_get_tree_num_elements(mesh.forest, itree) + global_itree = t8_forest_global_tree_id(mesh.forest, itree) + + for ielement in 0:(num_elements_in_tree - 1) + element = t8_forest_get_element_in_tree(mesh.forest, itree, ielement) + element_level = t8_element_level(eclass_scheme, element) + + # Note, `t8_hex_len` is encoded as an integer (Morton encoding) in + # relation to `t8_hex_root_len`. This line transforms the + # "integer" length to a float in relation to the unit interval [0,1]. + element_length = t8_hex_len(element_level) / t8_hex_root_len + + element_coords = Vector{Float64}(undef, 3) + t8_element_vertex_reference_coords( + eclass_scheme, element, 0, + pointer(element_coords) + ) + + nodes_out_x = ( + 2 * + (element_length * 0.5f0 * (nodes .+ 1) .+ element_coords[1]) .- + 1 + ) + nodes_out_y = ( + 2 * + (element_length * 0.5f0 * (nodes .+ 1) .+ element_coords[2]) .- + 1 + ) + nodes_out_z = ( + 2 * + (element_length * 0.5f0 * (nodes .+ 1) .+ element_coords[3]) .- + 1 + ) + + polynomial_interpolation_matrix!( + matrix1, mesh.nodes, nodes_out_x, + baryweights_in + ) + polynomial_interpolation_matrix!( + matrix2, mesh.nodes, nodes_out_y, + baryweights_in + ) + polynomial_interpolation_matrix!( + matrix3, mesh.nodes, nodes_out_z, + baryweights_in + ) + + multiply_dimensionwise!( + view( + node_coordinates, :, :, :, :, + current_index += 1 + ), + matrix1, matrix2, matrix3, + view( + mesh.tree_node_coordinates, :, :, :, :, + global_itree + 1 + ), + tmp1 + ) + end end + + return node_coordinates end - return node_coordinates -end - -# This routine was copied and adapted from `src/dgsem_p4est/containers_3d.jl`: `orientation_to_indices_p4est`. -function init_mortar_neighbor_ids!(mortars::P4estMortarContainer{3}, my_face, - other_face, orientation, neighbor_ielements, - mortar_id) - # my_face and other_face are the face directions (zero-based) - # of "my side" and "other side" respectively. - # Face corner 0 of the face with the lower face direction connects to a corner of the other face. - # The number of this corner is the orientation code in `p4est`. - lower = my_face <= other_face - - # x_pos, y_neg, and z_pos are the directions in which the face has right-handed coordinates - # when looked at from the outside. - my_right_handed = my_face in (1, 2, 5) - other_right_handed = other_face in (1, 2, 5) - - # If both or none are right-handed when looked at from the outside, they will have different - # orientations when looked at from the same side of the interface. - flipped = my_right_handed == other_right_handed - - # In the following illustrations, the face corner numbering of `p4est` is shown. - # ξ and η are the local coordinates of the respective face. - # We're looking at both faces from the same side of the interface, so that "other side" - # (in the illustrations on the left) has right-handed coordinates. - if !flipped - if orientation == 0 - # Corner 0 of other side matches corner 0 of my side - # 2┌──────┐3 2┌──────┐3 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 0└──────┘1 - # η η - # ↑ ↑ - # │ │ - # └───> ξ └───> ξ - - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[1] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[2] + 1 - mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[3] + 1 - mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[4] + 1 - - elseif ((lower && orientation == 2) # Corner 0 of my side matches corner 2 of other side - || - (!lower && orientation == 1)) # Corner 0 of other side matches corner 1 of my side - # 2┌──────┐3 0┌──────┐2 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 1└──────┘3 - # η ┌───> η - # ↑ │ - # │ ↓ - # └───> ξ ξ - - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[2] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[4] + 1 - mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[1] + 1 - mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[3] + 1 - - elseif ((lower && orientation == 1) # Corner 0 of my side matches corner 1 of other side - || - (!lower && orientation == 2)) # Corner 0 of other side matches corner 2 of my side - # 2┌──────┐3 3┌──────┐1 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 2└──────┘0 - # η ξ - # ↑ ↑ - # │ │ - # └───> ξ η <───┘ - - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[3] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[1] + 1 - mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[4] + 1 - mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[2] + 1 - - else # orientation == 3 - # Corner 0 of my side matches corner 3 of other side and - # corner 0 of other side matches corner 3 of my side. - # 2┌──────┐3 1┌──────┐0 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 3└──────┘2 - # η ξ <───┐ - # ↑ │ - # │ ↓ - # └───> ξ η - - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[4] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[3] + 1 - mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[2] + 1 - mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[1] + 1 - end - else # flipped - if orientation == 0 - # Corner 0 of other side matches corner 0 of my side - # 2┌──────┐3 1┌──────┐3 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 0└──────┘2 - # η ξ - # ↑ ↑ - # │ │ - # └───> ξ └───> η - - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[1] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[3] + 1 - mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[2] + 1 - mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[4] + 1 - - elseif orientation == 2 - # Corner 0 of my side matches corner 2 of other side and - # corner 0 of other side matches corner 2 of my side. - # 2┌──────┐3 0┌──────┐1 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 2└──────┘3 - # η ┌───> ξ - # ↑ │ - # │ ↓ - # └───> ξ η - - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[3] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[4] + 1 - mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[1] + 1 - mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[2] + 1 - - elseif orientation == 1 - # Corner 0 of my side matches corner 1 of other side and - # corner 0 of other side matches corner 1 of my side. - # 2┌──────┐3 3┌──────┐2 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 1└──────┘0 - # η η - # ↑ ↑ - # │ │ - # └───> ξ ξ <───┘ - - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[2] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[1] + 1 - mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[4] + 1 - mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[3] + 1 - - else # orientation == 3 - # Corner 0 of my side matches corner 3 of other side and - # corner 0 of other side matches corner 3 of my side. - # 2┌──────┐3 2┌──────┐0 - # │ │ │ │ - # │ │ │ │ - # 0└──────┘1 3└──────┘1 - # η η <───┐ - # ↑ │ - # │ ↓ - # └───> ξ ξ - - mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[4] + 1 - mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[2] + 1 - mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[3] + 1 - mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[1] + 1 + # This routine was copied and adapted from `src/dgsem_p4est/containers_3d.jl`: `orientation_to_indices_p4est`. + function init_mortar_neighbor_ids!( + mortars::P4estMortarContainer{3}, my_face, + other_face, orientation, neighbor_ielements, + mortar_id + ) + # my_face and other_face are the face directions (zero-based) + # of "my side" and "other side" respectively. + # Face corner 0 of the face with the lower face direction connects to a corner of the other face. + # The number of this corner is the orientation code in `p4est`. + lower = my_face <= other_face + + # x_pos, y_neg, and z_pos are the directions in which the face has right-handed coordinates + # when looked at from the outside. + my_right_handed = my_face in (1, 2, 5) + other_right_handed = other_face in (1, 2, 5) + + # If both or none are right-handed when looked at from the outside, they will have different + # orientations when looked at from the same side of the interface. + flipped = my_right_handed == other_right_handed + + # In the following illustrations, the face corner numbering of `p4est` is shown. + # ξ and η are the local coordinates of the respective face. + # We're looking at both faces from the same side of the interface, so that "other side" + # (in the illustrations on the left) has right-handed coordinates. + if !flipped + if orientation == 0 + # Corner 0 of other side matches corner 0 of my side + # 2┌──────┐3 2┌──────┐3 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 0└──────┘1 + # η η + # ↑ ↑ + # │ │ + # └───> ξ └───> ξ + + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[1] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[2] + 1 + mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[3] + 1 + mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[4] + 1 + + elseif ( + (lower && orientation == 2) # Corner 0 of my side matches corner 2 of other side + || + (!lower && orientation == 1) + ) # Corner 0 of other side matches corner 1 of my side + # 2┌──────┐3 0┌──────┐2 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 1└──────┘3 + # η ┌───> η + # ↑ │ + # │ ↓ + # └───> ξ ξ + + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[2] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[4] + 1 + mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[1] + 1 + mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[3] + 1 + + elseif ( + (lower && orientation == 1) # Corner 0 of my side matches corner 1 of other side + || + (!lower && orientation == 2) + ) # Corner 0 of other side matches corner 2 of my side + # 2┌──────┐3 3┌──────┐1 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 2└──────┘0 + # η ξ + # ↑ ↑ + # │ │ + # └───> ξ η <───┘ + + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[3] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[1] + 1 + mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[4] + 1 + mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[2] + 1 + + else # orientation == 3 + # Corner 0 of my side matches corner 3 of other side and + # corner 0 of other side matches corner 3 of my side. + # 2┌──────┐3 1┌──────┐0 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 3└──────┘2 + # η ξ <───┐ + # ↑ │ + # │ ↓ + # └───> ξ η + + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[4] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[3] + 1 + mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[2] + 1 + mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[1] + 1 + end + else # flipped + if orientation == 0 + # Corner 0 of other side matches corner 0 of my side + # 2┌──────┐3 1┌──────┐3 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 0└──────┘2 + # η ξ + # ↑ ↑ + # │ │ + # └───> ξ └───> η + + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[1] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[3] + 1 + mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[2] + 1 + mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[4] + 1 + + elseif orientation == 2 + # Corner 0 of my side matches corner 2 of other side and + # corner 0 of other side matches corner 2 of my side. + # 2┌──────┐3 0┌──────┐1 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 2└──────┘3 + # η ┌───> ξ + # ↑ │ + # │ ↓ + # └───> ξ η + + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[3] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[4] + 1 + mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[1] + 1 + mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[2] + 1 + + elseif orientation == 1 + # Corner 0 of my side matches corner 1 of other side and + # corner 0 of other side matches corner 1 of my side. + # 2┌──────┐3 3┌──────┐2 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 1└──────┘0 + # η η + # ↑ ↑ + # │ │ + # └───> ξ ξ <───┘ + + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[2] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[1] + 1 + mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[4] + 1 + mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[3] + 1 + + else # orientation == 3 + # Corner 0 of my side matches corner 3 of other side and + # corner 0 of other side matches corner 3 of my side. + # 2┌──────┐3 2┌──────┐0 + # │ │ │ │ + # │ │ │ │ + # 0└──────┘1 3└──────┘1 + # η η <───┐ + # ↑ │ + # │ ↓ + # └───> ξ ξ + + mortars.neighbor_ids[1, mortar_id] = neighbor_ielements[4] + 1 + mortars.neighbor_ids[2, mortar_id] = neighbor_ielements[2] + 1 + mortars.neighbor_ids[3, mortar_id] = neighbor_ielements[3] + 1 + mortars.neighbor_ids[4, mortar_id] = neighbor_ielements[1] + 1 + end end end -end end # @muladd diff --git a/src/solvers/dgsem_t8code/containers_parallel.jl b/src/solvers/dgsem_t8code/containers_parallel.jl index 0cb3f5887a..a866405532 100644 --- a/src/solvers/dgsem_t8code/containers_parallel.jl +++ b/src/solvers/dgsem_t8code/containers_parallel.jl @@ -1,6 +1,6 @@ function reinitialize_containers!(mesh::ParallelT8codeMesh, equations, dg::DGSEM, cache) @unpack elements, interfaces, boundaries, mortars, mpi_interfaces, mpi_mortars, - mpi_cache = cache + mpi_cache = cache resize!(elements, ncells(mesh)) init_elements!(elements, mesh, dg.basis) @@ -17,21 +17,29 @@ function reinitialize_containers!(mesh::ParallelT8codeMesh, equations, dg::DGSEM resize!(mpi_mortars, required.mpi_mortars) - mpi_mesh_info = (mpi_mortars = mpi_mortars, - mpi_interfaces = mpi_interfaces, - - # Temporary arrays for updating `mpi_cache`. - global_mortar_ids = fill(UInt64(0), nmpimortars(mpi_mortars)), - global_interface_ids = fill(UInt64(0), nmpiinterfaces(mpi_interfaces)), - neighbor_ranks_mortar = Vector{Vector{Int}}(undef, - nmpimortars(mpi_mortars)), - neighbor_ranks_interface = fill(-1, nmpiinterfaces(mpi_interfaces))) - - fill_mesh_info!(mesh, interfaces, mortars, boundaries, - mesh.boundary_names; mpi_mesh_info = mpi_mesh_info) - - init_mpi_cache!(mpi_cache, mesh, mpi_mesh_info, nvariables(equations), nnodes(dg), - eltype(elements)) + mpi_mesh_info = ( + mpi_mortars = mpi_mortars, + mpi_interfaces = mpi_interfaces, + + # Temporary arrays for updating `mpi_cache`. + global_mortar_ids = fill(UInt64(0), nmpimortars(mpi_mortars)), + global_interface_ids = fill(UInt64(0), nmpiinterfaces(mpi_interfaces)), + neighbor_ranks_mortar = Vector{Vector{Int}}( + undef, + nmpimortars(mpi_mortars) + ), + neighbor_ranks_interface = fill(-1, nmpiinterfaces(mpi_interfaces)), + ) + + fill_mesh_info!( + mesh, interfaces, mortars, boundaries, + mesh.boundary_names; mpi_mesh_info = mpi_mesh_info + ) + + init_mpi_cache!( + mpi_cache, mesh, mpi_mesh_info, nvariables(equations), nnodes(dg), + eltype(elements) + ) empty!(mpi_mesh_info.global_mortar_ids) empty!(mpi_mesh_info.global_interface_ids) diff --git a/src/solvers/dgsem_t8code/dg.jl b/src/solvers/dgsem_t8code/dg.jl index e01b12e0f8..b1fffb8f73 100644 --- a/src/solvers/dgsem_t8code/dg.jl +++ b/src/solvers/dgsem_t8code/dg.jl @@ -1,35 +1,41 @@ @muladd begin -#! format: noindent - -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::T8codeMesh, equations::AbstractEquations, dg::DG, ::Any, - ::Type{uEltype}) where {uEltype <: Real} - count_required_surfaces!(mesh) - - elements = init_elements(mesh, equations, dg.basis, uEltype) - interfaces = init_interfaces(mesh, equations, dg.basis, elements) - boundaries = init_boundaries(mesh, equations, dg.basis, elements) - mortars = init_mortars(mesh, equations, dg.basis, elements) - - fill_mesh_info!(mesh, interfaces, mortars, boundaries, - mesh.boundary_names) - - cache = (; elements, interfaces, boundaries, mortars) - - # Add specialized parts of the cache required to compute the volume integral etc. - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) - - return cache -end - -include("containers.jl") -include("containers_2d.jl") -include("containers_3d.jl") - -include("containers_parallel.jl") -include("dg_parallel.jl") + #! format: noindent + + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::T8codeMesh, equations::AbstractEquations, dg::DG, ::Any, + ::Type{uEltype} + ) where {uEltype <: Real} + count_required_surfaces!(mesh) + + elements = init_elements(mesh, equations, dg.basis, uEltype) + interfaces = init_interfaces(mesh, equations, dg.basis, elements) + boundaries = init_boundaries(mesh, equations, dg.basis, elements) + mortars = init_mortars(mesh, equations, dg.basis, elements) + + fill_mesh_info!( + mesh, interfaces, mortars, boundaries, + mesh.boundary_names + ) + + cache = (; elements, interfaces, boundaries, mortars) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) + + return cache + end + + include("containers.jl") + include("containers_2d.jl") + include("containers_3d.jl") + + include("containers_parallel.jl") + include("dg_parallel.jl") end # @muladd diff --git a/src/solvers/dgsem_t8code/dg_parallel.jl b/src/solvers/dgsem_t8code/dg_parallel.jl index 2683026135..6155347673 100644 --- a/src/solvers/dgsem_t8code/dg_parallel.jl +++ b/src/solvers/dgsem_t8code/dg_parallel.jl @@ -1,135 +1,167 @@ @muladd begin -#! format: noindent - -# This method is called when a `SemidiscretizationHyperbolic` is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::ParallelT8codeMesh, equations::AbstractEquations, dg::DG, - ::Any, - ::Type{uEltype}) where {uEltype <: Real} - # Make sure to balance and partition the forest before creating any - # containers in case someone has tampered with forest after creating the - # mesh. - balance!(mesh) - partition!(mesh) + #! format: noindent + + # This method is called when a `SemidiscretizationHyperbolic` is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::ParallelT8codeMesh, equations::AbstractEquations, dg::DG, + ::Any, + ::Type{uEltype} + ) where {uEltype <: Real} + # Make sure to balance and partition the forest before creating any + # containers in case someone has tampered with forest after creating the + # mesh. + balance!(mesh) + partition!(mesh) + + count_required_surfaces!(mesh) + + elements = init_elements(mesh, equations, dg.basis, uEltype) + mortars = init_mortars(mesh, equations, dg.basis, elements) + interfaces = init_interfaces(mesh, equations, dg.basis, elements) + boundaries = init_boundaries(mesh, equations, dg.basis, elements) + + mpi_mortars = init_mpi_mortars(mesh, equations, dg.basis, elements) + mpi_interfaces = init_mpi_interfaces(mesh, equations, dg.basis, elements) + + mpi_mesh_info = ( + mpi_mortars = mpi_mortars, + mpi_interfaces = mpi_interfaces, + global_mortar_ids = fill(UInt64(0), nmpimortars(mpi_mortars)), + global_interface_ids = fill( + UInt64(0), + nmpiinterfaces(mpi_interfaces) + ), + neighbor_ranks_mortar = Vector{Vector{Int}}( + undef, + nmpimortars(mpi_mortars) + ), + neighbor_ranks_interface = fill( + -1, + nmpiinterfaces(mpi_interfaces) + ), + ) + + fill_mesh_info!( + mesh, interfaces, mortars, boundaries, + mesh.boundary_names; mpi_mesh_info = mpi_mesh_info + ) + + mpi_cache = init_mpi_cache( + mesh, mpi_mesh_info, nvariables(equations), nnodes(dg), + uEltype + ) + + empty!(mpi_mesh_info.global_mortar_ids) + empty!(mpi_mesh_info.global_interface_ids) + empty!(mpi_mesh_info.neighbor_ranks_mortar) + empty!(mpi_mesh_info.neighbor_ranks_interface) + + init_normal_directions!(mpi_mortars, dg.basis, elements) + exchange_normal_directions!(mpi_mortars, mpi_cache, mesh, nnodes(dg)) + + cache = (; + elements, interfaces, mpi_interfaces, boundaries, mortars, mpi_mortars, + mpi_cache, + ) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) + + return cache + end + + function init_mpi_cache(mesh::ParallelT8codeMesh, mpi_mesh_info, nvars, nnodes, uEltype) + mpi_cache = P4estMPICache(uEltype) + init_mpi_cache!(mpi_cache, mesh, mpi_mesh_info, nvars, nnodes, uEltype) + return mpi_cache + end + + function init_mpi_cache!( + mpi_cache::P4estMPICache, mesh::ParallelT8codeMesh, + mpi_mesh_info, nvars, nnodes, uEltype + ) + mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars = init_mpi_neighbor_connectivity( + mpi_mesh_info, + mesh + ) + + mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests = init_mpi_data_structures( + mpi_neighbor_interfaces, + mpi_neighbor_mortars, + ndims(mesh), + nvars, + nnodes, + uEltype + ) + + n_elements_global = Int(t8_forest_get_global_num_elements(mesh.forest)) + n_elements_local = Int(t8_forest_get_local_num_elements(mesh.forest)) - count_required_surfaces!(mesh) - - elements = init_elements(mesh, equations, dg.basis, uEltype) - mortars = init_mortars(mesh, equations, dg.basis, elements) - interfaces = init_interfaces(mesh, equations, dg.basis, elements) - boundaries = init_boundaries(mesh, equations, dg.basis, elements) - - mpi_mortars = init_mpi_mortars(mesh, equations, dg.basis, elements) - mpi_interfaces = init_mpi_interfaces(mesh, equations, dg.basis, elements) - - mpi_mesh_info = (mpi_mortars = mpi_mortars, - mpi_interfaces = mpi_interfaces, - global_mortar_ids = fill(UInt64(0), nmpimortars(mpi_mortars)), - global_interface_ids = fill(UInt64(0), - nmpiinterfaces(mpi_interfaces)), - neighbor_ranks_mortar = Vector{Vector{Int}}(undef, - nmpimortars(mpi_mortars)), - neighbor_ranks_interface = fill(-1, - nmpiinterfaces(mpi_interfaces))) - - fill_mesh_info!(mesh, interfaces, mortars, boundaries, - mesh.boundary_names; mpi_mesh_info = mpi_mesh_info) - - mpi_cache = init_mpi_cache(mesh, mpi_mesh_info, nvariables(equations), nnodes(dg), - uEltype) - - empty!(mpi_mesh_info.global_mortar_ids) - empty!(mpi_mesh_info.global_interface_ids) - empty!(mpi_mesh_info.neighbor_ranks_mortar) - empty!(mpi_mesh_info.neighbor_ranks_interface) - - init_normal_directions!(mpi_mortars, dg.basis, elements) - exchange_normal_directions!(mpi_mortars, mpi_cache, mesh, nnodes(dg)) - - cache = (; elements, interfaces, mpi_interfaces, boundaries, mortars, mpi_mortars, - mpi_cache) - - # Add specialized parts of the cache required to compute the volume integral etc. - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) - - return cache -end - -function init_mpi_cache(mesh::ParallelT8codeMesh, mpi_mesh_info, nvars, nnodes, uEltype) - mpi_cache = P4estMPICache(uEltype) - init_mpi_cache!(mpi_cache, mesh, mpi_mesh_info, nvars, nnodes, uEltype) - return mpi_cache -end - -function init_mpi_cache!(mpi_cache::P4estMPICache, mesh::ParallelT8codeMesh, - mpi_mesh_info, nvars, nnodes, uEltype) - mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars = init_mpi_neighbor_connectivity(mpi_mesh_info, - mesh) - - mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests = init_mpi_data_structures(mpi_neighbor_interfaces, - mpi_neighbor_mortars, - ndims(mesh), - nvars, - nnodes, - uEltype) - - n_elements_global = Int(t8_forest_get_global_num_elements(mesh.forest)) - n_elements_local = Int(t8_forest_get_local_num_elements(mesh.forest)) - - n_elements_by_rank = Vector{Int}(undef, mpi_nranks()) - n_elements_by_rank[mpi_rank() + 1] = n_elements_local - - MPI.Allgather!(MPI.UBuffer(n_elements_by_rank, 1), mpi_comm()) - - n_elements_by_rank = OffsetArray(n_elements_by_rank, 0:(mpi_nranks() - 1)) - - # Account for 1-based indexing in Julia. - first_element_global_id = sum(n_elements_by_rank[0:(mpi_rank() - 1)]) + 1 - - @assert n_elements_global==sum(n_elements_by_rank) "error in total number of elements" - - @pack! mpi_cache = mpi_neighbor_ranks, mpi_neighbor_interfaces, - mpi_neighbor_mortars, - mpi_send_buffers, mpi_recv_buffers, - mpi_send_requests, mpi_recv_requests, - n_elements_by_rank, n_elements_global, - first_element_global_id - - return mpi_cache -end - -function init_mpi_neighbor_connectivity(mpi_mesh_info, mesh::ParallelT8codeMesh) - @unpack mpi_interfaces, mpi_mortars, global_interface_ids, neighbor_ranks_interface, global_mortar_ids, neighbor_ranks_mortar = mpi_mesh_info - - mpi_neighbor_ranks = vcat(neighbor_ranks_interface, neighbor_ranks_mortar...) |> - sort |> unique - - p = sortperm(global_interface_ids) - - neighbor_ranks_interface .= neighbor_ranks_interface[p] - interface_ids = collect(1:nmpiinterfaces(mpi_interfaces))[p] + n_elements_by_rank = Vector{Int}(undef, mpi_nranks()) + n_elements_by_rank[mpi_rank() + 1] = n_elements_local - p = sortperm(global_mortar_ids) - neighbor_ranks_mortar .= neighbor_ranks_mortar[p] - mortar_ids = collect(1:nmpimortars(mpi_mortars))[p] - - # For each neighbor rank, init connectivity data structures - mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) - mpi_neighbor_mortars = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) - for (index, rank) in enumerate(mpi_neighbor_ranks) - mpi_neighbor_interfaces[index] = interface_ids[findall(==(rank), - neighbor_ranks_interface)] - mpi_neighbor_mortars[index] = mortar_ids[findall(x -> (rank in x), - neighbor_ranks_mortar)] + MPI.Allgather!(MPI.UBuffer(n_elements_by_rank, 1), mpi_comm()) + + n_elements_by_rank = OffsetArray(n_elements_by_rank, 0:(mpi_nranks() - 1)) + + # Account for 1-based indexing in Julia. + first_element_global_id = sum(n_elements_by_rank[0:(mpi_rank() - 1)]) + 1 + + @assert n_elements_global == sum(n_elements_by_rank) "error in total number of elements" + + @pack! mpi_cache = mpi_neighbor_ranks, mpi_neighbor_interfaces, + mpi_neighbor_mortars, + mpi_send_buffers, mpi_recv_buffers, + mpi_send_requests, mpi_recv_requests, + n_elements_by_rank, n_elements_global, + first_element_global_id + + return mpi_cache end - # Check that all interfaces were counted exactly once - @assert mapreduce(length, +, mpi_neighbor_interfaces; init = 0) == + function init_mpi_neighbor_connectivity(mpi_mesh_info, mesh::ParallelT8codeMesh) + @unpack mpi_interfaces, mpi_mortars, global_interface_ids, neighbor_ranks_interface, global_mortar_ids, neighbor_ranks_mortar = mpi_mesh_info + + mpi_neighbor_ranks = vcat(neighbor_ranks_interface, neighbor_ranks_mortar...) |> + sort |> unique + + p = sortperm(global_interface_ids) + + neighbor_ranks_interface .= neighbor_ranks_interface[p] + interface_ids = collect(1:nmpiinterfaces(mpi_interfaces))[p] + + p = sortperm(global_mortar_ids) + neighbor_ranks_mortar .= neighbor_ranks_mortar[p] + mortar_ids = collect(1:nmpimortars(mpi_mortars))[p] + + # For each neighbor rank, init connectivity data structures + mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) + mpi_neighbor_mortars = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) + for (index, rank) in enumerate(mpi_neighbor_ranks) + mpi_neighbor_interfaces[index] = interface_ids[ + findall( + ==(rank), + neighbor_ranks_interface + ), + ] + mpi_neighbor_mortars[index] = mortar_ids[ + findall( + x -> (rank in x), + neighbor_ranks_mortar + ), + ] + end + + # Check that all interfaces were counted exactly once + @assert mapreduce(length, +, mpi_neighbor_interfaces; init = 0) == nmpiinterfaces(mpi_interfaces) - return mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars -end + return mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars + end end # @muladd diff --git a/src/solvers/dgsem_tree/container_viscous_1d.jl b/src/solvers/dgsem_tree/container_viscous_1d.jl index 71c68dfc6d..fff3eab74e 100644 --- a/src/solvers/dgsem_tree/container_viscous_1d.jl +++ b/src/solvers/dgsem_tree/container_viscous_1d.jl @@ -8,20 +8,26 @@ mutable struct ViscousContainer1D{uEltype <: Real} _gradients::Vector{uEltype} _flux_viscous::Vector{uEltype} - function ViscousContainer1D{uEltype}(n_vars::Integer, n_nodes::Integer, - n_elements::Integer) where {uEltype <: Real} - new(Array{uEltype, 3}(undef, n_vars, n_nodes, n_elements), + function ViscousContainer1D{uEltype}( + n_vars::Integer, n_nodes::Integer, + n_elements::Integer + ) where {uEltype <: Real} + new( + Array{uEltype, 3}(undef, n_vars, n_nodes, n_elements), Array{uEltype, 3}(undef, n_vars, n_nodes, n_elements), Array{uEltype, 3}(undef, n_vars, n_nodes, n_elements), Vector{uEltype}(undef, n_vars * n_nodes * n_elements), Vector{uEltype}(undef, n_vars * n_nodes * n_elements), - Vector{uEltype}(undef, n_vars * n_nodes * n_elements)) + Vector{uEltype}(undef, n_vars * n_nodes * n_elements) + ) end end -function init_viscous_container_1d(n_vars::Integer, n_nodes::Integer, - n_elements::Integer, - ::Type{uEltype}) where {uEltype <: Real} +function init_viscous_container_1d( + n_vars::Integer, n_nodes::Integer, + n_elements::Integer, + ::Type{uEltype} + ) where {uEltype <: Real} return ViscousContainer1D{uEltype}(n_vars, n_nodes, n_elements) end @@ -36,23 +42,35 @@ function Base.resize!(viscous_container::ViscousContainer1D, equations, dg, cach resize!(viscous_container._gradients, capacity) resize!(viscous_container._flux_viscous, capacity) - viscous_container.u_transformed = unsafe_wrap(Array, - pointer(viscous_container._u_transformed), - (nvariables(equations), - nnodes(dg), - nelements(dg, cache))) - - viscous_container.gradients = unsafe_wrap(Array, - pointer(viscous_container._gradients), - (nvariables(equations), - nnodes(dg), - nelements(dg, cache))) - - viscous_container.flux_viscous = unsafe_wrap(Array, - pointer(viscous_container._flux_viscous), - (nvariables(equations), - nnodes(dg), - nelements(dg, cache))) + viscous_container.u_transformed = unsafe_wrap( + Array, + pointer(viscous_container._u_transformed), + ( + nvariables(equations), + nnodes(dg), + nelements(dg, cache), + ) + ) + + viscous_container.gradients = unsafe_wrap( + Array, + pointer(viscous_container._gradients), + ( + nvariables(equations), + nnodes(dg), + nelements(dg, cache), + ) + ) + + viscous_container.flux_viscous = unsafe_wrap( + Array, + pointer(viscous_container._flux_viscous), + ( + nvariables(equations), + nnodes(dg), + nelements(dg, cache), + ) + ) return nothing end diff --git a/src/solvers/dgsem_tree/container_viscous_2d.jl b/src/solvers/dgsem_tree/container_viscous_2d.jl index bd7ff413af..48594fdf82 100644 --- a/src/solvers/dgsem_tree/container_viscous_2d.jl +++ b/src/solvers/dgsem_tree/container_viscous_2d.jl @@ -2,8 +2,8 @@ mutable struct ViscousContainer2D{uEltype <: Real} u_transformed::Array{uEltype, 4} # Using an outer fixed-size datastructure leads to nasty implementations, # see https://github.com/trixi-framework/Trixi.jl/pull/1629#discussion_r1355293953. - # Also: This does not result in speed up compared to using tuples for the internal - # datastructures, see + # Also: This does not result in speed up compared to using tuples for the internal + # datastructures, see # https://github.com/trixi-framework/Trixi.jl/pull/1629#discussion_r1363352188. gradients::Vector{Array{uEltype, 4}} flux_viscous::Vector{Array{uEltype, 4}} @@ -14,22 +14,32 @@ mutable struct ViscousContainer2D{uEltype <: Real} _gradients::Tuple{Vector{uEltype}, Vector{uEltype}} _flux_viscous::Tuple{Vector{uEltype}, Vector{uEltype}} - function ViscousContainer2D{uEltype}(n_vars::Integer, n_nodes::Integer, - n_elements::Integer) where {uEltype <: Real} - new(Array{uEltype, 4}(undef, n_vars, n_nodes, n_nodes, n_elements), + function ViscousContainer2D{uEltype}( + n_vars::Integer, n_nodes::Integer, + n_elements::Integer + ) where {uEltype <: Real} + new( + Array{uEltype, 4}(undef, n_vars, n_nodes, n_nodes, n_elements), [Array{uEltype, 4}(undef, n_vars, n_nodes, n_nodes, n_elements) for _ in 1:2], [Array{uEltype, 4}(undef, n_vars, n_nodes, n_nodes, n_elements) for _ in 1:2], Vector{uEltype}(undef, n_vars * n_nodes^2 * n_elements), - (Vector{uEltype}(undef, n_vars * n_nodes^2 * n_elements), - Vector{uEltype}(undef, n_vars * n_nodes^2 * n_elements)), - (Vector{uEltype}(undef, n_vars * n_nodes^2 * n_elements), - Vector{uEltype}(undef, n_vars * n_nodes^2 * n_elements))) + ( + Vector{uEltype}(undef, n_vars * n_nodes^2 * n_elements), + Vector{uEltype}(undef, n_vars * n_nodes^2 * n_elements), + ), + ( + Vector{uEltype}(undef, n_vars * n_nodes^2 * n_elements), + Vector{uEltype}(undef, n_vars * n_nodes^2 * n_elements), + ) + ) end end -function init_viscous_container_2d(n_vars::Integer, n_nodes::Integer, - n_elements::Integer, - ::Type{uEltype}) where {uEltype <: Real} +function init_viscous_container_2d( + n_vars::Integer, n_nodes::Integer, + n_elements::Integer, + ::Type{uEltype} + ) where {uEltype <: Real} return ViscousContainer2D{uEltype}(n_vars, n_nodes, n_elements) end @@ -46,24 +56,36 @@ function Base.resize!(viscous_container::ViscousContainer2D, equations, dg, cach resize!(viscous_container._flux_viscous[dim], capacity) end - viscous_container.u_transformed = unsafe_wrap(Array, - pointer(viscous_container._u_transformed), - (nvariables(equations), - nnodes(dg), nnodes(dg), - nelements(dg, cache))) + viscous_container.u_transformed = unsafe_wrap( + Array, + pointer(viscous_container._u_transformed), + ( + nvariables(equations), + nnodes(dg), nnodes(dg), + nelements(dg, cache), + ) + ) for dim in 1:2 - viscous_container.gradients[dim] = unsafe_wrap(Array, - pointer(viscous_container._gradients[dim]), - (nvariables(equations), - nnodes(dg), nnodes(dg), - nelements(dg, cache))) + viscous_container.gradients[dim] = unsafe_wrap( + Array, + pointer(viscous_container._gradients[dim]), + ( + nvariables(equations), + nnodes(dg), nnodes(dg), + nelements(dg, cache), + ) + ) - viscous_container.flux_viscous[dim] = unsafe_wrap(Array, - pointer(viscous_container._flux_viscous[dim]), - (nvariables(equations), - nnodes(dg), nnodes(dg), - nelements(dg, cache))) + viscous_container.flux_viscous[dim] = unsafe_wrap( + Array, + pointer(viscous_container._flux_viscous[dim]), + ( + nvariables(equations), + nnodes(dg), nnodes(dg), + nelements(dg, cache), + ) + ) end return nothing end diff --git a/src/solvers/dgsem_tree/container_viscous_3d.jl b/src/solvers/dgsem_tree/container_viscous_3d.jl index 64d283fe18..b706d4660d 100644 --- a/src/solvers/dgsem_tree/container_viscous_3d.jl +++ b/src/solvers/dgsem_tree/container_viscous_3d.jl @@ -2,8 +2,8 @@ mutable struct ViscousContainer3D{uEltype <: Real} u_transformed::Array{uEltype, 5} # Using an outer fixed-size datastructure leads to nasty implementations, # see https://github.com/trixi-framework/Trixi.jl/pull/1629#discussion_r1355293953. - # Also: This does not result in speed up compared to using tuples for the internal - # datastructures, see + # Also: This does not result in speed up compared to using tuples for the internal + # datastructures, see # https://github.com/trixi-framework/Trixi.jl/pull/1629#discussion_r1363352188. gradients::Vector{Array{uEltype, 5}} flux_viscous::Vector{Array{uEltype, 5}} @@ -14,26 +14,40 @@ mutable struct ViscousContainer3D{uEltype <: Real} _gradients::Tuple{Vector{uEltype}, Vector{uEltype}, Vector{uEltype}} _flux_viscous::Tuple{Vector{uEltype}, Vector{uEltype}, Vector{uEltype}} - function ViscousContainer3D{uEltype}(n_vars::Integer, n_nodes::Integer, - n_elements::Integer) where {uEltype <: Real} - new(Array{uEltype, 5}(undef, n_vars, n_nodes, n_nodes, n_nodes, n_elements), - [Array{uEltype, 5}(undef, n_vars, n_nodes, n_nodes, n_nodes, n_elements) - for _ in 1:3], - [Array{uEltype, 5}(undef, n_vars, n_nodes, n_nodes, n_nodes, n_elements) - for _ in 1:3], + function ViscousContainer3D{uEltype}( + n_vars::Integer, n_nodes::Integer, + n_elements::Integer + ) where {uEltype <: Real} + new( + Array{uEltype, 5}(undef, n_vars, n_nodes, n_nodes, n_nodes, n_elements), + [ + Array{uEltype, 5}(undef, n_vars, n_nodes, n_nodes, n_nodes, n_elements) + for _ in 1:3 + ], + [ + Array{uEltype, 5}(undef, n_vars, n_nodes, n_nodes, n_nodes, n_elements) + for _ in 1:3 + ], Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), - (Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), - Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), - Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements)), - (Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), - Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), - Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements))) + ( + Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), + Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), + Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), + ), + ( + Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), + Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), + Vector{uEltype}(undef, n_vars * n_nodes^3 * n_elements), + ) + ) end end -function init_viscous_container_3d(n_vars::Integer, n_nodes::Integer, - n_elements::Integer, - ::Type{uEltype}) where {uEltype <: Real} +function init_viscous_container_3d( + n_vars::Integer, n_nodes::Integer, + n_elements::Integer, + ::Type{uEltype} + ) where {uEltype <: Real} return ViscousContainer3D{uEltype}(n_vars, n_nodes, n_elements) end @@ -44,32 +58,44 @@ end # internal storage. function Base.resize!(viscous_container::ViscousContainer3D, equations, dg, cache) capacity = nvariables(equations) * nnodes(dg) * nnodes(dg) * nnodes(dg) * - nelements(dg, cache) + nelements(dg, cache) resize!(viscous_container._u_transformed, capacity) for dim in 1:3 resize!(viscous_container._gradients[dim], capacity) resize!(viscous_container._flux_viscous[dim], capacity) end - viscous_container.u_transformed = unsafe_wrap(Array, - pointer(viscous_container._u_transformed), - (nvariables(equations), - nnodes(dg), nnodes(dg), nnodes(dg), - nelements(dg, cache))) + viscous_container.u_transformed = unsafe_wrap( + Array, + pointer(viscous_container._u_transformed), + ( + nvariables(equations), + nnodes(dg), nnodes(dg), nnodes(dg), + nelements(dg, cache), + ) + ) for dim in 1:3 - viscous_container.gradients[dim] = unsafe_wrap(Array, - pointer(viscous_container._gradients[dim]), - (nvariables(equations), - nnodes(dg), nnodes(dg), nnodes(dg), - nelements(dg, cache))) + viscous_container.gradients[dim] = unsafe_wrap( + Array, + pointer(viscous_container._gradients[dim]), + ( + nvariables(equations), + nnodes(dg), nnodes(dg), nnodes(dg), + nelements(dg, cache), + ) + ) - viscous_container.flux_viscous[dim] = unsafe_wrap(Array, - pointer(viscous_container._flux_viscous[dim]), - (nvariables(equations), - nnodes(dg), nnodes(dg), - nnodes(dg), - nelements(dg, cache))) + viscous_container.flux_viscous[dim] = unsafe_wrap( + Array, + pointer(viscous_container._flux_viscous[dim]), + ( + nvariables(equations), + nnodes(dg), nnodes(dg), + nnodes(dg), + nelements(dg, cache), + ) + ) end return nothing end diff --git a/src/solvers/dgsem_tree/containers.jl b/src/solvers/dgsem_tree/containers.jl index 3f05daf81d..acb6d0ad2f 100644 --- a/src/solvers/dgsem_tree/containers.jl +++ b/src/solvers/dgsem_tree/containers.jl @@ -3,57 +3,59 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Dimension independent code related to containers of the DG solver -# with the mesh type TreeMesh - -function reinitialize_containers!(mesh::TreeMesh, equations, dg::DGSEM, cache) - # Get new list of leaf cells - leaf_cell_ids = local_leaf_cells(mesh.tree) - - # re-initialize elements container - @unpack elements = cache - resize!(elements, length(leaf_cell_ids)) - init_elements!(elements, leaf_cell_ids, mesh, dg.basis) - - # re-initialize interfaces container - @unpack interfaces = cache - resize!(interfaces, count_required_interfaces(mesh, leaf_cell_ids)) - init_interfaces!(interfaces, elements, mesh) - - # re-initialize boundaries container - @unpack boundaries = cache - resize!(boundaries, count_required_boundaries(mesh, leaf_cell_ids)) - init_boundaries!(boundaries, elements, mesh) - - # re-initialize mortars container - if hasproperty(cache, :mortars) # cache_parabolic does not carry mortars - @unpack mortars = cache - resize!(mortars, count_required_mortars(mesh, leaf_cell_ids)) - init_mortars!(mortars, elements, mesh) + #! format: noindent + + # Dimension independent code related to containers of the DG solver + # with the mesh type TreeMesh + + function reinitialize_containers!(mesh::TreeMesh, equations, dg::DGSEM, cache) + # Get new list of leaf cells + leaf_cell_ids = local_leaf_cells(mesh.tree) + + # re-initialize elements container + @unpack elements = cache + resize!(elements, length(leaf_cell_ids)) + init_elements!(elements, leaf_cell_ids, mesh, dg.basis) + + # re-initialize interfaces container + @unpack interfaces = cache + resize!(interfaces, count_required_interfaces(mesh, leaf_cell_ids)) + init_interfaces!(interfaces, elements, mesh) + + # re-initialize boundaries container + @unpack boundaries = cache + resize!(boundaries, count_required_boundaries(mesh, leaf_cell_ids)) + init_boundaries!(boundaries, elements, mesh) + + # re-initialize mortars container + if hasproperty(cache, :mortars) # cache_parabolic does not carry mortars + @unpack mortars = cache + resize!(mortars, count_required_mortars(mesh, leaf_cell_ids)) + init_mortars!(mortars, elements, mesh) + end + + if mpi_isparallel() + # re-initialize mpi_interfaces container + @unpack mpi_interfaces = cache + resize!(mpi_interfaces, count_required_mpi_interfaces(mesh, leaf_cell_ids)) + init_mpi_interfaces!(mpi_interfaces, elements, mesh) + + # re-initialize mpi_mortars container + @unpack mpi_mortars = cache + resize!(mpi_mortars, count_required_mpi_mortars(mesh, leaf_cell_ids)) + init_mpi_mortars!(mpi_mortars, elements, mesh) + + # re-initialize mpi cache + @unpack mpi_cache = cache + init_mpi_cache!( + mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, + nvariables(equations), nnodes(dg), eltype(elements) + ) + end end - if mpi_isparallel() - # re-initialize mpi_interfaces container - @unpack mpi_interfaces = cache - resize!(mpi_interfaces, count_required_mpi_interfaces(mesh, leaf_cell_ids)) - init_mpi_interfaces!(mpi_interfaces, elements, mesh) - - # re-initialize mpi_mortars container - @unpack mpi_mortars = cache - resize!(mpi_mortars, count_required_mpi_mortars(mesh, leaf_cell_ids)) - init_mpi_mortars!(mpi_mortars, elements, mesh) - - # re-initialize mpi cache - @unpack mpi_cache = cache - init_mpi_cache!(mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, - nvariables(equations), nnodes(dg), eltype(elements)) - end -end - -# Dimension-specific implementations -include("containers_1d.jl") -include("containers_2d.jl") -include("containers_3d.jl") + # Dimension-specific implementations + include("containers_1d.jl") + include("containers_2d.jl") + include("containers_3d.jl") end # @muladd diff --git a/src/solvers/dgsem_tree/containers_1d.jl b/src/solvers/dgsem_tree/containers_1d.jl index ecbcc1c4d9..a66e381d11 100644 --- a/src/solvers/dgsem_tree/containers_1d.jl +++ b/src/solvers/dgsem_tree/containers_1d.jl @@ -3,460 +3,524 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Container data structure (structure-of-arrays style) for DG elements -mutable struct ElementContainer1D{RealT <: Real, uEltype <: Real} <: AbstractContainer - inverse_jacobian::Vector{RealT} # [elements] - node_coordinates::Array{RealT, 3} # [orientation, i, elements] - surface_flux_values::Array{uEltype, 3} # [variables, direction, elements] - cell_ids::Vector{Int} # [elements] - # internal `resize!`able storage - _node_coordinates::Vector{RealT} - _surface_flux_values::Vector{uEltype} -end - -nvariables(elements::ElementContainer1D) = size(elements.surface_flux_values, 1) -nnodes(elements::ElementContainer1D) = size(elements.node_coordinates, 2) -Base.eltype(elements::ElementContainer1D) = eltype(elements.surface_flux_values) - -# Only one-dimensional `Array`s are `resize!`able in Julia. -# Hence, we use `Vector`s as internal storage and `resize!` -# them whenever needed. Then, we reuse the same memory by -# `unsafe_wrap`ping multi-dimensional `Array`s around the -# internal storage. -function Base.resize!(elements::ElementContainer1D, capacity) - n_nodes = nnodes(elements) - n_variables = nvariables(elements) - @unpack _node_coordinates, _surface_flux_values, - inverse_jacobian, cell_ids = elements - - resize!(inverse_jacobian, capacity) - - resize!(_node_coordinates, 1 * n_nodes * capacity) - elements.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (1, n_nodes, capacity)) - - resize!(_surface_flux_values, n_variables * 2 * 1 * capacity) - elements.surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), - (n_variables, 2 * 1, capacity)) - - resize!(cell_ids, capacity) - - return nothing -end - -function ElementContainer1D{RealT, uEltype}(capacity::Integer, n_variables, - n_nodes) where {RealT <: Real, - uEltype <: Real} - nan_RealT = convert(RealT, NaN) - nan_uEltype = convert(uEltype, NaN) - - # Initialize fields with defaults - inverse_jacobian = fill(nan_RealT, capacity) - - _node_coordinates = fill(nan_RealT, 1 * n_nodes * capacity) - node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (1, n_nodes, capacity)) - - _surface_flux_values = fill(nan_uEltype, n_variables * 2 * 1 * capacity) - surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), - (n_variables, 2 * 1, capacity)) - - cell_ids = fill(typemin(Int), capacity) - - return ElementContainer1D{RealT, uEltype}(inverse_jacobian, node_coordinates, - surface_flux_values, cell_ids, - _node_coordinates, _surface_flux_values) -end - -# Return number of elements -@inline nelements(elements::ElementContainer1D) = length(elements.cell_ids) -# TODO: Taal performance, 1:nelements(elements) vs. Base.OneTo(nelements(elements)) -""" - eachelement(elements::ElementContainer1D) - -Return an iterator over the indices that specify the location in relevant data structures -for the elements in `elements`. -In particular, not the elements themselves are returned. -""" -@inline eachelement(elements::ElementContainer1D) = Base.OneTo(nelements(elements)) -@inline Base.real(elements::ElementContainer1D) = eltype(elements.node_coordinates) - -# Create element container and initialize element data -function init_elements(cell_ids, mesh::TreeMesh1D, - equations::AbstractEquations{1}, - basis, ::Type{RealT}, - ::Type{uEltype}) where {RealT <: Real, uEltype <: Real} - # Initialize container - n_elements = length(cell_ids) - elements = ElementContainer1D{RealT, uEltype}(n_elements, nvariables(equations), - nnodes(basis)) - - init_elements!(elements, cell_ids, mesh, basis) - return elements -end - -function init_elements!(elements, cell_ids, mesh::TreeMesh1D, basis) - nodes = get_nodes(basis) - # Compute the length of the 1D reference interval by integrating - # the function with constant value unity on the corresponding - # element data type (using \circ) - reference_length = integrate(one ∘ eltype, nodes, basis) - # Compute the offset of the midpoint of the 1D reference interval - # (its difference from zero) - reference_offset = (first(nodes) + last(nodes)) / 2 - - # Store cell ids - elements.cell_ids .= cell_ids - - # Calculate inverse Jacobian and node coordinates - for element in eachelement(elements) - # Get cell id - cell_id = cell_ids[element] - - # Get cell length - dx = length_at_cell(mesh.tree, cell_id) - - # Calculate inverse Jacobian - jacobian = dx / reference_length - elements.inverse_jacobian[element] = inv(jacobian) - - # Calculate node coordinates - # Note that the `tree_coordinates` are the midpoints of the cells. - # Hence, we need to add an offset for `nodes` with a midpoint - # different from zero. - for i in eachnode(basis) - elements.node_coordinates[1, i, element] = (mesh.tree.coordinates[1, - cell_id] + - jacobian * - (nodes[i] - reference_offset)) - end + #! format: noindent + + # Container data structure (structure-of-arrays style) for DG elements + mutable struct ElementContainer1D{RealT <: Real, uEltype <: Real} <: AbstractContainer + inverse_jacobian::Vector{RealT} # [elements] + node_coordinates::Array{RealT, 3} # [orientation, i, elements] + surface_flux_values::Array{uEltype, 3} # [variables, direction, elements] + cell_ids::Vector{Int} # [elements] + # internal `resize!`able storage + _node_coordinates::Vector{RealT} + _surface_flux_values::Vector{uEltype} end - return elements -end - -# Container data structure (structure-of-arrays style) for DG interfaces -mutable struct InterfaceContainer1D{uEltype <: Real} <: AbstractContainer - u::Array{uEltype, 3} # [leftright, variables, interfaces] - neighbor_ids::Matrix{Int} # [leftright, interfaces] - orientations::Vector{Int} # [interfaces] - # internal `resize!`able storage - _u::Vector{uEltype} - _neighbor_ids::Vector{Int} -end - -nvariables(interfaces::InterfaceContainer1D) = size(interfaces.u, 2) -Base.eltype(interfaces::InterfaceContainer1D) = eltype(interfaces.u) - -# See explanation of Base.resize! for the element container -function Base.resize!(interfaces::InterfaceContainer1D, capacity) - n_variables = nvariables(interfaces) - @unpack _u, _neighbor_ids, orientations = interfaces - - resize!(_u, 2 * n_variables * capacity) - interfaces.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, capacity)) - - resize!(_neighbor_ids, 2 * capacity) - interfaces.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (2, capacity)) - - resize!(orientations, capacity) - - return nothing -end - -function InterfaceContainer1D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} - nan = convert(uEltype, NaN) - - # Initialize fields with defaults - _u = fill(nan, 2 * n_variables * capacity) - u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, capacity)) - - _neighbor_ids = fill(typemin(Int), 2 * capacity) - neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (2, capacity)) - - orientations = fill(typemin(Int), capacity) - - return InterfaceContainer1D{uEltype}(u, neighbor_ids, orientations, - _u, _neighbor_ids) -end - -# Return number of interfaces -@inline ninterfaces(interfaces::InterfaceContainer1D) = length(interfaces.orientations) - -# Create interface container and initialize interface data in `elements`. -function init_interfaces(cell_ids, mesh::TreeMesh1D, - elements::ElementContainer1D) - # Initialize container - n_interfaces = count_required_interfaces(mesh, cell_ids) - interfaces = InterfaceContainer1D{eltype(elements)}(n_interfaces, - nvariables(elements), - nnodes(elements)) - - # Connect elements with interfaces - init_interfaces!(interfaces, elements, mesh) - return interfaces -end - -# Count the number of interfaces that need to be created -function count_required_interfaces(mesh::TreeMesh1D, cell_ids) - count = 0 - - # Iterate over all cells - for cell_id in cell_ids - for direction in eachdirection(mesh.tree) - # Only count interfaces in positive direction to avoid double counting - if direction == 1 - continue - end + nvariables(elements::ElementContainer1D) = size(elements.surface_flux_values, 1) + nnodes(elements::ElementContainer1D) = size(elements.node_coordinates, 2) + Base.eltype(elements::ElementContainer1D) = eltype(elements.surface_flux_values) + + # Only one-dimensional `Array`s are `resize!`able in Julia. + # Hence, we use `Vector`s as internal storage and `resize!` + # them whenever needed. Then, we reuse the same memory by + # `unsafe_wrap`ping multi-dimensional `Array`s around the + # internal storage. + function Base.resize!(elements::ElementContainer1D, capacity) + n_nodes = nnodes(elements) + n_variables = nvariables(elements) + @unpack _node_coordinates, _surface_flux_values, + inverse_jacobian, cell_ids = elements + + resize!(inverse_jacobian, capacity) + + resize!(_node_coordinates, 1 * n_nodes * capacity) + elements.node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (1, n_nodes, capacity) + ) + + resize!(_surface_flux_values, n_variables * 2 * 1 * capacity) + elements.surface_flux_values = unsafe_wrap( + Array, pointer(_surface_flux_values), + (n_variables, 2 * 1, capacity) + ) + + resize!(cell_ids, capacity) + + return nothing + end - # Skip if no neighbor exists - if !has_any_neighbor(mesh.tree, cell_id, direction) - continue - end + function ElementContainer1D{RealT, uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where { + RealT <: Real, + uEltype <: Real, + } + nan_RealT = convert(RealT, NaN) + nan_uEltype = convert(uEltype, NaN) + + # Initialize fields with defaults + inverse_jacobian = fill(nan_RealT, capacity) + + _node_coordinates = fill(nan_RealT, 1 * n_nodes * capacity) + node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (1, n_nodes, capacity) + ) + + _surface_flux_values = fill(nan_uEltype, n_variables * 2 * 1 * capacity) + surface_flux_values = unsafe_wrap( + Array, pointer(_surface_flux_values), + (n_variables, 2 * 1, capacity) + ) + + cell_ids = fill(typemin(Int), capacity) + + return ElementContainer1D{RealT, uEltype}( + inverse_jacobian, node_coordinates, + surface_flux_values, cell_ids, + _node_coordinates, _surface_flux_values + ) + end - count += 1 + # Return number of elements + @inline nelements(elements::ElementContainer1D) = length(elements.cell_ids) + # TODO: Taal performance, 1:nelements(elements) vs. Base.OneTo(nelements(elements)) + """ + eachelement(elements::ElementContainer1D) + + Return an iterator over the indices that specify the location in relevant data structures + for the elements in `elements`. + In particular, not the elements themselves are returned. + """ + @inline eachelement(elements::ElementContainer1D) = Base.OneTo(nelements(elements)) + @inline Base.real(elements::ElementContainer1D) = eltype(elements.node_coordinates) + + # Create element container and initialize element data + function init_elements( + cell_ids, mesh::TreeMesh1D, + equations::AbstractEquations{1}, + basis, ::Type{RealT}, + ::Type{uEltype} + ) where {RealT <: Real, uEltype <: Real} + # Initialize container + n_elements = length(cell_ids) + elements = ElementContainer1D{RealT, uEltype}( + n_elements, nvariables(equations), + nnodes(basis) + ) + + init_elements!(elements, cell_ids, mesh, basis) + return elements + end + + function init_elements!(elements, cell_ids, mesh::TreeMesh1D, basis) + nodes = get_nodes(basis) + # Compute the length of the 1D reference interval by integrating + # the function with constant value unity on the corresponding + # element data type (using \circ) + reference_length = integrate(one ∘ eltype, nodes, basis) + # Compute the offset of the midpoint of the 1D reference interval + # (its difference from zero) + reference_offset = (first(nodes) + last(nodes)) / 2 + + # Store cell ids + elements.cell_ids .= cell_ids + + # Calculate inverse Jacobian and node coordinates + for element in eachelement(elements) + # Get cell id + cell_id = cell_ids[element] + + # Get cell length + dx = length_at_cell(mesh.tree, cell_id) + + # Calculate inverse Jacobian + jacobian = dx / reference_length + elements.inverse_jacobian[element] = inv(jacobian) + + # Calculate node coordinates + # Note that the `tree_coordinates` are the midpoints of the cells. + # Hence, we need to add an offset for `nodes` with a midpoint + # different from zero. + for i in eachnode(basis) + elements.node_coordinates[1, i, element] = ( + mesh.tree.coordinates[ + 1, + cell_id, + ] + + jacobian * + (nodes[i] - reference_offset) + ) + end end + + return elements + end + + # Container data structure (structure-of-arrays style) for DG interfaces + mutable struct InterfaceContainer1D{uEltype <: Real} <: AbstractContainer + u::Array{uEltype, 3} # [leftright, variables, interfaces] + neighbor_ids::Matrix{Int} # [leftright, interfaces] + orientations::Vector{Int} # [interfaces] + # internal `resize!`able storage + _u::Vector{uEltype} + _neighbor_ids::Vector{Int} end - return count -end + nvariables(interfaces::InterfaceContainer1D) = size(interfaces.u, 2) + Base.eltype(interfaces::InterfaceContainer1D) = eltype(interfaces.u) -# Initialize connectivity between elements and interfaces -function init_interfaces!(interfaces, elements, mesh::TreeMesh1D) - # Construct cell -> element mapping for easier algorithm implementation - tree = mesh.tree - c2e = zeros(Int, length(tree)) - for element in eachelement(elements) - c2e[elements.cell_ids[element]] = element + # See explanation of Base.resize! for the element container + function Base.resize!(interfaces::InterfaceContainer1D, capacity) + n_variables = nvariables(interfaces) + @unpack _u, _neighbor_ids, orientations = interfaces + + resize!(_u, 2 * n_variables * capacity) + interfaces.u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, capacity) + ) + + resize!(_neighbor_ids, 2 * capacity) + interfaces.neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (2, capacity) + ) + + resize!(orientations, capacity) + + return nothing end - # Reset interface count - count = 0 + function InterfaceContainer1D{uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where {uEltype <: Real} + nan = convert(uEltype, NaN) + + # Initialize fields with defaults + _u = fill(nan, 2 * n_variables * capacity) + u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, capacity) + ) + + _neighbor_ids = fill(typemin(Int), 2 * capacity) + neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (2, capacity) + ) + + orientations = fill(typemin(Int), capacity) + + return InterfaceContainer1D{uEltype}( + u, neighbor_ids, orientations, + _u, _neighbor_ids + ) + end - # Iterate over all elements to find neighbors and to connect via interfaces - for element in eachelement(elements) - # Get cell id - cell_id = elements.cell_ids[element] + # Return number of interfaces + @inline ninterfaces(interfaces::InterfaceContainer1D) = length(interfaces.orientations) + + # Create interface container and initialize interface data in `elements`. + function init_interfaces( + cell_ids, mesh::TreeMesh1D, + elements::ElementContainer1D + ) + # Initialize container + n_interfaces = count_required_interfaces(mesh, cell_ids) + interfaces = InterfaceContainer1D{eltype(elements)}( + n_interfaces, + nvariables(elements), + nnodes(elements) + ) + + # Connect elements with interfaces + init_interfaces!(interfaces, elements, mesh) + return interfaces + end - # Loop over directions - for direction in eachdirection(mesh.tree) - # Only create interfaces in positive direction - if direction == 1 - continue - end + # Count the number of interfaces that need to be created + function count_required_interfaces(mesh::TreeMesh1D, cell_ids) + count = 0 + + # Iterate over all cells + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # Only count interfaces in positive direction to avoid double counting + if direction == 1 + continue + end + + # Skip if no neighbor exists + if !has_any_neighbor(mesh.tree, cell_id, direction) + continue + end - # Skip if no neighbor exists and current cell is not small - if !has_any_neighbor(mesh.tree, cell_id, direction) - continue + count += 1 end + end + + return count + end + + # Initialize connectivity between elements and interfaces + function init_interfaces!(interfaces, elements, mesh::TreeMesh1D) + # Construct cell -> element mapping for easier algorithm implementation + tree = mesh.tree + c2e = zeros(Int, length(tree)) + for element in eachelement(elements) + c2e[elements.cell_ids[element]] = element + end + + # Reset interface count + count = 0 - count += 1 + # Iterate over all elements to find neighbors and to connect via interfaces + for element in eachelement(elements) + # Get cell id + cell_id = elements.cell_ids[element] + + # Loop over directions + for direction in eachdirection(mesh.tree) + # Only create interfaces in positive direction + if direction == 1 + continue + end - if has_neighbor(mesh.tree, cell_id, direction) - neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] - if has_children(mesh.tree, neighbor_cell_id) # Cell has small neighbor - interfaces.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[1, - neighbor_cell_id]] - else # Cell has same refinement level neighbor + # Skip if no neighbor exists and current cell is not small + if !has_any_neighbor(mesh.tree, cell_id, direction) + continue + end + + count += 1 + + if has_neighbor(mesh.tree, cell_id, direction) + neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] + if has_children(mesh.tree, neighbor_cell_id) # Cell has small neighbor + interfaces.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 1, + neighbor_cell_id, + ], + ] + else # Cell has same refinement level neighbor + interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id] + end + else # Cell is small and has large neighbor + parent_id = mesh.tree.parent_ids[cell_id] + neighbor_cell_id = mesh.tree.neighbor_ids[direction, parent_id] interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id] end - else # Cell is small and has large neighbor - parent_id = mesh.tree.parent_ids[cell_id] - neighbor_cell_id = mesh.tree.neighbor_ids[direction, parent_id] - interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id] - end - interfaces.neighbor_ids[1, count] = element - # Set orientation (x -> 1) - interfaces.orientations[count] = 1 + interfaces.neighbor_ids[1, count] = element + # Set orientation (x -> 1) + interfaces.orientations[count] = 1 + end end + + @assert count == ninterfaces(interfaces) ( + "Actual interface count ($count) does not match " * + "expectations $(ninterfaces(interfaces))" + ) end - @assert count==ninterfaces(interfaces) ("Actual interface count ($count) does not match "* - "expectations $(ninterfaces(interfaces))") -end - -# Container data structure (structure-of-arrays style) for DG boundaries -mutable struct BoundaryContainer1D{RealT <: Real, uEltype <: Real} <: AbstractContainer - u::Array{uEltype, 3} # [leftright, variables, boundaries] - neighbor_ids::Vector{Int} # [boundaries] - orientations::Vector{Int} # [boundaries] - neighbor_sides::Vector{Int} # [boundaries] - node_coordinates::Array{RealT, 2} # [orientation, elements] - n_boundaries_per_direction::SVector{2, Int} # [direction] - # internal `resize!`able storage - _u::Vector{uEltype} - _node_coordinates::Vector{RealT} -end - -nvariables(boundaries::BoundaryContainer1D) = size(boundaries.u, 2) -Base.eltype(boundaries::BoundaryContainer1D) = eltype(boundaries.u) - -# See explanation of Base.resize! for the element container -function Base.resize!(boundaries::BoundaryContainer1D, capacity) - n_variables = nvariables(boundaries) - @unpack _u, _node_coordinates, - neighbor_ids, orientations, neighbor_sides = boundaries - - resize!(_u, 2 * n_variables * capacity) - boundaries.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, capacity)) - - resize!(_node_coordinates, 1 * capacity) - boundaries.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (1, capacity)) - - resize!(neighbor_ids, capacity) - - resize!(orientations, capacity) - - resize!(neighbor_sides, capacity) - - return nothing -end - -function BoundaryContainer1D{RealT, uEltype}(capacity::Integer, n_variables, - n_nodes) where {RealT <: Real, - uEltype <: Real} - nan_RealT = convert(RealT, NaN) - nan_uEltype = convert(uEltype, NaN) - - # Initialize fields with defaults - _u = fill(nan_uEltype, 2 * n_variables * capacity) - u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, capacity)) - - neighbor_ids = fill(typemin(Int), capacity) - - orientations = fill(typemin(Int), capacity) - - neighbor_sides = fill(typemin(Int), capacity) - - _node_coordinates = fill(nan_RealT, 1 * capacity) - node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (1, capacity)) - - n_boundaries_per_direction = SVector(0, 0) - - return BoundaryContainer1D{RealT, uEltype}(u, neighbor_ids, orientations, - neighbor_sides, - node_coordinates, - n_boundaries_per_direction, - _u, _node_coordinates) -end - -# Return number of boundaries -nboundaries(boundaries::BoundaryContainer1D) = length(boundaries.orientations) - -# Create boundaries container and initialize boundary data in `elements`. -function init_boundaries(cell_ids, mesh::TreeMesh1D, - elements::ElementContainer1D) - # Initialize container - n_boundaries = count_required_boundaries(mesh, cell_ids) - boundaries = BoundaryContainer1D{real(elements), eltype(elements)}(n_boundaries, - nvariables(elements), - nnodes(elements)) - - # Connect elements with boundaries - init_boundaries!(boundaries, elements, mesh) - return boundaries -end - -# Count the number of boundaries that need to be created -function count_required_boundaries(mesh::TreeMesh1D, cell_ids) - count = 0 - - # Iterate over all cells - for cell_id in cell_ids - for direction in eachdirection(mesh.tree) - # If neighbor exists, current cell is not at a boundary - if has_neighbor(mesh.tree, cell_id, direction) - continue - end + # Container data structure (structure-of-arrays style) for DG boundaries + mutable struct BoundaryContainer1D{RealT <: Real, uEltype <: Real} <: AbstractContainer + u::Array{uEltype, 3} # [leftright, variables, boundaries] + neighbor_ids::Vector{Int} # [boundaries] + orientations::Vector{Int} # [boundaries] + neighbor_sides::Vector{Int} # [boundaries] + node_coordinates::Array{RealT, 2} # [orientation, elements] + n_boundaries_per_direction::SVector{2, Int} # [direction] + # internal `resize!`able storage + _u::Vector{uEltype} + _node_coordinates::Vector{RealT} + end - # If coarse neighbor exists, current cell is not at a boundary - if has_coarse_neighbor(mesh.tree, cell_id, direction) - continue - end + nvariables(boundaries::BoundaryContainer1D) = size(boundaries.u, 2) + Base.eltype(boundaries::BoundaryContainer1D) = eltype(boundaries.u) - # No neighbor exists in this direction -> must be a boundary - count += 1 - end + # See explanation of Base.resize! for the element container + function Base.resize!(boundaries::BoundaryContainer1D, capacity) + n_variables = nvariables(boundaries) + @unpack _u, _node_coordinates, + neighbor_ids, orientations, neighbor_sides = boundaries + + resize!(_u, 2 * n_variables * capacity) + boundaries.u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, capacity) + ) + + resize!(_node_coordinates, 1 * capacity) + boundaries.node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (1, capacity) + ) + + resize!(neighbor_ids, capacity) + + resize!(orientations, capacity) + + resize!(neighbor_sides, capacity) + + return nothing end - return count -end + function BoundaryContainer1D{RealT, uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where { + RealT <: Real, + uEltype <: Real, + } + nan_RealT = convert(RealT, NaN) + nan_uEltype = convert(uEltype, NaN) + + # Initialize fields with defaults + _u = fill(nan_uEltype, 2 * n_variables * capacity) + u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, capacity) + ) + + neighbor_ids = fill(typemin(Int), capacity) + + orientations = fill(typemin(Int), capacity) + + neighbor_sides = fill(typemin(Int), capacity) + + _node_coordinates = fill(nan_RealT, 1 * capacity) + node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (1, capacity) + ) + + n_boundaries_per_direction = SVector(0, 0) + + return BoundaryContainer1D{RealT, uEltype}( + u, neighbor_ids, orientations, + neighbor_sides, + node_coordinates, + n_boundaries_per_direction, + _u, _node_coordinates + ) + end -# Initialize connectivity between elements and boundaries -function init_boundaries!(boundaries, elements, mesh::TreeMesh1D) - # Reset boundaries count - count = 0 + # Return number of boundaries + nboundaries(boundaries::BoundaryContainer1D) = length(boundaries.orientations) + + # Create boundaries container and initialize boundary data in `elements`. + function init_boundaries( + cell_ids, mesh::TreeMesh1D, + elements::ElementContainer1D + ) + # Initialize container + n_boundaries = count_required_boundaries(mesh, cell_ids) + boundaries = BoundaryContainer1D{real(elements), eltype(elements)}( + n_boundaries, + nvariables(elements), + nnodes(elements) + ) + + # Connect elements with boundaries + init_boundaries!(boundaries, elements, mesh) + return boundaries + end - # Initialize boundary counts - counts_per_direction = MVector(0, 0) + # Count the number of boundaries that need to be created + function count_required_boundaries(mesh::TreeMesh1D, cell_ids) + count = 0 - # OBS! Iterate over directions first, then over elements, and count boundaries in each direction - # Rationale: This way the boundaries are internally sorted by the directions -x, +x, -y etc., - # obviating the need to store the boundary condition to be applied explicitly. - # Loop over directions - for direction in eachdirection(mesh.tree) - # Iterate over all elements to find missing neighbors and to connect to boundaries - for element in eachelement(elements) - # Get cell id - cell_id = elements.cell_ids[element] + # Iterate over all cells + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # If neighbor exists, current cell is not at a boundary + if has_neighbor(mesh.tree, cell_id, direction) + continue + end - # If neighbor exists, current cell is not at a boundary - if has_neighbor(mesh.tree, cell_id, direction) - continue - end + # If coarse neighbor exists, current cell is not at a boundary + if has_coarse_neighbor(mesh.tree, cell_id, direction) + continue + end - # If coarse neighbor exists, current cell is not at a boundary - if has_coarse_neighbor(mesh.tree, cell_id, direction) - continue + # No neighbor exists in this direction -> must be a boundary + count += 1 end + end - # Create boundary - count += 1 - counts_per_direction[direction] += 1 + return count + end - # Set neighbor element id - boundaries.neighbor_ids[count] = element + # Initialize connectivity between elements and boundaries + function init_boundaries!(boundaries, elements, mesh::TreeMesh1D) + # Reset boundaries count + count = 0 - # Set neighbor side, which denotes the direction (1 -> negative, 2 -> positive) of the element - if direction == 2 - boundaries.neighbor_sides[count] = 1 - else - boundaries.neighbor_sides[count] = 2 - end + # Initialize boundary counts + counts_per_direction = MVector(0, 0) + + # OBS! Iterate over directions first, then over elements, and count boundaries in each direction + # Rationale: This way the boundaries are internally sorted by the directions -x, +x, -y etc., + # obviating the need to store the boundary condition to be applied explicitly. + # Loop over directions + for direction in eachdirection(mesh.tree) + # Iterate over all elements to find missing neighbors and to connect to boundaries + for element in eachelement(elements) + # Get cell id + cell_id = elements.cell_ids[element] + + # If neighbor exists, current cell is not at a boundary + if has_neighbor(mesh.tree, cell_id, direction) + continue + end + + # If coarse neighbor exists, current cell is not at a boundary + if has_coarse_neighbor(mesh.tree, cell_id, direction) + continue + end + + # Create boundary + count += 1 + counts_per_direction[direction] += 1 + + # Set neighbor element id + boundaries.neighbor_ids[count] = element - # Set orientation (x -> 1) - boundaries.orientations[count] = 1 - - # Store node coordinates - enc = elements.node_coordinates - if direction == 1 # -x direction - boundaries.node_coordinates[:, count] .= enc[:, 1, element] - elseif direction == 2 # +x direction - boundaries.node_coordinates[:, count] .= enc[:, end, element] - else - error("should not happen") + # Set neighbor side, which denotes the direction (1 -> negative, 2 -> positive) of the element + if direction == 2 + boundaries.neighbor_sides[count] = 1 + else + boundaries.neighbor_sides[count] = 2 + end + + # Set orientation (x -> 1) + boundaries.orientations[count] = 1 + + # Store node coordinates + enc = elements.node_coordinates + if direction == 1 # -x direction + boundaries.node_coordinates[:, count] .= enc[:, 1, element] + elseif direction == 2 # +x direction + boundaries.node_coordinates[:, count] .= enc[:, end, element] + else + error("should not happen") + end end end - end - @assert count==nboundaries(boundaries) ("Actual boundaries count ($count) does not match "* - "expectations $(nboundaries(boundaries))") - @assert sum(counts_per_direction) == count + @assert count == nboundaries(boundaries) ( + "Actual boundaries count ($count) does not match " * + "expectations $(nboundaries(boundaries))" + ) + @assert sum(counts_per_direction) == count - boundaries.n_boundaries_per_direction = SVector(counts_per_direction) + boundaries.n_boundaries_per_direction = SVector(counts_per_direction) - return boundaries.n_boundaries_per_direction -end + return boundaries.n_boundaries_per_direction + end end # @muladd diff --git a/src/solvers/dgsem_tree/containers_2d.jl b/src/solvers/dgsem_tree/containers_2d.jl index f0d66fd235..0e2d45b9fc 100644 --- a/src/solvers/dgsem_tree/containers_2d.jl +++ b/src/solvers/dgsem_tree/containers_2d.jl @@ -3,1423 +3,1619 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Container data structure (structure-of-arrays style) for DG elements -mutable struct ElementContainer2D{RealT <: Real, uEltype <: Real} <: AbstractContainer - inverse_jacobian::Vector{RealT} # [elements] - node_coordinates::Array{RealT, 4} # [orientation, i, j, elements] - surface_flux_values::Array{uEltype, 4} # [variables, i, direction, elements] - cell_ids::Vector{Int} # [elements] - # internal `resize!`able storage - _node_coordinates::Vector{RealT} - _surface_flux_values::Vector{uEltype} -end - -nvariables(elements::ElementContainer2D) = size(elements.surface_flux_values, 1) -nnodes(elements::ElementContainer2D) = size(elements.node_coordinates, 2) -Base.eltype(elements::ElementContainer2D) = eltype(elements.surface_flux_values) - -# Only one-dimensional `Array`s are `resize!`able in Julia. -# Hence, we use `Vector`s as internal storage and `resize!` -# them whenever needed. Then, we reuse the same memory by -# `unsafe_wrap`ping multi-dimensional `Array`s around the -# internal storage. -function Base.resize!(elements::ElementContainer2D, capacity) - n_nodes = nnodes(elements) - n_variables = nvariables(elements) - @unpack _node_coordinates, _surface_flux_values, - inverse_jacobian, cell_ids = elements - - resize!(inverse_jacobian, capacity) - - resize!(_node_coordinates, 2 * n_nodes * n_nodes * capacity) - elements.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (2, n_nodes, n_nodes, capacity)) - - resize!(_surface_flux_values, n_variables * n_nodes * 2 * 2 * capacity) - elements.surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), - (n_variables, n_nodes, 2 * 2, capacity)) - - resize!(cell_ids, capacity) - - return nothing -end - -function ElementContainer2D{RealT, uEltype}(capacity::Integer, n_variables, - n_nodes) where {RealT <: Real, - uEltype <: Real} - nan_RealT = convert(RealT, NaN) - nan_uEltype = convert(uEltype, NaN) - - # Initialize fields with defaults - inverse_jacobian = fill(nan_RealT, capacity) - - _node_coordinates = fill(nan_RealT, 2 * n_nodes * n_nodes * capacity) - node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (2, n_nodes, n_nodes, capacity)) - - _surface_flux_values = fill(nan_uEltype, n_variables * n_nodes * 2 * 2 * capacity) - surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), - (n_variables, n_nodes, 2 * 2, capacity)) - - cell_ids = fill(typemin(Int), capacity) - - return ElementContainer2D{RealT, uEltype}(inverse_jacobian, node_coordinates, - surface_flux_values, cell_ids, - _node_coordinates, _surface_flux_values) -end - -# Return number of elements -@inline nelements(elements::ElementContainer2D) = length(elements.cell_ids) -# TODO: Taal performance, 1:nelements(elements) vs. Base.OneTo(nelements(elements)) -""" - eachelement(elements::ElementContainer2D) - -Return an iterator over the indices that specify the location in relevant data structures -for the elements in `elements`. -In particular, not the elements themselves are returned. -""" -@inline eachelement(elements::ElementContainer2D) = Base.OneTo(nelements(elements)) -@inline Base.real(elements::ElementContainer2D) = eltype(elements.node_coordinates) - -# Create element container and initialize element data -function init_elements(cell_ids, mesh::TreeMesh2D, - equations::AbstractEquations{2}, - basis, ::Type{RealT}, - ::Type{uEltype}) where {RealT <: Real, uEltype <: Real} - # Initialize container - n_elements = length(cell_ids) - elements = ElementContainer2D{RealT, uEltype}(n_elements, nvariables(equations), - nnodes(basis)) - - init_elements!(elements, cell_ids, mesh, basis) - return elements -end - -function init_elements!(elements, cell_ids, mesh::TreeMesh2D, basis) - nodes = get_nodes(basis) - # Compute the length of the 1D reference interval by integrating - # the function with constant value unity on the corresponding - # element data type (using \circ) - reference_length = integrate(one ∘ eltype, nodes, basis) - # Compute the offset of the midpoint of the 1D reference interval - # (its difference from zero) - reference_offset = (first(nodes) + last(nodes)) / 2 - - # Store cell ids - elements.cell_ids .= cell_ids - - # Calculate inverse Jacobian and node coordinates - for element in eachelement(elements) - # Get cell id - cell_id = cell_ids[element] - - # Get cell length - dx = length_at_cell(mesh.tree, cell_id) - - # Calculate inverse Jacobian - jacobian = dx / reference_length - elements.inverse_jacobian[element] = inv(jacobian) - - # Calculate node coordinates - # Note that the `tree_coordinates` are the midpoints of the cells. - # Hence, we need to add an offset for `nodes` with a midpoint - # different from zero. - for j in eachnode(basis), i in eachnode(basis) - elements.node_coordinates[1, i, j, element] = (mesh.tree.coordinates[1, - cell_id] + - jacobian * - (nodes[i] - reference_offset)) - elements.node_coordinates[2, i, j, element] = (mesh.tree.coordinates[2, - cell_id] + - jacobian * - (nodes[j] - reference_offset)) - end + #! format: noindent + + # Container data structure (structure-of-arrays style) for DG elements + mutable struct ElementContainer2D{RealT <: Real, uEltype <: Real} <: AbstractContainer + inverse_jacobian::Vector{RealT} # [elements] + node_coordinates::Array{RealT, 4} # [orientation, i, j, elements] + surface_flux_values::Array{uEltype, 4} # [variables, i, direction, elements] + cell_ids::Vector{Int} # [elements] + # internal `resize!`able storage + _node_coordinates::Vector{RealT} + _surface_flux_values::Vector{uEltype} end - return elements -end - -# Container data structure (structure-of-arrays style) for DG interfaces -mutable struct InterfaceContainer2D{uEltype <: Real} <: AbstractContainer - u::Array{uEltype, 4} # [leftright, variables, i, interfaces] - neighbor_ids::Array{Int, 2} # [leftright, interfaces] - orientations::Vector{Int} # [interfaces] - # internal `resize!`able storage - _u::Vector{uEltype} - _neighbor_ids::Vector{Int} -end - -nvariables(interfaces::InterfaceContainer2D) = size(interfaces.u, 2) -nnodes(interfaces::InterfaceContainer2D) = size(interfaces.u, 3) -Base.eltype(interfaces::InterfaceContainer2D) = eltype(interfaces.u) - -# See explanation of Base.resize! for the element container -function Base.resize!(interfaces::InterfaceContainer2D, capacity) - n_nodes = nnodes(interfaces) - n_variables = nvariables(interfaces) - @unpack _u, _neighbor_ids, orientations = interfaces - - resize!(_u, 2 * n_variables * n_nodes * capacity) - interfaces.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, capacity)) - - resize!(_neighbor_ids, 2 * capacity) - interfaces.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (2, capacity)) - - resize!(orientations, capacity) - - return nothing -end - -function InterfaceContainer2D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} - nan = convert(uEltype, NaN) - - # Initialize fields with defaults - _u = fill(nan, 2 * n_variables * n_nodes * capacity) - u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, capacity)) - - _neighbor_ids = fill(typemin(Int), 2 * capacity) - neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (2, capacity)) - - orientations = fill(typemin(Int), capacity) - - return InterfaceContainer2D{uEltype}(u, neighbor_ids, orientations, - _u, _neighbor_ids) -end - -# Return number of interfaces -@inline ninterfaces(interfaces::InterfaceContainer2D) = length(interfaces.orientations) - -# Create interface container and initialize interface data in `elements`. -function init_interfaces(cell_ids, mesh::TreeMesh2D, - elements::ElementContainer2D) - # Initialize container - n_interfaces = count_required_interfaces(mesh, cell_ids) - interfaces = InterfaceContainer2D{eltype(elements)}(n_interfaces, - nvariables(elements), - nnodes(elements)) - - # Connect elements with interfaces - init_interfaces!(interfaces, elements, mesh) - return interfaces -end - -# Count the number of interfaces that need to be created -function count_required_interfaces(mesh::TreeMesh2D, cell_ids) - count = 0 - - # Iterate over all cells - for cell_id in cell_ids - for direction in eachdirection(mesh.tree) - # Only count interfaces in positive direction to avoid double counting - if direction % 2 == 1 - continue - end - - # If no neighbor exists, current cell is small or at boundary and thus we need a mortar - if !has_neighbor(mesh.tree, cell_id, direction) - continue - end + nvariables(elements::ElementContainer2D) = size(elements.surface_flux_values, 1) + nnodes(elements::ElementContainer2D) = size(elements.node_coordinates, 2) + Base.eltype(elements::ElementContainer2D) = eltype(elements.surface_flux_values) + + # Only one-dimensional `Array`s are `resize!`able in Julia. + # Hence, we use `Vector`s as internal storage and `resize!` + # them whenever needed. Then, we reuse the same memory by + # `unsafe_wrap`ping multi-dimensional `Array`s around the + # internal storage. + function Base.resize!(elements::ElementContainer2D, capacity) + n_nodes = nnodes(elements) + n_variables = nvariables(elements) + @unpack _node_coordinates, _surface_flux_values, + inverse_jacobian, cell_ids = elements + + resize!(inverse_jacobian, capacity) + + resize!(_node_coordinates, 2 * n_nodes * n_nodes * capacity) + elements.node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (2, n_nodes, n_nodes, capacity) + ) + + resize!(_surface_flux_values, n_variables * n_nodes * 2 * 2 * capacity) + elements.surface_flux_values = unsafe_wrap( + Array, pointer(_surface_flux_values), + (n_variables, n_nodes, 2 * 2, capacity) + ) + + resize!(cell_ids, capacity) - # Skip if neighbor has children - neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] - if has_children(mesh.tree, neighbor_cell_id) - continue - end + return nothing + end - # Skip if neighbor is on different rank -> create MPI interface instead - if mpi_isparallel() && !is_own_cell(mesh.tree, neighbor_cell_id) - continue - end + function ElementContainer2D{RealT, uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where { + RealT <: Real, + uEltype <: Real, + } + nan_RealT = convert(RealT, NaN) + nan_uEltype = convert(uEltype, NaN) + + # Initialize fields with defaults + inverse_jacobian = fill(nan_RealT, capacity) + + _node_coordinates = fill(nan_RealT, 2 * n_nodes * n_nodes * capacity) + node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (2, n_nodes, n_nodes, capacity) + ) + + _surface_flux_values = fill(nan_uEltype, n_variables * n_nodes * 2 * 2 * capacity) + surface_flux_values = unsafe_wrap( + Array, pointer(_surface_flux_values), + (n_variables, n_nodes, 2 * 2, capacity) + ) + + cell_ids = fill(typemin(Int), capacity) + + return ElementContainer2D{RealT, uEltype}( + inverse_jacobian, node_coordinates, + surface_flux_values, cell_ids, + _node_coordinates, _surface_flux_values + ) + end - count += 1 - end + # Return number of elements + @inline nelements(elements::ElementContainer2D) = length(elements.cell_ids) + # TODO: Taal performance, 1:nelements(elements) vs. Base.OneTo(nelements(elements)) + """ + eachelement(elements::ElementContainer2D) + + Return an iterator over the indices that specify the location in relevant data structures + for the elements in `elements`. + In particular, not the elements themselves are returned. + """ + @inline eachelement(elements::ElementContainer2D) = Base.OneTo(nelements(elements)) + @inline Base.real(elements::ElementContainer2D) = eltype(elements.node_coordinates) + + # Create element container and initialize element data + function init_elements( + cell_ids, mesh::TreeMesh2D, + equations::AbstractEquations{2}, + basis, ::Type{RealT}, + ::Type{uEltype} + ) where {RealT <: Real, uEltype <: Real} + # Initialize container + n_elements = length(cell_ids) + elements = ElementContainer2D{RealT, uEltype}( + n_elements, nvariables(equations), + nnodes(basis) + ) + + init_elements!(elements, cell_ids, mesh, basis) + return elements end - return count -end + function init_elements!(elements, cell_ids, mesh::TreeMesh2D, basis) + nodes = get_nodes(basis) + # Compute the length of the 1D reference interval by integrating + # the function with constant value unity on the corresponding + # element data type (using \circ) + reference_length = integrate(one ∘ eltype, nodes, basis) + # Compute the offset of the midpoint of the 1D reference interval + # (its difference from zero) + reference_offset = (first(nodes) + last(nodes)) / 2 -# Initialize connectivity between elements and interfaces -function init_interfaces!(interfaces, elements, mesh::TreeMesh2D) - # Exit early if there are no interfaces to initialize - if ninterfaces(interfaces) == 0 - return nothing + # Store cell ids + elements.cell_ids .= cell_ids + + # Calculate inverse Jacobian and node coordinates + for element in eachelement(elements) + # Get cell id + cell_id = cell_ids[element] + + # Get cell length + dx = length_at_cell(mesh.tree, cell_id) + + # Calculate inverse Jacobian + jacobian = dx / reference_length + elements.inverse_jacobian[element] = inv(jacobian) + + # Calculate node coordinates + # Note that the `tree_coordinates` are the midpoints of the cells. + # Hence, we need to add an offset for `nodes` with a midpoint + # different from zero. + for j in eachnode(basis), i in eachnode(basis) + elements.node_coordinates[1, i, j, element] = ( + mesh.tree.coordinates[ + 1, + cell_id, + ] + + jacobian * + (nodes[i] - reference_offset) + ) + elements.node_coordinates[2, i, j, element] = ( + mesh.tree.coordinates[ + 2, + cell_id, + ] + + jacobian * + (nodes[j] - reference_offset) + ) + end + end + + return elements end - # Construct cell -> element mapping for easier algorithm implementation - tree = mesh.tree - c2e = zeros(Int, length(tree)) - for element in eachelement(elements) - c2e[elements.cell_ids[element]] = element + # Container data structure (structure-of-arrays style) for DG interfaces + mutable struct InterfaceContainer2D{uEltype <: Real} <: AbstractContainer + u::Array{uEltype, 4} # [leftright, variables, i, interfaces] + neighbor_ids::Array{Int, 2} # [leftright, interfaces] + orientations::Vector{Int} # [interfaces] + # internal `resize!`able storage + _u::Vector{uEltype} + _neighbor_ids::Vector{Int} end - # Reset interface count - count = 0 + nvariables(interfaces::InterfaceContainer2D) = size(interfaces.u, 2) + nnodes(interfaces::InterfaceContainer2D) = size(interfaces.u, 3) + Base.eltype(interfaces::InterfaceContainer2D) = eltype(interfaces.u) - # Iterate over all elements to find neighbors and to connect via interfaces - for element in eachelement(elements) - # Get cell id - cell_id = elements.cell_ids[element] + # See explanation of Base.resize! for the element container + function Base.resize!(interfaces::InterfaceContainer2D, capacity) + n_nodes = nnodes(interfaces) + n_variables = nvariables(interfaces) + @unpack _u, _neighbor_ids, orientations = interfaces - # Loop over directions - for direction in eachdirection(mesh.tree) - # Only create interfaces in positive direction - if direction % 2 == 1 - continue - end + resize!(_u, 2 * n_variables * n_nodes * capacity) + interfaces.u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, capacity) + ) - # If no neighbor exists, current cell is small and thus we need a mortar - if !has_neighbor(mesh.tree, cell_id, direction) - continue - end + resize!(_neighbor_ids, 2 * capacity) + interfaces.neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (2, capacity) + ) - # Skip if neighbor has children - neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] - if has_children(mesh.tree, neighbor_cell_id) - continue - end + resize!(orientations, capacity) - # Skip if neighbor is on different rank -> create MPI interface instead - if mpi_isparallel() && !is_own_cell(mesh.tree, neighbor_cell_id) - continue - end + return nothing + end - # Create interface between elements (1 -> "left" of interface, 2 -> "right" of interface) - count += 1 - interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id] - interfaces.neighbor_ids[1, count] = element + function InterfaceContainer2D{uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where {uEltype <: Real} + nan = convert(uEltype, NaN) + + # Initialize fields with defaults + _u = fill(nan, 2 * n_variables * n_nodes * capacity) + u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, capacity) + ) + + _neighbor_ids = fill(typemin(Int), 2 * capacity) + neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (2, capacity) + ) + + orientations = fill(typemin(Int), capacity) + + return InterfaceContainer2D{uEltype}( + u, neighbor_ids, orientations, + _u, _neighbor_ids + ) + end - # Set orientation (x -> 1, y -> 2) - interfaces.orientations[count] = div(direction, 2) - end + # Return number of interfaces + @inline ninterfaces(interfaces::InterfaceContainer2D) = length(interfaces.orientations) + + # Create interface container and initialize interface data in `elements`. + function init_interfaces( + cell_ids, mesh::TreeMesh2D, + elements::ElementContainer2D + ) + # Initialize container + n_interfaces = count_required_interfaces(mesh, cell_ids) + interfaces = InterfaceContainer2D{eltype(elements)}( + n_interfaces, + nvariables(elements), + nnodes(elements) + ) + + # Connect elements with interfaces + init_interfaces!(interfaces, elements, mesh) + return interfaces end - @assert count==ninterfaces(interfaces) ("Actual interface count ($count) does not match "* - "expectations $(ninterfaces(interfaces))") -end - -# Container data structure (structure-of-arrays style) for DG boundaries -mutable struct BoundaryContainer2D{RealT <: Real, uEltype <: Real} <: AbstractContainer - u::Array{uEltype, 4} # [leftright, variables, i, boundaries] - neighbor_ids::Vector{Int} # [boundaries] - orientations::Vector{Int} # [boundaries] - neighbor_sides::Vector{Int} # [boundaries] - node_coordinates::Array{RealT, 3} # [orientation, i, elements] - n_boundaries_per_direction::SVector{4, Int} # [direction] - # internal `resize!`able storage - _u::Vector{uEltype} - _node_coordinates::Vector{RealT} -end - -nvariables(boundaries::BoundaryContainer2D) = size(boundaries.u, 2) -nnodes(boundaries::BoundaryContainer2D) = size(boundaries.u, 3) -Base.eltype(boundaries::BoundaryContainer2D) = eltype(boundaries.u) - -# See explanation of Base.resize! for the element container -function Base.resize!(boundaries::BoundaryContainer2D, capacity) - n_nodes = nnodes(boundaries) - n_variables = nvariables(boundaries) - @unpack _u, _node_coordinates, - neighbor_ids, orientations, neighbor_sides = boundaries - - resize!(_u, 2 * n_variables * n_nodes * capacity) - boundaries.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, capacity)) - - resize!(_node_coordinates, 2 * n_nodes * capacity) - boundaries.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (2, n_nodes, capacity)) - - resize!(neighbor_ids, capacity) - - resize!(orientations, capacity) - - resize!(neighbor_sides, capacity) - - return nothing -end - -function BoundaryContainer2D{RealT, uEltype}(capacity::Integer, n_variables, - n_nodes) where {RealT <: Real, - uEltype <: Real} - nan_RealT = convert(RealT, NaN) - nan_uEltype = convert(uEltype, NaN) - - # Initialize fields with defaults - _u = fill(nan_uEltype, 2 * n_variables * n_nodes * capacity) - u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, capacity)) - - neighbor_ids = fill(typemin(Int), capacity) - - orientations = fill(typemin(Int), capacity) - - neighbor_sides = fill(typemin(Int), capacity) - - _node_coordinates = fill(nan_RealT, 2 * n_nodes * capacity) - node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (2, n_nodes, capacity)) - - n_boundaries_per_direction = SVector(0, 0, 0, 0) - - return BoundaryContainer2D{RealT, uEltype}(u, neighbor_ids, orientations, - neighbor_sides, - node_coordinates, - n_boundaries_per_direction, - _u, _node_coordinates) -end - -# Return number of boundaries -@inline nboundaries(boundaries::BoundaryContainer2D) = length(boundaries.orientations) - -# Create boundaries container and initialize boundary data in `elements`. -function init_boundaries(cell_ids, mesh::TreeMesh2D, - elements::ElementContainer2D) - # Initialize container - n_boundaries = count_required_boundaries(mesh, cell_ids) - boundaries = BoundaryContainer2D{real(elements), eltype(elements)}(n_boundaries, - nvariables(elements), - nnodes(elements)) - - # Connect elements with boundaries - init_boundaries!(boundaries, elements, mesh) - return boundaries -end - -# Count the number of boundaries that need to be created -function count_required_boundaries(mesh::TreeMesh2D, cell_ids) - count = 0 - - # Iterate over all cells - for cell_id in cell_ids - for direction in eachdirection(mesh.tree) - # If neighbor exists, current cell is not at a boundary - if has_neighbor(mesh.tree, cell_id, direction) - continue - end + # Count the number of interfaces that need to be created + function count_required_interfaces(mesh::TreeMesh2D, cell_ids) + count = 0 - # If coarse neighbor exists, current cell is not at a boundary - if has_coarse_neighbor(mesh.tree, cell_id, direction) - continue - end + # Iterate over all cells + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # Only count interfaces in positive direction to avoid double counting + if direction % 2 == 1 + continue + end - # No neighbor exists in this direction -> must be a boundary - count += 1 - end - end + # If no neighbor exists, current cell is small or at boundary and thus we need a mortar + if !has_neighbor(mesh.tree, cell_id, direction) + continue + end - return count -end + # Skip if neighbor has children + neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] + if has_children(mesh.tree, neighbor_cell_id) + continue + end -# Initialize connectivity between elements and boundaries -function init_boundaries!(boundaries, elements, mesh::TreeMesh2D) - # Exit early if there are no boundaries to initialize - if nboundaries(boundaries) == 0 - # In this case n_boundaries_per_direction still needs to be reset! - boundaries.n_boundaries_per_direction = SVector(0, 0, 0, 0) - return nothing + # Skip if neighbor is on different rank -> create MPI interface instead + if mpi_isparallel() && !is_own_cell(mesh.tree, neighbor_cell_id) + continue + end + + count += 1 + end + end + + return count end - # Reset boundaries count - count = 0 + # Initialize connectivity between elements and interfaces + function init_interfaces!(interfaces, elements, mesh::TreeMesh2D) + # Exit early if there are no interfaces to initialize + if ninterfaces(interfaces) == 0 + return nothing + end - # Initialize boundary counts - counts_per_direction = MVector(0, 0, 0, 0) + # Construct cell -> element mapping for easier algorithm implementation + tree = mesh.tree + c2e = zeros(Int, length(tree)) + for element in eachelement(elements) + c2e[elements.cell_ids[element]] = element + end + + # Reset interface count + count = 0 - # OBS! Iterate over directions first, then over elements, and count boundaries in each direction - # Rationale: This way the boundaries are internally sorted by the directions -x, +x, -y etc., - # obviating the need to store the boundary condition to be applied explicitly. - # Loop over directions - for direction in eachdirection(mesh.tree) - # Iterate over all elements to find missing neighbors and to connect to boundaries + # Iterate over all elements to find neighbors and to connect via interfaces for element in eachelement(elements) # Get cell id cell_id = elements.cell_ids[element] - # If neighbor exists, current cell is not at a boundary - if has_neighbor(mesh.tree, cell_id, direction) - continue - end - - # If coarse neighbor exists, current cell is not at a boundary - if has_coarse_neighbor(mesh.tree, cell_id, direction) - continue - end + # Loop over directions + for direction in eachdirection(mesh.tree) + # Only create interfaces in positive direction + if direction % 2 == 1 + continue + end - # Create boundary - count += 1 - counts_per_direction[direction] += 1 + # If no neighbor exists, current cell is small and thus we need a mortar + if !has_neighbor(mesh.tree, cell_id, direction) + continue + end - # Set neighbor element id - boundaries.neighbor_ids[count] = element + # Skip if neighbor has children + neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] + if has_children(mesh.tree, neighbor_cell_id) + continue + end - # Set neighbor side, which denotes the direction (1 -> negative, 2 -> positive) of the element - if iseven(direction) - boundaries.neighbor_sides[count] = 1 - else - boundaries.neighbor_sides[count] = 2 - end + # Skip if neighbor is on different rank -> create MPI interface instead + if mpi_isparallel() && !is_own_cell(mesh.tree, neighbor_cell_id) + continue + end - # Set orientation (x -> 1, y -> 2) - if direction in (1, 2) - boundaries.orientations[count] = 1 - else - boundaries.orientations[count] = 2 - end + # Create interface between elements (1 -> "left" of interface, 2 -> "right" of interface) + count += 1 + interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id] + interfaces.neighbor_ids[1, count] = element - # Store node coordinates - enc = elements.node_coordinates - if direction == 1 # -x direction - boundaries.node_coordinates[:, :, count] .= enc[:, 1, :, element] - elseif direction == 2 # +x direction - boundaries.node_coordinates[:, :, count] .= enc[:, end, :, element] - elseif direction == 3 # -y direction - boundaries.node_coordinates[:, :, count] .= enc[:, :, 1, element] - elseif direction == 4 # +y direction - boundaries.node_coordinates[:, :, count] .= enc[:, :, end, element] - else - error("should not happen") + # Set orientation (x -> 1, y -> 2) + interfaces.orientations[count] = div(direction, 2) end end + + @assert count == ninterfaces(interfaces) ( + "Actual interface count ($count) does not match " * + "expectations $(ninterfaces(interfaces))" + ) end - @assert count==nboundaries(boundaries) ("Actual boundaries count ($count) does not match "* - "expectations $(nboundaries(boundaries))") - @assert sum(counts_per_direction) == count - - boundaries.n_boundaries_per_direction = SVector(counts_per_direction) - - return boundaries.n_boundaries_per_direction -end - -# Container data structure (structure-of-arrays style) for DG L2 mortars -# Positions/directions for orientations = 1, large_sides = 2: -# mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar -# | | -# upper = 2 | | -# | | -# | 3 = large side -# | | -# lower = 1 | | -# | | -mutable struct L2MortarContainer2D{uEltype <: Real} <: AbstractContainer - u_upper::Array{uEltype, 4} # [leftright, variables, i, mortars] - u_lower::Array{uEltype, 4} # [leftright, variables, i, mortars] - neighbor_ids::Array{Int, 2} # [position, mortars] - # Large sides: left -> 1, right -> 2 - large_sides::Vector{Int} # [mortars] - orientations::Vector{Int} # [mortars] - # internal `resize!`able storage - _u_upper::Vector{uEltype} - _u_lower::Vector{uEltype} - _neighbor_ids::Vector{Int} -end - -nvariables(mortars::L2MortarContainer2D) = size(mortars.u_upper, 2) -nnodes(mortars::L2MortarContainer2D) = size(mortars.u_upper, 3) -Base.eltype(mortars::L2MortarContainer2D) = eltype(mortars.u_upper) - -# See explanation of Base.resize! for the element container -function Base.resize!(mortars::L2MortarContainer2D, capacity) - n_nodes = nnodes(mortars) - n_variables = nvariables(mortars) - @unpack _u_upper, _u_lower, _neighbor_ids, - large_sides, orientations = mortars - - resize!(_u_upper, 2 * n_variables * n_nodes * capacity) - mortars.u_upper = unsafe_wrap(Array, pointer(_u_upper), - (2, n_variables, n_nodes, capacity)) - - resize!(_u_lower, 2 * n_variables * n_nodes * capacity) - mortars.u_lower = unsafe_wrap(Array, pointer(_u_lower), - (2, n_variables, n_nodes, capacity)) - - resize!(_neighbor_ids, 3 * capacity) - mortars.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (3, capacity)) - - resize!(large_sides, capacity) - - resize!(orientations, capacity) - - return nothing -end - -function L2MortarContainer2D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} - nan = convert(uEltype, NaN) - - # Initialize fields with defaults - _u_upper = fill(nan, 2 * n_variables * n_nodes * capacity) - u_upper = unsafe_wrap(Array, pointer(_u_upper), - (2, n_variables, n_nodes, capacity)) - - _u_lower = fill(nan, 2 * n_variables * n_nodes * capacity) - u_lower = unsafe_wrap(Array, pointer(_u_lower), - (2, n_variables, n_nodes, capacity)) - - _neighbor_ids = fill(typemin(Int), 3 * capacity) - neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (3, capacity)) - - large_sides = fill(typemin(Int), capacity) - - orientations = fill(typemin(Int), capacity) - - return L2MortarContainer2D{uEltype}(u_upper, u_lower, neighbor_ids, large_sides, - orientations, - _u_upper, _u_lower, _neighbor_ids) -end - -# Return number of L2 mortars -@inline nmortars(l2mortars::L2MortarContainer2D) = length(l2mortars.orientations) - -# Allow printing container contents -function Base.show(io::IO, ::MIME"text/plain", c::L2MortarContainer2D) - @nospecialize c # reduce precompilation time - - println(io, '*'^20) - for idx in CartesianIndices(c.u_upper) - println(io, "c.u_upper[$idx] = $(c.u_upper[idx])") + # Container data structure (structure-of-arrays style) for DG boundaries + mutable struct BoundaryContainer2D{RealT <: Real, uEltype <: Real} <: AbstractContainer + u::Array{uEltype, 4} # [leftright, variables, i, boundaries] + neighbor_ids::Vector{Int} # [boundaries] + orientations::Vector{Int} # [boundaries] + neighbor_sides::Vector{Int} # [boundaries] + node_coordinates::Array{RealT, 3} # [orientation, i, elements] + n_boundaries_per_direction::SVector{4, Int} # [direction] + # internal `resize!`able storage + _u::Vector{uEltype} + _node_coordinates::Vector{RealT} end - for idx in CartesianIndices(c.u_lower) - println(io, "c.u_lower[$idx] = $(c.u_lower[idx])") + + nvariables(boundaries::BoundaryContainer2D) = size(boundaries.u, 2) + nnodes(boundaries::BoundaryContainer2D) = size(boundaries.u, 3) + Base.eltype(boundaries::BoundaryContainer2D) = eltype(boundaries.u) + + # See explanation of Base.resize! for the element container + function Base.resize!(boundaries::BoundaryContainer2D, capacity) + n_nodes = nnodes(boundaries) + n_variables = nvariables(boundaries) + @unpack _u, _node_coordinates, + neighbor_ids, orientations, neighbor_sides = boundaries + + resize!(_u, 2 * n_variables * n_nodes * capacity) + boundaries.u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, capacity) + ) + + resize!(_node_coordinates, 2 * n_nodes * capacity) + boundaries.node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (2, n_nodes, capacity) + ) + + resize!(neighbor_ids, capacity) + + resize!(orientations, capacity) + + resize!(neighbor_sides, capacity) + + return nothing end - println(io, "transpose(c.neighbor_ids) = $(transpose(c.neighbor_ids))") - println(io, "c.large_sides = $(c.large_sides)") - println(io, "c.orientations = $(c.orientations)") - print(io, '*'^20) -end - -# Create mortar container and initialize mortar data in `elements`. -function init_mortars(cell_ids, mesh::TreeMesh2D, - elements::ElementContainer2D, - ::LobattoLegendreMortarL2) - # Initialize containers - n_mortars = count_required_mortars(mesh, cell_ids) - mortars = L2MortarContainer2D{eltype(elements)}(n_mortars, nvariables(elements), - nnodes(elements)) - - # Connect elements with mortars - init_mortars!(mortars, elements, mesh) - return mortars -end - -# Count the number of mortars that need to be created -function count_required_mortars(mesh::TreeMesh2D, cell_ids) - count = 0 - - # Iterate over all cells and count mortars from perspective of coarse cells - for cell_id in cell_ids - for direction in eachdirection(mesh.tree) - # If no neighbor exists, cell is small with large neighbor or at boundary -> do nothing - if !has_neighbor(mesh.tree, cell_id, direction) - continue - end - # If neighbor has no children, this is a conforming interface -> do nothing - neighbor_id = mesh.tree.neighbor_ids[direction, cell_id] - if !has_children(mesh.tree, neighbor_id) - continue - end + function BoundaryContainer2D{RealT, uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where { + RealT <: Real, + uEltype <: Real, + } + nan_RealT = convert(RealT, NaN) + nan_uEltype = convert(uEltype, NaN) + + # Initialize fields with defaults + _u = fill(nan_uEltype, 2 * n_variables * n_nodes * capacity) + u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, capacity) + ) + + neighbor_ids = fill(typemin(Int), capacity) + + orientations = fill(typemin(Int), capacity) + + neighbor_sides = fill(typemin(Int), capacity) + + _node_coordinates = fill(nan_RealT, 2 * n_nodes * capacity) + node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (2, n_nodes, capacity) + ) + + n_boundaries_per_direction = SVector(0, 0, 0, 0) + + return BoundaryContainer2D{RealT, uEltype}( + u, neighbor_ids, orientations, + neighbor_sides, + node_coordinates, + n_boundaries_per_direction, + _u, _node_coordinates + ) + end - # Skip if one of the small cells is on different rank -> create mpi mortar instead - # (the coarse cell is always on the local rank) - if mpi_isparallel() - if direction == 1 # small cells left, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[2, neighbor_id] - upper_cell_id = mesh.tree.child_ids[4, neighbor_id] - elseif direction == 2 # small cells right, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[1, neighbor_id] - upper_cell_id = mesh.tree.child_ids[3, neighbor_id] - elseif direction == 3 # small cells left, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[3, neighbor_id] - upper_cell_id = mesh.tree.child_ids[4, neighbor_id] - else # direction == 4, small cells right, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[1, neighbor_id] - upper_cell_id = mesh.tree.child_ids[2, neighbor_id] + # Return number of boundaries + @inline nboundaries(boundaries::BoundaryContainer2D) = length(boundaries.orientations) + + # Create boundaries container and initialize boundary data in `elements`. + function init_boundaries( + cell_ids, mesh::TreeMesh2D, + elements::ElementContainer2D + ) + # Initialize container + n_boundaries = count_required_boundaries(mesh, cell_ids) + boundaries = BoundaryContainer2D{real(elements), eltype(elements)}( + n_boundaries, + nvariables(elements), + nnodes(elements) + ) + + # Connect elements with boundaries + init_boundaries!(boundaries, elements, mesh) + return boundaries + end + + # Count the number of boundaries that need to be created + function count_required_boundaries(mesh::TreeMesh2D, cell_ids) + count = 0 + + # Iterate over all cells + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # If neighbor exists, current cell is not at a boundary + if has_neighbor(mesh.tree, cell_id, direction) + continue end - small_cell_ids = (lower_cell_id, upper_cell_id) - if any(cell -> !is_own_cell(mesh.tree, cell), small_cell_ids) + + # If coarse neighbor exists, current cell is not at a boundary + if has_coarse_neighbor(mesh.tree, cell_id, direction) continue end - end - count += 1 + # No neighbor exists in this direction -> must be a boundary + count += 1 + end end - end - - return count -end -# Initialize connectivity between elements and mortars -function init_mortars!(mortars, elements, mesh::TreeMesh2D) - # Exit early if there are no mortars to initialize - if nmortars(mortars) == 0 - return nothing + return count end - # Construct cell -> element mapping for easier algorithm implementation - tree = mesh.tree - c2e = zeros(Int, length(tree)) - for element in eachelement(elements) - c2e[elements.cell_ids[element]] = element - end + # Initialize connectivity between elements and boundaries + function init_boundaries!(boundaries, elements, mesh::TreeMesh2D) + # Exit early if there are no boundaries to initialize + if nboundaries(boundaries) == 0 + # In this case n_boundaries_per_direction still needs to be reset! + boundaries.n_boundaries_per_direction = SVector(0, 0, 0, 0) + return nothing + end - # Reset interface count - count = 0 + # Reset boundaries count + count = 0 - # Iterate over all elements to find neighbors and to connect via interfaces - for element in eachelement(elements) - # Get cell id - cell_id = elements.cell_ids[element] + # Initialize boundary counts + counts_per_direction = MVector(0, 0, 0, 0) + # OBS! Iterate over directions first, then over elements, and count boundaries in each direction + # Rationale: This way the boundaries are internally sorted by the directions -x, +x, -y etc., + # obviating the need to store the boundary condition to be applied explicitly. + # Loop over directions for direction in eachdirection(mesh.tree) - # If no neighbor exists, cell is small with large neighbor -> do nothing - if !has_neighbor(mesh.tree, cell_id, direction) - continue - end + # Iterate over all elements to find missing neighbors and to connect to boundaries + for element in eachelement(elements) + # Get cell id + cell_id = elements.cell_ids[element] - # If neighbor has no children, this is a conforming interface -> do nothing - neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] - if !has_children(mesh.tree, neighbor_cell_id) - continue - end - - # Skip if one of the small cells is on different rank -> create mpi mortar instead - # (the coarse cell is always on the local rank) - if mpi_isparallel() - if direction == 1 # small cells left, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[2, neighbor_cell_id] - upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id] - elseif direction == 2 # small cells right, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id] - upper_cell_id = mesh.tree.child_ids[3, neighbor_cell_id] - elseif direction == 3 # small cells left, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[3, neighbor_cell_id] - upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id] - else # direction == 4, small cells right, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id] - upper_cell_id = mesh.tree.child_ids[2, neighbor_cell_id] + # If neighbor exists, current cell is not at a boundary + if has_neighbor(mesh.tree, cell_id, direction) + continue end - small_cell_ids = (lower_cell_id, upper_cell_id) - if any(cell -> !is_own_cell(mesh.tree, cell), small_cell_ids) + + # If coarse neighbor exists, current cell is not at a boundary + if has_coarse_neighbor(mesh.tree, cell_id, direction) continue end - end - # Create mortar between elements: - # 1 -> small element in negative coordinate direction - # 2 -> small element in positive coordinate direction - # 3 -> large element - count += 1 - mortars.neighbor_ids[3, count] = element - if direction == 1 - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[2, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[4, - neighbor_cell_id]] - elseif direction == 2 - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[1, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[3, - neighbor_cell_id]] - elseif direction == 3 - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[3, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[4, - neighbor_cell_id]] - elseif direction == 4 - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[1, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[2, - neighbor_cell_id]] - else - error("should not happen") - end + # Create boundary + count += 1 + counts_per_direction[direction] += 1 - # Set large side, which denotes the direction (1 -> negative, 2 -> positive) of the large side - if iseven(direction) - mortars.large_sides[count] = 1 - else - mortars.large_sides[count] = 2 - end + # Set neighbor element id + boundaries.neighbor_ids[count] = element + + # Set neighbor side, which denotes the direction (1 -> negative, 2 -> positive) of the element + if iseven(direction) + boundaries.neighbor_sides[count] = 1 + else + boundaries.neighbor_sides[count] = 2 + end - # Set orientation (x -> 1, y -> 2) - if direction in (1, 2) - mortars.orientations[count] = 1 - else - mortars.orientations[count] = 2 + # Set orientation (x -> 1, y -> 2) + if direction in (1, 2) + boundaries.orientations[count] = 1 + else + boundaries.orientations[count] = 2 + end + + # Store node coordinates + enc = elements.node_coordinates + if direction == 1 # -x direction + boundaries.node_coordinates[:, :, count] .= enc[:, 1, :, element] + elseif direction == 2 # +x direction + boundaries.node_coordinates[:, :, count] .= enc[:, end, :, element] + elseif direction == 3 # -y direction + boundaries.node_coordinates[:, :, count] .= enc[:, :, 1, element] + elseif direction == 4 # +y direction + boundaries.node_coordinates[:, :, count] .= enc[:, :, end, element] + else + error("should not happen") + end end end + + @assert count == nboundaries(boundaries) ( + "Actual boundaries count ($count) does not match " * + "expectations $(nboundaries(boundaries))" + ) + @assert sum(counts_per_direction) == count + + boundaries.n_boundaries_per_direction = SVector(counts_per_direction) + + return boundaries.n_boundaries_per_direction end - @assert count==nmortars(mortars) ("Actual mortar count ($count) does not match "* - "expectations $(nmortars(mortars))") -end - -# Container data structure (structure-of-arrays style) for DG MPI interfaces -mutable struct MPIInterfaceContainer2D{uEltype <: Real} <: AbstractContainer - u::Array{uEltype, 4} # [leftright, variables, i, interfaces] - # Note: `local_neighbor_ids` stores the MPI-local neighbors, but with globally valid index! - local_neighbor_ids::Vector{Int} # [interfaces] - orientations::Vector{Int} # [interfaces] - remote_sides::Vector{Int} # [interfaces] - # internal `resize!`able storage - _u::Vector{uEltype} -end - -nvariables(mpi_interfaces::MPIInterfaceContainer2D) = size(mpi_interfaces.u, 2) -nnodes(mpi_interfaces::MPIInterfaceContainer2D) = size(mpi_interfaces.u, 3) -Base.eltype(mpi_interfaces::MPIInterfaceContainer2D) = eltype(mpi_interfaces.u) - -# See explanation of Base.resize! for the element container -function Base.resize!(mpi_interfaces::MPIInterfaceContainer2D, capacity) - n_nodes = nnodes(mpi_interfaces) - n_variables = nvariables(mpi_interfaces) - @unpack _u, local_neighbor_ids, orientations, remote_sides = mpi_interfaces - - resize!(_u, 2 * n_variables * n_nodes * capacity) - mpi_interfaces.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, capacity)) - - resize!(local_neighbor_ids, capacity) - - resize!(orientations, capacity) - - resize!(remote_sides, capacity) - - return nothing -end - -function MPIInterfaceContainer2D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} - nan = convert(uEltype, NaN) - - # Initialize fields with defaults - _u = fill(nan, 2 * n_variables * n_nodes * capacity) - u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, capacity)) - - local_neighbor_ids = fill(typemin(Int), capacity) - - orientations = fill(typemin(Int), capacity) - - remote_sides = fill(typemin(Int), capacity) - - return MPIInterfaceContainer2D{uEltype}(u, local_neighbor_ids, orientations, - remote_sides, - _u) -end - -# TODO: Taal, rename to ninterfaces? -# Return number of interfaces -function nmpiinterfaces(mpi_interfaces::MPIInterfaceContainer2D) - length(mpi_interfaces.orientations) -end - -# Create MPI interface container and initialize MPI interface data in `elements`. -function init_mpi_interfaces(cell_ids, mesh::TreeMesh2D, - elements::ElementContainer2D) - # Initialize container - n_mpi_interfaces = count_required_mpi_interfaces(mesh, cell_ids) - mpi_interfaces = MPIInterfaceContainer2D{eltype(elements)}(n_mpi_interfaces, - nvariables(elements), - nnodes(elements)) - - # Connect elements with interfaces - init_mpi_interfaces!(mpi_interfaces, elements, mesh) - return mpi_interfaces -end - -# Count the number of MPI interfaces that need to be created -function count_required_mpi_interfaces(mesh::TreeMesh2D, cell_ids) - # No MPI interfaces needed if MPI is not used - if !mpi_isparallel() - return 0 + # Container data structure (structure-of-arrays style) for DG L2 mortars + # Positions/directions for orientations = 1, large_sides = 2: + # mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar + # | | + # upper = 2 | | + # | | + # | 3 = large side + # | | + # lower = 1 | | + # | | + mutable struct L2MortarContainer2D{uEltype <: Real} <: AbstractContainer + u_upper::Array{uEltype, 4} # [leftright, variables, i, mortars] + u_lower::Array{uEltype, 4} # [leftright, variables, i, mortars] + neighbor_ids::Array{Int, 2} # [position, mortars] + # Large sides: left -> 1, right -> 2 + large_sides::Vector{Int} # [mortars] + orientations::Vector{Int} # [mortars] + # internal `resize!`able storage + _u_upper::Vector{uEltype} + _u_lower::Vector{uEltype} + _neighbor_ids::Vector{Int} end - count = 0 + nvariables(mortars::L2MortarContainer2D) = size(mortars.u_upper, 2) + nnodes(mortars::L2MortarContainer2D) = size(mortars.u_upper, 3) + Base.eltype(mortars::L2MortarContainer2D) = eltype(mortars.u_upper) - # Iterate over all cells - for cell_id in cell_ids - for direction in eachdirection(mesh.tree) - # If no neighbor exists, current cell is small or at boundary and thus we need a mortar - if !has_neighbor(mesh.tree, cell_id, direction) - continue - end + # See explanation of Base.resize! for the element container + function Base.resize!(mortars::L2MortarContainer2D, capacity) + n_nodes = nnodes(mortars) + n_variables = nvariables(mortars) + @unpack _u_upper, _u_lower, _neighbor_ids, + large_sides, orientations = mortars - # Skip if neighbor has children - neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] - if has_children(mesh.tree, neighbor_cell_id) - continue - end + resize!(_u_upper, 2 * n_variables * n_nodes * capacity) + mortars.u_upper = unsafe_wrap( + Array, pointer(_u_upper), + (2, n_variables, n_nodes, capacity) + ) - # Skip if neighbor is on this rank -> create regular interface instead - if is_own_cell(mesh.tree, neighbor_cell_id) - continue - end + resize!(_u_lower, 2 * n_variables * n_nodes * capacity) + mortars.u_lower = unsafe_wrap( + Array, pointer(_u_lower), + (2, n_variables, n_nodes, capacity) + ) - count += 1 - end - end + resize!(_neighbor_ids, 3 * capacity) + mortars.neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (3, capacity) + ) - return count -end + resize!(large_sides, capacity) + + resize!(orientations, capacity) -# Initialize connectivity between elements and interfaces -function init_mpi_interfaces!(mpi_interfaces, elements, mesh::TreeMesh2D) - # Exit early if there are no MPI interfaces to initialize - if nmpiinterfaces(mpi_interfaces) == 0 return nothing end - # Reset interface count - count = 0 + function L2MortarContainer2D{uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where {uEltype <: Real} + nan = convert(uEltype, NaN) + + # Initialize fields with defaults + _u_upper = fill(nan, 2 * n_variables * n_nodes * capacity) + u_upper = unsafe_wrap( + Array, pointer(_u_upper), + (2, n_variables, n_nodes, capacity) + ) + + _u_lower = fill(nan, 2 * n_variables * n_nodes * capacity) + u_lower = unsafe_wrap( + Array, pointer(_u_lower), + (2, n_variables, n_nodes, capacity) + ) + + _neighbor_ids = fill(typemin(Int), 3 * capacity) + neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (3, capacity) + ) + + large_sides = fill(typemin(Int), capacity) + + orientations = fill(typemin(Int), capacity) + + return L2MortarContainer2D{uEltype}( + u_upper, u_lower, neighbor_ids, large_sides, + orientations, + _u_upper, _u_lower, _neighbor_ids + ) + end - # Iterate over all elements to find neighbors and to connect via mpi_interfaces - for element in eachelement(elements) - # Get cell id - cell_id = elements.cell_ids[element] + # Return number of L2 mortars + @inline nmortars(l2mortars::L2MortarContainer2D) = length(l2mortars.orientations) - # Loop over directions - for direction in eachdirection(mesh.tree) - # If no neighbor exists, current cell is small and thus we need a mortar - if !has_neighbor(mesh.tree, cell_id, direction) - continue - end + # Allow printing container contents + function Base.show(io::IO, ::MIME"text/plain", c::L2MortarContainer2D) + @nospecialize c # reduce precompilation time - # Skip if neighbor has children - neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] - if has_children(mesh.tree, neighbor_cell_id) - continue - end + println(io, '*'^20) + for idx in CartesianIndices(c.u_upper) + println(io, "c.u_upper[$idx] = $(c.u_upper[idx])") + end + for idx in CartesianIndices(c.u_lower) + println(io, "c.u_lower[$idx] = $(c.u_lower[idx])") + end + println(io, "transpose(c.neighbor_ids) = $(transpose(c.neighbor_ids))") + println(io, "c.large_sides = $(c.large_sides)") + println(io, "c.orientations = $(c.orientations)") + print(io, '*'^20) + end - # Skip if neighbor is on this MPI rank -> create regular interface instead - if is_own_cell(mesh.tree, neighbor_cell_id) - continue - end + # Create mortar container and initialize mortar data in `elements`. + function init_mortars( + cell_ids, mesh::TreeMesh2D, + elements::ElementContainer2D, + ::LobattoLegendreMortarL2 + ) + # Initialize containers + n_mortars = count_required_mortars(mesh, cell_ids) + mortars = L2MortarContainer2D{eltype(elements)}( + n_mortars, nvariables(elements), + nnodes(elements) + ) + + # Connect elements with mortars + init_mortars!(mortars, elements, mesh) + return mortars + end - # Create interface between elements - count += 1 - # Note: `local_neighbor_ids` stores the MPI-local neighbors, - # but with globally valid index! - mpi_interfaces.local_neighbor_ids[count] = element + # Count the number of mortars that need to be created + function count_required_mortars(mesh::TreeMesh2D, cell_ids) + count = 0 - if iseven(direction) # element is "left" of interface, remote cell is "right" of interface - mpi_interfaces.remote_sides[count] = 2 - else - mpi_interfaces.remote_sides[count] = 1 - end + # Iterate over all cells and count mortars from perspective of coarse cells + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # If no neighbor exists, cell is small with large neighbor or at boundary -> do nothing + if !has_neighbor(mesh.tree, cell_id, direction) + continue + end + + # If neighbor has no children, this is a conforming interface -> do nothing + neighbor_id = mesh.tree.neighbor_ids[direction, cell_id] + if !has_children(mesh.tree, neighbor_id) + continue + end + + # Skip if one of the small cells is on different rank -> create mpi mortar instead + # (the coarse cell is always on the local rank) + if mpi_isparallel() + if direction == 1 # small cells left, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[2, neighbor_id] + upper_cell_id = mesh.tree.child_ids[4, neighbor_id] + elseif direction == 2 # small cells right, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[1, neighbor_id] + upper_cell_id = mesh.tree.child_ids[3, neighbor_id] + elseif direction == 3 # small cells left, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[3, neighbor_id] + upper_cell_id = mesh.tree.child_ids[4, neighbor_id] + else # direction == 4, small cells right, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[1, neighbor_id] + upper_cell_id = mesh.tree.child_ids[2, neighbor_id] + end + small_cell_ids = (lower_cell_id, upper_cell_id) + if any(cell -> !is_own_cell(mesh.tree, cell), small_cell_ids) + continue + end + end - # Set orientation (x -> 1, y -> 2) - if direction in (1, 2) # x-direction - mpi_interfaces.orientations[count] = 1 - else # y-direction - mpi_interfaces.orientations[count] = 2 + count += 1 end end - end - @assert count==nmpiinterfaces(mpi_interfaces) ("Actual interface count ($count) does not match " - *"expectations $(nmpiinterfaces(mpi_interfaces))") -end - -# Container data structure (structure-of-arrays style) for DG L2 mortars -# Positions/directions for orientations = 1, large_sides = 2: -# mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar -# | | -# upper = 2 | | -# | | -# | 3 = large side -# | | -# lower = 1 | | -# | | -mutable struct MPIL2MortarContainer2D{uEltype <: Real} <: AbstractContainer - u_upper::Array{uEltype, 4} # [leftright, variables, i, mortars] - u_lower::Array{uEltype, 4} # [leftright, variables, i, mortars] - # Note: `local_neighbor_ids` stores the MPI-local neighbors, but with globally valid index! - local_neighbor_ids::Vector{Vector{Int}} # [mortars][ids] - local_neighbor_positions::Vector{Vector{Int}} # [mortars][positions] - # Large sides: left -> 1, right -> 2 - large_sides::Vector{Int} # [mortars] - orientations::Vector{Int} # [mortars] - # internal `resize!`able storage - _u_upper::Vector{uEltype} - _u_lower::Vector{uEltype} -end - -nvariables(mpi_mortars::MPIL2MortarContainer2D) = size(mpi_mortars.u_upper, 2) -nnodes(mpi_mortars::MPIL2MortarContainer2D) = size(mpi_mortars.u_upper, 3) -Base.eltype(mpi_mortars::MPIL2MortarContainer2D) = eltype(mpi_mortars.u_upper) - -# See explanation of Base.resize! for the element container -function Base.resize!(mpi_mortars::MPIL2MortarContainer2D, capacity) - n_nodes = nnodes(mpi_mortars) - n_variables = nvariables(mpi_mortars) - @unpack _u_upper, _u_lower, local_neighbor_ids, local_neighbor_positions, - large_sides, orientations = mpi_mortars - - resize!(_u_upper, 2 * n_variables * n_nodes * capacity) - mpi_mortars.u_upper = unsafe_wrap(Array, pointer(_u_upper), - (2, n_variables, n_nodes, capacity)) - - resize!(_u_lower, 2 * n_variables * n_nodes * capacity) - mpi_mortars.u_lower = unsafe_wrap(Array, pointer(_u_lower), - (2, n_variables, n_nodes, capacity)) - - resize!(local_neighbor_ids, capacity) - resize!(local_neighbor_positions, capacity) - - resize!(large_sides, capacity) - - resize!(orientations, capacity) - - return nothing -end - -function MPIL2MortarContainer2D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} - nan = convert(uEltype, NaN) - - # Initialize fields with defaults - _u_upper = fill(nan, 2 * n_variables * n_nodes * capacity) - u_upper = unsafe_wrap(Array, pointer(_u_upper), - (2, n_variables, n_nodes, capacity)) - - _u_lower = fill(nan, 2 * n_variables * n_nodes * capacity) - u_lower = unsafe_wrap(Array, pointer(_u_lower), - (2, n_variables, n_nodes, capacity)) - - local_neighbor_ids = fill(Vector{Int}(), capacity) - local_neighbor_positions = fill(Vector{Int}(), capacity) - - large_sides = fill(typemin(Int), capacity) - - orientations = fill(typemin(Int), capacity) - - return MPIL2MortarContainer2D{uEltype}(u_upper, u_lower, local_neighbor_ids, - local_neighbor_positions, large_sides, - orientations, - _u_upper, _u_lower) -end - -# Return number of L2 mortars -@inline function nmpimortars(mpi_l2mortars::MPIL2MortarContainer2D) - length(mpi_l2mortars.orientations) -end - -# Create MPI mortar container and initialize MPI mortar data in `elements`. -function init_mpi_mortars(cell_ids, mesh::TreeMesh2D, - elements::ElementContainer2D, - ::LobattoLegendreMortarL2) - # Initialize containers - n_mpi_mortars = count_required_mpi_mortars(mesh, cell_ids) - mpi_mortars = MPIL2MortarContainer2D{eltype(elements)}(n_mpi_mortars, - nvariables(elements), - nnodes(elements)) - - # Connect elements with mortars - init_mpi_mortars!(mpi_mortars, elements, mesh) - return mpi_mortars -end - -# Count the number of MPI mortars that need to be created -function count_required_mpi_mortars(mesh::TreeMesh2D, cell_ids) - # No MPI mortars needed if MPI is not used - if !mpi_isparallel() - return 0 + return count end - count = 0 + # Initialize connectivity between elements and mortars + function init_mortars!(mortars, elements, mesh::TreeMesh2D) + # Exit early if there are no mortars to initialize + if nmortars(mortars) == 0 + return nothing + end - for cell_id in cell_ids - for direction in eachdirection(mesh.tree) - # If no neighbor exists, cell is small with large neighbor or at boundary - if !has_neighbor(mesh.tree, cell_id, direction) - # If no large neighbor exists, cell is at boundary -> do nothing - if !has_coarse_neighbor(mesh.tree, cell_id, direction) + # Construct cell -> element mapping for easier algorithm implementation + tree = mesh.tree + c2e = zeros(Int, length(tree)) + for element in eachelement(elements) + c2e[elements.cell_ids[element]] = element + end + + # Reset interface count + count = 0 + + # Iterate over all elements to find neighbors and to connect via interfaces + for element in eachelement(elements) + # Get cell id + cell_id = elements.cell_ids[element] + + for direction in eachdirection(mesh.tree) + # If no neighbor exists, cell is small with large neighbor -> do nothing + if !has_neighbor(mesh.tree, cell_id, direction) continue end - # Skip if the large neighbor is on the same rank to prevent double counting - parent_id = mesh.tree.parent_ids[cell_id] - large_cell_id = mesh.tree.neighbor_ids[direction, parent_id] - if is_own_cell(mesh.tree, large_cell_id) + # If neighbor has no children, this is a conforming interface -> do nothing + neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] + if !has_children(mesh.tree, neighbor_cell_id) continue end - # Current cell is small with large neighbor on a different rank, find the other - # small cell - if direction == 1 # small cells right, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[1, parent_id] - upper_cell_id = mesh.tree.child_ids[3, parent_id] - elseif direction == 2 # small cells left, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[2, parent_id] - upper_cell_id = mesh.tree.child_ids[4, parent_id] - elseif direction == 3 # small cells right, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[1, parent_id] - upper_cell_id = mesh.tree.child_ids[2, parent_id] - else # direction == 4, small cells left, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[3, parent_id] - upper_cell_id = mesh.tree.child_ids[4, parent_id] + # Skip if one of the small cells is on different rank -> create mpi mortar instead + # (the coarse cell is always on the local rank) + if mpi_isparallel() + if direction == 1 # small cells left, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[2, neighbor_cell_id] + upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id] + elseif direction == 2 # small cells right, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id] + upper_cell_id = mesh.tree.child_ids[3, neighbor_cell_id] + elseif direction == 3 # small cells left, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[3, neighbor_cell_id] + upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id] + else # direction == 4, small cells right, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id] + upper_cell_id = mesh.tree.child_ids[2, neighbor_cell_id] + end + small_cell_ids = (lower_cell_id, upper_cell_id) + if any(cell -> !is_own_cell(mesh.tree, cell), small_cell_ids) + continue + end end - if cell_id == lower_cell_id - sibling_id = upper_cell_id - elseif cell_id == upper_cell_id - sibling_id = lower_cell_id + # Create mortar between elements: + # 1 -> small element in negative coordinate direction + # 2 -> small element in positive coordinate direction + # 3 -> large element + count += 1 + mortars.neighbor_ids[3, count] = element + if direction == 1 + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 2, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 4, + neighbor_cell_id, + ], + ] + elseif direction == 2 + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 1, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 3, + neighbor_cell_id, + ], + ] + elseif direction == 3 + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 3, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 4, + neighbor_cell_id, + ], + ] + elseif direction == 4 + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 1, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 2, + neighbor_cell_id, + ], + ] else error("should not happen") end - # Skip if the other small cell is on the same rank and its id is smaller than the current - # cell id to prevent double counting - if is_own_cell(mesh.tree, sibling_id) && sibling_id < cell_id - continue - end - else # Cell has a neighbor - # If neighbor has no children, this is a conforming interface -> do nothing - neighbor_id = mesh.tree.neighbor_ids[direction, cell_id] - if !has_children(mesh.tree, neighbor_id) - continue + # Set large side, which denotes the direction (1 -> negative, 2 -> positive) of the large side + if iseven(direction) + mortars.large_sides[count] = 1 + else + mortars.large_sides[count] = 2 end - # Skip if both small cells are on this rank -> create regular mortar instead - if direction == 1 # small cells left, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[2, neighbor_id] - upper_cell_id = mesh.tree.child_ids[4, neighbor_id] - elseif direction == 2 # small cells right, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[1, neighbor_id] - upper_cell_id = mesh.tree.child_ids[3, neighbor_id] - elseif direction == 3 # small cells left, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[3, neighbor_id] - upper_cell_id = mesh.tree.child_ids[4, neighbor_id] - else # direction == 4, small cells right, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[1, neighbor_id] - upper_cell_id = mesh.tree.child_ids[2, neighbor_id] - end - small_cell_ids = (lower_cell_id, upper_cell_id) - if all(cell -> is_own_cell(mesh.tree, cell), small_cell_ids) - continue + # Set orientation (x -> 1, y -> 2) + if direction in (1, 2) + mortars.orientations[count] = 1 + else + mortars.orientations[count] = 2 end end - - count += 1 end + + @assert count == nmortars(mortars) ( + "Actual mortar count ($count) does not match " * + "expectations $(nmortars(mortars))" + ) + end + + # Container data structure (structure-of-arrays style) for DG MPI interfaces + mutable struct MPIInterfaceContainer2D{uEltype <: Real} <: AbstractContainer + u::Array{uEltype, 4} # [leftright, variables, i, interfaces] + # Note: `local_neighbor_ids` stores the MPI-local neighbors, but with globally valid index! + local_neighbor_ids::Vector{Int} # [interfaces] + orientations::Vector{Int} # [interfaces] + remote_sides::Vector{Int} # [interfaces] + # internal `resize!`able storage + _u::Vector{uEltype} end - return count -end + nvariables(mpi_interfaces::MPIInterfaceContainer2D) = size(mpi_interfaces.u, 2) + nnodes(mpi_interfaces::MPIInterfaceContainer2D) = size(mpi_interfaces.u, 3) + Base.eltype(mpi_interfaces::MPIInterfaceContainer2D) = eltype(mpi_interfaces.u) + + # See explanation of Base.resize! for the element container + function Base.resize!(mpi_interfaces::MPIInterfaceContainer2D, capacity) + n_nodes = nnodes(mpi_interfaces) + n_variables = nvariables(mpi_interfaces) + @unpack _u, local_neighbor_ids, orientations, remote_sides = mpi_interfaces + + resize!(_u, 2 * n_variables * n_nodes * capacity) + mpi_interfaces.u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, capacity) + ) + + resize!(local_neighbor_ids, capacity) + + resize!(orientations, capacity) + + resize!(remote_sides, capacity) -# Initialize connectivity between elements and mortars -function init_mpi_mortars!(mpi_mortars, elements, mesh::TreeMesh2D) - # Exit early if there are no MPI mortars to initialize - if nmpimortars(mpi_mortars) == 0 return nothing end - # Construct cell -> element mapping for easier algorithm implementation - tree = mesh.tree - c2e = zeros(Int, length(tree)) - for element in eachelement(elements) - c2e[elements.cell_ids[element]] = element + function MPIInterfaceContainer2D{uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where {uEltype <: Real} + nan = convert(uEltype, NaN) + + # Initialize fields with defaults + _u = fill(nan, 2 * n_variables * n_nodes * capacity) + u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, capacity) + ) + + local_neighbor_ids = fill(typemin(Int), capacity) + + orientations = fill(typemin(Int), capacity) + + remote_sides = fill(typemin(Int), capacity) + + return MPIInterfaceContainer2D{uEltype}( + u, local_neighbor_ids, orientations, + remote_sides, + _u + ) end - # Reset mortar count - count = 0 + # TODO: Taal, rename to ninterfaces? + # Return number of interfaces + function nmpiinterfaces(mpi_interfaces::MPIInterfaceContainer2D) + length(mpi_interfaces.orientations) + end - # Iterate over all elements to find neighbors and to connect via mortars - for element in eachelement(elements) - cell_id = elements.cell_ids[element] + # Create MPI interface container and initialize MPI interface data in `elements`. + function init_mpi_interfaces( + cell_ids, mesh::TreeMesh2D, + elements::ElementContainer2D + ) + # Initialize container + n_mpi_interfaces = count_required_mpi_interfaces(mesh, cell_ids) + mpi_interfaces = MPIInterfaceContainer2D{eltype(elements)}( + n_mpi_interfaces, + nvariables(elements), + nnodes(elements) + ) + + # Connect elements with interfaces + init_mpi_interfaces!(mpi_interfaces, elements, mesh) + return mpi_interfaces + end - for direction in eachdirection(mesh.tree) - # If no neighbor exists, cell is small with large neighbor or at boundary - if !has_neighbor(mesh.tree, cell_id, direction) - # If no large neighbor exists, cell is at boundary -> do nothing - if !has_coarse_neighbor(mesh.tree, cell_id, direction) + # Count the number of MPI interfaces that need to be created + function count_required_mpi_interfaces(mesh::TreeMesh2D, cell_ids) + # No MPI interfaces needed if MPI is not used + if !mpi_isparallel() + return 0 + end + + count = 0 + + # Iterate over all cells + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # If no neighbor exists, current cell is small or at boundary and thus we need a mortar + if !has_neighbor(mesh.tree, cell_id, direction) continue end - # Skip if the large neighbor is on the same rank -> will be handled in another iteration - parent_cell_id = mesh.tree.parent_ids[cell_id] - large_cell_id = mesh.tree.neighbor_ids[direction, parent_cell_id] - if is_own_cell(mesh.tree, large_cell_id) + # Skip if neighbor has children + neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] + if has_children(mesh.tree, neighbor_cell_id) continue end - # Current cell is small with large neighbor on a different rank, find the other - # small cell - if direction == 1 # small cells right, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[1, parent_cell_id] - upper_cell_id = mesh.tree.child_ids[3, parent_cell_id] - elseif direction == 2 # small cells left, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[2, parent_cell_id] - upper_cell_id = mesh.tree.child_ids[4, parent_cell_id] - elseif direction == 3 # small cells right, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[1, parent_cell_id] - upper_cell_id = mesh.tree.child_ids[2, parent_cell_id] - else # direction == 4, small cells left, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[3, parent_cell_id] - upper_cell_id = mesh.tree.child_ids[4, parent_cell_id] + # Skip if neighbor is on this rank -> create regular interface instead + if is_own_cell(mesh.tree, neighbor_cell_id) + continue end - if cell_id == lower_cell_id - sibling_id = upper_cell_id - elseif cell_id == upper_cell_id - sibling_id = lower_cell_id - else - error("should not happen") - end + count += 1 + end + end + + return count + end + + # Initialize connectivity between elements and interfaces + function init_mpi_interfaces!(mpi_interfaces, elements, mesh::TreeMesh2D) + # Exit early if there are no MPI interfaces to initialize + if nmpiinterfaces(mpi_interfaces) == 0 + return nothing + end - # Skip if the other small cell is on the same rank and its id is smaller than the current - # cell id to prevent double counting - if is_own_cell(mesh.tree, sibling_id) && sibling_id < cell_id + # Reset interface count + count = 0 + + # Iterate over all elements to find neighbors and to connect via mpi_interfaces + for element in eachelement(elements) + # Get cell id + cell_id = elements.cell_ids[element] + + # Loop over directions + for direction in eachdirection(mesh.tree) + # If no neighbor exists, current cell is small and thus we need a mortar + if !has_neighbor(mesh.tree, cell_id, direction) continue end - else # Cell has a neighbor - large_cell_id = cell_id # save explicitly for later processing - # If neighbor has no children, this is a conforming interface -> do nothing + # Skip if neighbor has children neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] - if !has_children(mesh.tree, neighbor_cell_id) + if has_children(mesh.tree, neighbor_cell_id) continue end - # Skip if both small cells are on this rank -> create regular mortar instead - if direction == 1 # small cells left, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[2, neighbor_cell_id] - upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id] - elseif direction == 2 # small cells right, mortar in x-direction - lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id] - upper_cell_id = mesh.tree.child_ids[3, neighbor_cell_id] - elseif direction == 3 # small cells left, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[3, neighbor_cell_id] - upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id] - else # direction == 4, small cells right, mortar in y-direction - lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id] - upper_cell_id = mesh.tree.child_ids[2, neighbor_cell_id] - end - small_cell_ids = (lower_cell_id, upper_cell_id) - if all(cell -> is_own_cell(mesh.tree, cell), small_cell_ids) + # Skip if neighbor is on this MPI rank -> create regular interface instead + if is_own_cell(mesh.tree, neighbor_cell_id) continue end - end - # Create mortar between elements: - # 1 -> small element in negative coordinate direction - # 2 -> small element in positive coordinate direction - # 3 -> large element - count += 1 - - # Note: `local_neighbor_ids` stores the MPI-local neighbors, - # but with globally valid index! - local_neighbor_ids = Vector{Int}() - local_neighbor_positions = Vector{Int}() - if is_own_cell(mesh.tree, lower_cell_id) - push!(local_neighbor_ids, c2e[lower_cell_id]) - push!(local_neighbor_positions, 1) - end - if is_own_cell(mesh.tree, upper_cell_id) - push!(local_neighbor_ids, c2e[upper_cell_id]) - push!(local_neighbor_positions, 2) - end - if is_own_cell(mesh.tree, large_cell_id) - push!(local_neighbor_ids, c2e[large_cell_id]) - push!(local_neighbor_positions, 3) + # Create interface between elements + count += 1 + # Note: `local_neighbor_ids` stores the MPI-local neighbors, + # but with globally valid index! + mpi_interfaces.local_neighbor_ids[count] = element + + if iseven(direction) # element is "left" of interface, remote cell is "right" of interface + mpi_interfaces.remote_sides[count] = 2 + else + mpi_interfaces.remote_sides[count] = 1 + end + + # Set orientation (x -> 1, y -> 2) + if direction in (1, 2) # x-direction + mpi_interfaces.orientations[count] = 1 + else # y-direction + mpi_interfaces.orientations[count] = 2 + end end + end + + @assert count == nmpiinterfaces(mpi_interfaces) ( + "Actual interface count ($count) does not match " + * "expectations $(nmpiinterfaces(mpi_interfaces))" + ) + end + + # Container data structure (structure-of-arrays style) for DG L2 mortars + # Positions/directions for orientations = 1, large_sides = 2: + # mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar + # | | + # upper = 2 | | + # | | + # | 3 = large side + # | | + # lower = 1 | | + # | | + mutable struct MPIL2MortarContainer2D{uEltype <: Real} <: AbstractContainer + u_upper::Array{uEltype, 4} # [leftright, variables, i, mortars] + u_lower::Array{uEltype, 4} # [leftright, variables, i, mortars] + # Note: `local_neighbor_ids` stores the MPI-local neighbors, but with globally valid index! + local_neighbor_ids::Vector{Vector{Int}} # [mortars][ids] + local_neighbor_positions::Vector{Vector{Int}} # [mortars][positions] + # Large sides: left -> 1, right -> 2 + large_sides::Vector{Int} # [mortars] + orientations::Vector{Int} # [mortars] + # internal `resize!`able storage + _u_upper::Vector{uEltype} + _u_lower::Vector{uEltype} + end - mpi_mortars.local_neighbor_ids[count] = local_neighbor_ids - mpi_mortars.local_neighbor_positions[count] = local_neighbor_positions - - # Set large side, which denotes the direction (1 -> negative, 2 -> positive) of the large side - # To prevent double counting, the mortars are always identified from the point of view of - # a large cell, if it is on this rank. In that case, direction points towards the small cells. - # If the large cell is not on this rank, the point of view of a small cell is taken instead, - # hence direction points towards the large cell in this case. - if iseven(direction) - mpi_mortars.large_sides[count] = is_own_cell(mesh.tree, large_cell_id) ? - 1 : 2 - else - mpi_mortars.large_sides[count] = is_own_cell(mesh.tree, large_cell_id) ? - 2 : 1 + nvariables(mpi_mortars::MPIL2MortarContainer2D) = size(mpi_mortars.u_upper, 2) + nnodes(mpi_mortars::MPIL2MortarContainer2D) = size(mpi_mortars.u_upper, 3) + Base.eltype(mpi_mortars::MPIL2MortarContainer2D) = eltype(mpi_mortars.u_upper) + + # See explanation of Base.resize! for the element container + function Base.resize!(mpi_mortars::MPIL2MortarContainer2D, capacity) + n_nodes = nnodes(mpi_mortars) + n_variables = nvariables(mpi_mortars) + @unpack _u_upper, _u_lower, local_neighbor_ids, local_neighbor_positions, + large_sides, orientations = mpi_mortars + + resize!(_u_upper, 2 * n_variables * n_nodes * capacity) + mpi_mortars.u_upper = unsafe_wrap( + Array, pointer(_u_upper), + (2, n_variables, n_nodes, capacity) + ) + + resize!(_u_lower, 2 * n_variables * n_nodes * capacity) + mpi_mortars.u_lower = unsafe_wrap( + Array, pointer(_u_lower), + (2, n_variables, n_nodes, capacity) + ) + + resize!(local_neighbor_ids, capacity) + resize!(local_neighbor_positions, capacity) + + resize!(large_sides, capacity) + + resize!(orientations, capacity) + + return nothing + end + + function MPIL2MortarContainer2D{uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where {uEltype <: Real} + nan = convert(uEltype, NaN) + + # Initialize fields with defaults + _u_upper = fill(nan, 2 * n_variables * n_nodes * capacity) + u_upper = unsafe_wrap( + Array, pointer(_u_upper), + (2, n_variables, n_nodes, capacity) + ) + + _u_lower = fill(nan, 2 * n_variables * n_nodes * capacity) + u_lower = unsafe_wrap( + Array, pointer(_u_lower), + (2, n_variables, n_nodes, capacity) + ) + + local_neighbor_ids = fill(Vector{Int}(), capacity) + local_neighbor_positions = fill(Vector{Int}(), capacity) + + large_sides = fill(typemin(Int), capacity) + + orientations = fill(typemin(Int), capacity) + + return MPIL2MortarContainer2D{uEltype}( + u_upper, u_lower, local_neighbor_ids, + local_neighbor_positions, large_sides, + orientations, + _u_upper, _u_lower + ) + end + + # Return number of L2 mortars + @inline function nmpimortars(mpi_l2mortars::MPIL2MortarContainer2D) + length(mpi_l2mortars.orientations) + end + + # Create MPI mortar container and initialize MPI mortar data in `elements`. + function init_mpi_mortars( + cell_ids, mesh::TreeMesh2D, + elements::ElementContainer2D, + ::LobattoLegendreMortarL2 + ) + # Initialize containers + n_mpi_mortars = count_required_mpi_mortars(mesh, cell_ids) + mpi_mortars = MPIL2MortarContainer2D{eltype(elements)}( + n_mpi_mortars, + nvariables(elements), + nnodes(elements) + ) + + # Connect elements with mortars + init_mpi_mortars!(mpi_mortars, elements, mesh) + return mpi_mortars + end + + # Count the number of MPI mortars that need to be created + function count_required_mpi_mortars(mesh::TreeMesh2D, cell_ids) + # No MPI mortars needed if MPI is not used + if !mpi_isparallel() + return 0 + end + + count = 0 + + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # If no neighbor exists, cell is small with large neighbor or at boundary + if !has_neighbor(mesh.tree, cell_id, direction) + # If no large neighbor exists, cell is at boundary -> do nothing + if !has_coarse_neighbor(mesh.tree, cell_id, direction) + continue + end + + # Skip if the large neighbor is on the same rank to prevent double counting + parent_id = mesh.tree.parent_ids[cell_id] + large_cell_id = mesh.tree.neighbor_ids[direction, parent_id] + if is_own_cell(mesh.tree, large_cell_id) + continue + end + + # Current cell is small with large neighbor on a different rank, find the other + # small cell + if direction == 1 # small cells right, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[1, parent_id] + upper_cell_id = mesh.tree.child_ids[3, parent_id] + elseif direction == 2 # small cells left, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[2, parent_id] + upper_cell_id = mesh.tree.child_ids[4, parent_id] + elseif direction == 3 # small cells right, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[1, parent_id] + upper_cell_id = mesh.tree.child_ids[2, parent_id] + else # direction == 4, small cells left, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[3, parent_id] + upper_cell_id = mesh.tree.child_ids[4, parent_id] + end + + if cell_id == lower_cell_id + sibling_id = upper_cell_id + elseif cell_id == upper_cell_id + sibling_id = lower_cell_id + else + error("should not happen") + end + + # Skip if the other small cell is on the same rank and its id is smaller than the current + # cell id to prevent double counting + if is_own_cell(mesh.tree, sibling_id) && sibling_id < cell_id + continue + end + else # Cell has a neighbor + # If neighbor has no children, this is a conforming interface -> do nothing + neighbor_id = mesh.tree.neighbor_ids[direction, cell_id] + if !has_children(mesh.tree, neighbor_id) + continue + end + + # Skip if both small cells are on this rank -> create regular mortar instead + if direction == 1 # small cells left, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[2, neighbor_id] + upper_cell_id = mesh.tree.child_ids[4, neighbor_id] + elseif direction == 2 # small cells right, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[1, neighbor_id] + upper_cell_id = mesh.tree.child_ids[3, neighbor_id] + elseif direction == 3 # small cells left, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[3, neighbor_id] + upper_cell_id = mesh.tree.child_ids[4, neighbor_id] + else # direction == 4, small cells right, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[1, neighbor_id] + upper_cell_id = mesh.tree.child_ids[2, neighbor_id] + end + small_cell_ids = (lower_cell_id, upper_cell_id) + if all(cell -> is_own_cell(mesh.tree, cell), small_cell_ids) + continue + end + end + + count += 1 end + end + + return count + end + + # Initialize connectivity between elements and mortars + function init_mpi_mortars!(mpi_mortars, elements, mesh::TreeMesh2D) + # Exit early if there are no MPI mortars to initialize + if nmpimortars(mpi_mortars) == 0 + return nothing + end - # Set orientation (1, 2 -> x; 3, 4 -> y) - if direction in (1, 2) - mpi_mortars.orientations[count] = 1 - else - mpi_mortars.orientations[count] = 2 + # Construct cell -> element mapping for easier algorithm implementation + tree = mesh.tree + c2e = zeros(Int, length(tree)) + for element in eachelement(elements) + c2e[elements.cell_ids[element]] = element + end + + # Reset mortar count + count = 0 + + # Iterate over all elements to find neighbors and to connect via mortars + for element in eachelement(elements) + cell_id = elements.cell_ids[element] + + for direction in eachdirection(mesh.tree) + # If no neighbor exists, cell is small with large neighbor or at boundary + if !has_neighbor(mesh.tree, cell_id, direction) + # If no large neighbor exists, cell is at boundary -> do nothing + if !has_coarse_neighbor(mesh.tree, cell_id, direction) + continue + end + + # Skip if the large neighbor is on the same rank -> will be handled in another iteration + parent_cell_id = mesh.tree.parent_ids[cell_id] + large_cell_id = mesh.tree.neighbor_ids[direction, parent_cell_id] + if is_own_cell(mesh.tree, large_cell_id) + continue + end + + # Current cell is small with large neighbor on a different rank, find the other + # small cell + if direction == 1 # small cells right, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[1, parent_cell_id] + upper_cell_id = mesh.tree.child_ids[3, parent_cell_id] + elseif direction == 2 # small cells left, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[2, parent_cell_id] + upper_cell_id = mesh.tree.child_ids[4, parent_cell_id] + elseif direction == 3 # small cells right, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[1, parent_cell_id] + upper_cell_id = mesh.tree.child_ids[2, parent_cell_id] + else # direction == 4, small cells left, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[3, parent_cell_id] + upper_cell_id = mesh.tree.child_ids[4, parent_cell_id] + end + + if cell_id == lower_cell_id + sibling_id = upper_cell_id + elseif cell_id == upper_cell_id + sibling_id = lower_cell_id + else + error("should not happen") + end + + # Skip if the other small cell is on the same rank and its id is smaller than the current + # cell id to prevent double counting + if is_own_cell(mesh.tree, sibling_id) && sibling_id < cell_id + continue + end + else # Cell has a neighbor + large_cell_id = cell_id # save explicitly for later processing + + # If neighbor has no children, this is a conforming interface -> do nothing + neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] + if !has_children(mesh.tree, neighbor_cell_id) + continue + end + + # Skip if both small cells are on this rank -> create regular mortar instead + if direction == 1 # small cells left, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[2, neighbor_cell_id] + upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id] + elseif direction == 2 # small cells right, mortar in x-direction + lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id] + upper_cell_id = mesh.tree.child_ids[3, neighbor_cell_id] + elseif direction == 3 # small cells left, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[3, neighbor_cell_id] + upper_cell_id = mesh.tree.child_ids[4, neighbor_cell_id] + else # direction == 4, small cells right, mortar in y-direction + lower_cell_id = mesh.tree.child_ids[1, neighbor_cell_id] + upper_cell_id = mesh.tree.child_ids[2, neighbor_cell_id] + end + small_cell_ids = (lower_cell_id, upper_cell_id) + if all(cell -> is_own_cell(mesh.tree, cell), small_cell_ids) + continue + end + end + + # Create mortar between elements: + # 1 -> small element in negative coordinate direction + # 2 -> small element in positive coordinate direction + # 3 -> large element + count += 1 + + # Note: `local_neighbor_ids` stores the MPI-local neighbors, + # but with globally valid index! + local_neighbor_ids = Vector{Int}() + local_neighbor_positions = Vector{Int}() + if is_own_cell(mesh.tree, lower_cell_id) + push!(local_neighbor_ids, c2e[lower_cell_id]) + push!(local_neighbor_positions, 1) + end + if is_own_cell(mesh.tree, upper_cell_id) + push!(local_neighbor_ids, c2e[upper_cell_id]) + push!(local_neighbor_positions, 2) + end + if is_own_cell(mesh.tree, large_cell_id) + push!(local_neighbor_ids, c2e[large_cell_id]) + push!(local_neighbor_positions, 3) + end + + mpi_mortars.local_neighbor_ids[count] = local_neighbor_ids + mpi_mortars.local_neighbor_positions[count] = local_neighbor_positions + + # Set large side, which denotes the direction (1 -> negative, 2 -> positive) of the large side + # To prevent double counting, the mortars are always identified from the point of view of + # a large cell, if it is on this rank. In that case, direction points towards the small cells. + # If the large cell is not on this rank, the point of view of a small cell is taken instead, + # hence direction points towards the large cell in this case. + if iseven(direction) + mpi_mortars.large_sides[count] = is_own_cell(mesh.tree, large_cell_id) ? + 1 : 2 + else + mpi_mortars.large_sides[count] = is_own_cell(mesh.tree, large_cell_id) ? + 2 : 1 + end + + # Set orientation (1, 2 -> x; 3, 4 -> y) + if direction in (1, 2) + mpi_mortars.orientations[count] = 1 + else + mpi_mortars.orientations[count] = 2 + end end end + + return nothing + end + + # Container data structure (structure-of-arrays style) for FCT-type antidiffusive fluxes + # (i, j+1) + # | + # flux2(i, j+1) + # | + # (i-1, j) ---flux1(i, j)--- (i, j) ---flux1(i+1, j)--- (i+1, j) + # | + # flux2(i, j) + # | + # (i, j-1) + mutable struct ContainerAntidiffusiveFlux2D{uEltype <: Real} + antidiffusive_flux1_L::Array{uEltype, 4} # [variables, i, j, elements] + antidiffusive_flux1_R::Array{uEltype, 4} # [variables, i, j, elements] + antidiffusive_flux2_L::Array{uEltype, 4} # [variables, i, j, elements] + antidiffusive_flux2_R::Array{uEltype, 4} # [variables, i, j, elements] + # internal `resize!`able storage + _antidiffusive_flux1_L::Vector{uEltype} + _antidiffusive_flux1_R::Vector{uEltype} + _antidiffusive_flux2_L::Vector{uEltype} + _antidiffusive_flux2_R::Vector{uEltype} end - return nothing -end - -# Container data structure (structure-of-arrays style) for FCT-type antidiffusive fluxes -# (i, j+1) -# | -# flux2(i, j+1) -# | -# (i-1, j) ---flux1(i, j)--- (i, j) ---flux1(i+1, j)--- (i+1, j) -# | -# flux2(i, j) -# | -# (i, j-1) -mutable struct ContainerAntidiffusiveFlux2D{uEltype <: Real} - antidiffusive_flux1_L::Array{uEltype, 4} # [variables, i, j, elements] - antidiffusive_flux1_R::Array{uEltype, 4} # [variables, i, j, elements] - antidiffusive_flux2_L::Array{uEltype, 4} # [variables, i, j, elements] - antidiffusive_flux2_R::Array{uEltype, 4} # [variables, i, j, elements] - # internal `resize!`able storage - _antidiffusive_flux1_L::Vector{uEltype} - _antidiffusive_flux1_R::Vector{uEltype} - _antidiffusive_flux2_L::Vector{uEltype} - _antidiffusive_flux2_R::Vector{uEltype} -end - -function ContainerAntidiffusiveFlux2D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} - nan_uEltype = convert(uEltype, NaN) - - # Initialize fields with defaults - _antidiffusive_flux1_L = fill(nan_uEltype, - n_variables * (n_nodes + 1) * n_nodes * capacity) - antidiffusive_flux1_L = unsafe_wrap(Array, pointer(_antidiffusive_flux1_L), - (n_variables, n_nodes + 1, n_nodes, capacity)) - _antidiffusive_flux1_R = fill(nan_uEltype, - n_variables * (n_nodes + 1) * n_nodes * capacity) - antidiffusive_flux1_R = unsafe_wrap(Array, pointer(_antidiffusive_flux1_R), - (n_variables, n_nodes + 1, n_nodes, capacity)) - - _antidiffusive_flux2_L = fill(nan_uEltype, - n_variables * n_nodes * (n_nodes + 1) * capacity) - antidiffusive_flux2_L = unsafe_wrap(Array, pointer(_antidiffusive_flux2_L), - (n_variables, n_nodes, n_nodes + 1, capacity)) - _antidiffusive_flux2_R = fill(nan_uEltype, - n_variables * n_nodes * (n_nodes + 1) * capacity) - antidiffusive_flux2_R = unsafe_wrap(Array, pointer(_antidiffusive_flux2_R), - (n_variables, n_nodes, n_nodes + 1, capacity)) - - return ContainerAntidiffusiveFlux2D{uEltype}(antidiffusive_flux1_L, - antidiffusive_flux1_R, - antidiffusive_flux2_L, - antidiffusive_flux2_R, - _antidiffusive_flux1_L, - _antidiffusive_flux1_R, - _antidiffusive_flux2_L, - _antidiffusive_flux2_R) -end - -nvariables(fluxes::ContainerAntidiffusiveFlux2D) = size(fluxes.antidiffusive_flux1_L, 1) -nnodes(fluxes::ContainerAntidiffusiveFlux2D) = size(fluxes.antidiffusive_flux1_L, 3) - -# Only one-dimensional `Array`s are `resize!`able in Julia. -# Hence, we use `Vector`s as internal storage and `resize!` -# them whenever needed. Then, we reuse the same memory by -# `unsafe_wrap`ping multi-dimensional `Array`s around the -# internal storage. -function Base.resize!(fluxes::ContainerAntidiffusiveFlux2D, capacity) - n_nodes = nnodes(fluxes) - n_variables = nvariables(fluxes) - - @unpack _antidiffusive_flux1_L, _antidiffusive_flux2_L, _antidiffusive_flux1_R, _antidiffusive_flux2_R = fluxes - - resize!(_antidiffusive_flux1_L, n_variables * (n_nodes + 1) * n_nodes * capacity) - fluxes.antidiffusive_flux1_L = unsafe_wrap(Array, pointer(_antidiffusive_flux1_L), - (n_variables, n_nodes + 1, n_nodes, - capacity)) - resize!(_antidiffusive_flux1_R, n_variables * (n_nodes + 1) * n_nodes * capacity) - fluxes.antidiffusive_flux1_R = unsafe_wrap(Array, pointer(_antidiffusive_flux1_R), - (n_variables, n_nodes + 1, n_nodes, - capacity)) - resize!(_antidiffusive_flux2_L, n_variables * n_nodes * (n_nodes + 1) * capacity) - fluxes.antidiffusive_flux2_L = unsafe_wrap(Array, pointer(_antidiffusive_flux2_L), - (n_variables, n_nodes, n_nodes + 1, - capacity)) - resize!(_antidiffusive_flux2_R, n_variables * n_nodes * (n_nodes + 1) * capacity) - fluxes.antidiffusive_flux2_R = unsafe_wrap(Array, pointer(_antidiffusive_flux2_R), - (n_variables, n_nodes, n_nodes + 1, - capacity)) - - return nothing -end - -# Container data structure (structure-of-arrays style) for variables used for IDP limiting -mutable struct ContainerSubcellLimiterIDP2D{uEltype <: Real} - alpha::Array{uEltype, 3} # [i, j, element] - alpha1::Array{uEltype, 3} - alpha2::Array{uEltype, 3} - variable_bounds::Dict{Symbol, Array{uEltype, 3}} - # internal `resize!`able storage - _alpha::Vector{uEltype} - _alpha1::Vector{uEltype} - _alpha2::Vector{uEltype} - _variable_bounds::Dict{Symbol, Vector{uEltype}} -end - -function ContainerSubcellLimiterIDP2D{uEltype}(capacity::Integer, n_nodes, - bound_keys) where {uEltype <: Real} - nan_uEltype = convert(uEltype, NaN) - - # Initialize fields with defaults - _alpha = fill(nan_uEltype, n_nodes * n_nodes * capacity) - alpha = unsafe_wrap(Array, pointer(_alpha), (n_nodes, n_nodes, capacity)) - _alpha1 = fill(nan_uEltype, (n_nodes + 1) * n_nodes * capacity) - alpha1 = unsafe_wrap(Array, pointer(_alpha1), (n_nodes + 1, n_nodes, capacity)) - _alpha2 = fill(nan_uEltype, n_nodes * (n_nodes + 1) * capacity) - alpha2 = unsafe_wrap(Array, pointer(_alpha2), (n_nodes, n_nodes + 1, capacity)) - - _variable_bounds = Dict{Symbol, Vector{uEltype}}() - variable_bounds = Dict{Symbol, Array{uEltype, 3}}() - for key in bound_keys - _variable_bounds[key] = fill(nan_uEltype, n_nodes * n_nodes * capacity) - variable_bounds[key] = unsafe_wrap(Array, pointer(_variable_bounds[key]), - (n_nodes, n_nodes, capacity)) + function ContainerAntidiffusiveFlux2D{uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where {uEltype <: Real} + nan_uEltype = convert(uEltype, NaN) + + # Initialize fields with defaults + _antidiffusive_flux1_L = fill( + nan_uEltype, + n_variables * (n_nodes + 1) * n_nodes * capacity + ) + antidiffusive_flux1_L = unsafe_wrap( + Array, pointer(_antidiffusive_flux1_L), + (n_variables, n_nodes + 1, n_nodes, capacity) + ) + _antidiffusive_flux1_R = fill( + nan_uEltype, + n_variables * (n_nodes + 1) * n_nodes * capacity + ) + antidiffusive_flux1_R = unsafe_wrap( + Array, pointer(_antidiffusive_flux1_R), + (n_variables, n_nodes + 1, n_nodes, capacity) + ) + + _antidiffusive_flux2_L = fill( + nan_uEltype, + n_variables * n_nodes * (n_nodes + 1) * capacity + ) + antidiffusive_flux2_L = unsafe_wrap( + Array, pointer(_antidiffusive_flux2_L), + (n_variables, n_nodes, n_nodes + 1, capacity) + ) + _antidiffusive_flux2_R = fill( + nan_uEltype, + n_variables * n_nodes * (n_nodes + 1) * capacity + ) + antidiffusive_flux2_R = unsafe_wrap( + Array, pointer(_antidiffusive_flux2_R), + (n_variables, n_nodes, n_nodes + 1, capacity) + ) + + return ContainerAntidiffusiveFlux2D{uEltype}( + antidiffusive_flux1_L, + antidiffusive_flux1_R, + antidiffusive_flux2_L, + antidiffusive_flux2_R, + _antidiffusive_flux1_L, + _antidiffusive_flux1_R, + _antidiffusive_flux2_L, + _antidiffusive_flux2_R + ) + end + + nvariables(fluxes::ContainerAntidiffusiveFlux2D) = size(fluxes.antidiffusive_flux1_L, 1) + nnodes(fluxes::ContainerAntidiffusiveFlux2D) = size(fluxes.antidiffusive_flux1_L, 3) + + # Only one-dimensional `Array`s are `resize!`able in Julia. + # Hence, we use `Vector`s as internal storage and `resize!` + # them whenever needed. Then, we reuse the same memory by + # `unsafe_wrap`ping multi-dimensional `Array`s around the + # internal storage. + function Base.resize!(fluxes::ContainerAntidiffusiveFlux2D, capacity) + n_nodes = nnodes(fluxes) + n_variables = nvariables(fluxes) + + @unpack _antidiffusive_flux1_L, _antidiffusive_flux2_L, _antidiffusive_flux1_R, _antidiffusive_flux2_R = fluxes + + resize!(_antidiffusive_flux1_L, n_variables * (n_nodes + 1) * n_nodes * capacity) + fluxes.antidiffusive_flux1_L = unsafe_wrap( + Array, pointer(_antidiffusive_flux1_L), + ( + n_variables, n_nodes + 1, n_nodes, + capacity, + ) + ) + resize!(_antidiffusive_flux1_R, n_variables * (n_nodes + 1) * n_nodes * capacity) + fluxes.antidiffusive_flux1_R = unsafe_wrap( + Array, pointer(_antidiffusive_flux1_R), + ( + n_variables, n_nodes + 1, n_nodes, + capacity, + ) + ) + resize!(_antidiffusive_flux2_L, n_variables * n_nodes * (n_nodes + 1) * capacity) + fluxes.antidiffusive_flux2_L = unsafe_wrap( + Array, pointer(_antidiffusive_flux2_L), + ( + n_variables, n_nodes, n_nodes + 1, + capacity, + ) + ) + resize!(_antidiffusive_flux2_R, n_variables * n_nodes * (n_nodes + 1) * capacity) + fluxes.antidiffusive_flux2_R = unsafe_wrap( + Array, pointer(_antidiffusive_flux2_R), + ( + n_variables, n_nodes, n_nodes + 1, + capacity, + ) + ) + + return nothing end - return ContainerSubcellLimiterIDP2D{uEltype}(alpha, alpha1, alpha2, - variable_bounds, - _alpha, _alpha1, _alpha2, - _variable_bounds) -end - -nnodes(container::ContainerSubcellLimiterIDP2D) = size(container.alpha, 1) - -# Only one-dimensional `Array`s are `resize!`able in Julia. -# Hence, we use `Vector`s as internal storage and `resize!` -# them whenever needed. Then, we reuse the same memory by -# `unsafe_wrap`ping multi-dimensional `Array`s around the -# internal storage. -function Base.resize!(container::ContainerSubcellLimiterIDP2D, capacity) - n_nodes = nnodes(container) - - (; _alpha, _alpha1, _alpha2) = container - resize!(_alpha, n_nodes * n_nodes * capacity) - container.alpha = unsafe_wrap(Array, pointer(_alpha), (n_nodes, n_nodes, capacity)) - container.alpha .= convert(eltype(container.alpha), NaN) - resize!(_alpha1, (n_nodes + 1) * n_nodes * capacity) - container.alpha1 = unsafe_wrap(Array, pointer(_alpha1), - (n_nodes + 1, n_nodes, capacity)) - resize!(_alpha2, n_nodes * (n_nodes + 1) * capacity) - container.alpha2 = unsafe_wrap(Array, pointer(_alpha2), - (n_nodes, n_nodes + 1, capacity)) - - (; _variable_bounds) = container - for (key, _) in _variable_bounds - resize!(_variable_bounds[key], n_nodes * n_nodes * capacity) - container.variable_bounds[key] = unsafe_wrap(Array, - pointer(_variable_bounds[key]), - (n_nodes, n_nodes, capacity)) + # Container data structure (structure-of-arrays style) for variables used for IDP limiting + mutable struct ContainerSubcellLimiterIDP2D{uEltype <: Real} + alpha::Array{uEltype, 3} # [i, j, element] + alpha1::Array{uEltype, 3} + alpha2::Array{uEltype, 3} + variable_bounds::Dict{Symbol, Array{uEltype, 3}} + # internal `resize!`able storage + _alpha::Vector{uEltype} + _alpha1::Vector{uEltype} + _alpha2::Vector{uEltype} + _variable_bounds::Dict{Symbol, Vector{uEltype}} end - return nothing -end + function ContainerSubcellLimiterIDP2D{uEltype}( + capacity::Integer, n_nodes, + bound_keys + ) where {uEltype <: Real} + nan_uEltype = convert(uEltype, NaN) + + # Initialize fields with defaults + _alpha = fill(nan_uEltype, n_nodes * n_nodes * capacity) + alpha = unsafe_wrap(Array, pointer(_alpha), (n_nodes, n_nodes, capacity)) + _alpha1 = fill(nan_uEltype, (n_nodes + 1) * n_nodes * capacity) + alpha1 = unsafe_wrap(Array, pointer(_alpha1), (n_nodes + 1, n_nodes, capacity)) + _alpha2 = fill(nan_uEltype, n_nodes * (n_nodes + 1) * capacity) + alpha2 = unsafe_wrap(Array, pointer(_alpha2), (n_nodes, n_nodes + 1, capacity)) + + _variable_bounds = Dict{Symbol, Vector{uEltype}}() + variable_bounds = Dict{Symbol, Array{uEltype, 3}}() + for key in bound_keys + _variable_bounds[key] = fill(nan_uEltype, n_nodes * n_nodes * capacity) + variable_bounds[key] = unsafe_wrap( + Array, pointer(_variable_bounds[key]), + (n_nodes, n_nodes, capacity) + ) + end + + return ContainerSubcellLimiterIDP2D{uEltype}( + alpha, alpha1, alpha2, + variable_bounds, + _alpha, _alpha1, _alpha2, + _variable_bounds + ) + end + + nnodes(container::ContainerSubcellLimiterIDP2D) = size(container.alpha, 1) + + # Only one-dimensional `Array`s are `resize!`able in Julia. + # Hence, we use `Vector`s as internal storage and `resize!` + # them whenever needed. Then, we reuse the same memory by + # `unsafe_wrap`ping multi-dimensional `Array`s around the + # internal storage. + function Base.resize!(container::ContainerSubcellLimiterIDP2D, capacity) + n_nodes = nnodes(container) + + (; _alpha, _alpha1, _alpha2) = container + resize!(_alpha, n_nodes * n_nodes * capacity) + container.alpha = unsafe_wrap(Array, pointer(_alpha), (n_nodes, n_nodes, capacity)) + container.alpha .= convert(eltype(container.alpha), NaN) + resize!(_alpha1, (n_nodes + 1) * n_nodes * capacity) + container.alpha1 = unsafe_wrap( + Array, pointer(_alpha1), + (n_nodes + 1, n_nodes, capacity) + ) + resize!(_alpha2, n_nodes * (n_nodes + 1) * capacity) + container.alpha2 = unsafe_wrap( + Array, pointer(_alpha2), + (n_nodes, n_nodes + 1, capacity) + ) + + (; _variable_bounds) = container + for (key, _) in _variable_bounds + resize!(_variable_bounds[key], n_nodes * n_nodes * capacity) + container.variable_bounds[key] = unsafe_wrap( + Array, + pointer(_variable_bounds[key]), + (n_nodes, n_nodes, capacity) + ) + end + + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_tree/containers_3d.jl b/src/solvers/dgsem_tree/containers_3d.jl index 5fc027ad00..55f21351e3 100644 --- a/src/solvers/dgsem_tree/containers_3d.jl +++ b/src/solvers/dgsem_tree/containers_3d.jl @@ -3,813 +3,1023 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Container data structure (structure-of-arrays style) for DG elements -mutable struct ElementContainer3D{RealT <: Real, uEltype <: Real} <: AbstractContainer - inverse_jacobian::Vector{RealT} # [elements] - node_coordinates::Array{RealT, 5} # [orientation, i, j, k, elements] - surface_flux_values::Array{uEltype, 5} # [variables, i, j, direction, elements] - cell_ids::Vector{Int} # [elements] - # internal `resize!`able storage - _node_coordinates::Vector{RealT} - _surface_flux_values::Vector{uEltype} -end - -nvariables(elements::ElementContainer3D) = size(elements.surface_flux_values, 1) -nnodes(elements::ElementContainer3D) = size(elements.node_coordinates, 2) -Base.eltype(elements::ElementContainer3D) = eltype(elements.surface_flux_values) - -# Only one-dimensional `Array`s are `resize!`able in Julia. -# Hence, we use `Vector`s as internal storage and `resize!` -# them whenever needed. Then, we reuse the same memory by -# `unsafe_wrap`ping multi-dimensional `Array`s around the -# internal storage. -function Base.resize!(elements::ElementContainer3D, capacity) - n_nodes = nnodes(elements) - n_variables = nvariables(elements) - @unpack _node_coordinates, _surface_flux_values, - inverse_jacobian, cell_ids = elements - - resize!(inverse_jacobian, capacity) - - resize!(_node_coordinates, 3 * n_nodes * n_nodes * n_nodes * capacity) - elements.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (3, n_nodes, n_nodes, n_nodes, capacity)) - - resize!(_surface_flux_values, n_variables * n_nodes * n_nodes * 2 * 3 * capacity) - elements.surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), - (n_variables, n_nodes, n_nodes, 2 * 3, - capacity)) - - resize!(cell_ids, capacity) - - return nothing -end - -function ElementContainer3D{RealT, uEltype}(capacity::Integer, n_variables, - n_nodes) where {RealT <: Real, - uEltype <: Real} - nan_RealT = convert(RealT, NaN) - nan_uEltype = convert(uEltype, NaN) - - # Initialize fields with defaults - inverse_jacobian = fill(nan_RealT, capacity) - - _node_coordinates = fill(nan_RealT, 3 * n_nodes * n_nodes * n_nodes * capacity) - node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (3, n_nodes, n_nodes, n_nodes, capacity)) - - _surface_flux_values = fill(nan_uEltype, - n_variables * n_nodes * n_nodes * 2 * 3 * capacity) - surface_flux_values = unsafe_wrap(Array, pointer(_surface_flux_values), - (n_variables, n_nodes, n_nodes, 2 * 3, capacity)) - - cell_ids = fill(typemin(Int), capacity) - - return ElementContainer3D{RealT, uEltype}(inverse_jacobian, node_coordinates, - surface_flux_values, cell_ids, - _node_coordinates, _surface_flux_values) -end - -# Return number of elements -nelements(elements::ElementContainer3D) = length(elements.cell_ids) -# TODO: Taal performance, 1:nelements(elements) vs. Base.OneTo(nelements(elements)) -""" - eachelement(elements::ElementContainer3D) - -Return an iterator over the indices that specify the location in relevant data structures -for the elements in `elements`. -In particular, not the elements themselves are returned. -""" -@inline eachelement(elements::ElementContainer3D) = Base.OneTo(nelements(elements)) -@inline Base.real(elements::ElementContainer3D) = eltype(elements.node_coordinates) - -# Create element container and initialize element data -function init_elements(cell_ids, mesh::TreeMesh3D, - equations::AbstractEquations{3}, - basis, ::Type{RealT}, - ::Type{uEltype}) where {RealT <: Real, uEltype <: Real} - # Initialize container - n_elements = length(cell_ids) - elements = ElementContainer3D{RealT, uEltype}(n_elements, nvariables(equations), - nnodes(basis)) - - init_elements!(elements, cell_ids, mesh, basis) - return elements -end - -function init_elements!(elements, cell_ids, mesh::TreeMesh3D, basis) - nodes = get_nodes(basis) - # Compute the length of the 1D reference interval by integrating - # the function with constant value unity on the corresponding - # element data type (using \circ) - reference_length = integrate(one ∘ eltype, nodes, basis) - # Compute the offset of the midpoint of the 1D reference interval - # (its difference from zero) - reference_offset = (first(nodes) + last(nodes)) / 2 - - # Store cell ids - elements.cell_ids .= cell_ids - - # Calculate inverse Jacobian and node coordinates - for element in eachelement(elements) - # Get cell id - cell_id = cell_ids[element] - - # Get cell length - dx = length_at_cell(mesh.tree, cell_id) - - # Calculate inverse Jacobian - jacobian = dx / reference_length - elements.inverse_jacobian[element] = inv(jacobian) - - # Calculate node coordinates - # Note that the `tree_coordinates` are the midpoints of the cells. - # Hence, we need to add an offset for `nodes` with a midpoint - # different from zero. - for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) - elements.node_coordinates[1, i, j, k, element] = (mesh.tree.coordinates[1, - cell_id] + - jacobian * (nodes[i] - - reference_offset)) - elements.node_coordinates[2, i, j, k, element] = (mesh.tree.coordinates[2, - cell_id] + - jacobian * (nodes[j] - - reference_offset)) - elements.node_coordinates[3, i, j, k, element] = (mesh.tree.coordinates[3, - cell_id] + - jacobian * (nodes[k] - - reference_offset)) - end + #! format: noindent + + # Container data structure (structure-of-arrays style) for DG elements + mutable struct ElementContainer3D{RealT <: Real, uEltype <: Real} <: AbstractContainer + inverse_jacobian::Vector{RealT} # [elements] + node_coordinates::Array{RealT, 5} # [orientation, i, j, k, elements] + surface_flux_values::Array{uEltype, 5} # [variables, i, j, direction, elements] + cell_ids::Vector{Int} # [elements] + # internal `resize!`able storage + _node_coordinates::Vector{RealT} + _surface_flux_values::Vector{uEltype} end - return elements -end - -# Container data structure (structure-of-arrays style) for DG interfaces -mutable struct InterfaceContainer3D{uEltype <: Real} <: AbstractContainer - u::Array{uEltype, 5} # [leftright, variables, i, j, interfaces] - neighbor_ids::Matrix{Int} # [leftright, interfaces] - orientations::Vector{Int} # [interfaces] - # internal `resize!`able storage - _u::Vector{uEltype} - _neighbor_ids::Vector{Int} -end - -nvariables(interfaces::InterfaceContainer3D) = size(interfaces.u, 2) -nnodes(interfaces::InterfaceContainer3D) = size(interfaces.u, 3) -Base.eltype(interfaces::InterfaceContainer3D) = eltype(interfaces.u) - -# See explanation of Base.resize! for the element container -function Base.resize!(interfaces::InterfaceContainer3D, capacity) - n_nodes = nnodes(interfaces) - n_variables = nvariables(interfaces) - @unpack _u, _neighbor_ids, orientations = interfaces - - resize!(_u, 2 * n_variables * n_nodes * n_nodes * capacity) - interfaces.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, n_nodes, capacity)) - - resize!(_neighbor_ids, 2 * capacity) - interfaces.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (2, capacity)) - - resize!(orientations, capacity) - - return nothing -end - -function InterfaceContainer3D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} - nan = convert(uEltype, NaN) - - # Initialize fields with defaults - _u = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) - u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, n_nodes, capacity)) - - _neighbor_ids = fill(typemin(Int), 2 * capacity) - neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (2, capacity)) - - orientations = fill(typemin(Int), capacity) - - return InterfaceContainer3D{uEltype}(u, neighbor_ids, orientations, - _u, _neighbor_ids) -end - -# Return number of interfaces -ninterfaces(interfaces::InterfaceContainer3D) = length(interfaces.orientations) - -# Create interface container and initialize interface data in `elements`. -function init_interfaces(cell_ids, mesh::TreeMesh3D, - elements::ElementContainer3D) - # Initialize container - n_interfaces = count_required_interfaces(mesh, cell_ids) - interfaces = InterfaceContainer3D{eltype(elements)}(n_interfaces, - nvariables(elements), - nnodes(elements)) - - # Connect elements with interfaces - init_interfaces!(interfaces, elements, mesh) - return interfaces -end - -# Count the number of interfaces that need to be created -function count_required_interfaces(mesh::TreeMesh3D, cell_ids) - count = 0 - - # Iterate over all cells - for cell_id in cell_ids - for direction in eachdirection(mesh.tree) - # Only count interfaces in positive direction to avoid double counting - if direction % 2 == 1 - continue - end + nvariables(elements::ElementContainer3D) = size(elements.surface_flux_values, 1) + nnodes(elements::ElementContainer3D) = size(elements.node_coordinates, 2) + Base.eltype(elements::ElementContainer3D) = eltype(elements.surface_flux_values) + + # Only one-dimensional `Array`s are `resize!`able in Julia. + # Hence, we use `Vector`s as internal storage and `resize!` + # them whenever needed. Then, we reuse the same memory by + # `unsafe_wrap`ping multi-dimensional `Array`s around the + # internal storage. + function Base.resize!(elements::ElementContainer3D, capacity) + n_nodes = nnodes(elements) + n_variables = nvariables(elements) + @unpack _node_coordinates, _surface_flux_values, + inverse_jacobian, cell_ids = elements + + resize!(inverse_jacobian, capacity) + + resize!(_node_coordinates, 3 * n_nodes * n_nodes * n_nodes * capacity) + elements.node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (3, n_nodes, n_nodes, n_nodes, capacity) + ) + + resize!(_surface_flux_values, n_variables * n_nodes * n_nodes * 2 * 3 * capacity) + elements.surface_flux_values = unsafe_wrap( + Array, pointer(_surface_flux_values), + ( + n_variables, n_nodes, n_nodes, 2 * 3, + capacity, + ) + ) + + resize!(cell_ids, capacity) + + return nothing + end - # If no neighbor exists, current cell is small or at boundary and thus we need a mortar - if !has_neighbor(mesh.tree, cell_id, direction) - continue - end + function ElementContainer3D{RealT, uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where { + RealT <: Real, + uEltype <: Real, + } + nan_RealT = convert(RealT, NaN) + nan_uEltype = convert(uEltype, NaN) + + # Initialize fields with defaults + inverse_jacobian = fill(nan_RealT, capacity) + + _node_coordinates = fill(nan_RealT, 3 * n_nodes * n_nodes * n_nodes * capacity) + node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (3, n_nodes, n_nodes, n_nodes, capacity) + ) + + _surface_flux_values = fill( + nan_uEltype, + n_variables * n_nodes * n_nodes * 2 * 3 * capacity + ) + surface_flux_values = unsafe_wrap( + Array, pointer(_surface_flux_values), + (n_variables, n_nodes, n_nodes, 2 * 3, capacity) + ) + + cell_ids = fill(typemin(Int), capacity) + + return ElementContainer3D{RealT, uEltype}( + inverse_jacobian, node_coordinates, + surface_flux_values, cell_ids, + _node_coordinates, _surface_flux_values + ) + end - # Skip if neighbor has children - neighbor_id = mesh.tree.neighbor_ids[direction, cell_id] - if has_children(mesh.tree, neighbor_id) - continue - end + # Return number of elements + nelements(elements::ElementContainer3D) = length(elements.cell_ids) + # TODO: Taal performance, 1:nelements(elements) vs. Base.OneTo(nelements(elements)) + """ + eachelement(elements::ElementContainer3D) + + Return an iterator over the indices that specify the location in relevant data structures + for the elements in `elements`. + In particular, not the elements themselves are returned. + """ + @inline eachelement(elements::ElementContainer3D) = Base.OneTo(nelements(elements)) + @inline Base.real(elements::ElementContainer3D) = eltype(elements.node_coordinates) + + # Create element container and initialize element data + function init_elements( + cell_ids, mesh::TreeMesh3D, + equations::AbstractEquations{3}, + basis, ::Type{RealT}, + ::Type{uEltype} + ) where {RealT <: Real, uEltype <: Real} + # Initialize container + n_elements = length(cell_ids) + elements = ElementContainer3D{RealT, uEltype}( + n_elements, nvariables(equations), + nnodes(basis) + ) + + init_elements!(elements, cell_ids, mesh, basis) + return elements + end + + function init_elements!(elements, cell_ids, mesh::TreeMesh3D, basis) + nodes = get_nodes(basis) + # Compute the length of the 1D reference interval by integrating + # the function with constant value unity on the corresponding + # element data type (using \circ) + reference_length = integrate(one ∘ eltype, nodes, basis) + # Compute the offset of the midpoint of the 1D reference interval + # (its difference from zero) + reference_offset = (first(nodes) + last(nodes)) / 2 - count += 1 + # Store cell ids + elements.cell_ids .= cell_ids + + # Calculate inverse Jacobian and node coordinates + for element in eachelement(elements) + # Get cell id + cell_id = cell_ids[element] + + # Get cell length + dx = length_at_cell(mesh.tree, cell_id) + + # Calculate inverse Jacobian + jacobian = dx / reference_length + elements.inverse_jacobian[element] = inv(jacobian) + + # Calculate node coordinates + # Note that the `tree_coordinates` are the midpoints of the cells. + # Hence, we need to add an offset for `nodes` with a midpoint + # different from zero. + for k in eachnode(basis), j in eachnode(basis), i in eachnode(basis) + elements.node_coordinates[1, i, j, k, element] = ( + mesh.tree.coordinates[ + 1, + cell_id, + ] + + jacobian * ( + nodes[i] - + reference_offset + ) + ) + elements.node_coordinates[2, i, j, k, element] = ( + mesh.tree.coordinates[ + 2, + cell_id, + ] + + jacobian * ( + nodes[j] - + reference_offset + ) + ) + elements.node_coordinates[3, i, j, k, element] = ( + mesh.tree.coordinates[ + 3, + cell_id, + ] + + jacobian * ( + nodes[k] - + reference_offset + ) + ) + end end - end - return count -end + return elements + end -# Initialize connectivity between elements and interfaces -function init_interfaces!(interfaces, elements, mesh::TreeMesh3D) - # Construct cell -> element mapping for easier algorithm implementation - tree = mesh.tree - c2e = zeros(Int, length(tree)) - for element in eachelement(elements) - c2e[elements.cell_ids[element]] = element + # Container data structure (structure-of-arrays style) for DG interfaces + mutable struct InterfaceContainer3D{uEltype <: Real} <: AbstractContainer + u::Array{uEltype, 5} # [leftright, variables, i, j, interfaces] + neighbor_ids::Matrix{Int} # [leftright, interfaces] + orientations::Vector{Int} # [interfaces] + # internal `resize!`able storage + _u::Vector{uEltype} + _neighbor_ids::Vector{Int} end - # Reset interface count - count = 0 + nvariables(interfaces::InterfaceContainer3D) = size(interfaces.u, 2) + nnodes(interfaces::InterfaceContainer3D) = size(interfaces.u, 3) + Base.eltype(interfaces::InterfaceContainer3D) = eltype(interfaces.u) - # Iterate over all elements to find neighbors and to connect via interfaces - for element in eachelement(elements) - # Get cell id - cell_id = elements.cell_ids[element] + # See explanation of Base.resize! for the element container + function Base.resize!(interfaces::InterfaceContainer3D, capacity) + n_nodes = nnodes(interfaces) + n_variables = nvariables(interfaces) + @unpack _u, _neighbor_ids, orientations = interfaces - # Loop over directions - for direction in eachdirection(mesh.tree) - # Only create interfaces in positive direction - if direction % 2 == 1 - continue - end + resize!(_u, 2 * n_variables * n_nodes * n_nodes * capacity) + interfaces.u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, n_nodes, capacity) + ) - # If no neighbor exists, current cell is small and thus we need a mortar - if !has_neighbor(mesh.tree, cell_id, direction) - continue - end + resize!(_neighbor_ids, 2 * capacity) + interfaces.neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (2, capacity) + ) - # Skip if neighbor has children - neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] - if has_children(mesh.tree, neighbor_cell_id) - continue - end + resize!(orientations, capacity) - # Create interface between elements (1 -> "left" of interface, 2 -> "right" of interface) - count += 1 - interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id] - interfaces.neighbor_ids[1, count] = element - - # Set orientation (x -> 1, y -> 2, z -> 3) - if direction in (1, 2) - interfaces.orientations[count] = 1 - elseif direction in (3, 4) - interfaces.orientations[count] = 2 - else - interfaces.orientations[count] = 3 - end - end + return nothing end - @assert count==ninterfaces(interfaces) ("Actual interface count ($count) does not match "* - "expectations $(ninterfaces(interfaces))") -end - -# Container data structure (structure-of-arrays style) for DG boundaries -mutable struct BoundaryContainer3D{RealT <: Real, uEltype <: Real} <: AbstractContainer - u::Array{uEltype, 5} # [leftright, variables, i, j, boundaries] - neighbor_ids::Vector{Int} # [boundaries] - orientations::Vector{Int} # [boundaries] - neighbor_sides::Vector{Int} # [boundaries] - node_coordinates::Array{RealT, 4} # [orientation, i, j, elements] - n_boundaries_per_direction::SVector{6, Int} # [direction] - # internal `resize!`able storage - _u::Vector{uEltype} - _node_coordinates::Vector{RealT} -end - -nvariables(boundaries::BoundaryContainer3D) = size(boundaries.u, 2) -nnodes(boundaries::BoundaryContainer3D) = size(boundaries.u, 3) -Base.eltype(boundaries::BoundaryContainer3D) = eltype(boundaries.u) - -# See explanation of Base.resize! for the element container -function Base.resize!(boundaries::BoundaryContainer3D, capacity) - n_nodes = nnodes(boundaries) - n_variables = nvariables(boundaries) - @unpack _u, _node_coordinates, - neighbor_ids, orientations, neighbor_sides = boundaries - - resize!(_u, 2 * n_variables * n_nodes * n_nodes * capacity) - boundaries.u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, n_nodes, capacity)) - - resize!(_node_coordinates, 3 * n_nodes * n_nodes * capacity) - boundaries.node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (3, n_nodes, n_nodes, capacity)) - - resize!(neighbor_ids, capacity) - - resize!(orientations, capacity) - - resize!(neighbor_sides, capacity) - - return nothing -end - -function BoundaryContainer3D{RealT, uEltype}(capacity::Integer, n_variables, - n_nodes) where {RealT <: Real, - uEltype <: Real} - nan_RealT = convert(RealT, NaN) - nan_uEltype = convert(uEltype, NaN) - - # Initialize fields with defaults - _u = fill(nan_uEltype, 2 * n_variables * n_nodes * n_nodes * capacity) - u = unsafe_wrap(Array, pointer(_u), - (2, n_variables, n_nodes, n_nodes, capacity)) - - neighbor_ids = fill(typemin(Int), capacity) - - orientations = fill(typemin(Int), capacity) - - neighbor_sides = fill(typemin(Int), capacity) - - _node_coordinates = fill(nan_RealT, 3 * n_nodes * n_nodes * capacity) - node_coordinates = unsafe_wrap(Array, pointer(_node_coordinates), - (3, n_nodes, n_nodes, capacity)) - - n_boundaries_per_direction = SVector(0, 0, 0, 0, 0, 0) - - return BoundaryContainer3D{RealT, uEltype}(u, neighbor_ids, orientations, - neighbor_sides, - node_coordinates, - n_boundaries_per_direction, - _u, _node_coordinates) -end - -# Return number of boundaries -nboundaries(boundaries::BoundaryContainer3D) = length(boundaries.orientations) - -# Create boundaries container and initialize boundary data in `elements`. -function init_boundaries(cell_ids, mesh::TreeMesh3D, - elements::ElementContainer3D) - # Initialize container - n_boundaries = count_required_boundaries(mesh, cell_ids) - boundaries = BoundaryContainer3D{real(elements), eltype(elements)}(n_boundaries, - nvariables(elements), - nnodes(elements)) - - # Connect elements with boundaries - init_boundaries!(boundaries, elements, mesh) - return boundaries -end - -# Count the number of boundaries that need to be created -function count_required_boundaries(mesh::TreeMesh3D, cell_ids) - count = 0 - - # Iterate over all cells - for cell_id in cell_ids - for direction in eachdirection(mesh.tree) - # If neighbor exists, current cell is not at a boundary - if has_neighbor(mesh.tree, cell_id, direction) - continue - end + function InterfaceContainer3D{uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where {uEltype <: Real} + nan = convert(uEltype, NaN) + + # Initialize fields with defaults + _u = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) + u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + _neighbor_ids = fill(typemin(Int), 2 * capacity) + neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (2, capacity) + ) + + orientations = fill(typemin(Int), capacity) + + return InterfaceContainer3D{uEltype}( + u, neighbor_ids, orientations, + _u, _neighbor_ids + ) + end - # If coarse neighbor exists, current cell is not at a boundary - if has_coarse_neighbor(mesh.tree, cell_id, direction) - continue - end + # Return number of interfaces + ninterfaces(interfaces::InterfaceContainer3D) = length(interfaces.orientations) + + # Create interface container and initialize interface data in `elements`. + function init_interfaces( + cell_ids, mesh::TreeMesh3D, + elements::ElementContainer3D + ) + # Initialize container + n_interfaces = count_required_interfaces(mesh, cell_ids) + interfaces = InterfaceContainer3D{eltype(elements)}( + n_interfaces, + nvariables(elements), + nnodes(elements) + ) + + # Connect elements with interfaces + init_interfaces!(interfaces, elements, mesh) + return interfaces + end - # No neighbor exists in this direction -> must be a boundary - count += 1 + # Count the number of interfaces that need to be created + function count_required_interfaces(mesh::TreeMesh3D, cell_ids) + count = 0 + + # Iterate over all cells + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # Only count interfaces in positive direction to avoid double counting + if direction % 2 == 1 + continue + end + + # If no neighbor exists, current cell is small or at boundary and thus we need a mortar + if !has_neighbor(mesh.tree, cell_id, direction) + continue + end + + # Skip if neighbor has children + neighbor_id = mesh.tree.neighbor_ids[direction, cell_id] + if has_children(mesh.tree, neighbor_id) + continue + end + + count += 1 + end end - end - return count -end + return count + end -# Initialize connectivity between elements and boundaries -function init_boundaries!(boundaries, elements, mesh::TreeMesh3D) - # Reset boundaries count - count = 0 + # Initialize connectivity between elements and interfaces + function init_interfaces!(interfaces, elements, mesh::TreeMesh3D) + # Construct cell -> element mapping for easier algorithm implementation + tree = mesh.tree + c2e = zeros(Int, length(tree)) + for element in eachelement(elements) + c2e[elements.cell_ids[element]] = element + end - # Initialize boundary counts - counts_per_direction = MVector(0, 0, 0, 0, 0, 0) + # Reset interface count + count = 0 - # OBS! Iterate over directions first, then over elements, and count boundaries in each direction - # Rationale: This way the boundaries are internally sorted by the directions -x, +x, -y etc., - # obviating the need to store the boundary condition to be applied explicitly. - # Loop over directions - for direction in eachdirection(mesh.tree) - # Iterate over all elements to find missing neighbors and to connect to boundaries + # Iterate over all elements to find neighbors and to connect via interfaces for element in eachelement(elements) # Get cell id cell_id = elements.cell_ids[element] - # If neighbor exists, current cell is not at a boundary - if has_neighbor(mesh.tree, cell_id, direction) - continue + # Loop over directions + for direction in eachdirection(mesh.tree) + # Only create interfaces in positive direction + if direction % 2 == 1 + continue + end + + # If no neighbor exists, current cell is small and thus we need a mortar + if !has_neighbor(mesh.tree, cell_id, direction) + continue + end + + # Skip if neighbor has children + neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] + if has_children(mesh.tree, neighbor_cell_id) + continue + end + + # Create interface between elements (1 -> "left" of interface, 2 -> "right" of interface) + count += 1 + interfaces.neighbor_ids[2, count] = c2e[neighbor_cell_id] + interfaces.neighbor_ids[1, count] = element + + # Set orientation (x -> 1, y -> 2, z -> 3) + if direction in (1, 2) + interfaces.orientations[count] = 1 + elseif direction in (3, 4) + interfaces.orientations[count] = 2 + else + interfaces.orientations[count] = 3 + end end + end - # If coarse neighbor exists, current cell is not at a boundary - if has_coarse_neighbor(mesh.tree, cell_id, direction) - continue - end + @assert count == ninterfaces(interfaces) ( + "Actual interface count ($count) does not match " * + "expectations $(ninterfaces(interfaces))" + ) + end - # Create boundary - count += 1 - counts_per_direction[direction] += 1 + # Container data structure (structure-of-arrays style) for DG boundaries + mutable struct BoundaryContainer3D{RealT <: Real, uEltype <: Real} <: AbstractContainer + u::Array{uEltype, 5} # [leftright, variables, i, j, boundaries] + neighbor_ids::Vector{Int} # [boundaries] + orientations::Vector{Int} # [boundaries] + neighbor_sides::Vector{Int} # [boundaries] + node_coordinates::Array{RealT, 4} # [orientation, i, j, elements] + n_boundaries_per_direction::SVector{6, Int} # [direction] + # internal `resize!`able storage + _u::Vector{uEltype} + _node_coordinates::Vector{RealT} + end - # Set neighbor element id - boundaries.neighbor_ids[count] = element + nvariables(boundaries::BoundaryContainer3D) = size(boundaries.u, 2) + nnodes(boundaries::BoundaryContainer3D) = size(boundaries.u, 3) + Base.eltype(boundaries::BoundaryContainer3D) = eltype(boundaries.u) - # Set neighbor side, which denotes the direction (1 -> negative, 2 -> positive) of the element - if iseven(direction) - boundaries.neighbor_sides[count] = 1 - else - boundaries.neighbor_sides[count] = 2 - end + # See explanation of Base.resize! for the element container + function Base.resize!(boundaries::BoundaryContainer3D, capacity) + n_nodes = nnodes(boundaries) + n_variables = nvariables(boundaries) + @unpack _u, _node_coordinates, + neighbor_ids, orientations, neighbor_sides = boundaries - # Set orientation (x -> 1, y -> 2) - if direction in (1, 2) - boundaries.orientations[count] = 1 - elseif direction in (3, 4) - boundaries.orientations[count] = 2 - else - boundaries.orientations[count] = 3 - end + resize!(_u, 2 * n_variables * n_nodes * n_nodes * capacity) + boundaries.u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, n_nodes, capacity) + ) - # Store node coordinates - enc = elements.node_coordinates - if direction == 1 # -x direction - boundaries.node_coordinates[:, :, :, count] .= enc[:, 1, :, :, element] - elseif direction == 2 # +x direction - boundaries.node_coordinates[:, :, :, count] .= enc[:, end, :, :, - element] - elseif direction == 3 # -y direction - boundaries.node_coordinates[:, :, :, count] .= enc[:, :, 1, :, element] - elseif direction == 4 # +y direction - boundaries.node_coordinates[:, :, :, count] .= enc[:, :, end, :, - element] - elseif direction == 5 # -z direction - boundaries.node_coordinates[:, :, :, count] .= enc[:, :, :, 1, element] - elseif direction == 6 # +z direction - boundaries.node_coordinates[:, :, :, count] .= enc[:, :, :, end, - element] - else - error("should not happen") - end - end - end + resize!(_node_coordinates, 3 * n_nodes * n_nodes * capacity) + boundaries.node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (3, n_nodes, n_nodes, capacity) + ) + + resize!(neighbor_ids, capacity) - @assert count==nboundaries(boundaries) ("Actual boundaries count ($count) does not match "* - "expectations $(nboundaries(boundaries))") - @assert sum(counts_per_direction) == count - - boundaries.n_boundaries_per_direction = SVector(counts_per_direction) - - return SVector(counts_per_direction) -end - -# Container data structure (structure-of-arrays style) for DG L2 mortars -# Positions/directions for orientations = 1, large_sides = 2: -# mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar -# /----------------------------\ /----------------------------\ -# | | | | | -# | upper, left | upper, right | | | -# | 3 | 4 | | | -# | | | | large | -# |-------------|--------------| | 5 | -# z | | | | | -# | lower, left | lower, right | | | -# ^ | 1 | 2 | | | -# | | | | | | -# | \----------------------------/ \----------------------------/ -# | -# ⋅----> y -# Left and right are always wrt to a coordinate direction: -# * left is always the negative direction -# * right is always the positive direction -# -# Left and right are used *both* for the numbering of the mortar faces *and* for the position of the -# elements with respect to the axis orthogonal to the mortar. -mutable struct L2MortarContainer3D{uEltype <: Real} <: AbstractContainer - u_upper_left::Array{uEltype, 5} # [leftright, variables, i, j, mortars] - u_upper_right::Array{uEltype, 5} # [leftright, variables, i, j, mortars] - u_lower_left::Array{uEltype, 5} # [leftright, variables, i, j, mortars] - u_lower_right::Array{uEltype, 5} # [leftright, variables, i, j, mortars] - neighbor_ids::Array{Int, 2} # [position, mortars] - # Large sides: left -> 1, right -> 2 - large_sides::Vector{Int} # [mortars] - orientations::Vector{Int} # [mortars] - # internal `resize!`able storage - _u_upper_left::Vector{uEltype} - _u_upper_right::Vector{uEltype} - _u_lower_left::Vector{uEltype} - _u_lower_right::Vector{uEltype} - _neighbor_ids::Vector{Int} -end - -nvariables(mortars::L2MortarContainer3D) = size(mortars.u_upper_left, 2) -nnodes(mortars::L2MortarContainer3D) = size(mortars.u_upper_left, 3) -Base.eltype(mortars::L2MortarContainer3D) = eltype(mortars.u_upper_left) - -# See explanation of Base.resize! for the element container -function Base.resize!(mortars::L2MortarContainer3D, capacity) - n_nodes = nnodes(mortars) - n_variables = nvariables(mortars) - @unpack _u_upper_left, _u_upper_right, _u_lower_left, _u_lower_right, - _neighbor_ids, large_sides, orientations = mortars - - resize!(_u_upper_left, 2 * n_variables * n_nodes * n_nodes * capacity) - mortars.u_upper_left = unsafe_wrap(Array, pointer(_u_upper_left), - (2, n_variables, n_nodes, n_nodes, capacity)) - - resize!(_u_upper_right, 2 * n_variables * n_nodes * n_nodes * capacity) - mortars.u_upper_right = unsafe_wrap(Array, pointer(_u_upper_right), - (2, n_variables, n_nodes, n_nodes, capacity)) - - resize!(_u_lower_left, 2 * n_variables * n_nodes * n_nodes * capacity) - mortars.u_lower_left = unsafe_wrap(Array, pointer(_u_lower_left), - (2, n_variables, n_nodes, n_nodes, capacity)) - - resize!(_u_lower_right, 2 * n_variables * n_nodes * n_nodes * capacity) - mortars.u_lower_right = unsafe_wrap(Array, pointer(_u_lower_right), - (2, n_variables, n_nodes, n_nodes, capacity)) - - resize!(_neighbor_ids, 5 * capacity) - mortars.neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (5, capacity)) - - resize!(large_sides, capacity) - - resize!(orientations, capacity) - - return nothing -end - -function L2MortarContainer3D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} - nan = convert(uEltype, NaN) - - # Initialize fields with defaults - _u_upper_left = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) - u_upper_left = unsafe_wrap(Array, pointer(_u_upper_left), - (2, n_variables, n_nodes, n_nodes, capacity)) - - _u_upper_right = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) - u_upper_right = unsafe_wrap(Array, pointer(_u_upper_right), - (2, n_variables, n_nodes, n_nodes, capacity)) - - _u_lower_left = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) - u_lower_left = unsafe_wrap(Array, pointer(_u_lower_left), - (2, n_variables, n_nodes, n_nodes, capacity)) - - _u_lower_right = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) - u_lower_right = unsafe_wrap(Array, pointer(_u_lower_right), - (2, n_variables, n_nodes, n_nodes, capacity)) - - _neighbor_ids = fill(typemin(Int), 5 * capacity) - neighbor_ids = unsafe_wrap(Array, pointer(_neighbor_ids), - (5, capacity)) - - large_sides = fill(typemin(Int), capacity) - - orientations = fill(typemin(Int), capacity) - - return L2MortarContainer3D{uEltype}(u_upper_left, u_upper_right, - u_lower_left, u_lower_right, - neighbor_ids, large_sides, orientations, - _u_upper_left, _u_upper_right, - _u_lower_left, _u_lower_right, - _neighbor_ids) -end - -# Return number of L2 mortars -nmortars(l2mortars::L2MortarContainer3D) = length(l2mortars.orientations) - -# Allow printing container contents -function Base.show(io::IO, ::MIME"text/plain", c::L2MortarContainer3D) - @nospecialize c # reduce precompilation time - - println(io, '*'^20) - for idx in CartesianIndices(c.u_upper_left) - println(io, "c.u_upper_left[$idx] = $(c.u_upper_left[idx])") + resize!(orientations, capacity) + + resize!(neighbor_sides, capacity) + + return nothing end - for idx in CartesianIndices(c.u_upper_right) - println(io, "c.u_upper_right[$idx] = $(c.u_upper_right[idx])") + + function BoundaryContainer3D{RealT, uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where { + RealT <: Real, + uEltype <: Real, + } + nan_RealT = convert(RealT, NaN) + nan_uEltype = convert(uEltype, NaN) + + # Initialize fields with defaults + _u = fill(nan_uEltype, 2 * n_variables * n_nodes * n_nodes * capacity) + u = unsafe_wrap( + Array, pointer(_u), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + neighbor_ids = fill(typemin(Int), capacity) + + orientations = fill(typemin(Int), capacity) + + neighbor_sides = fill(typemin(Int), capacity) + + _node_coordinates = fill(nan_RealT, 3 * n_nodes * n_nodes * capacity) + node_coordinates = unsafe_wrap( + Array, pointer(_node_coordinates), + (3, n_nodes, n_nodes, capacity) + ) + + n_boundaries_per_direction = SVector(0, 0, 0, 0, 0, 0) + + return BoundaryContainer3D{RealT, uEltype}( + u, neighbor_ids, orientations, + neighbor_sides, + node_coordinates, + n_boundaries_per_direction, + _u, _node_coordinates + ) end - for idx in CartesianIndices(c.u_lower_left) - println(io, "c.u_lower_left[$idx] = $(c.u_lower_left[idx])") + + # Return number of boundaries + nboundaries(boundaries::BoundaryContainer3D) = length(boundaries.orientations) + + # Create boundaries container and initialize boundary data in `elements`. + function init_boundaries( + cell_ids, mesh::TreeMesh3D, + elements::ElementContainer3D + ) + # Initialize container + n_boundaries = count_required_boundaries(mesh, cell_ids) + boundaries = BoundaryContainer3D{real(elements), eltype(elements)}( + n_boundaries, + nvariables(elements), + nnodes(elements) + ) + + # Connect elements with boundaries + init_boundaries!(boundaries, elements, mesh) + return boundaries end - for idx in CartesianIndices(c.u_lower_right) - println(io, "c.u_lower_right[$idx] = $(c.u_lower_right[idx])") + + # Count the number of boundaries that need to be created + function count_required_boundaries(mesh::TreeMesh3D, cell_ids) + count = 0 + + # Iterate over all cells + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # If neighbor exists, current cell is not at a boundary + if has_neighbor(mesh.tree, cell_id, direction) + continue + end + + # If coarse neighbor exists, current cell is not at a boundary + if has_coarse_neighbor(mesh.tree, cell_id, direction) + continue + end + + # No neighbor exists in this direction -> must be a boundary + count += 1 + end + end + + return count end - println(io, "transpose(c.neighbor_ids) = $(transpose(c.neighbor_ids))") - println(io, "c.large_sides = $(c.large_sides)") - println(io, "c.orientations = $(c.orientations)") - print(io, '*'^20) -end - -# Create mortar container and initialize mortar data in `elements`. -function init_mortars(cell_ids, mesh::TreeMesh3D, - elements::ElementContainer3D, - mortar::LobattoLegendreMortarL2) - # Initialize containers - n_mortars = count_required_mortars(mesh, cell_ids) - mortars = L2MortarContainer3D{eltype(elements)}(n_mortars, nvariables(elements), - nnodes(elements)) - - # Connect elements with mortars - init_mortars!(mortars, elements, mesh) - return mortars -end - -# Count the number of mortars that need to be created -function count_required_mortars(mesh::TreeMesh3D, cell_ids) - count = 0 - - # Iterate over all cells and count mortars from perspective of coarse cells - for cell_id in cell_ids + + # Initialize connectivity between elements and boundaries + function init_boundaries!(boundaries, elements, mesh::TreeMesh3D) + # Reset boundaries count + count = 0 + + # Initialize boundary counts + counts_per_direction = MVector(0, 0, 0, 0, 0, 0) + + # OBS! Iterate over directions first, then over elements, and count boundaries in each direction + # Rationale: This way the boundaries are internally sorted by the directions -x, +x, -y etc., + # obviating the need to store the boundary condition to be applied explicitly. + # Loop over directions for direction in eachdirection(mesh.tree) - # If no neighbor exists, cell is small with large neighbor or at boundary -> do nothing - if !has_neighbor(mesh.tree, cell_id, direction) - continue + # Iterate over all elements to find missing neighbors and to connect to boundaries + for element in eachelement(elements) + # Get cell id + cell_id = elements.cell_ids[element] + + # If neighbor exists, current cell is not at a boundary + if has_neighbor(mesh.tree, cell_id, direction) + continue + end + + # If coarse neighbor exists, current cell is not at a boundary + if has_coarse_neighbor(mesh.tree, cell_id, direction) + continue + end + + # Create boundary + count += 1 + counts_per_direction[direction] += 1 + + # Set neighbor element id + boundaries.neighbor_ids[count] = element + + # Set neighbor side, which denotes the direction (1 -> negative, 2 -> positive) of the element + if iseven(direction) + boundaries.neighbor_sides[count] = 1 + else + boundaries.neighbor_sides[count] = 2 + end + + # Set orientation (x -> 1, y -> 2) + if direction in (1, 2) + boundaries.orientations[count] = 1 + elseif direction in (3, 4) + boundaries.orientations[count] = 2 + else + boundaries.orientations[count] = 3 + end + + # Store node coordinates + enc = elements.node_coordinates + if direction == 1 # -x direction + boundaries.node_coordinates[:, :, :, count] .= enc[:, 1, :, :, element] + elseif direction == 2 # +x direction + boundaries.node_coordinates[:, :, :, count] .= enc[ + :, end, :, :, + element, + ] + elseif direction == 3 # -y direction + boundaries.node_coordinates[:, :, :, count] .= enc[:, :, 1, :, element] + elseif direction == 4 # +y direction + boundaries.node_coordinates[:, :, :, count] .= enc[ + :, :, end, :, + element, + ] + elseif direction == 5 # -z direction + boundaries.node_coordinates[:, :, :, count] .= enc[:, :, :, 1, element] + elseif direction == 6 # +z direction + boundaries.node_coordinates[:, :, :, count] .= enc[ + :, :, :, end, + element, + ] + else + error("should not happen") + end end + end - # If neighbor has no children, this is a conforming interface -> do nothing - neighbor_id = mesh.tree.neighbor_ids[direction, cell_id] - if !has_children(mesh.tree, neighbor_id) - continue - end + @assert count == nboundaries(boundaries) ( + "Actual boundaries count ($count) does not match " * + "expectations $(nboundaries(boundaries))" + ) + @assert sum(counts_per_direction) == count - count += 1 - end + boundaries.n_boundaries_per_direction = SVector(counts_per_direction) + + return SVector(counts_per_direction) end - return count -end + # Container data structure (structure-of-arrays style) for DG L2 mortars + # Positions/directions for orientations = 1, large_sides = 2: + # mortar is orthogonal to x-axis, large side is in positive coordinate direction wrt mortar + # /----------------------------\ /----------------------------\ + # | | | | | + # | upper, left | upper, right | | | + # | 3 | 4 | | | + # | | | | large | + # |-------------|--------------| | 5 | + # z | | | | | + # | lower, left | lower, right | | | + # ^ | 1 | 2 | | | + # | | | | | | + # | \----------------------------/ \----------------------------/ + # | + # ⋅----> y + # Left and right are always wrt to a coordinate direction: + # * left is always the negative direction + # * right is always the positive direction + # + # Left and right are used *both* for the numbering of the mortar faces *and* for the position of the + # elements with respect to the axis orthogonal to the mortar. + mutable struct L2MortarContainer3D{uEltype <: Real} <: AbstractContainer + u_upper_left::Array{uEltype, 5} # [leftright, variables, i, j, mortars] + u_upper_right::Array{uEltype, 5} # [leftright, variables, i, j, mortars] + u_lower_left::Array{uEltype, 5} # [leftright, variables, i, j, mortars] + u_lower_right::Array{uEltype, 5} # [leftright, variables, i, j, mortars] + neighbor_ids::Array{Int, 2} # [position, mortars] + # Large sides: left -> 1, right -> 2 + large_sides::Vector{Int} # [mortars] + orientations::Vector{Int} # [mortars] + # internal `resize!`able storage + _u_upper_left::Vector{uEltype} + _u_upper_right::Vector{uEltype} + _u_lower_left::Vector{uEltype} + _u_lower_right::Vector{uEltype} + _neighbor_ids::Vector{Int} + end -# Initialize connectivity between elements and mortars -function init_mortars!(mortars, elements, mesh::TreeMesh3D) - # Construct cell -> element mapping for easier algorithm implementation - tree = mesh.tree - c2e = zeros(Int, length(tree)) - for element in eachelement(elements) - c2e[elements.cell_ids[element]] = element + nvariables(mortars::L2MortarContainer3D) = size(mortars.u_upper_left, 2) + nnodes(mortars::L2MortarContainer3D) = size(mortars.u_upper_left, 3) + Base.eltype(mortars::L2MortarContainer3D) = eltype(mortars.u_upper_left) + + # See explanation of Base.resize! for the element container + function Base.resize!(mortars::L2MortarContainer3D, capacity) + n_nodes = nnodes(mortars) + n_variables = nvariables(mortars) + @unpack _u_upper_left, _u_upper_right, _u_lower_left, _u_lower_right, + _neighbor_ids, large_sides, orientations = mortars + + resize!(_u_upper_left, 2 * n_variables * n_nodes * n_nodes * capacity) + mortars.u_upper_left = unsafe_wrap( + Array, pointer(_u_upper_left), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + resize!(_u_upper_right, 2 * n_variables * n_nodes * n_nodes * capacity) + mortars.u_upper_right = unsafe_wrap( + Array, pointer(_u_upper_right), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + resize!(_u_lower_left, 2 * n_variables * n_nodes * n_nodes * capacity) + mortars.u_lower_left = unsafe_wrap( + Array, pointer(_u_lower_left), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + resize!(_u_lower_right, 2 * n_variables * n_nodes * n_nodes * capacity) + mortars.u_lower_right = unsafe_wrap( + Array, pointer(_u_lower_right), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + resize!(_neighbor_ids, 5 * capacity) + mortars.neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (5, capacity) + ) + + resize!(large_sides, capacity) + + resize!(orientations, capacity) + + return nothing end - # Reset interface count - count = 0 + function L2MortarContainer3D{uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where {uEltype <: Real} + nan = convert(uEltype, NaN) + + # Initialize fields with defaults + _u_upper_left = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) + u_upper_left = unsafe_wrap( + Array, pointer(_u_upper_left), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + _u_upper_right = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) + u_upper_right = unsafe_wrap( + Array, pointer(_u_upper_right), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + _u_lower_left = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) + u_lower_left = unsafe_wrap( + Array, pointer(_u_lower_left), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + _u_lower_right = fill(nan, 2 * n_variables * n_nodes * n_nodes * capacity) + u_lower_right = unsafe_wrap( + Array, pointer(_u_lower_right), + (2, n_variables, n_nodes, n_nodes, capacity) + ) + + _neighbor_ids = fill(typemin(Int), 5 * capacity) + neighbor_ids = unsafe_wrap( + Array, pointer(_neighbor_ids), + (5, capacity) + ) + + large_sides = fill(typemin(Int), capacity) + + orientations = fill(typemin(Int), capacity) + + return L2MortarContainer3D{uEltype}( + u_upper_left, u_upper_right, + u_lower_left, u_lower_right, + neighbor_ids, large_sides, orientations, + _u_upper_left, _u_upper_right, + _u_lower_left, _u_lower_right, + _neighbor_ids + ) + end - # Iterate over all elements to find neighbors and to connect via interfaces - for element in eachelement(elements) - # Get cell id - cell_id = elements.cell_ids[element] + # Return number of L2 mortars + nmortars(l2mortars::L2MortarContainer3D) = length(l2mortars.orientations) - for direction in eachdirection(mesh.tree) - # If no neighbor exists, cell is small with large neighbor -> do nothing - if !has_neighbor(mesh.tree, cell_id, direction) - continue - end + # Allow printing container contents + function Base.show(io::IO, ::MIME"text/plain", c::L2MortarContainer3D) + @nospecialize c # reduce precompilation time - # If neighbor has no children, this is a conforming interface -> do nothing - neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] - if !has_children(mesh.tree, neighbor_cell_id) - continue - end + println(io, '*'^20) + for idx in CartesianIndices(c.u_upper_left) + println(io, "c.u_upper_left[$idx] = $(c.u_upper_left[idx])") + end + for idx in CartesianIndices(c.u_upper_right) + println(io, "c.u_upper_right[$idx] = $(c.u_upper_right[idx])") + end + for idx in CartesianIndices(c.u_lower_left) + println(io, "c.u_lower_left[$idx] = $(c.u_lower_left[idx])") + end + for idx in CartesianIndices(c.u_lower_right) + println(io, "c.u_lower_right[$idx] = $(c.u_lower_right[idx])") + end + println(io, "transpose(c.neighbor_ids) = $(transpose(c.neighbor_ids))") + println(io, "c.large_sides = $(c.large_sides)") + println(io, "c.orientations = $(c.orientations)") + print(io, '*'^20) + end - # Create mortar between elements (3 possible orientations): - # - # mortar in x-direction: - # 1 -> small element in lower, left position (-y, -z) - # 2 -> small element in lower, right position (+y, -z) - # 3 -> small element in upper, left position (-y, +z) - # 4 -> small element in upper, right position (+y, +z) - # - # mortar in y-direction: - # 1 -> small element in lower, left position (-x, -z) - # 2 -> small element in lower, right position (+x, -z) - # 3 -> small element in upper, left position (-x, +z) - # 4 -> small element in upper, right position (+x, +z) - # - # mortar in z-direction: - # 1 -> small element in lower, left position (-x, -y) - # 2 -> small element in lower, right position (+x, -y) - # 3 -> small element in upper, left position (-x, +y) - # 4 -> small element in upper, right position (+x, +y) - # - # Always the case: - # 5 -> large element - # - count += 1 - mortars.neighbor_ids[5, count] = element - - # Directions are from the perspective of the large element - # ("Where are the small elements? Ah, in the ... direction!") - if direction == 1 # -x - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[2, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[4, - neighbor_cell_id]] - mortars.neighbor_ids[3, count] = c2e[mesh.tree.child_ids[6, - neighbor_cell_id]] - mortars.neighbor_ids[4, count] = c2e[mesh.tree.child_ids[8, - neighbor_cell_id]] - elseif direction == 2 # +x - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[1, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[3, - neighbor_cell_id]] - mortars.neighbor_ids[3, count] = c2e[mesh.tree.child_ids[5, - neighbor_cell_id]] - mortars.neighbor_ids[4, count] = c2e[mesh.tree.child_ids[7, - neighbor_cell_id]] - elseif direction == 3 # -y - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[3, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[4, - neighbor_cell_id]] - mortars.neighbor_ids[3, count] = c2e[mesh.tree.child_ids[7, - neighbor_cell_id]] - mortars.neighbor_ids[4, count] = c2e[mesh.tree.child_ids[8, - neighbor_cell_id]] - elseif direction == 4 # +y - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[1, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[2, - neighbor_cell_id]] - mortars.neighbor_ids[3, count] = c2e[mesh.tree.child_ids[5, - neighbor_cell_id]] - mortars.neighbor_ids[4, count] = c2e[mesh.tree.child_ids[6, - neighbor_cell_id]] - elseif direction == 5 # -z - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[5, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[6, - neighbor_cell_id]] - mortars.neighbor_ids[3, count] = c2e[mesh.tree.child_ids[7, - neighbor_cell_id]] - mortars.neighbor_ids[4, count] = c2e[mesh.tree.child_ids[8, - neighbor_cell_id]] - elseif direction == 6 # +z - mortars.neighbor_ids[1, count] = c2e[mesh.tree.child_ids[1, - neighbor_cell_id]] - mortars.neighbor_ids[2, count] = c2e[mesh.tree.child_ids[2, - neighbor_cell_id]] - mortars.neighbor_ids[3, count] = c2e[mesh.tree.child_ids[3, - neighbor_cell_id]] - mortars.neighbor_ids[4, count] = c2e[mesh.tree.child_ids[4, - neighbor_cell_id]] - else - error("should not happen") - end + # Create mortar container and initialize mortar data in `elements`. + function init_mortars( + cell_ids, mesh::TreeMesh3D, + elements::ElementContainer3D, + mortar::LobattoLegendreMortarL2 + ) + # Initialize containers + n_mortars = count_required_mortars(mesh, cell_ids) + mortars = L2MortarContainer3D{eltype(elements)}( + n_mortars, nvariables(elements), + nnodes(elements) + ) + + # Connect elements with mortars + init_mortars!(mortars, elements, mesh) + return mortars + end - # Set large side, which denotes the direction (1 -> negative, 2 -> positive) of the large side - if iseven(direction) - mortars.large_sides[count] = 1 - else - mortars.large_sides[count] = 2 + # Count the number of mortars that need to be created + function count_required_mortars(mesh::TreeMesh3D, cell_ids) + count = 0 + + # Iterate over all cells and count mortars from perspective of coarse cells + for cell_id in cell_ids + for direction in eachdirection(mesh.tree) + # If no neighbor exists, cell is small with large neighbor or at boundary -> do nothing + if !has_neighbor(mesh.tree, cell_id, direction) + continue + end + + # If neighbor has no children, this is a conforming interface -> do nothing + neighbor_id = mesh.tree.neighbor_ids[direction, cell_id] + if !has_children(mesh.tree, neighbor_id) + continue + end + + count += 1 end + end + + return count + end - # Set orientation (x -> 1, y -> 2, z -> 3) - if direction in (1, 2) - mortars.orientations[count] = 1 - elseif direction in (3, 4) - mortars.orientations[count] = 2 - else - mortars.orientations[count] = 3 + # Initialize connectivity between elements and mortars + function init_mortars!(mortars, elements, mesh::TreeMesh3D) + # Construct cell -> element mapping for easier algorithm implementation + tree = mesh.tree + c2e = zeros(Int, length(tree)) + for element in eachelement(elements) + c2e[elements.cell_ids[element]] = element + end + + # Reset interface count + count = 0 + + # Iterate over all elements to find neighbors and to connect via interfaces + for element in eachelement(elements) + # Get cell id + cell_id = elements.cell_ids[element] + + for direction in eachdirection(mesh.tree) + # If no neighbor exists, cell is small with large neighbor -> do nothing + if !has_neighbor(mesh.tree, cell_id, direction) + continue + end + + # If neighbor has no children, this is a conforming interface -> do nothing + neighbor_cell_id = mesh.tree.neighbor_ids[direction, cell_id] + if !has_children(mesh.tree, neighbor_cell_id) + continue + end + + # Create mortar between elements (3 possible orientations): + # + # mortar in x-direction: + # 1 -> small element in lower, left position (-y, -z) + # 2 -> small element in lower, right position (+y, -z) + # 3 -> small element in upper, left position (-y, +z) + # 4 -> small element in upper, right position (+y, +z) + # + # mortar in y-direction: + # 1 -> small element in lower, left position (-x, -z) + # 2 -> small element in lower, right position (+x, -z) + # 3 -> small element in upper, left position (-x, +z) + # 4 -> small element in upper, right position (+x, +z) + # + # mortar in z-direction: + # 1 -> small element in lower, left position (-x, -y) + # 2 -> small element in lower, right position (+x, -y) + # 3 -> small element in upper, left position (-x, +y) + # 4 -> small element in upper, right position (+x, +y) + # + # Always the case: + # 5 -> large element + # + count += 1 + mortars.neighbor_ids[5, count] = element + + # Directions are from the perspective of the large element + # ("Where are the small elements? Ah, in the ... direction!") + if direction == 1 # -x + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 2, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 4, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[3, count] = c2e[ + mesh.tree.child_ids[ + 6, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[4, count] = c2e[ + mesh.tree.child_ids[ + 8, + neighbor_cell_id, + ], + ] + elseif direction == 2 # +x + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 1, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 3, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[3, count] = c2e[ + mesh.tree.child_ids[ + 5, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[4, count] = c2e[ + mesh.tree.child_ids[ + 7, + neighbor_cell_id, + ], + ] + elseif direction == 3 # -y + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 3, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 4, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[3, count] = c2e[ + mesh.tree.child_ids[ + 7, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[4, count] = c2e[ + mesh.tree.child_ids[ + 8, + neighbor_cell_id, + ], + ] + elseif direction == 4 # +y + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 1, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 2, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[3, count] = c2e[ + mesh.tree.child_ids[ + 5, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[4, count] = c2e[ + mesh.tree.child_ids[ + 6, + neighbor_cell_id, + ], + ] + elseif direction == 5 # -z + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 5, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 6, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[3, count] = c2e[ + mesh.tree.child_ids[ + 7, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[4, count] = c2e[ + mesh.tree.child_ids[ + 8, + neighbor_cell_id, + ], + ] + elseif direction == 6 # +z + mortars.neighbor_ids[1, count] = c2e[ + mesh.tree.child_ids[ + 1, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[2, count] = c2e[ + mesh.tree.child_ids[ + 2, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[3, count] = c2e[ + mesh.tree.child_ids[ + 3, + neighbor_cell_id, + ], + ] + mortars.neighbor_ids[4, count] = c2e[ + mesh.tree.child_ids[ + 4, + neighbor_cell_id, + ], + ] + else + error("should not happen") + end + + # Set large side, which denotes the direction (1 -> negative, 2 -> positive) of the large side + if iseven(direction) + mortars.large_sides[count] = 1 + else + mortars.large_sides[count] = 2 + end + + # Set orientation (x -> 1, y -> 2, z -> 3) + if direction in (1, 2) + mortars.orientations[count] = 1 + elseif direction in (3, 4) + mortars.orientations[count] = 2 + else + mortars.orientations[count] = 3 + end end end - end - @assert count==nmortars(mortars) ("Actual mortar count ($count) does not match "* - "expectations $(nmortars(mortars))") -end + @assert count == nmortars(mortars) ( + "Actual mortar count ($count) does not match " * + "expectations $(nmortars(mortars))" + ) + end end # @muladd diff --git a/src/solvers/dgsem_tree/dg.jl b/src/solvers/dgsem_tree/dg.jl index febfb0b121..cd0dfc43ad 100644 --- a/src/solvers/dgsem_tree/dg.jl +++ b/src/solvers/dgsem_tree/dg.jl @@ -3,90 +3,94 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# du .= zero(eltype(du)) doesn't scale when using multiple threads. -# See https://github.com/trixi-framework/Trixi.jl/pull/924 for a performance comparison. -function reset_du!(du, dg, cache) - @threaded for element in eachelement(dg, cache) - du[.., element] .= zero(eltype(du)) + # du .= zero(eltype(du)) doesn't scale when using multiple threads. + # See https://github.com/trixi-framework/Trixi.jl/pull/924 for a performance comparison. + function reset_du!(du, dg, cache) + @threaded for element in eachelement(dg, cache) + du[.., element] .= zero(eltype(du)) + end + + return du end - return du -end - -# pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, dg, cache) -# -# Given blending factors `alpha` and the solver `dg`, fill -# `element_ids_dg` with the IDs of elements using a pure DG scheme and -# `element_ids_dgfv` with the IDs of elements using a blended DG-FV scheme. -function pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, dg::DG, - cache) - empty!(element_ids_dg) - empty!(element_ids_dgfv) - # For `Float64`, this gives 1.8189894035458565e-12 - # For `Float32`, this gives 1.1920929f-5 - RealT = eltype(alpha) - atol = max(100 * eps(RealT), eps(RealT)^convert(RealT, 0.75f0)) - - for element in eachelement(dg, cache) - # Clip blending factor for values close to zero (-> pure DG) - dg_only = isapprox(alpha[element], 0, atol = atol) - if dg_only - push!(element_ids_dg, element) - else - push!(element_ids_dgfv, element) + # pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, dg, cache) + # + # Given blending factors `alpha` and the solver `dg`, fill + # `element_ids_dg` with the IDs of elements using a pure DG scheme and + # `element_ids_dgfv` with the IDs of elements using a blended DG-FV scheme. + function pure_and_blended_element_ids!( + element_ids_dg, element_ids_dgfv, alpha, dg::DG, + cache + ) + empty!(element_ids_dg) + empty!(element_ids_dgfv) + # For `Float64`, this gives 1.8189894035458565e-12 + # For `Float32`, this gives 1.1920929f-5 + RealT = eltype(alpha) + atol = max(100 * eps(RealT), eps(RealT)^convert(RealT, 0.75f0)) + + for element in eachelement(dg, cache) + # Clip blending factor for values close to zero (-> pure DG) + dg_only = isapprox(alpha[element], 0, atol = atol) + if dg_only + push!(element_ids_dg, element) + else + push!(element_ids_dgfv, element) + end end - end - return nothing -end + return nothing + end -function volume_jacobian(element, mesh::TreeMesh, cache) - return inv(cache.elements.inverse_jacobian[element])^ndims(mesh) -end + function volume_jacobian(element, mesh::TreeMesh, cache) + return inv(cache.elements.inverse_jacobian[element])^ndims(mesh) + end -@inline function get_inverse_jacobian(inverse_jacobian, mesh::TreeMesh, - indices...) - element = last(indices) - return inverse_jacobian[element] -end + @inline function get_inverse_jacobian( + inverse_jacobian, mesh::TreeMesh, + indices... + ) + element = last(indices) + return inverse_jacobian[element] + end -# Indicators used for shock-capturing and AMR -include("indicators.jl") -include("indicators_1d.jl") -include("indicators_2d.jl") -include("indicators_3d.jl") + # Indicators used for shock-capturing and AMR + include("indicators.jl") + include("indicators_1d.jl") + include("indicators_2d.jl") + include("indicators_3d.jl") -# Container data structures -include("containers.jl") + # Container data structures + include("containers.jl") -# Dimension-agnostic parallel setup -include("dg_parallel.jl") + # Dimension-agnostic parallel setup + include("dg_parallel.jl") -# Helper structs for parabolic AMR -include("containers_viscous.jl") + # Helper structs for parabolic AMR + include("containers_viscous.jl") -# 1D DG implementation -include("dg_1d.jl") -include("dg_1d_parabolic.jl") + # 1D DG implementation + include("dg_1d.jl") + include("dg_1d_parabolic.jl") -# 2D DG implementation -include("dg_2d.jl") -include("dg_2d_parallel.jl") -include("dg_2d_parabolic.jl") + # 2D DG implementation + include("dg_2d.jl") + include("dg_2d_parallel.jl") + include("dg_2d_parabolic.jl") -# 3D DG implementation -include("dg_3d.jl") -include("dg_3d_parabolic.jl") + # 3D DG implementation + include("dg_3d.jl") + include("dg_3d_parabolic.jl") -# Auxiliary functions that are specialized on this solver -# as well as specialized implementations used to improve performance -include("dg_2d_compressible_euler.jl") -include("dg_3d_compressible_euler.jl") + # Auxiliary functions that are specialized on this solver + # as well as specialized implementations used to improve performance + include("dg_2d_compressible_euler.jl") + include("dg_3d_compressible_euler.jl") -# Subcell limiters -include("subcell_limiters.jl") -include("subcell_limiters_2d.jl") -include("dg_2d_subcell_limiters.jl") + # Subcell limiters + include("subcell_limiters.jl") + include("subcell_limiters_2d.jl") + include("dg_2d_subcell_limiters.jl") end # @muladd diff --git a/src/solvers/dgsem_tree/dg_1d.jl b/src/solvers/dgsem_tree/dg_1d.jl index 641c944d5f..c2b443980b 100644 --- a/src/solvers/dgsem_tree/dg_1d.jl +++ b/src/solvers/dgsem_tree/dg_1d.jl @@ -3,668 +3,794 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# everything related to a DG semidiscretization in 1D, -# currently limited to Lobatto-Legendre nodes - -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::TreeMesh{1}, equations, - dg::DG, RealT, uEltype) - # Get cells for which an element needs to be created (i.e. all leaf cells) - leaf_cell_ids = local_leaf_cells(mesh.tree) - - elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype) - - interfaces = init_interfaces(leaf_cell_ids, mesh, elements) - - boundaries = init_boundaries(leaf_cell_ids, mesh, elements) - - cache = (; elements, interfaces, boundaries) - - # Add specialized parts of the cache required to compute the volume integral etc. - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - - return cache -end - -# The methods below are specialized on the volume integral type -# and called from the basic `create_cache` method at the top. -function create_cache(mesh::Union{TreeMesh{1}, StructuredMesh{1}, P4estMesh{1}}, - equations, - volume_integral::VolumeIntegralFluxDifferencing, dg::DG, uEltype) - NamedTuple() -end - -function create_cache(mesh::Union{TreeMesh{1}, StructuredMesh{1}, P4estMesh{1}}, - equations, - volume_integral::VolumeIntegralShockCapturingHG, dg::DG, uEltype) - element_ids_dg = Int[] - element_ids_dgfv = Int[] - - cache = create_cache(mesh, equations, - VolumeIntegralFluxDifferencing(volume_integral.volume_flux_dg), - dg, uEltype) - - A2dp1_x = Array{uEltype, 2} - fstar1_L_threaded = A2dp1_x[A2dp1_x(undef, nvariables(equations), nnodes(dg) + 1) - for _ in 1:Threads.nthreads()] - fstar1_R_threaded = A2dp1_x[A2dp1_x(undef, nvariables(equations), nnodes(dg) + 1) - for _ in 1:Threads.nthreads()] - - return (; cache..., element_ids_dg, element_ids_dgfv, fstar1_L_threaded, - fstar1_R_threaded) -end - -function create_cache(mesh::Union{TreeMesh{1}, StructuredMesh{1}, P4estMesh{1}}, - equations, - volume_integral::VolumeIntegralPureLGLFiniteVolume, dg::DG, - uEltype) - A2dp1_x = Array{uEltype, 2} - fstar1_L_threaded = A2dp1_x[A2dp1_x(undef, nvariables(equations), nnodes(dg) + 1) - for _ in 1:Threads.nthreads()] - fstar1_R_threaded = A2dp1_x[A2dp1_x(undef, nvariables(equations), nnodes(dg) + 1) - for _ in 1:Threads.nthreads()] - - return (; fstar1_L_threaded, fstar1_R_threaded) -end - -# TODO: Taal discuss/refactor timer, allowing users to pass a custom timer? - -function rhs!(du, u, t, - mesh::TreeMesh{1}, equations, - initial_condition, boundary_conditions, source_terms::Source, - dg::DG, cache) where {Source} - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) - end + #! format: noindent - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache, u, mesh, equations, - dg.surface_integral, dg) - end + # everything related to a DG semidiscretization in 1D, + # currently limited to Lobatto-Legendre nodes + + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::TreeMesh{1}, equations, + dg::DG, RealT, uEltype + ) + # Get cells for which an element needs to be created (i.e. all leaf cells) + leaf_cell_ids = local_leaf_cells(mesh.tree) + + elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype) + + interfaces = init_interfaces(leaf_cell_ids, mesh, elements) + + boundaries = init_boundaries(leaf_cell_ids, mesh, elements) - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg, cache) + cache = (; elements, interfaces, boundaries) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + + return cache end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache, u, mesh, equations, - dg.surface_integral, dg) + # The methods below are specialized on the volume integral type + # and called from the basic `create_cache` method at the top. + function create_cache( + mesh::Union{TreeMesh{1}, StructuredMesh{1}, P4estMesh{1}}, + equations, + volume_integral::VolumeIntegralFluxDifferencing, dg::DG, uEltype + ) + NamedTuple() end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations, - dg.surface_integral, dg) + function create_cache( + mesh::Union{TreeMesh{1}, StructuredMesh{1}, P4estMesh{1}}, + equations, + volume_integral::VolumeIntegralShockCapturingHG, dg::DG, uEltype + ) + element_ids_dg = Int[] + element_ids_dgfv = Int[] + + cache = create_cache( + mesh, equations, + VolumeIntegralFluxDifferencing(volume_integral.volume_flux_dg), + dg, uEltype + ) + + A2dp1_x = Array{uEltype, 2} + fstar1_L_threaded = A2dp1_x[ + A2dp1_x(undef, nvariables(equations), nnodes(dg) + 1) + for _ in 1:Threads.nthreads() + ] + fstar1_R_threaded = A2dp1_x[ + A2dp1_x(undef, nvariables(equations), nnodes(dg) + 1) + for _ in 1:Threads.nthreads() + ] + + return (; + cache..., element_ids_dg, element_ids_dgfv, fstar1_L_threaded, + fstar1_R_threaded, + ) end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) + function create_cache( + mesh::Union{TreeMesh{1}, StructuredMesh{1}, P4estMesh{1}}, + equations, + volume_integral::VolumeIntegralPureLGLFiniteVolume, dg::DG, + uEltype + ) + A2dp1_x = Array{uEltype, 2} + fstar1_L_threaded = A2dp1_x[ + A2dp1_x(undef, nvariables(equations), nnodes(dg) + 1) + for _ in 1:Threads.nthreads() + ] + fstar1_R_threaded = A2dp1_x[ + A2dp1_x(undef, nvariables(equations), nnodes(dg) + 1) + for _ in 1:Threads.nthreads() + ] + + return (; fstar1_L_threaded, fstar1_R_threaded) end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + # TODO: Taal discuss/refactor timer, allowing users to pass a custom timer? + + function rhs!( + du, u, t, + mesh::TreeMesh{1}, equations, + initial_condition, boundary_conditions, source_terms::Source, + dg::DG, cache + ) where {Source} + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end + + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end - # Calculate source terms - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, equations, dg, cache) - end + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg, cache + ) + end + + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end + + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, t, boundary_conditions, mesh, equations, + dg.surface_integral, dg + ) + end + + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end + + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + + # Calculate source terms + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, equations, dg, cache) + end - return nothing -end - -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralWeakForm, - dg::DGSEM, cache) - @threaded for element in eachelement(dg, cache) - weak_form_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - dg, cache) + return nothing end - return nothing -end + function calc_volume_integral!( + du, u, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + nonconservative_terms, equations, + volume_integral::VolumeIntegralWeakForm, + dg::DGSEM, cache + ) + @threaded for element in eachelement(dg, cache) + weak_form_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + dg, cache + ) + end -#= + return nothing + end + + #= `weak_form_kernel!` is only implemented for conserved terms as non-conservative terms should always be discretized in conjunction with a flux-splitting scheme, see `flux_differencing_kernel!`. This treatment is required to achieve, e.g., entropy-stability or well-balancedness. See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-1765644064 =# -@inline function weak_form_kernel!(du, u, - element, mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - nonconservative_terms::False, equations, - dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_dhat = dg.basis - - for i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, element) - - flux1 = flux(u_node, 1, equations) - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[ii, i], flux1, - equations, dg, ii, element) + @inline function weak_form_kernel!( + du, u, + element, mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + nonconservative_terms::False, equations, + dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_dhat = dg.basis + + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + + flux1 = flux(u_node, 1, equations) + for ii in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[ii, i], flux1, + equations, dg, ii, element + ) + end end - end - return nothing -end - -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralFluxDifferencing, - dg::DGSEM, cache) - @threaded for element in eachelement(dg, cache) - flux_differencing_kernel!(du, u, element, mesh, nonconservative_terms, - equations, - volume_integral.volume_flux, dg, cache) + return nothing end -end - -@inline function flux_differencing_kernel!(du, u, - element, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - nonconservative_terms::False, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_split = dg.basis - - # Calculate volume integral in one element - for i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, element) - - # All diagonal entries of `derivative_split` are zero. Thus, we can skip - # the computation of the diagonal terms. In addition, we use the symmetry - # of the `volume_flux` to save half of the possible two-point flux - # computations. - - # x direction - for ii in (i + 1):nnodes(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, element) - flux1 = volume_flux(u_node, u_node_ii, 1, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[i, ii], flux1, - equations, dg, i, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[ii, i], flux1, - equations, dg, ii, element) + + function calc_volume_integral!( + du, u, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + nonconservative_terms, equations, + volume_integral::VolumeIntegralFluxDifferencing, + dg::DGSEM, cache + ) + @threaded for element in eachelement(dg, cache) + flux_differencing_kernel!( + du, u, element, mesh, nonconservative_terms, + equations, + volume_integral.volume_flux, dg, cache + ) end end -end - -@inline function flux_differencing_kernel!(du, u, - element, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - nonconservative_terms::True, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_split = dg.basis - symmetric_flux, nonconservative_flux = volume_flux - - # Apply the symmetric flux as usual - flux_differencing_kernel!(du, u, element, mesh, False(), equations, symmetric_flux, - dg, cache, alpha) - - # Calculate the remaining volume terms using the nonsymmetric generalized flux - for i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, element) - - # The diagonal terms are zero since the diagonal of `derivative_split` - # is zero. We ignore this for now. - - # x direction - integral_contribution = zero(u_node) - for ii in eachnode(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, element) - noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations) - integral_contribution = integral_contribution + - derivative_split[i, ii] * noncons_flux1 - end - # The factor 0.5 cancels the factor 2 in the flux differencing form - multiply_add_to_node_vars!(du, alpha * 0.5f0, integral_contribution, equations, - dg, i, element) + @inline function flux_differencing_kernel!( + du, u, + element, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + nonconservative_terms::False, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_split = dg.basis + + # Calculate volume integral in one element + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + + # All diagonal entries of `derivative_split` are zero. Thus, we can skip + # the computation of the diagonal terms. In addition, we use the symmetry + # of the `volume_flux` to save half of the possible two-point flux + # computations. + + # x direction + for ii in (i + 1):nnodes(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, element) + flux1 = volume_flux(u_node, u_node_ii, 1, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[i, ii], flux1, + equations, dg, i, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[ii, i], flux1, + equations, dg, ii, element + ) + end + end end -end - -# TODO: Taal dimension agnostic -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralShockCapturingHG, - dg::DGSEM, cache) - @unpack element_ids_dg, element_ids_dgfv = cache - @unpack volume_flux_dg, volume_flux_fv, indicator = volume_integral - - # Calculate blending factors α: u = u_DG * (1 - α) + u_FV * α - alpha = @trixi_timeit timer() "blending factors" indicator(u, mesh, equations, dg, - cache) - - # Determine element ids for DG-only and blended DG-FV volume integral - pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, dg, cache) - - # Loop over pure DG elements - @trixi_timeit timer() "pure DG" @threaded for idx_element in eachindex(element_ids_dg) - element = element_ids_dg[idx_element] - flux_differencing_kernel!(du, u, element, mesh, nonconservative_terms, - equations, - volume_flux_dg, dg, cache) + + @inline function flux_differencing_kernel!( + du, u, + element, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + nonconservative_terms::True, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_split = dg.basis + symmetric_flux, nonconservative_flux = volume_flux + + # Apply the symmetric flux as usual + flux_differencing_kernel!( + du, u, element, mesh, False(), equations, symmetric_flux, + dg, cache, alpha + ) + + # Calculate the remaining volume terms using the nonsymmetric generalized flux + for i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, element) + + # The diagonal terms are zero since the diagonal of `derivative_split` + # is zero. We ignore this for now. + + # x direction + integral_contribution = zero(u_node) + for ii in eachnode(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, element) + noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations) + integral_contribution = integral_contribution + + derivative_split[i, ii] * noncons_flux1 + end + + # The factor 0.5 cancels the factor 2 in the flux differencing form + multiply_add_to_node_vars!( + du, alpha * 0.5f0, integral_contribution, equations, + dg, i, element + ) + end end - # Loop over blended DG-FV elements - @trixi_timeit timer() "blended DG-FV" @threaded for idx_element in eachindex(element_ids_dgfv) - element = element_ids_dgfv[idx_element] - alpha_element = alpha[element] + # TODO: Taal dimension agnostic + function calc_volume_integral!( + du, u, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + nonconservative_terms, equations, + volume_integral::VolumeIntegralShockCapturingHG, + dg::DGSEM, cache + ) + @unpack element_ids_dg, element_ids_dgfv = cache + @unpack volume_flux_dg, volume_flux_fv, indicator = volume_integral + + # Calculate blending factors α: u = u_DG * (1 - α) + u_FV * α + alpha = @trixi_timeit timer() "blending factors" indicator( + u, mesh, equations, dg, + cache + ) + + # Determine element ids for DG-only and blended DG-FV volume integral + pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, dg, cache) + + # Loop over pure DG elements + @trixi_timeit timer() "pure DG" @threaded for idx_element in eachindex(element_ids_dg) + element = element_ids_dg[idx_element] + flux_differencing_kernel!( + du, u, element, mesh, nonconservative_terms, + equations, + volume_flux_dg, dg, cache + ) + end - # Calculate DG volume integral contribution - flux_differencing_kernel!(du, u, element, mesh, nonconservative_terms, - equations, - volume_flux_dg, dg, cache, 1 - alpha_element) + # Loop over blended DG-FV elements + @trixi_timeit timer() "blended DG-FV" @threaded for idx_element in eachindex(element_ids_dgfv) + element = element_ids_dgfv[idx_element] + alpha_element = alpha[element] + + # Calculate DG volume integral contribution + flux_differencing_kernel!( + du, u, element, mesh, nonconservative_terms, + equations, + volume_flux_dg, dg, cache, 1 - alpha_element + ) + + # Calculate FV volume integral contribution + fv_kernel!( + du, u, mesh, nonconservative_terms, equations, volume_flux_fv, + dg, cache, element, alpha_element + ) + end - # Calculate FV volume integral contribution - fv_kernel!(du, u, mesh, nonconservative_terms, equations, volume_flux_fv, - dg, cache, element, alpha_element) + return nothing end - return nothing -end - -# TODO: Taal dimension agnostic -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralPureLGLFiniteVolume, - dg::DGSEM, cache) - @unpack volume_flux_fv = volume_integral - - # Calculate LGL FV volume integral - @threaded for element in eachelement(dg, cache) - fv_kernel!(du, u, mesh, nonconservative_terms, equations, volume_flux_fv, - dg, cache, element, true) + # TODO: Taal dimension agnostic + function calc_volume_integral!( + du, u, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + nonconservative_terms, equations, + volume_integral::VolumeIntegralPureLGLFiniteVolume, + dg::DGSEM, cache + ) + @unpack volume_flux_fv = volume_integral + + # Calculate LGL FV volume integral + @threaded for element in eachelement(dg, cache) + fv_kernel!( + du, u, mesh, nonconservative_terms, equations, volume_flux_fv, + dg, cache, element, true + ) + end + + return nothing end - return nothing -end - -@inline function fv_kernel!(du, u, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - nonconservative_terms, equations, - volume_flux_fv, dg::DGSEM, cache, element, alpha = true) - @unpack fstar1_L_threaded, fstar1_R_threaded = cache - @unpack inverse_weights = dg.basis - - # Calculate FV two-point fluxes - fstar1_L = fstar1_L_threaded[Threads.threadid()] - fstar1_R = fstar1_R_threaded[Threads.threadid()] - calcflux_fv!(fstar1_L, fstar1_R, u, mesh, nonconservative_terms, equations, - volume_flux_fv, - dg, element, cache) - - # Calculate FV volume integral contribution - for i in eachnode(dg) - for v in eachvariable(equations) - du[v, i, element] += (alpha * - (inverse_weights[i] * - (fstar1_L[v, i + 1] - fstar1_R[v, i]))) + @inline function fv_kernel!( + du, u, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + nonconservative_terms, equations, + volume_flux_fv, dg::DGSEM, cache, element, alpha = true + ) + @unpack fstar1_L_threaded, fstar1_R_threaded = cache + @unpack inverse_weights = dg.basis + + # Calculate FV two-point fluxes + fstar1_L = fstar1_L_threaded[Threads.threadid()] + fstar1_R = fstar1_R_threaded[Threads.threadid()] + calcflux_fv!( + fstar1_L, fstar1_R, u, mesh, nonconservative_terms, equations, + volume_flux_fv, + dg, element, cache + ) + + # Calculate FV volume integral contribution + for i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, element] += ( + alpha * + ( + inverse_weights[i] * + (fstar1_L[v, i + 1] - fstar1_R[v, i]) + ) + ) + end end - end - return nothing -end - -@inline function calcflux_fv!(fstar1_L, fstar1_R, u::AbstractArray{<:Any, 3}, - mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - nonconservative_terms::False, - equations, volume_flux_fv, dg::DGSEM, element, cache) - fstar1_L[:, 1] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1] .= zero(eltype(fstar1_R)) - - for i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, element) - u_rr = get_node_vars(u, equations, dg, i, element) - flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction - set_node_vars!(fstar1_L, flux, equations, dg, i) - set_node_vars!(fstar1_R, flux, equations, dg, i) + return nothing end - return nothing -end - -@inline function calcflux_fv!(fstar1_L, fstar1_R, u::AbstractArray{<:Any, 3}, - mesh::TreeMesh{1}, - nonconservative_terms::True, - equations, volume_flux_fv, dg::DGSEM, element, cache) - volume_flux, nonconservative_flux = volume_flux_fv - - fstar1_L[:, 1] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1] .= zero(eltype(fstar1_R)) - - for i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, element) - u_rr = get_node_vars(u, equations, dg, i, element) - - # Compute conservative part - f1 = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction - - # Compute nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - f1_L = f1 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations) - f1_R = f1 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations) - - # Copy to temporary storage - set_node_vars!(fstar1_L, f1_L, equations, dg, i) - set_node_vars!(fstar1_R, f1_R, equations, dg, i) + @inline function calcflux_fv!( + fstar1_L, fstar1_R, u::AbstractArray{<:Any, 3}, + mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + nonconservative_terms::False, + equations, volume_flux_fv, dg::DGSEM, element, cache + ) + fstar1_L[:, 1] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1] .= zero(eltype(fstar1_R)) + + for i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, element) + u_rr = get_node_vars(u, equations, dg, i, element) + flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction + set_node_vars!(fstar1_L, flux, equations, dg, i) + set_node_vars!(fstar1_R, flux, equations, dg, i) + end + + return nothing end - return nothing -end + @inline function calcflux_fv!( + fstar1_L, fstar1_R, u::AbstractArray{<:Any, 3}, + mesh::TreeMesh{1}, + nonconservative_terms::True, + equations, volume_flux_fv, dg::DGSEM, element, cache + ) + volume_flux, nonconservative_flux = volume_flux_fv -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache, u, - mesh::TreeMesh{1}, equations, surface_integral, dg::DG) - @unpack interfaces = cache - @unpack neighbor_ids = interfaces - interfaces_u = interfaces.u + fstar1_L[:, 1] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1] .= zero(eltype(fstar1_R)) - @threaded for interface in eachinterface(dg, cache) - left_element = neighbor_ids[1, interface] - right_element = neighbor_ids[2, interface] + for i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, element) + u_rr = get_node_vars(u, equations, dg, i, element) - # interface in x-direction - for v in eachvariable(equations) - interfaces_u[1, v, interface] = u[v, nnodes(dg), left_element] - interfaces_u[2, v, interface] = u[v, 1, right_element] - end - end + # Compute conservative part + f1 = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction - return nothing -end - -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{1}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u, neighbor_ids, orientations = cache.interfaces - - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] - - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 - - # Call pointwise Riemann solver - u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface) - flux = surface_flux(u_ll, u_rr, orientations[interface], equations) - - # Copy flux to left and right element storage - for v in eachvariable(equations) - surface_flux_values[v, left_direction, left_id] = flux[v] - surface_flux_values[v, right_direction, right_id] = flux[v] - end - end -end - -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{1}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache) - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack u, neighbor_ids, orientations = cache.interfaces - - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] - - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 - - # Call pointwise Riemann solver - orientation = orientations[interface] - u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface) - flux = surface_flux(u_ll, u_rr, orientation, equations) - - # Compute both nonconservative fluxes - noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations) - noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations) - - # Copy flux to left and right element storage - for v in eachvariable(equations) + # Compute nonconservative part # Note the factor 0.5 necessary for the nonconservative fluxes based on # the interpretation of global SBP operators coupled discontinuously via # central fluxes/SATs - surface_flux_values[v, left_direction, left_id] = flux[v] + - 0.5f0 * noncons_left[v] - surface_flux_values[v, right_direction, right_id] = flux[v] + - 0.5f0 * noncons_right[v] + f1_L = f1 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations) + f1_R = f1 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations) + + # Copy to temporary storage + set_node_vars!(fstar1_L, f1_L, equations, dg, i) + set_node_vars!(fstar1_R, f1_R, equations, dg, i) end - end - return nothing -end + return nothing + end -function prolong2boundaries!(cache, u, - mesh::TreeMesh{1}, equations, surface_integral, dg::DG) - @unpack boundaries = cache - @unpack neighbor_sides = boundaries + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache, u, + mesh::TreeMesh{1}, equations, surface_integral, dg::DG + ) + @unpack interfaces = cache + @unpack neighbor_ids = interfaces + interfaces_u = interfaces.u - @threaded for boundary in eachboundary(dg, cache) - element = boundaries.neighbor_ids[boundary] + @threaded for interface in eachinterface(dg, cache) + left_element = neighbor_ids[1, interface] + right_element = neighbor_ids[2, interface] - # boundary in x-direction - if neighbor_sides[boundary] == 1 - # element in -x direction of boundary + # interface in x-direction for v in eachvariable(equations) - boundaries.u[1, v, boundary] = u[v, nnodes(dg), element] + interfaces_u[1, v, interface] = u[v, nnodes(dg), left_element] + interfaces_u[2, v, interface] = u[v, 1, right_element] end - else # Element in +x direction of boundary + end + + return nothing + end + + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{1}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u, neighbor_ids, orientations = cache.interfaces + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 + + # Call pointwise Riemann solver + u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface) + flux = surface_flux(u_ll, u_rr, orientations[interface], equations) + + # Copy flux to left and right element storage for v in eachvariable(equations) - boundaries.u[2, v, boundary] = u[v, 1, element] + surface_flux_values[v, left_direction, left_id] = flux[v] + surface_flux_values[v, right_direction, right_id] = flux[v] end end end - return nothing -end - -# TODO: Taal dimension agnostic -function calc_boundary_flux!(cache, t, boundary_condition::BoundaryConditionPeriodic, - mesh::TreeMesh{1}, equations, surface_integral, dg::DG) - @assert isempty(eachboundary(dg, cache)) -end - -function calc_boundary_flux!(cache, t, boundary_conditions::NamedTuple, - mesh::TreeMesh{1}, equations, surface_integral, dg::DG) - @unpack surface_flux_values = cache.elements - @unpack n_boundaries_per_direction = cache.boundaries - - # Calculate indices - lasts = accumulate(+, n_boundaries_per_direction) - firsts = lasts - n_boundaries_per_direction .+ 1 - - # Calc boundary fluxes in each direction - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[1], - have_nonconservative_terms(equations), equations, - surface_integral, dg, cache, - 1, firsts[1], lasts[1]) - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[2], - have_nonconservative_terms(equations), equations, - surface_integral, dg, cache, - 2, firsts[2], lasts[2]) -end - -function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 3}, - t, - boundary_condition, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - direction, first_boundary, last_boundary) - @unpack surface_flux = surface_integral - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] - - # Get boundary flux - u_ll, u_rr = get_surface_node_vars(u, equations, dg, boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - u_inner = u_ll - else # Element is on the right, boundary on the left - u_inner = u_rr - end - x = get_node_coords(node_coordinates, equations, dg, boundary) - flux = boundary_condition(u_inner, orientations[boundary], direction, x, t, - surface_flux, - equations) - - # Copy flux to left and right element storage - for v in eachvariable(equations) - surface_flux_values[v, direction, neighbor] = flux[v] + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{1}, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache + ) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack u, neighbor_ids, orientations = cache.interfaces + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 + + # Call pointwise Riemann solver + orientation = orientations[interface] + u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface) + flux = surface_flux(u_ll, u_rr, orientation, equations) + + # Compute both nonconservative fluxes + noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations) + noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations) + + # Copy flux to left and right element storage + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[v, left_direction, left_id] = flux[v] + + 0.5f0 * noncons_left[v] + surface_flux_values[v, right_direction, right_id] = flux[v] + + 0.5f0 * noncons_right[v] + end end + + return nothing end - return nothing -end - -function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 3}, - t, - boundary_condition, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache, - direction, first_boundary, last_boundary) - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] - - # Get boundary flux - u_ll, u_rr = get_surface_node_vars(u, equations, dg, boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - u_inner = u_ll - else # Element is on the right, boundary on the left - u_inner = u_rr - end - x = get_node_coords(node_coordinates, equations, dg, boundary) - flux = boundary_condition(u_inner, orientations[boundary], direction, x, t, - surface_flux, - equations) - noncons_flux = boundary_condition(u_inner, orientations[boundary], direction, x, - t, nonconservative_flux, - equations) - - # Copy flux to left and right element storage - for v in eachvariable(equations) - surface_flux_values[v, direction, neighbor] = flux[v] + - 0.5f0 * noncons_flux[v] + function prolong2boundaries!( + cache, u, + mesh::TreeMesh{1}, equations, surface_integral, dg::DG + ) + @unpack boundaries = cache + @unpack neighbor_sides = boundaries + + @threaded for boundary in eachboundary(dg, cache) + element = boundaries.neighbor_ids[boundary] + + # boundary in x-direction + if neighbor_sides[boundary] == 1 + # element in -x direction of boundary + for v in eachvariable(equations) + boundaries.u[1, v, boundary] = u[v, nnodes(dg), element] + end + else # Element in +x direction of boundary + for v in eachvariable(equations) + boundaries.u[2, v, boundary] = u[v, 1, element] + end + end end + + return nothing end - return nothing -end - -function calc_surface_integral!(du, u, mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - equations, surface_integral, dg::DGSEM, cache) - @unpack boundary_interpolation = dg.basis - @unpack surface_flux_values = cache.elements - - # Note that all fluxes have been computed with outward-pointing normal vectors. - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` to let `@muladd` turn these - # into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] - @threaded for element in eachelement(dg, cache) - for v in eachvariable(equations) - # surface at -x - du[v, 1, element] = (du[v, 1, element] - - surface_flux_values[v, 1, element] * factor_1) - - # surface at +x - du[v, nnodes(dg), element] = (du[v, nnodes(dg), element] + - surface_flux_values[v, 2, element] * factor_2) - end + # TODO: Taal dimension agnostic + function calc_boundary_flux!( + cache, t, boundary_condition::BoundaryConditionPeriodic, + mesh::TreeMesh{1}, equations, surface_integral, dg::DG + ) + @assert isempty(eachboundary(dg, cache)) end - return nothing -end + function calc_boundary_flux!( + cache, t, boundary_conditions::NamedTuple, + mesh::TreeMesh{1}, equations, surface_integral, dg::DG + ) + @unpack surface_flux_values = cache.elements + @unpack n_boundaries_per_direction = cache.boundaries + + # Calculate indices + lasts = accumulate(+, n_boundaries_per_direction) + firsts = lasts - n_boundaries_per_direction .+ 1 + + # Calc boundary fluxes in each direction + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[1], + have_nonconservative_terms(equations), equations, + surface_integral, dg, cache, + 1, firsts[1], lasts[1] + ) + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[2], + have_nonconservative_terms(equations), equations, + surface_integral, dg, cache, + 2, firsts[2], lasts[2] + ) + end -function apply_jacobian!(du, mesh::Union{TreeMesh{1}, StructuredMesh{1}}, - equations, dg::DG, cache) - @unpack inverse_jacobian = cache.elements + function calc_boundary_flux_by_direction!( + surface_flux_values::AbstractArray{<:Any, 3}, + t, + boundary_condition, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + direction, first_boundary, last_boundary + ) + @unpack surface_flux = surface_integral + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] + + # Get boundary flux + u_ll, u_rr = get_surface_node_vars(u, equations, dg, boundary) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + u_inner = u_ll + else # Element is on the right, boundary on the left + u_inner = u_rr + end + x = get_node_coords(node_coordinates, equations, dg, boundary) + flux = boundary_condition( + u_inner, orientations[boundary], direction, x, t, + surface_flux, + equations + ) + + # Copy flux to left and right element storage + for v in eachvariable(equations) + surface_flux_values[v, direction, neighbor] = flux[v] + end + end - @threaded for element in eachelement(dg, cache) - factor = -inverse_jacobian[element] + return nothing + end - for i in eachnode(dg) + function calc_boundary_flux_by_direction!( + surface_flux_values::AbstractArray{<:Any, 3}, + t, + boundary_condition, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache, + direction, first_boundary, last_boundary + ) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] + + # Get boundary flux + u_ll, u_rr = get_surface_node_vars(u, equations, dg, boundary) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + u_inner = u_ll + else # Element is on the right, boundary on the left + u_inner = u_rr + end + x = get_node_coords(node_coordinates, equations, dg, boundary) + flux = boundary_condition( + u_inner, orientations[boundary], direction, x, t, + surface_flux, + equations + ) + noncons_flux = boundary_condition( + u_inner, orientations[boundary], direction, x, + t, nonconservative_flux, + equations + ) + + # Copy flux to left and right element storage for v in eachvariable(equations) - du[v, i, element] *= factor + surface_flux_values[v, direction, neighbor] = flux[v] + + 0.5f0 * noncons_flux[v] end end + + return nothing end - return nothing -end + function calc_surface_integral!( + du, u, mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + equations, surface_integral, dg::DGSEM, cache + ) + @unpack boundary_interpolation = dg.basis + @unpack surface_flux_values = cache.elements + + # Note that all fluxes have been computed with outward-pointing normal vectors. + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for v in eachvariable(equations) + # surface at -x + du[v, 1, element] = ( + du[v, 1, element] - + surface_flux_values[v, 1, element] * factor_1 + ) + + # surface at +x + du[v, nnodes(dg), element] = ( + du[v, nnodes(dg), element] + + surface_flux_values[v, 2, element] * factor_2 + ) + end + end -# TODO: Taal dimension agnostic -function calc_sources!(du, u, t, source_terms::Nothing, - equations::AbstractEquations{1}, dg::DG, cache) - return nothing -end + return nothing + end -function calc_sources!(du, u, t, source_terms, - equations::AbstractEquations{1}, dg::DG, cache) - @unpack node_coordinates = cache.elements + function apply_jacobian!( + du, mesh::Union{TreeMesh{1}, StructuredMesh{1}}, + equations, dg::DG, cache + ) + @unpack inverse_jacobian = cache.elements - @threaded for element in eachelement(dg, cache) - for i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, element) - x_local = get_node_coords(node_coordinates, equations, dg, - i, element) - du_local = source_terms(u_local, x_local, t, equations) - add_to_node_vars!(du, du_local, equations, dg, i, element) + @threaded for element in eachelement(dg, cache) + factor = -inverse_jacobian[element] + + for i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, element] *= factor + end + end end + + return nothing end - return nothing -end + # TODO: Taal dimension agnostic + function calc_sources!( + du, u, t, source_terms::Nothing, + equations::AbstractEquations{1}, dg::DG, cache + ) + return nothing + end + + function calc_sources!( + du, u, t, source_terms, + equations::AbstractEquations{1}, dg::DG, cache + ) + @unpack node_coordinates = cache.elements + + @threaded for element in eachelement(dg, cache) + for i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, element) + x_local = get_node_coords( + node_coordinates, equations, dg, + i, element + ) + du_local = source_terms(u_local, x_local, t, equations) + add_to_node_vars!(du, du_local, equations, dg, i, element) + end + end + + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_tree/dg_1d_parabolic.jl b/src/solvers/dgsem_tree/dg_1d_parabolic.jl index fb96e0535a..570e11756e 100644 --- a/src/solvers/dgsem_tree/dg_1d_parabolic.jl +++ b/src/solvers/dgsem_tree/dg_1d_parabolic.jl @@ -3,444 +3,206 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This file collects all methods that have been updated to work with parabolic systems of equations -# -# assumptions: parabolic terms are of the form div(f(u, grad(u))) and -# will be discretized first order form as follows: -# 1. compute grad(u) -# 2. compute f(u, grad(u)) -# 3. compute div(f(u, grad(u))) (i.e., the "regular" rhs! call) -# boundary conditions will be applied to both grad(u) and div(f(u, grad(u))). -function rhs_parabolic!(du, u, t, mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - initial_condition, boundary_conditions_parabolic, source_terms, - dg::DG, parabolic_scheme, cache, cache_parabolic) - @unpack viscous_container = cache_parabolic - @unpack u_transformed, gradients, flux_viscous = viscous_container - - # Convert conservative variables to a form more suitable for viscous flux calculations - @trixi_timeit timer() "transform variables" begin - transform_variables!(u_transformed, u, mesh, equations_parabolic, - dg, parabolic_scheme, cache, cache_parabolic) - end - - # Compute the gradients of the transformed variables - @trixi_timeit timer() "calculate gradient" begin - calc_gradient!(gradients, u_transformed, t, mesh, equations_parabolic, - boundary_conditions_parabolic, dg, cache, cache_parabolic) - end - - # Compute and store the viscous fluxes - @trixi_timeit timer() "calculate viscous fluxes" begin - calc_viscous_fluxes!(flux_viscous, gradients, u_transformed, mesh, - equations_parabolic, dg, cache, cache_parabolic) - end + #! format: noindent - # The remainder of this function is essentially a regular rhs! for - # parabolic equations (i.e., it computes the divergence of the viscous fluxes) + # This file collects all methods that have been updated to work with parabolic systems of equations # - # OBS! In `calc_viscous_fluxes!`, the viscous flux values at the volume nodes of each element have - # been computed and stored in `fluxes_viscous`. In the following, we *reuse* (abuse) the - # `interfaces` and `boundaries` containers in `cache_parabolic` to interpolate and store the - # *fluxes* at the element surfaces, as opposed to interpolating and storing the *solution* (as it - # is done in the hyperbolic operator). That is, `interfaces.u`/`boundaries.u` store *viscous flux values* - # and *not the solution*. The advantage is that a) we do not need to allocate more storage, b) we - # do not need to recreate the existing data structure only with a different name, and c) we do not - # need to interpolate solutions *and* gradients to the surfaces. - - # TODO: parabolic; reconsider current data structure reuse strategy - - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, flux_viscous, mesh, equations_parabolic, dg, cache) - end - - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache_parabolic, flux_viscous, mesh, equations_parabolic, - dg.surface_integral, dg, cache) - end - - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache_parabolic.elements.surface_flux_values, mesh, - equations_parabolic, dg, cache_parabolic) - end - - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache_parabolic, flux_viscous, mesh, equations_parabolic, - dg.surface_integral, dg, cache) - end - - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux_divergence!(cache_parabolic, t, - boundary_conditions_parabolic, mesh, - equations_parabolic, - dg.surface_integral, dg) - end - - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations_parabolic, - dg.surface_integral, dg, cache_parabolic) - end - - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" begin - apply_jacobian_parabolic!(du, mesh, equations_parabolic, dg, cache_parabolic) - end - - return nothing -end - -# Transform solution variables prior to taking the gradient -# (e.g., conservative to primitive variables). Defaults to doing nothing. -# TODO: can we avoid copying data? -function transform_variables!(u_transformed, u, mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, parabolic_scheme, cache, cache_parabolic) - transformation = gradient_variable_transformation(equations_parabolic) - - @threaded for element in eachelement(dg, cache) - # Calculate volume terms in one element - for i in eachnode(dg) - u_node = get_node_vars(u, equations_parabolic, dg, i, element) - u_transformed_node = transformation(u_node, equations_parabolic) - set_node_vars!(u_transformed, u_transformed_node, equations_parabolic, dg, - i, element) - end - end -end - -# This is the version used when calculating the divergence of the viscous fluxes -function calc_volume_integral!(du, flux_viscous, - mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - dg::DGSEM, cache) - @unpack derivative_dhat = dg.basis - - @threaded for element in eachelement(dg, cache) - # Calculate volume terms in one element - for i in eachnode(dg) - flux_1_node = get_node_vars(flux_viscous, equations_parabolic, dg, i, - element) - - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[ii, i], flux_1_node, - equations_parabolic, dg, ii, element) - end + # assumptions: parabolic terms are of the form div(f(u, grad(u))) and + # will be discretized first order form as follows: + # 1. compute grad(u) + # 2. compute f(u, grad(u)) + # 3. compute div(f(u, grad(u))) (i.e., the "regular" rhs! call) + # boundary conditions will be applied to both grad(u) and div(f(u, grad(u))). + function rhs_parabolic!( + du, u, t, mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + initial_condition, boundary_conditions_parabolic, source_terms, + dg::DG, parabolic_scheme, cache, cache_parabolic + ) + @unpack viscous_container = cache_parabolic + @unpack u_transformed, gradients, flux_viscous = viscous_container + + # Convert conservative variables to a form more suitable for viscous flux calculations + @trixi_timeit timer() "transform variables" begin + transform_variables!( + u_transformed, u, mesh, equations_parabolic, + dg, parabolic_scheme, cache, cache_parabolic + ) end - end - return nothing -end - -# This is the version used when calculating the divergence of the viscous fluxes -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache_parabolic, flux_viscous, - mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - @unpack interfaces = cache_parabolic - @unpack neighbor_ids = interfaces - interfaces_u = interfaces.u - - @threaded for interface in eachinterface(dg, cache) - left_element = neighbor_ids[1, interface] - right_element = neighbor_ids[2, interface] - - # interface in x-direction - for v in eachvariable(equations_parabolic) - # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! - interfaces_u[1, v, interface] = flux_viscous[v, nnodes(dg), left_element] - interfaces_u[2, v, interface] = flux_viscous[v, 1, right_element] + # Compute the gradients of the transformed variables + @trixi_timeit timer() "calculate gradient" begin + calc_gradient!( + gradients, u_transformed, t, mesh, equations_parabolic, + boundary_conditions_parabolic, dg, cache, cache_parabolic + ) end - end - return nothing -end - -# This is the version used when calculating the divergence of the viscous fluxes -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{1}, equations_parabolic, - dg::DG, cache_parabolic) - @unpack neighbor_ids, orientations = cache_parabolic.interfaces - - @threaded for interface in eachinterface(dg, cache_parabolic) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] - - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 - - # Get precomputed fluxes at interfaces - flux_ll, flux_rr = get_surface_node_vars(cache_parabolic.interfaces.u, - equations_parabolic, - dg, interface) - - # Compute interface flux as mean of left and right viscous fluxes - # TODO: parabolic; only BR1 at the moment - flux = 0.5f0 * (flux_ll + flux_rr) - - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, left_direction, left_id] = flux[v] - surface_flux_values[v, right_direction, right_id] = flux[v] + # Compute and store the viscous fluxes + @trixi_timeit timer() "calculate viscous fluxes" begin + calc_viscous_fluxes!( + flux_viscous, gradients, u_transformed, mesh, + equations_parabolic, dg, cache, cache_parabolic + ) end - end - - return nothing -end -# This is the version used when calculating the divergence of the viscous fluxes -function prolong2boundaries!(cache_parabolic, flux_viscous, - mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - @unpack boundaries = cache_parabolic - @unpack neighbor_sides, neighbor_ids = boundaries - boundaries_u = boundaries.u - - @threaded for boundary in eachboundary(dg, cache_parabolic) - element = neighbor_ids[boundary] - - if neighbor_sides[boundary] == 1 - # element in -x direction of boundary - for v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[1, v, boundary] = flux_viscous[v, nnodes(dg), element] - end - else # Element in +x direction of boundary - for v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[2, v, boundary] = flux_viscous[v, 1, element] - end + # The remainder of this function is essentially a regular rhs! for + # parabolic equations (i.e., it computes the divergence of the viscous fluxes) + # + # OBS! In `calc_viscous_fluxes!`, the viscous flux values at the volume nodes of each element have + # been computed and stored in `fluxes_viscous`. In the following, we *reuse* (abuse) the + # `interfaces` and `boundaries` containers in `cache_parabolic` to interpolate and store the + # *fluxes* at the element surfaces, as opposed to interpolating and storing the *solution* (as it + # is done in the hyperbolic operator). That is, `interfaces.u`/`boundaries.u` store *viscous flux values* + # and *not the solution*. The advantage is that a) we do not need to allocate more storage, b) we + # do not need to recreate the existing data structure only with a different name, and c) we do not + # need to interpolate solutions *and* gradients to the surfaces. + + # TODO: parabolic; reconsider current data structure reuse strategy + + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!(du, flux_viscous, mesh, equations_parabolic, dg, cache) end - end - return nothing -end - -function calc_viscous_fluxes!(flux_viscous, gradients, u_transformed, mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, cache, cache_parabolic) - @threaded for element in eachelement(dg, cache) - for i in eachnode(dg) - # Get solution and gradients - u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, element) - gradients_1_node = get_node_vars(gradients, equations_parabolic, dg, i, - element) - - # Calculate viscous flux and store each component for later use - flux_viscous_node = flux(u_node, gradients_1_node, 1, equations_parabolic) - set_node_vars!(flux_viscous, flux_viscous_node, equations_parabolic, dg, i, - element) + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache_parabolic, flux_viscous, mesh, equations_parabolic, + dg.surface_integral, dg, cache + ) end - end -end - -function calc_boundary_flux_gradients!(cache, t, - boundary_conditions_parabolic::BoundaryConditionPeriodic, - mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - return nothing -end - -function calc_boundary_flux_divergence!(cache, t, - boundary_conditions_parabolic::BoundaryConditionPeriodic, - mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - return nothing -end - -function calc_boundary_flux_gradients!(cache, t, - boundary_conditions_parabolic::NamedTuple, - mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - @unpack surface_flux_values = cache.elements - @unpack n_boundaries_per_direction = cache.boundaries - - # Calculate indices - lasts = accumulate(+, n_boundaries_per_direction) - firsts = lasts - n_boundaries_per_direction .+ 1 - - # Calc boundary fluxes in each direction - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[1], - equations_parabolic, surface_integral, dg, - cache, - 1, firsts[1], lasts[1]) - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[2], - equations_parabolic, surface_integral, dg, - cache, - 2, firsts[2], lasts[2]) -end - -function calc_boundary_flux_by_direction_gradient!(surface_flux_values::AbstractArray{<:Any, - 3}, - t, - boundary_condition, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - direction, first_boundary, - last_boundary) - @unpack surface_flux = surface_integral - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] - - # Get boundary flux - u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - u_inner = u_ll - else # Element is on the right, boundary on the left - u_inner = u_rr - end - - # TODO: revisit if we want more general boundary treatments. - # This assumes the gradient numerical flux at the boundary is the gradient variable, - # which is consistent with BR1, LDG. - flux_inner = u_inner - - x = get_node_coords(node_coordinates, equations_parabolic, dg, boundary) - flux = boundary_condition(flux_inner, u_inner, orientations[boundary], - direction, - x, t, Gradient(), equations_parabolic) - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, direction, neighbor] = flux[v] + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache_parabolic.elements.surface_flux_values, mesh, + equations_parabolic, dg, cache_parabolic + ) end - end - return nothing -end - -function calc_boundary_flux_divergence!(cache, t, - boundary_conditions_parabolic::NamedTuple, - mesh::TreeMesh{1}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - @unpack surface_flux_values = cache.elements - @unpack n_boundaries_per_direction = cache.boundaries - - # Calculate indices - lasts = accumulate(+, n_boundaries_per_direction) - firsts = lasts - n_boundaries_per_direction .+ 1 - - # Calc boundary fluxes in each direction - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[1], - equations_parabolic, surface_integral, - dg, cache, - 1, firsts[1], lasts[1]) - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[2], - equations_parabolic, surface_integral, - dg, cache, - 2, firsts[2], lasts[2]) -end -function calc_boundary_flux_by_direction_divergence!(surface_flux_values::AbstractArray{<:Any, - 3}, - t, - boundary_condition, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - direction, first_boundary, - last_boundary) - @unpack surface_flux = surface_integral - - # Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction") - # of the viscous flux, as computed in `prolong2boundaries!` - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] - - # Get viscous boundary fluxes - flux_ll, flux_rr = get_surface_node_vars(u, equations_parabolic, dg, boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - flux_inner = flux_ll - else # Element is on the right, boundary on the left - flux_inner = flux_rr + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache_parabolic, flux_viscous, mesh, equations_parabolic, + dg.surface_integral, dg, cache + ) end - x = get_node_coords(node_coordinates, equations_parabolic, dg, boundary) + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux_divergence!( + cache_parabolic, t, + boundary_conditions_parabolic, mesh, + equations_parabolic, + dg.surface_integral, dg + ) + end - # TODO: add a field in `cache.boundaries` for gradient information. - # Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information. - # This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion2D and - # NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion2D as of 2022-6-27. - # It will not work with implementations which utilize `u_inner` to impose boundary conditions. - flux = boundary_condition(flux_inner, nothing, orientations[boundary], - direction, - x, t, Divergence(), equations_parabolic) + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations_parabolic, + dg.surface_integral, dg, cache_parabolic + ) + end - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, direction, neighbor] = flux[v] + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" begin + apply_jacobian_parabolic!(du, mesh, equations_parabolic, dg, cache_parabolic) end - end - return nothing -end + return nothing + end -# Calculate the gradient of the transformed variables -function calc_gradient!(gradients, u_transformed, t, - mesh::TreeMesh{1}, equations_parabolic, - boundary_conditions_parabolic, dg::DG, cache, cache_parabolic) + # Transform solution variables prior to taking the gradient + # (e.g., conservative to primitive variables). Defaults to doing nothing. + # TODO: can we avoid copying data? + function transform_variables!( + u_transformed, u, mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, parabolic_scheme, cache, cache_parabolic + ) + transformation = gradient_variable_transformation(equations_parabolic) - # Reset du - @trixi_timeit timer() "reset gradients" begin - reset_du!(gradients, dg, cache) + @threaded for element in eachelement(dg, cache) + # Calculate volume terms in one element + for i in eachnode(dg) + u_node = get_node_vars(u, equations_parabolic, dg, i, element) + u_transformed_node = transformation(u_node, equations_parabolic) + set_node_vars!( + u_transformed, u_transformed_node, equations_parabolic, dg, + i, element + ) + end + end end - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin + # This is the version used when calculating the divergence of the viscous fluxes + function calc_volume_integral!( + du, flux_viscous, + mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + dg::DGSEM, cache + ) @unpack derivative_dhat = dg.basis - @threaded for element in eachelement(dg, cache) + @threaded for element in eachelement(dg, cache) # Calculate volume terms in one element for i in eachnode(dg) - u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, - element) + flux_1_node = get_node_vars( + flux_viscous, equations_parabolic, dg, i, + element + ) for ii in eachnode(dg) - multiply_add_to_node_vars!(gradients, derivative_dhat[ii, i], - u_node, equations_parabolic, dg, ii, - element) + multiply_add_to_node_vars!( + du, derivative_dhat[ii, i], flux_1_node, + equations_parabolic, dg, ii, element + ) end end end + + return nothing end - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" prolong2interfaces!(cache_parabolic, - u_transformed, mesh, - equations_parabolic, - dg.surface_integral, - dg) + # This is the version used when calculating the divergence of the viscous fluxes + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache_parabolic, flux_viscous, + mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + @unpack interfaces = cache_parabolic + @unpack neighbor_ids = interfaces + interfaces_u = interfaces.u + + @threaded for interface in eachinterface(dg, cache) + left_element = neighbor_ids[1, interface] + right_element = neighbor_ids[2, interface] + + # interface in x-direction + for v in eachvariable(equations_parabolic) + # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! + interfaces_u[1, v, interface] = flux_viscous[v, nnodes(dg), left_element] + interfaces_u[2, v, interface] = flux_viscous[v, 1, right_element] + end + end + + return nothing + end - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - @unpack surface_flux_values = cache_parabolic.elements + # This is the version used when calculating the divergence of the viscous fluxes + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{1}, equations_parabolic, + dg::DG, cache_parabolic + ) @unpack neighbor_ids, orientations = cache_parabolic.interfaces @threaded for interface in eachinterface(dg, cache_parabolic) @@ -453,10 +215,16 @@ function calc_gradient!(gradients, u_transformed, t, left_direction = 2 * orientations[interface] right_direction = 2 * orientations[interface] - 1 - # Call pointwise Riemann solver - u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u, - equations_parabolic, dg, interface) - flux = 0.5f0 * (u_ll + u_rr) + # Get precomputed fluxes at interfaces + flux_ll, flux_rr = get_surface_node_vars( + cache_parabolic.interfaces.u, + equations_parabolic, + dg, interface + ) + + # Compute interface flux as mean of left and right viscous fluxes + # TODO: parabolic; only BR1 at the moment + flux = 0.5f0 * (flux_ll + flux_rr) # Copy flux to left and right element storage for v in eachvariable(equations_parabolic) @@ -464,104 +232,436 @@ function calc_gradient!(gradients, u_transformed, t, surface_flux_values[v, right_direction, right_id] = flux[v] end end + + return nothing end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" prolong2boundaries!(cache_parabolic, - u_transformed, mesh, - equations_parabolic, - dg.surface_integral, - dg) - - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" calc_boundary_flux_gradients!(cache_parabolic, - t, - boundary_conditions_parabolic, - mesh, - equations_parabolic, - dg.surface_integral, - dg) - - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - @unpack boundary_interpolation = dg.basis - @unpack surface_flux_values = cache_parabolic.elements - - # Note that all fluxes have been computed with outward-pointing normal vectors. - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` to let `@muladd` turn these - # into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] + # This is the version used when calculating the divergence of the viscous fluxes + function prolong2boundaries!( + cache_parabolic, flux_viscous, + mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + @unpack boundaries = cache_parabolic + @unpack neighbor_sides, neighbor_ids = boundaries + boundaries_u = boundaries.u + + @threaded for boundary in eachboundary(dg, cache_parabolic) + element = neighbor_ids[boundary] + + if neighbor_sides[boundary] == 1 + # element in -x direction of boundary + for v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[1, v, boundary] = flux_viscous[v, nnodes(dg), element] + end + else # Element in +x direction of boundary + for v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[2, v, boundary] = flux_viscous[v, 1, element] + end + end + end + + return nothing + end + + function calc_viscous_fluxes!( + flux_viscous, gradients, u_transformed, mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, cache, cache_parabolic + ) @threaded for element in eachelement(dg, cache) - for v in eachvariable(equations_parabolic) - # surface at -x - gradients[v, 1, element] = (gradients[v, 1, element] - - surface_flux_values[v, 1, element] * - factor_1) - - # surface at +x - gradients[v, nnodes(dg), element] = (gradients[v, nnodes(dg), element] + - surface_flux_values[v, 2, - element] * - factor_2) + for i in eachnode(dg) + # Get solution and gradients + u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, element) + gradients_1_node = get_node_vars( + gradients, equations_parabolic, dg, i, + element + ) + + # Calculate viscous flux and store each component for later use + flux_viscous_node = flux(u_node, gradients_1_node, 1, equations_parabolic) + set_node_vars!( + flux_viscous, flux_viscous_node, equations_parabolic, dg, i, + element + ) end end end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" begin - apply_jacobian_parabolic!(gradients, mesh, equations_parabolic, dg, - cache_parabolic) + function calc_boundary_flux_gradients!( + cache, t, + boundary_conditions_parabolic::BoundaryConditionPeriodic, + mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + return nothing end - return nothing -end + function calc_boundary_flux_divergence!( + cache, t, + boundary_conditions_parabolic::BoundaryConditionPeriodic, + mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + return nothing + end -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache_parabolic(mesh::TreeMesh{1}, - equations_hyperbolic::AbstractEquations, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, parabolic_scheme, RealT, uEltype) - # Get cells for which an element needs to be created (i.e. all leaf cells) - leaf_cell_ids = local_leaf_cells(mesh.tree) + function calc_boundary_flux_gradients!( + cache, t, + boundary_conditions_parabolic::NamedTuple, + mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + @unpack surface_flux_values = cache.elements + @unpack n_boundaries_per_direction = cache.boundaries + + # Calculate indices + lasts = accumulate(+, n_boundaries_per_direction) + firsts = lasts - n_boundaries_per_direction .+ 1 + + # Calc boundary fluxes in each direction + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[1], + equations_parabolic, surface_integral, dg, + cache, + 1, firsts[1], lasts[1] + ) + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[2], + equations_parabolic, surface_integral, dg, + cache, + 2, firsts[2], lasts[2] + ) + end - elements = init_elements(leaf_cell_ids, mesh, equations_hyperbolic, dg.basis, RealT, - uEltype) + function calc_boundary_flux_by_direction_gradient!( + surface_flux_values::AbstractArray{ + <:Any, + 3, + }, + t, + boundary_condition, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + direction, first_boundary, + last_boundary + ) + @unpack surface_flux = surface_integral + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] + + # Get boundary flux + u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, boundary) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + u_inner = u_ll + else # Element is on the right, boundary on the left + u_inner = u_rr + end - interfaces = init_interfaces(leaf_cell_ids, mesh, elements) + # TODO: revisit if we want more general boundary treatments. + # This assumes the gradient numerical flux at the boundary is the gradient variable, + # which is consistent with BR1, LDG. + flux_inner = u_inner - boundaries = init_boundaries(leaf_cell_ids, mesh, elements) + x = get_node_coords(node_coordinates, equations_parabolic, dg, boundary) + flux = boundary_condition( + flux_inner, u_inner, orientations[boundary], + direction, + x, t, Gradient(), equations_parabolic + ) - viscous_container = init_viscous_container_1d(nvariables(equations_hyperbolic), - nnodes(elements), nelements(elements), - uEltype) + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, direction, neighbor] = flux[v] + end + end - cache = (; elements, interfaces, boundaries, viscous_container) + return nothing + end - return cache -end + function calc_boundary_flux_divergence!( + cache, t, + boundary_conditions_parabolic::NamedTuple, + mesh::TreeMesh{1}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + @unpack surface_flux_values = cache.elements + @unpack n_boundaries_per_direction = cache.boundaries + + # Calculate indices + lasts = accumulate(+, n_boundaries_per_direction) + firsts = lasts - n_boundaries_per_direction .+ 1 + + # Calc boundary fluxes in each direction + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[1], + equations_parabolic, surface_integral, + dg, cache, + 1, firsts[1], lasts[1] + ) + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[2], + equations_parabolic, surface_integral, + dg, cache, + 2, firsts[2], lasts[2] + ) + end + function calc_boundary_flux_by_direction_divergence!( + surface_flux_values::AbstractArray{ + <:Any, + 3, + }, + t, + boundary_condition, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + direction, first_boundary, + last_boundary + ) + @unpack surface_flux = surface_integral + + # Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction") + # of the viscous flux, as computed in `prolong2boundaries!` + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] + + # Get viscous boundary fluxes + flux_ll, flux_rr = get_surface_node_vars(u, equations_parabolic, dg, boundary) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + flux_inner = flux_ll + else # Element is on the right, boundary on the left + flux_inner = flux_rr + end -# Needed to *not* flip the sign of the inverse Jacobian. -# This is because the parabolic fluxes are assumed to be of the form -# `du/dt + df/dx = dg/dx + source(x,t)`, -# where f(u) is the inviscid flux and g(u) is the viscous flux. -function apply_jacobian_parabolic!(du, mesh::TreeMesh{1}, - equations::AbstractEquationsParabolic, dg::DG, cache) - @unpack inverse_jacobian = cache.elements + x = get_node_coords(node_coordinates, equations_parabolic, dg, boundary) - @threaded for element in eachelement(dg, cache) - factor = inverse_jacobian[element] + # TODO: add a field in `cache.boundaries` for gradient information. + # Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information. + # This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion2D and + # NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion2D as of 2022-6-27. + # It will not work with implementations which utilize `u_inner` to impose boundary conditions. + flux = boundary_condition( + flux_inner, nothing, orientations[boundary], + direction, + x, t, Divergence(), equations_parabolic + ) - for i in eachnode(dg) - for v in eachvariable(equations) - du[v, i, element] *= factor + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, direction, neighbor] = flux[v] end end + + return nothing end - return nothing -end + # Calculate the gradient of the transformed variables + function calc_gradient!( + gradients, u_transformed, t, + mesh::TreeMesh{1}, equations_parabolic, + boundary_conditions_parabolic, dg::DG, cache, cache_parabolic + ) + + # Reset du + @trixi_timeit timer() "reset gradients" begin + reset_du!(gradients, dg, cache) + end + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + @unpack derivative_dhat = dg.basis + @threaded for element in eachelement(dg, cache) + + # Calculate volume terms in one element + for i in eachnode(dg) + u_node = get_node_vars( + u_transformed, equations_parabolic, dg, i, + element + ) + + for ii in eachnode(dg) + multiply_add_to_node_vars!( + gradients, derivative_dhat[ii, i], + u_node, equations_parabolic, dg, ii, + element + ) + end + end + end + end + + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" prolong2interfaces!( + cache_parabolic, + u_transformed, mesh, + equations_parabolic, + dg.surface_integral, + dg + ) + + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + @unpack surface_flux_values = cache_parabolic.elements + @unpack neighbor_ids, orientations = cache_parabolic.interfaces + + @threaded for interface in eachinterface(dg, cache_parabolic) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 + + # Call pointwise Riemann solver + u_ll, u_rr = get_surface_node_vars( + cache_parabolic.interfaces.u, + equations_parabolic, dg, interface + ) + flux = 0.5f0 * (u_ll + u_rr) + + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, left_direction, left_id] = flux[v] + surface_flux_values[v, right_direction, right_id] = flux[v] + end + end + end + + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" prolong2boundaries!( + cache_parabolic, + u_transformed, mesh, + equations_parabolic, + dg.surface_integral, + dg + ) + + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" calc_boundary_flux_gradients!( + cache_parabolic, + t, + boundary_conditions_parabolic, + mesh, + equations_parabolic, + dg.surface_integral, + dg + ) + + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + @unpack boundary_interpolation = dg.basis + @unpack surface_flux_values = cache_parabolic.elements + + # Note that all fluxes have been computed with outward-pointing normal vectors. + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for v in eachvariable(equations_parabolic) + # surface at -x + gradients[v, 1, element] = ( + gradients[v, 1, element] - + surface_flux_values[v, 1, element] * + factor_1 + ) + + # surface at +x + gradients[v, nnodes(dg), element] = ( + gradients[v, nnodes(dg), element] + + surface_flux_values[ + v, 2, + element, + ] * + factor_2 + ) + end + end + end + + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" begin + apply_jacobian_parabolic!( + gradients, mesh, equations_parabolic, dg, + cache_parabolic + ) + end + + return nothing + end + + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache_parabolic( + mesh::TreeMesh{1}, + equations_hyperbolic::AbstractEquations, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, parabolic_scheme, RealT, uEltype + ) + # Get cells for which an element needs to be created (i.e. all leaf cells) + leaf_cell_ids = local_leaf_cells(mesh.tree) + + elements = init_elements( + leaf_cell_ids, mesh, equations_hyperbolic, dg.basis, RealT, + uEltype + ) + + interfaces = init_interfaces(leaf_cell_ids, mesh, elements) + + boundaries = init_boundaries(leaf_cell_ids, mesh, elements) + + viscous_container = init_viscous_container_1d( + nvariables(equations_hyperbolic), + nnodes(elements), nelements(elements), + uEltype + ) + + cache = (; elements, interfaces, boundaries, viscous_container) + + return cache + end + + # Needed to *not* flip the sign of the inverse Jacobian. + # This is because the parabolic fluxes are assumed to be of the form + # `du/dt + df/dx = dg/dx + source(x,t)`, + # where f(u) is the inviscid flux and g(u) is the viscous flux. + function apply_jacobian_parabolic!( + du, mesh::TreeMesh{1}, + equations::AbstractEquationsParabolic, dg::DG, cache + ) + @unpack inverse_jacobian = cache.elements + + @threaded for element in eachelement(dg, cache) + factor = inverse_jacobian[element] + + for i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, element] *= factor + end + end + end + + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_tree/dg_2d.jl b/src/solvers/dgsem_tree/dg_2d.jl index 734a31d547..ed6a2401e8 100644 --- a/src/solvers/dgsem_tree/dg_2d.jl +++ b/src/solvers/dgsem_tree/dg_2d.jl @@ -3,1172 +3,1444 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# everything related to a DG semidiscretization in 2D, -# currently limited to Lobatto-Legendre nodes - -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::TreeMesh{2}, equations, - dg::DG, RealT, uEltype) - # Get cells for which an element needs to be created (i.e. all leaf cells) - leaf_cell_ids = local_leaf_cells(mesh.tree) - - elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype) - - interfaces = init_interfaces(leaf_cell_ids, mesh, elements) - - boundaries = init_boundaries(leaf_cell_ids, mesh, elements) - - mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) - - cache = (; elements, interfaces, boundaries, mortars) - - # Add specialized parts of the cache required to compute the volume integral etc. - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) - - return cache -end - -# The methods below are specialized on the volume integral type -# and called from the basic `create_cache` method at the top. -function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2}, - UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, - equations, volume_integral::VolumeIntegralFluxDifferencing, - dg::DG, uEltype) - NamedTuple() -end - -function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, equations, - volume_integral::VolumeIntegralShockCapturingHG, dg::DG, uEltype) - element_ids_dg = Int[] - element_ids_dgfv = Int[] - - cache = create_cache(mesh, equations, - VolumeIntegralFluxDifferencing(volume_integral.volume_flux_dg), - dg, uEltype) - - A3dp1_x = Array{uEltype, 3} - A3dp1_y = Array{uEltype, 3} - - fstar1_L_threaded = A3dp1_x[A3dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg)) for _ in 1:Threads.nthreads()] - fstar1_R_threaded = A3dp1_x[A3dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg)) for _ in 1:Threads.nthreads()] - fstar2_L_threaded = A3dp1_y[A3dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1) for _ in 1:Threads.nthreads()] - fstar2_R_threaded = A3dp1_y[A3dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1) for _ in 1:Threads.nthreads()] - - return (; cache..., element_ids_dg, element_ids_dgfv, - fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded) -end - -function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, equations, - volume_integral::VolumeIntegralPureLGLFiniteVolume, dg::DG, - uEltype) - A3dp1_x = Array{uEltype, 3} - A3dp1_y = Array{uEltype, 3} - - fstar1_L_threaded = A3dp1_x[A3dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg)) for _ in 1:Threads.nthreads()] - fstar1_R_threaded = A3dp1_x[A3dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg)) for _ in 1:Threads.nthreads()] - fstar2_L_threaded = A3dp1_y[A3dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1) for _ in 1:Threads.nthreads()] - fstar2_R_threaded = A3dp1_y[A3dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1) for _ in 1:Threads.nthreads()] - - return (; fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, - fstar2_R_threaded) -end - -# The methods below are specialized on the mortar type -# and called from the basic `create_cache` method at the top. -function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, - equations, mortar_l2::LobattoLegendreMortarL2, uEltype) - # TODO: Taal performance using different types - MA2d = MArray{Tuple{nvariables(equations), nnodes(mortar_l2)}, uEltype, 2, - nvariables(equations) * nnodes(mortar_l2)} - fstar_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] - fstar_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] - - # A2d = Array{uEltype, 2} - # fstar_upper_threaded = [A2d(undef, nvariables(equations), nnodes(mortar_l2)) for _ in 1:Threads.nthreads()] - # fstar_lower_threaded = [A2d(undef, nvariables(equations), nnodes(mortar_l2)) for _ in 1:Threads.nthreads()] - - (; fstar_upper_threaded, fstar_lower_threaded) -end - -# TODO: Taal discuss/refactor timer, allowing users to pass a custom timer? - -function rhs!(du, u, t, - mesh::Union{TreeMesh{2}, P4estMesh{2}, T8codeMesh{2}}, equations, - initial_condition, boundary_conditions, source_terms::Source, - dg::DG, cache) where {Source} - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) - end + #! format: noindent - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache, u, mesh, equations, - dg.surface_integral, dg) - end + # everything related to a DG semidiscretization in 2D, + # currently limited to Lobatto-Legendre nodes - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg, cache) - end + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::TreeMesh{2}, equations, + dg::DG, RealT, uEltype + ) + # Get cells for which an element needs to be created (i.e. all leaf cells) + leaf_cell_ids = local_leaf_cells(mesh.tree) + + elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype) + + interfaces = init_interfaces(leaf_cell_ids, mesh, elements) + + boundaries = init_boundaries(leaf_cell_ids, mesh, elements) - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache, u, mesh, equations, - dg.surface_integral, dg) + mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) + + cache = (; elements, interfaces, boundaries, mortars) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) + + return cache end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations, - dg.surface_integral, dg) + # The methods below are specialized on the volume integral type + # and called from the basic `create_cache` method at the top. + function create_cache( + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, StructuredMeshView{2}, + UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}, + }, + equations, volume_integral::VolumeIntegralFluxDifferencing, + dg::DG, uEltype + ) + NamedTuple() end - # Prolong solution to mortars - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u, mesh, equations, - dg.mortar, dg.surface_integral, dg) + function create_cache( + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}, + }, equations, + volume_integral::VolumeIntegralShockCapturingHG, dg::DG, uEltype + ) + element_ids_dg = Int[] + element_ids_dgfv = Int[] + + cache = create_cache( + mesh, equations, + VolumeIntegralFluxDifferencing(volume_integral.volume_flux_dg), + dg, uEltype + ) + + A3dp1_x = Array{uEltype, 3} + A3dp1_y = Array{uEltype, 3} + + fstar1_L_threaded = A3dp1_x[ + A3dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg) + ) for _ in 1:Threads.nthreads() + ] + fstar1_R_threaded = A3dp1_x[ + A3dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg) + ) for _ in 1:Threads.nthreads() + ] + fstar2_L_threaded = A3dp1_y[ + A3dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1 + ) for _ in 1:Threads.nthreads() + ] + fstar2_R_threaded = A3dp1_y[ + A3dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1 + ) for _ in 1:Threads.nthreads() + ] + + return (; + cache..., element_ids_dg, element_ids_dgfv, + fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded, + ) end - # Calculate mortar fluxes - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.mortar, dg.surface_integral, dg, cache) + function create_cache( + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}, + }, equations, + volume_integral::VolumeIntegralPureLGLFiniteVolume, dg::DG, + uEltype + ) + A3dp1_x = Array{uEltype, 3} + A3dp1_y = Array{uEltype, 3} + + fstar1_L_threaded = A3dp1_x[ + A3dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg) + ) for _ in 1:Threads.nthreads() + ] + fstar1_R_threaded = A3dp1_x[ + A3dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg) + ) for _ in 1:Threads.nthreads() + ] + fstar2_L_threaded = A3dp1_y[ + A3dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1 + ) for _ in 1:Threads.nthreads() + ] + fstar2_R_threaded = A3dp1_y[ + A3dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1 + ) for _ in 1:Threads.nthreads() + ] + + return (; + fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, + fstar2_R_threaded, + ) end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) + # The methods below are specialized on the mortar type + # and called from the basic `create_cache` method at the top. + function create_cache( + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}, + }, + equations, mortar_l2::LobattoLegendreMortarL2, uEltype + ) + # TODO: Taal performance using different types + MA2d = MArray{ + Tuple{nvariables(equations), nnodes(mortar_l2)}, uEltype, 2, + nvariables(equations) * nnodes(mortar_l2), + } + fstar_upper_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] + fstar_lower_threaded = MA2d[MA2d(undef) for _ in 1:Threads.nthreads()] + + # A2d = Array{uEltype, 2} + # fstar_upper_threaded = [A2d(undef, nvariables(equations), nnodes(mortar_l2)) for _ in 1:Threads.nthreads()] + # fstar_lower_threaded = [A2d(undef, nvariables(equations), nnodes(mortar_l2)) for _ in 1:Threads.nthreads()] + + (; fstar_upper_threaded, fstar_lower_threaded) end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + # TODO: Taal discuss/refactor timer, allowing users to pass a custom timer? + + function rhs!( + du, u, t, + mesh::Union{TreeMesh{2}, P4estMesh{2}, T8codeMesh{2}}, equations, + initial_condition, boundary_conditions, source_terms::Source, + dg::DG, cache + ) where {Source} + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - # Calculate source terms - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, equations, dg, cache) - end + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end + + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg, cache + ) + end + + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end + + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, t, boundary_conditions, mesh, equations, + dg.surface_integral, dg + ) + end + + # Prolong solution to mortars + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!( + cache, u, mesh, equations, + dg.mortar, dg.surface_integral, dg + ) + end + + # Calculate mortar fluxes + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.mortar, dg.surface_integral, dg, cache + ) + end - return nothing -end - -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - StructuredMeshView{2}, UnstructuredMesh2D, - P4estMesh{2}, T8codeMesh{2}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralWeakForm, - dg::DGSEM, cache) - @threaded for element in eachelement(dg, cache) - weak_form_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - dg, cache) + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end + + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + + # Calculate source terms + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, equations, dg, cache) + end + + return nothing end - return nothing -end + function calc_volume_integral!( + du, u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + StructuredMeshView{2}, UnstructuredMesh2D, + P4estMesh{2}, T8codeMesh{2}, + }, + nonconservative_terms, equations, + volume_integral::VolumeIntegralWeakForm, + dg::DGSEM, cache + ) + @threaded for element in eachelement(dg, cache) + weak_form_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + dg, cache + ) + end -#= + return nothing + end + + #= `weak_form_kernel!` is only implemented for conserved terms as non-conservative terms should always be discretized in conjunction with a flux-splitting scheme, see `flux_differencing_kernel!`. This treatment is required to achieve, e.g., entropy-stability or well-balancedness. See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-1765644064 =# -@inline function weak_form_kernel!(du, u, - element, mesh::TreeMesh{2}, - nonconservative_terms::False, equations, - dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_dhat = dg.basis - - # Calculate volume terms in one element - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - flux1 = flux(u_node, 1, equations) - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[ii, i], flux1, - equations, dg, ii, j, element) - end + @inline function weak_form_kernel!( + du, u, + element, mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_dhat = dg.basis + + # Calculate volume terms in one element + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + flux1 = flux(u_node, 1, equations) + for ii in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[ii, i], flux1, + equations, dg, ii, j, element + ) + end - flux2 = flux(u_node, 2, equations) - for jj in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[jj, j], flux2, - equations, dg, i, jj, element) + flux2 = flux(u_node, 2, equations) + for jj in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[jj, j], flux2, + equations, dg, i, jj, element + ) + end end + + return nothing end - return nothing -end - -# flux differencing volume integral. For curved meshes averaging of the -# mapping terms, stored in `cache.elements.contravariant_vectors`, is peeled apart -# from the evaluation of the physical fluxes in each Cartesian direction -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - StructuredMeshView{2}, - UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralFluxDifferencing, - dg::DGSEM, cache) - @threaded for element in eachelement(dg, cache) - flux_differencing_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - volume_integral.volume_flux, dg, cache) + # flux differencing volume integral. For curved meshes averaging of the + # mapping terms, stored in `cache.elements.contravariant_vectors`, is peeled apart + # from the evaluation of the physical fluxes in each Cartesian direction + function calc_volume_integral!( + du, u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + StructuredMeshView{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + nonconservative_terms, equations, + volume_integral::VolumeIntegralFluxDifferencing, + dg::DGSEM, cache + ) + @threaded for element in eachelement(dg, cache) + flux_differencing_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + volume_integral.volume_flux, dg, cache + ) + end end -end - -@inline function flux_differencing_kernel!(du, u, - element, mesh::TreeMesh{2}, - nonconservative_terms::False, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_split = dg.basis - - # Calculate volume integral in one element - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - # All diagonal entries of `derivative_split` are zero. Thus, we can skip - # the computation of the diagonal terms. In addition, we use the symmetry - # of the `volume_flux` to save half of the possible two-point flux - # computations. - - # x direction - for ii in (i + 1):nnodes(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, element) - flux1 = volume_flux(u_node, u_node_ii, 1, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[i, ii], flux1, - equations, dg, i, j, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[ii, i], flux1, - equations, dg, ii, j, element) + + @inline function flux_differencing_kernel!( + du, u, + element, mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_split = dg.basis + + # Calculate volume integral in one element + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # All diagonal entries of `derivative_split` are zero. Thus, we can skip + # the computation of the diagonal terms. In addition, we use the symmetry + # of the `volume_flux` to save half of the possible two-point flux + # computations. + + # x direction + for ii in (i + 1):nnodes(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, element) + flux1 = volume_flux(u_node, u_node_ii, 1, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[i, ii], flux1, + equations, dg, i, j, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[ii, i], flux1, + equations, dg, ii, j, element + ) + end + + # y direction + for jj in (j + 1):nnodes(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, element) + flux2 = volume_flux(u_node, u_node_jj, 2, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[j, jj], flux2, + equations, dg, i, j, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[jj, j], flux2, + equations, dg, i, jj, element + ) + end end + end - # y direction - for jj in (j + 1):nnodes(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, element) - flux2 = volume_flux(u_node, u_node_jj, 2, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[j, jj], flux2, - equations, dg, i, j, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[jj, j], flux2, - equations, dg, i, jj, element) + @inline function flux_differencing_kernel!( + du, u, + element, mesh::TreeMesh{2}, + nonconservative_terms::True, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_split = dg.basis + symmetric_flux, nonconservative_flux = volume_flux + + # Apply the symmetric flux as usual + flux_differencing_kernel!( + du, u, element, mesh, False(), equations, symmetric_flux, + dg, cache, alpha + ) + + # Calculate the remaining volume terms using the nonsymmetric generalized flux + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # The diagonal terms are zero since the diagonal of `derivative_split` + # is zero. We ignore this for now. + + # x direction + integral_contribution = zero(u_node) + for ii in eachnode(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, element) + noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations) + integral_contribution = integral_contribution + + derivative_split[i, ii] * noncons_flux1 + end + + # y direction + for jj in eachnode(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, element) + noncons_flux2 = nonconservative_flux(u_node, u_node_jj, 2, equations) + integral_contribution = integral_contribution + + derivative_split[j, jj] * noncons_flux2 + end + + # The factor 0.5 cancels the factor 2 in the flux differencing form + multiply_add_to_node_vars!( + du, alpha * 0.5f0, integral_contribution, equations, + dg, i, j, element + ) end end -end - -@inline function flux_differencing_kernel!(du, u, - element, mesh::TreeMesh{2}, - nonconservative_terms::True, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_split = dg.basis - symmetric_flux, nonconservative_flux = volume_flux - - # Apply the symmetric flux as usual - flux_differencing_kernel!(du, u, element, mesh, False(), equations, symmetric_flux, - dg, cache, alpha) - - # Calculate the remaining volume terms using the nonsymmetric generalized flux - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - # The diagonal terms are zero since the diagonal of `derivative_split` - # is zero. We ignore this for now. - - # x direction - integral_contribution = zero(u_node) - for ii in eachnode(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, element) - noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations) - integral_contribution = integral_contribution + - derivative_split[i, ii] * noncons_flux1 + + # TODO: Taal dimension agnostic + function calc_volume_integral!( + du, u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + nonconservative_terms, equations, + volume_integral::VolumeIntegralShockCapturingHG, + dg::DGSEM, cache + ) + @unpack element_ids_dg, element_ids_dgfv = cache + @unpack volume_flux_dg, volume_flux_fv, indicator = volume_integral + + # Calculate blending factors α: u = u_DG * (1 - α) + u_FV * α + alpha = @trixi_timeit timer() "blending factors" indicator( + u, mesh, equations, dg, + cache + ) + + # Determine element ids for DG-only and blended DG-FV volume integral + pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, dg, cache) + + # Loop over pure DG elements + @trixi_timeit timer() "pure DG" @threaded for idx_element in eachindex(element_ids_dg) + element = element_ids_dg[idx_element] + flux_differencing_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + volume_flux_dg, dg, cache + ) end - # y direction - for jj in eachnode(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, element) - noncons_flux2 = nonconservative_flux(u_node, u_node_jj, 2, equations) - integral_contribution = integral_contribution + - derivative_split[j, jj] * noncons_flux2 + # Loop over blended DG-FV elements + @trixi_timeit timer() "blended DG-FV" @threaded for idx_element in eachindex(element_ids_dgfv) + element = element_ids_dgfv[idx_element] + alpha_element = alpha[element] + + # Calculate DG volume integral contribution + flux_differencing_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + volume_flux_dg, dg, cache, 1 - alpha_element + ) + + # Calculate FV volume integral contribution + fv_kernel!( + du, u, mesh, nonconservative_terms, equations, volume_flux_fv, + dg, cache, element, alpha_element + ) end - # The factor 0.5 cancels the factor 2 in the flux differencing form - multiply_add_to_node_vars!(du, alpha * 0.5f0, integral_contribution, equations, - dg, i, j, element) - end -end - -# TODO: Taal dimension agnostic -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralShockCapturingHG, - dg::DGSEM, cache) - @unpack element_ids_dg, element_ids_dgfv = cache - @unpack volume_flux_dg, volume_flux_fv, indicator = volume_integral - - # Calculate blending factors α: u = u_DG * (1 - α) + u_FV * α - alpha = @trixi_timeit timer() "blending factors" indicator(u, mesh, equations, dg, - cache) - - # Determine element ids for DG-only and blended DG-FV volume integral - pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, dg, cache) - - # Loop over pure DG elements - @trixi_timeit timer() "pure DG" @threaded for idx_element in eachindex(element_ids_dg) - element = element_ids_dg[idx_element] - flux_differencing_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - volume_flux_dg, dg, cache) + return nothing end - # Loop over blended DG-FV elements - @trixi_timeit timer() "blended DG-FV" @threaded for idx_element in eachindex(element_ids_dgfv) - element = element_ids_dgfv[idx_element] - alpha_element = alpha[element] - - # Calculate DG volume integral contribution - flux_differencing_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - volume_flux_dg, dg, cache, 1 - alpha_element) + # TODO: Taal dimension agnostic + function calc_volume_integral!( + du, u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + nonconservative_terms, equations, + volume_integral::VolumeIntegralPureLGLFiniteVolume, + dg::DGSEM, cache + ) + @unpack volume_flux_fv = volume_integral + + # Calculate LGL FV volume integral + @threaded for element in eachelement(dg, cache) + fv_kernel!( + du, u, mesh, nonconservative_terms, equations, volume_flux_fv, + dg, cache, element, true + ) + end - # Calculate FV volume integral contribution - fv_kernel!(du, u, mesh, nonconservative_terms, equations, volume_flux_fv, - dg, cache, element, alpha_element) + return nothing end - return nothing -end - -# TODO: Taal dimension agnostic -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralPureLGLFiniteVolume, - dg::DGSEM, cache) - @unpack volume_flux_fv = volume_integral - - # Calculate LGL FV volume integral - @threaded for element in eachelement(dg, cache) - fv_kernel!(du, u, mesh, nonconservative_terms, equations, volume_flux_fv, - dg, cache, element, true) - end + @inline function fv_kernel!( + du, u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + UnstructuredMesh2D, P4estMesh{2}, + T8codeMesh{2}, + }, + nonconservative_terms, equations, + volume_flux_fv, dg::DGSEM, cache, element, alpha = true + ) + @unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded = cache + @unpack inverse_weights = dg.basis + + # Calculate FV two-point fluxes + fstar1_L = fstar1_L_threaded[Threads.threadid()] + fstar2_L = fstar2_L_threaded[Threads.threadid()] + fstar1_R = fstar1_R_threaded[Threads.threadid()] + fstar2_R = fstar2_R_threaded[Threads.threadid()] + calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, mesh, + nonconservative_terms, equations, volume_flux_fv, dg, element, cache + ) - return nothing -end - -@inline function fv_kernel!(du, u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - UnstructuredMesh2D, P4estMesh{2}, - T8codeMesh{2}}, - nonconservative_terms, equations, - volume_flux_fv, dg::DGSEM, cache, element, alpha = true) - @unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded = cache - @unpack inverse_weights = dg.basis - - # Calculate FV two-point fluxes - fstar1_L = fstar1_L_threaded[Threads.threadid()] - fstar2_L = fstar2_L_threaded[Threads.threadid()] - fstar1_R = fstar1_R_threaded[Threads.threadid()] - fstar2_R = fstar2_R_threaded[Threads.threadid()] - calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, mesh, - nonconservative_terms, equations, volume_flux_fv, dg, element, cache) - - # Calculate FV volume integral contribution - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - du[v, i, j, element] += (alpha * - (inverse_weights[i] * - (fstar1_L[v, i + 1, j] - fstar1_R[v, i, j]) + - inverse_weights[j] * - (fstar2_L[v, i, j + 1] - fstar2_R[v, i, j]))) + # Calculate FV volume integral contribution + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, j, element] += ( + alpha * + ( + inverse_weights[i] * + (fstar1_L[v, i + 1, j] - fstar1_R[v, i, j]) + + inverse_weights[j] * + (fstar2_L[v, i, j + 1] - fstar2_R[v, i, j]) + ) + ) + end end - end - return nothing -end - -# calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u_leftright, -# nonconservative_terms::False, equations, -# volume_flux_fv, dg, element) -# -# Calculate the finite volume fluxes inside the elements (**without non-conservative terms**). -# -# # Arguments -# - `fstar1_L::AbstractArray{<:Real, 3}` -# - `fstar1_R::AbstractArray{<:Real, 3}` -# - `fstar2_L::AbstractArray{<:Real, 3}` -# - `fstar2_R::AbstractArray{<:Real, 3}` -@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, - u::AbstractArray{<:Any, 4}, - mesh::TreeMesh{2}, nonconservative_terms::False, - equations, - volume_flux_fv, dg::DGSEM, element, cache) - fstar1_L[:, 1, :] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1, :] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_R)) - - for j in eachnode(dg), i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, j, element) - u_rr = get_node_vars(u, equations, dg, i, j, element) - flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction - set_node_vars!(fstar1_L, flux, equations, dg, i, j) - set_node_vars!(fstar1_R, flux, equations, dg, i, j) + return nothing end - fstar2_L[:, :, 1] .= zero(eltype(fstar2_L)) - fstar2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_L)) - fstar2_R[:, :, 1] .= zero(eltype(fstar2_R)) - fstar2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_R)) - - for j in 2:nnodes(dg), i in eachnode(dg) - u_ll = get_node_vars(u, equations, dg, i, j - 1, element) - u_rr = get_node_vars(u, equations, dg, i, j, element) - flux = volume_flux_fv(u_ll, u_rr, 2, equations) # orientation 2: y direction - set_node_vars!(fstar2_L, flux, equations, dg, i, j) - set_node_vars!(fstar2_R, flux, equations, dg, i, j) - end + # calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u_leftright, + # nonconservative_terms::False, equations, + # volume_flux_fv, dg, element) + # + # Calculate the finite volume fluxes inside the elements (**without non-conservative terms**). + # + # # Arguments + # - `fstar1_L::AbstractArray{<:Real, 3}` + # - `fstar1_R::AbstractArray{<:Real, 3}` + # - `fstar2_L::AbstractArray{<:Real, 3}` + # - `fstar2_R::AbstractArray{<:Real, 3}` + @inline function calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, + u::AbstractArray{<:Any, 4}, + mesh::TreeMesh{2}, nonconservative_terms::False, + equations, + volume_flux_fv, dg::DGSEM, element, cache + ) + fstar1_L[:, 1, :] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1, :] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_R)) + + for j in eachnode(dg), i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, j, element) + u_rr = get_node_vars(u, equations, dg, i, j, element) + flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction + set_node_vars!(fstar1_L, flux, equations, dg, i, j) + set_node_vars!(fstar1_R, flux, equations, dg, i, j) + end - return nothing -end - -# calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u_leftright, -# nonconservative_terms::True, equations, -# volume_flux_fv, dg, element) -# -# Calculate the finite volume fluxes inside the elements (**with non-conservative terms**). -# -# # Arguments -# - `fstar1_L::AbstractArray{<:Real, 3}`: -# - `fstar1_R::AbstractArray{<:Real, 3}`: -# - `fstar2_L::AbstractArray{<:Real, 3}`: -# - `fstar2_R::AbstractArray{<:Real, 3}`: -# - `u_leftright::AbstractArray{<:Real, 4}` -@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, - u::AbstractArray{<:Any, 4}, - mesh::TreeMesh{2}, nonconservative_terms::True, equations, - volume_flux_fv, dg::DGSEM, element, cache) - volume_flux, nonconservative_flux = volume_flux_fv - - # Fluxes in x - fstar1_L[:, 1, :] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1, :] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_R)) - - for j in eachnode(dg), i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, j, element) - u_rr = get_node_vars(u, equations, dg, i, j, element) - - # Compute conservative part - f1 = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction - - # Compute nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - f1_L = f1 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations) - f1_R = f1 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations) - - # Copy to temporary storage - set_node_vars!(fstar1_L, f1_L, equations, dg, i, j) - set_node_vars!(fstar1_R, f1_R, equations, dg, i, j) - end + fstar2_L[:, :, 1] .= zero(eltype(fstar2_L)) + fstar2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_L)) + fstar2_R[:, :, 1] .= zero(eltype(fstar2_R)) + fstar2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_R)) + + for j in 2:nnodes(dg), i in eachnode(dg) + u_ll = get_node_vars(u, equations, dg, i, j - 1, element) + u_rr = get_node_vars(u, equations, dg, i, j, element) + flux = volume_flux_fv(u_ll, u_rr, 2, equations) # orientation 2: y direction + set_node_vars!(fstar2_L, flux, equations, dg, i, j) + set_node_vars!(fstar2_R, flux, equations, dg, i, j) + end - # Fluxes in y - fstar2_L[:, :, 1] .= zero(eltype(fstar2_L)) - fstar2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_L)) - fstar2_R[:, :, 1] .= zero(eltype(fstar2_R)) - fstar2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_R)) - - # Compute inner fluxes - for j in 2:nnodes(dg), i in eachnode(dg) - u_ll = get_node_vars(u, equations, dg, i, j - 1, element) - u_rr = get_node_vars(u, equations, dg, i, j, element) - - # Compute conservative part - f2 = volume_flux(u_ll, u_rr, 2, equations) # orientation 2: y direction - - # Compute nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - f2_L = f2 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 2, equations) - f2_R = f2 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 2, equations) - - # Copy to temporary storage - set_node_vars!(fstar2_L, f2_L, equations, dg, i, j) - set_node_vars!(fstar2_R, f2_R, equations, dg, i, j) + return nothing end - return nothing -end + # calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u_leftright, + # nonconservative_terms::True, equations, + # volume_flux_fv, dg, element) + # + # Calculate the finite volume fluxes inside the elements (**with non-conservative terms**). + # + # # Arguments + # - `fstar1_L::AbstractArray{<:Real, 3}`: + # - `fstar1_R::AbstractArray{<:Real, 3}`: + # - `fstar2_L::AbstractArray{<:Real, 3}`: + # - `fstar2_R::AbstractArray{<:Real, 3}`: + # - `u_leftright::AbstractArray{<:Real, 4}` + @inline function calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, + u::AbstractArray{<:Any, 4}, + mesh::TreeMesh{2}, nonconservative_terms::True, equations, + volume_flux_fv, dg::DGSEM, element, cache + ) + volume_flux, nonconservative_flux = volume_flux_fv + + # Fluxes in x + fstar1_L[:, 1, :] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1, :] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fstar1_R)) + + for j in eachnode(dg), i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, j, element) + u_rr = get_node_vars(u, equations, dg, i, j, element) + + # Compute conservative part + f1 = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction + + # Compute nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + f1_L = f1 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations) + f1_R = f1 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations) + + # Copy to temporary storage + set_node_vars!(fstar1_L, f1_L, equations, dg, i, j) + set_node_vars!(fstar1_R, f1_R, equations, dg, i, j) + end -function prolong2interfaces!(cache, u, - mesh::TreeMesh{2}, equations, surface_integral, dg::DG) - @unpack interfaces = cache - @unpack orientations, neighbor_ids = interfaces - interfaces_u = interfaces.u + # Fluxes in y + fstar2_L[:, :, 1] .= zero(eltype(fstar2_L)) + fstar2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_L)) + fstar2_R[:, :, 1] .= zero(eltype(fstar2_R)) + fstar2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fstar2_R)) + + # Compute inner fluxes + for j in 2:nnodes(dg), i in eachnode(dg) + u_ll = get_node_vars(u, equations, dg, i, j - 1, element) + u_rr = get_node_vars(u, equations, dg, i, j, element) + + # Compute conservative part + f2 = volume_flux(u_ll, u_rr, 2, equations) # orientation 2: y direction + + # Compute nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + f2_L = f2 + 0.5f0 * nonconservative_flux(u_ll, u_rr, 2, equations) + f2_R = f2 + 0.5f0 * nonconservative_flux(u_rr, u_ll, 2, equations) + + # Copy to temporary storage + set_node_vars!(fstar2_L, f2_L, equations, dg, i, j) + set_node_vars!(fstar2_R, f2_R, equations, dg, i, j) + end - @threaded for interface in eachinterface(dg, cache) - left_element = neighbor_ids[1, interface] - right_element = neighbor_ids[2, interface] + return nothing + end - if orientations[interface] == 1 - # interface in x-direction - for j in eachnode(dg), v in eachvariable(equations) - interfaces_u[1, v, j, interface] = u[v, nnodes(dg), j, left_element] - interfaces_u[2, v, j, interface] = u[v, 1, j, right_element] - end - else # if orientations[interface] == 2 - # interface in y-direction - for i in eachnode(dg), v in eachvariable(equations) - interfaces_u[1, v, i, interface] = u[v, i, nnodes(dg), left_element] - interfaces_u[2, v, i, interface] = u[v, i, 1, right_element] + function prolong2interfaces!( + cache, u, + mesh::TreeMesh{2}, equations, surface_integral, dg::DG + ) + @unpack interfaces = cache + @unpack orientations, neighbor_ids = interfaces + interfaces_u = interfaces.u + + @threaded for interface in eachinterface(dg, cache) + left_element = neighbor_ids[1, interface] + right_element = neighbor_ids[2, interface] + + if orientations[interface] == 1 + # interface in x-direction + for j in eachnode(dg), v in eachvariable(equations) + interfaces_u[1, v, j, interface] = u[v, nnodes(dg), j, left_element] + interfaces_u[2, v, j, interface] = u[v, 1, j, right_element] + end + else # if orientations[interface] == 2 + # interface in y-direction + for i in eachnode(dg), v in eachvariable(equations) + interfaces_u[1, v, i, interface] = u[v, i, nnodes(dg), left_element] + interfaces_u[2, v, i, interface] = u[v, i, 1, right_element] + end end end + + return nothing end - return nothing -end + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u, neighbor_ids, orientations = cache.interfaces + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{2}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u, neighbor_ids, orientations = cache.interfaces + for i in eachnode(dg) + # Call pointwise Riemann solver + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface) + flux = surface_flux(u_ll, u_rr, orientations[interface], equations) + + # Copy flux to left and right element storage + for v in eachvariable(equations) + surface_flux_values[v, i, left_direction, left_id] = flux[v] + surface_flux_values[v, i, right_direction, right_id] = flux[v] + end + end + end - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] + return nothing + end - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache + ) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack u, neighbor_ids, orientations = cache.interfaces + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 - for i in eachnode(dg) - # Call pointwise Riemann solver - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface) - flux = surface_flux(u_ll, u_rr, orientations[interface], equations) - - # Copy flux to left and right element storage - for v in eachvariable(equations) - surface_flux_values[v, i, left_direction, left_id] = flux[v] - surface_flux_values[v, i, right_direction, right_id] = flux[v] + for i in eachnode(dg) + # Call pointwise Riemann solver + orientation = orientations[interface] + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface) + flux = surface_flux(u_ll, u_rr, orientation, equations) + + # Compute both nonconservative fluxes + noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations) + noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations) + + # Copy flux to left and right element storage + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[v, i, left_direction, left_id] = flux[v] + + 0.5f0 * + noncons_left[v] + surface_flux_values[v, i, right_direction, right_id] = flux[v] + + 0.5f0 * + noncons_right[v] + end end end - end - return nothing -end + return nothing + end -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{2}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache) - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack u, neighbor_ids, orientations = cache.interfaces + function prolong2boundaries!( + cache, u, + mesh::TreeMesh{2}, equations, surface_integral, dg::DG + ) + @unpack boundaries = cache + @unpack orientations, neighbor_sides = boundaries + + @threaded for boundary in eachboundary(dg, cache) + element = boundaries.neighbor_ids[boundary] + + if orientations[boundary] == 1 + # boundary in x-direction + if neighbor_sides[boundary] == 1 + # element in -x direction of boundary + for l in eachnode(dg), v in eachvariable(equations) + boundaries.u[1, v, l, boundary] = u[v, nnodes(dg), l, element] + end + else # Element in +x direction of boundary + for l in eachnode(dg), v in eachvariable(equations) + boundaries.u[2, v, l, boundary] = u[v, 1, l, element] + end + end + else # if orientations[boundary] == 2 + # boundary in y-direction + if neighbor_sides[boundary] == 1 + # element in -y direction of boundary + for l in eachnode(dg), v in eachvariable(equations) + boundaries.u[1, v, l, boundary] = u[v, l, nnodes(dg), element] + end + else + # element in +y direction of boundary + for l in eachnode(dg), v in eachvariable(equations) + boundaries.u[2, v, l, boundary] = u[v, l, 1, element] + end + end + end + end - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] + return nothing + end - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 + # TODO: Taal dimension agnostic + function calc_boundary_flux!( + cache, t, boundary_condition::BoundaryConditionPeriodic, + mesh::TreeMesh{2}, equations, surface_integral, dg::DG + ) + @assert isempty(eachboundary(dg, cache)) + end - for i in eachnode(dg) - # Call pointwise Riemann solver - orientation = orientations[interface] - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface) - flux = surface_flux(u_ll, u_rr, orientation, equations) + function calc_boundary_flux!( + cache, t, boundary_conditions::NamedTuple, + mesh::TreeMesh{2}, equations, surface_integral, dg::DG + ) + @unpack surface_flux_values = cache.elements + @unpack n_boundaries_per_direction = cache.boundaries + + # Calculate indices + lasts = accumulate(+, n_boundaries_per_direction) + firsts = lasts - n_boundaries_per_direction .+ 1 + + # Calc boundary fluxes in each direction + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[1], + have_nonconservative_terms(equations), + equations, surface_integral, dg, cache, + 1, firsts[1], lasts[1] + ) + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[2], + have_nonconservative_terms(equations), + equations, surface_integral, dg, cache, + 2, firsts[2], lasts[2] + ) + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[3], + have_nonconservative_terms(equations), + equations, surface_integral, dg, cache, + 3, firsts[3], lasts[3] + ) + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[4], + have_nonconservative_terms(equations), + equations, surface_integral, dg, cache, + 4, firsts[4], lasts[4] + ) + end - # Compute both nonconservative fluxes - noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations) - noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations) + function calc_boundary_flux_by_direction!( + surface_flux_values::AbstractArray{<:Any, 4}, + t, + boundary_condition, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + direction, first_boundary, last_boundary + ) + @unpack surface_flux = surface_integral + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] - # Copy flux to left and right element storage - for v in eachvariable(equations) - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - surface_flux_values[v, i, left_direction, left_id] = flux[v] + - 0.5f0 * - noncons_left[v] - surface_flux_values[v, i, right_direction, right_id] = flux[v] + - 0.5f0 * - noncons_right[v] + for i in eachnode(dg) + # Get boundary flux + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, boundary) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + u_inner = u_ll + else # Element is on the right, boundary on the left + u_inner = u_rr + end + x = get_node_coords(node_coordinates, equations, dg, i, boundary) + flux = boundary_condition( + u_inner, orientations[boundary], direction, x, t, + surface_flux, + equations + ) + + # Copy flux to left and right element storage + for v in eachvariable(equations) + surface_flux_values[v, i, direction, neighbor] = flux[v] + end end end + + return nothing end - return nothing -end + function calc_boundary_flux_by_direction!( + surface_flux_values::AbstractArray{<:Any, 4}, + t, + boundary_condition, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache, + direction, first_boundary, last_boundary + ) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] -function prolong2boundaries!(cache, u, - mesh::TreeMesh{2}, equations, surface_integral, dg::DG) - @unpack boundaries = cache - @unpack orientations, neighbor_sides = boundaries + for i in eachnode(dg) + # Get boundary flux + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, boundary) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + u_inner = u_ll + else # Element is on the right, boundary on the left + u_inner = u_rr + end + x = get_node_coords(node_coordinates, equations, dg, i, boundary) + flux = boundary_condition( + u_inner, orientations[boundary], direction, x, t, + surface_flux, + equations + ) + noncons_flux = boundary_condition( + u_inner, orientations[boundary], + direction, x, t, nonconservative_flux, + equations + ) + + # Copy flux to left and right element storage + for v in eachvariable(equations) + surface_flux_values[v, i, direction, neighbor] = flux[v] + + 0.5f0 * noncons_flux[v] + end + end + end - @threaded for boundary in eachboundary(dg, cache) - element = boundaries.neighbor_ids[boundary] + return nothing + end - if orientations[boundary] == 1 - # boundary in x-direction - if neighbor_sides[boundary] == 1 - # element in -x direction of boundary - for l in eachnode(dg), v in eachvariable(equations) - boundaries.u[1, v, l, boundary] = u[v, nnodes(dg), l, element] + function prolong2mortars!( + cache, u, + mesh::TreeMesh{2}, equations, + mortar_l2::LobattoLegendreMortarL2, surface_integral, + dg::DGSEM + ) + @threaded for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Copy solution small to small + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper[2, v, l, mortar] = u[ + v, 1, l, + upper_element, + ] + cache.mortars.u_lower[2, v, l, mortar] = u[ + v, 1, l, + lower_element, + ] + end + end + else + # L2 mortars in y-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper[2, v, l, mortar] = u[ + v, l, 1, + upper_element, + ] + cache.mortars.u_lower[2, v, l, mortar] = u[ + v, l, 1, + lower_element, + ] + end + end end - else # Element in +x direction of boundary - for l in eachnode(dg), v in eachvariable(equations) - boundaries.u[2, v, l, boundary] = u[v, 1, l, element] + else # large_sides[mortar] == 2 -> small elements on left side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper[1, v, l, mortar] = u[ + v, nnodes(dg), l, + upper_element, + ] + cache.mortars.u_lower[1, v, l, mortar] = u[ + v, nnodes(dg), l, + lower_element, + ] + end + end + else + # L2 mortars in y-direction + for l in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper[1, v, l, mortar] = u[ + v, l, nnodes(dg), + upper_element, + ] + cache.mortars.u_lower[1, v, l, mortar] = u[ + v, l, nnodes(dg), + lower_element, + ] + end + end end end - else # if orientations[boundary] == 2 - # boundary in y-direction - if neighbor_sides[boundary] == 1 - # element in -y direction of boundary - for l in eachnode(dg), v in eachvariable(equations) - boundaries.u[1, v, l, boundary] = u[v, l, nnodes(dg), element] + + # Interpolate large element face data to small interface locations + if cache.mortars.large_sides[mortar] == 1 # -> large element on left side + leftright = 1 + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(u, :, nnodes(dg), :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large + ) + else + # L2 mortars in y-direction + u_large = view(u, :, :, nnodes(dg), large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large + ) end - else - # element in +y direction of boundary - for l in eachnode(dg), v in eachvariable(equations) - boundaries.u[2, v, l, boundary] = u[v, l, 1, element] + else # large_sides[mortar] == 2 -> large element on right side + leftright = 2 + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(u, :, 1, :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large + ) + else + # L2 mortars in y-direction + u_large = view(u, :, :, 1, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large + ) end end end - end - return nothing -end - -# TODO: Taal dimension agnostic -function calc_boundary_flux!(cache, t, boundary_condition::BoundaryConditionPeriodic, - mesh::TreeMesh{2}, equations, surface_integral, dg::DG) - @assert isempty(eachboundary(dg, cache)) -end - -function calc_boundary_flux!(cache, t, boundary_conditions::NamedTuple, - mesh::TreeMesh{2}, equations, surface_integral, dg::DG) - @unpack surface_flux_values = cache.elements - @unpack n_boundaries_per_direction = cache.boundaries - - # Calculate indices - lasts = accumulate(+, n_boundaries_per_direction) - firsts = lasts - n_boundaries_per_direction .+ 1 - - # Calc boundary fluxes in each direction - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[1], - have_nonconservative_terms(equations), - equations, surface_integral, dg, cache, - 1, firsts[1], lasts[1]) - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[2], - have_nonconservative_terms(equations), - equations, surface_integral, dg, cache, - 2, firsts[2], lasts[2]) - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[3], - have_nonconservative_terms(equations), - equations, surface_integral, dg, cache, - 3, firsts[3], lasts[3]) - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[4], - have_nonconservative_terms(equations), - equations, surface_integral, dg, cache, - 4, firsts[4], lasts[4]) -end - -function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 4}, - t, - boundary_condition, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - direction, first_boundary, last_boundary) - @unpack surface_flux = surface_integral - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] + return nothing + end - for i in eachnode(dg) - # Get boundary flux - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - u_inner = u_ll - else # Element is on the right, boundary on the left - u_inner = u_rr - end - x = get_node_coords(node_coordinates, equations, dg, i, boundary) - flux = boundary_condition(u_inner, orientations[boundary], direction, x, t, - surface_flux, - equations) + @inline function element_solutions_to_mortars!( + mortars, + mortar_l2::LobattoLegendreMortarL2, + leftright, mortar, + u_large::AbstractArray{<:Any, 2} + ) + multiply_dimensionwise!( + view(mortars.u_upper, leftright, :, :, mortar), + mortar_l2.forward_upper, u_large + ) + multiply_dimensionwise!( + view(mortars.u_lower, leftright, :, :, mortar), + mortar_l2.forward_lower, u_large + ) + return nothing + end - # Copy flux to left and right element storage - for v in eachvariable(equations) - surface_flux_values[v, i, direction, neighbor] = flux[v] - end + function calc_mortar_flux!( + surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u_lower, u_upper, orientations = cache.mortars + @unpack fstar_upper_threaded, fstar_lower_threaded = cache + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar_upper = fstar_upper_threaded[Threads.threadid()] + fstar_lower = fstar_lower_threaded[Threads.threadid()] + + # Calculate fluxes + orientation = orientations[mortar] + calc_fstar!( + fstar_upper, equations, surface_flux, dg, u_upper, mortar, + orientation + ) + calc_fstar!( + fstar_lower, equations, surface_flux, dg, u_lower, mortar, + orientation + ) + + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, + mortar, fstar_upper, fstar_lower + ) end + + return nothing end - return nothing -end + function calc_mortar_flux!( + surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::True, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack u_lower, u_upper, orientations, large_sides = cache.mortars + @unpack fstar_upper_threaded, fstar_lower_threaded = cache + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar_upper = fstar_upper_threaded[Threads.threadid()] + fstar_lower = fstar_lower_threaded[Threads.threadid()] + + # Calculate fluxes + orientation = orientations[mortar] + calc_fstar!( + fstar_upper, equations, surface_flux, dg, u_upper, mortar, + orientation + ) + calc_fstar!( + fstar_lower, equations, surface_flux, dg, u_lower, mortar, + orientation + ) + + # Add nonconservative fluxes. + # These need to be adapted on the geometry (left/right) since the order of + # the arguments matters, based on the global SBP operator interpretation. + # The same interpretation (global SBP operators coupled discontinuously via + # central fluxes/SATs) explains why we need the factor 0.5. + # Alternatively, you can also follow the argumentation of Bohm et al. 2018 + # ("nonconservative diamond flux") + if large_sides[mortar] == 1 # -> small elements on right side + for i in eachnode(dg) + # Pull the left and right solutions + u_upper_ll, u_upper_rr = get_surface_node_vars( + u_upper, equations, dg, + i, mortar + ) + u_lower_ll, u_lower_rr = get_surface_node_vars( + u_lower, equations, dg, + i, mortar + ) + # Call pointwise nonconservative term + noncons_upper = nonconservative_flux( + u_upper_ll, u_upper_rr, + orientation, equations + ) + noncons_lower = nonconservative_flux( + u_lower_ll, u_lower_rr, + orientation, equations + ) + # Add to primary and secondary temporary storage + multiply_add_to_node_vars!( + fstar_upper, 0.5f0, noncons_upper, equations, + dg, i + ) + multiply_add_to_node_vars!( + fstar_lower, 0.5f0, noncons_lower, equations, + dg, i + ) + end + else # large_sides[mortar] == 2 -> small elements on the left + for i in eachnode(dg) + # Pull the left and right solutions + u_upper_ll, u_upper_rr = get_surface_node_vars( + u_upper, equations, dg, + i, mortar + ) + u_lower_ll, u_lower_rr = get_surface_node_vars( + u_lower, equations, dg, + i, mortar + ) + # Call pointwise nonconservative term + noncons_upper = nonconservative_flux( + u_upper_rr, u_upper_ll, + orientation, equations + ) + noncons_lower = nonconservative_flux( + u_lower_rr, u_lower_ll, + orientation, equations + ) + # Add to primary and secondary temporary storage + multiply_add_to_node_vars!( + fstar_upper, 0.5f0, noncons_upper, equations, + dg, i + ) + multiply_add_to_node_vars!( + fstar_lower, 0.5f0, noncons_lower, equations, + dg, i + ) + end + end -function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 4}, - t, - boundary_condition, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache, - direction, first_boundary, last_boundary) - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, + mortar, fstar_upper, fstar_lower + ) + end - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] + return nothing + end + @inline function calc_fstar!( + destination::AbstractArray{<:Any, 2}, equations, + surface_flux, dg::DGSEM, + u_interfaces, interface, orientation + ) for i in eachnode(dg) - # Get boundary flux - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - u_inner = u_ll - else # Element is on the right, boundary on the left - u_inner = u_rr - end - x = get_node_coords(node_coordinates, equations, dg, i, boundary) - flux = boundary_condition(u_inner, orientations[boundary], direction, x, t, - surface_flux, - equations) - noncons_flux = boundary_condition(u_inner, orientations[boundary], - direction, x, t, nonconservative_flux, - equations) + # Call pointwise two-point numerical flux function + u_ll, u_rr = get_surface_node_vars(u_interfaces, equations, dg, i, interface) + flux = surface_flux(u_ll, u_rr, orientation, equations) # Copy flux to left and right element storage - for v in eachvariable(equations) - surface_flux_values[v, i, direction, neighbor] = flux[v] + - 0.5f0 * noncons_flux[v] - end + set_node_vars!(destination, flux, equations, dg, i) end - end - return nothing -end + return nothing + end -function prolong2mortars!(cache, u, - mesh::TreeMesh{2}, equations, - mortar_l2::LobattoLegendreMortarL2, surface_integral, - dg::DGSEM) - @threaded for mortar in eachmortar(dg, cache) + @inline function mortar_fluxes_to_elements!( + surface_flux_values, + mesh::TreeMesh{2}, equations, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, + mortar, fstar_upper, fstar_lower + ) large_element = cache.mortars.neighbor_ids[3, mortar] upper_element = cache.mortars.neighbor_ids[2, mortar] lower_element = cache.mortars.neighbor_ids[1, mortar] - # Copy solution small to small + # Copy flux small to small if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - for l in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper[2, v, l, mortar] = u[v, 1, l, - upper_element] - cache.mortars.u_lower[2, v, l, mortar] = u[v, 1, l, - lower_element] - end - end + direction = 1 else # L2 mortars in y-direction - for l in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper[2, v, l, mortar] = u[v, l, 1, - upper_element] - cache.mortars.u_lower[2, v, l, mortar] = u[v, l, 1, - lower_element] - end - end + direction = 3 end else # large_sides[mortar] == 2 -> small elements on left side if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - for l in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper[1, v, l, mortar] = u[v, nnodes(dg), l, - upper_element] - cache.mortars.u_lower[1, v, l, mortar] = u[v, nnodes(dg), l, - lower_element] - end - end + direction = 2 else # L2 mortars in y-direction - for l in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper[1, v, l, mortar] = u[v, l, nnodes(dg), - upper_element] - cache.mortars.u_lower[1, v, l, mortar] = u[v, l, nnodes(dg), - lower_element] - end - end + direction = 4 end end + surface_flux_values[:, :, direction, upper_element] .= fstar_upper + surface_flux_values[:, :, direction, lower_element] .= fstar_lower - # Interpolate large element face data to small interface locations + # Project small fluxes to large element if cache.mortars.large_sides[mortar] == 1 # -> large element on left side - leftright = 1 if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - u_large = view(u, :, nnodes(dg), :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large) + direction = 2 else # L2 mortars in y-direction - u_large = view(u, :, :, nnodes(dg), large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large) + direction = 4 end else # large_sides[mortar] == 2 -> large element on right side - leftright = 2 if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - u_large = view(u, :, 1, :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large) + direction = 1 else # L2 mortars in y-direction - u_large = view(u, :, :, 1, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large) + direction = 3 end end - end - return nothing -end - -@inline function element_solutions_to_mortars!(mortars, - mortar_l2::LobattoLegendreMortarL2, - leftright, mortar, - u_large::AbstractArray{<:Any, 2}) - multiply_dimensionwise!(view(mortars.u_upper, leftright, :, :, mortar), - mortar_l2.forward_upper, u_large) - multiply_dimensionwise!(view(mortars.u_lower, leftright, :, :, mortar), - mortar_l2.forward_lower, u_large) - return nothing -end - -function calc_mortar_flux!(surface_flux_values, - mesh::TreeMesh{2}, - nonconservative_terms::False, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u_lower, u_upper, orientations = cache.mortars - @unpack fstar_upper_threaded, fstar_lower_threaded = cache - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar_upper = fstar_upper_threaded[Threads.threadid()] - fstar_lower = fstar_lower_threaded[Threads.threadid()] - - # Calculate fluxes - orientation = orientations[mortar] - calc_fstar!(fstar_upper, equations, surface_flux, dg, u_upper, mortar, - orientation) - calc_fstar!(fstar_lower, equations, surface_flux, dg, u_lower, mortar, - orientation) - - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, - mortar, fstar_upper, fstar_lower) + # TODO: Taal performance + # for v in eachvariable(equations) + # # The code below is semantically equivalent to + # # surface_flux_values[v, :, direction, large_element] .= + # # (mortar_l2.reverse_upper * fstar_upper[v, :] + mortar_l2.reverse_lower * fstar_lower[v, :]) + # # but faster and does not allocate. + # # Note that `true * some_float == some_float` in Julia, i.e. `true` acts as + # # a universal `one`. Hence, the second `mul!` means "add the matrix-vector + # # product to the current value of the destination". + # @views mul!(surface_flux_values[v, :, direction, large_element], + # mortar_l2.reverse_upper, fstar_upper[v, :]) + # @views mul!(surface_flux_values[v, :, direction, large_element], + # mortar_l2.reverse_lower, fstar_lower[v, :], true, true) + # end + # The code above could be replaced by the following code. However, the relative efficiency + # depends on the types of fstar_upper/fstar_lower and dg.l2mortar_reverse_upper. + # Using StaticArrays for both makes the code above faster for common test cases. + multiply_dimensionwise!( + view(surface_flux_values, :, :, direction, large_element), + mortar_l2.reverse_upper, fstar_upper, + mortar_l2.reverse_lower, fstar_lower + ) + + return nothing end - return nothing -end - -function calc_mortar_flux!(surface_flux_values, - mesh::TreeMesh{2}, - nonconservative_terms::True, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack u_lower, u_upper, orientations, large_sides = cache.mortars - @unpack fstar_upper_threaded, fstar_lower_threaded = cache - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar_upper = fstar_upper_threaded[Threads.threadid()] - fstar_lower = fstar_lower_threaded[Threads.threadid()] - - # Calculate fluxes - orientation = orientations[mortar] - calc_fstar!(fstar_upper, equations, surface_flux, dg, u_upper, mortar, - orientation) - calc_fstar!(fstar_lower, equations, surface_flux, dg, u_lower, mortar, - orientation) - - # Add nonconservative fluxes. - # These need to be adapted on the geometry (left/right) since the order of - # the arguments matters, based on the global SBP operator interpretation. - # The same interpretation (global SBP operators coupled discontinuously via - # central fluxes/SATs) explains why we need the factor 0.5. - # Alternatively, you can also follow the argumentation of Bohm et al. 2018 - # ("nonconservative diamond flux") - if large_sides[mortar] == 1 # -> small elements on right side - for i in eachnode(dg) - # Pull the left and right solutions - u_upper_ll, u_upper_rr = get_surface_node_vars(u_upper, equations, dg, - i, mortar) - u_lower_ll, u_lower_rr = get_surface_node_vars(u_lower, equations, dg, - i, mortar) - # Call pointwise nonconservative term - noncons_upper = nonconservative_flux(u_upper_ll, u_upper_rr, - orientation, equations) - noncons_lower = nonconservative_flux(u_lower_ll, u_lower_rr, - orientation, equations) - # Add to primary and secondary temporary storage - multiply_add_to_node_vars!(fstar_upper, 0.5f0, noncons_upper, equations, - dg, i) - multiply_add_to_node_vars!(fstar_lower, 0.5f0, noncons_lower, equations, - dg, i) - end - else # large_sides[mortar] == 2 -> small elements on the left - for i in eachnode(dg) - # Pull the left and right solutions - u_upper_ll, u_upper_rr = get_surface_node_vars(u_upper, equations, dg, - i, mortar) - u_lower_ll, u_lower_rr = get_surface_node_vars(u_lower, equations, dg, - i, mortar) - # Call pointwise nonconservative term - noncons_upper = nonconservative_flux(u_upper_rr, u_upper_ll, - orientation, equations) - noncons_lower = nonconservative_flux(u_lower_rr, u_lower_ll, - orientation, equations) - # Add to primary and secondary temporary storage - multiply_add_to_node_vars!(fstar_upper, 0.5f0, noncons_upper, equations, - dg, i) - multiply_add_to_node_vars!(fstar_lower, 0.5f0, noncons_lower, equations, - dg, i) + function calc_surface_integral!( + du, u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + StructuredMeshView{2}, + }, + equations, surface_integral::SurfaceIntegralWeakForm, + dg::DG, cache + ) + @unpack boundary_interpolation = dg.basis + @unpack surface_flux_values = cache.elements + + # Note that all fluxes have been computed with outward-pointing normal vectors. + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg) + for v in eachvariable(equations) + # surface at -x + du[v, 1, l, element] = ( + du[v, 1, l, element] - + surface_flux_values[v, l, 1, element] * + factor_1 + ) + + # surface at +x + du[v, nnodes(dg), l, element] = ( + du[v, nnodes(dg), l, element] + + surface_flux_values[v, l, 2, element] * + factor_2 + ) + + # surface at -y + du[v, l, 1, element] = ( + du[v, l, 1, element] - + surface_flux_values[v, l, 3, element] * + factor_1 + ) + + # surface at +y + du[v, l, nnodes(dg), element] = ( + du[v, l, nnodes(dg), element] + + surface_flux_values[v, l, 4, element] * + factor_2 + ) + end end end - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, - mortar, fstar_upper, fstar_lower) + return nothing end - return nothing -end - -@inline function calc_fstar!(destination::AbstractArray{<:Any, 2}, equations, - surface_flux, dg::DGSEM, - u_interfaces, interface, orientation) - for i in eachnode(dg) - # Call pointwise two-point numerical flux function - u_ll, u_rr = get_surface_node_vars(u_interfaces, equations, dg, i, interface) - flux = surface_flux(u_ll, u_rr, orientation, equations) - - # Copy flux to left and right element storage - set_node_vars!(destination, flux, equations, dg, i) - end + function apply_jacobian!( + du, mesh::TreeMesh{2}, + equations, dg::DG, cache + ) + @unpack inverse_jacobian = cache.elements - return nothing -end - -@inline function mortar_fluxes_to_elements!(surface_flux_values, - mesh::TreeMesh{2}, equations, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, - mortar, fstar_upper, fstar_lower) - large_element = cache.mortars.neighbor_ids[3, mortar] - upper_element = cache.mortars.neighbor_ids[2, mortar] - lower_element = cache.mortars.neighbor_ids[1, mortar] - - # Copy flux small to small - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 1 - else - # L2 mortars in y-direction - direction = 3 - end - else # large_sides[mortar] == 2 -> small elements on left side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 2 - else - # L2 mortars in y-direction - direction = 4 - end - end - surface_flux_values[:, :, direction, upper_element] .= fstar_upper - surface_flux_values[:, :, direction, lower_element] .= fstar_lower - - # Project small fluxes to large element - if cache.mortars.large_sides[mortar] == 1 # -> large element on left side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 2 - else - # L2 mortars in y-direction - direction = 4 - end - else # large_sides[mortar] == 2 -> large element on right side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 1 - else - # L2 mortars in y-direction - direction = 3 - end - end + @threaded for element in eachelement(dg, cache) + factor = -inverse_jacobian[element] - # TODO: Taal performance - # for v in eachvariable(equations) - # # The code below is semantically equivalent to - # # surface_flux_values[v, :, direction, large_element] .= - # # (mortar_l2.reverse_upper * fstar_upper[v, :] + mortar_l2.reverse_lower * fstar_lower[v, :]) - # # but faster and does not allocate. - # # Note that `true * some_float == some_float` in Julia, i.e. `true` acts as - # # a universal `one`. Hence, the second `mul!` means "add the matrix-vector - # # product to the current value of the destination". - # @views mul!(surface_flux_values[v, :, direction, large_element], - # mortar_l2.reverse_upper, fstar_upper[v, :]) - # @views mul!(surface_flux_values[v, :, direction, large_element], - # mortar_l2.reverse_lower, fstar_lower[v, :], true, true) - # end - # The code above could be replaced by the following code. However, the relative efficiency - # depends on the types of fstar_upper/fstar_lower and dg.l2mortar_reverse_upper. - # Using StaticArrays for both makes the code above faster for common test cases. - multiply_dimensionwise!(view(surface_flux_values, :, :, direction, large_element), - mortar_l2.reverse_upper, fstar_upper, - mortar_l2.reverse_lower, fstar_lower) - - return nothing -end - -function calc_surface_integral!(du, u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - StructuredMeshView{2}}, - equations, surface_integral::SurfaceIntegralWeakForm, - dg::DG, cache) - @unpack boundary_interpolation = dg.basis - @unpack surface_flux_values = cache.elements - - # Note that all fluxes have been computed with outward-pointing normal vectors. - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` to let `@muladd` turn these - # into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] - @threaded for element in eachelement(dg, cache) - for l in eachnode(dg) - for v in eachvariable(equations) - # surface at -x - du[v, 1, l, element] = (du[v, 1, l, element] - - surface_flux_values[v, l, 1, element] * - factor_1) - - # surface at +x - du[v, nnodes(dg), l, element] = (du[v, nnodes(dg), l, element] + - surface_flux_values[v, l, 2, element] * - factor_2) - - # surface at -y - du[v, l, 1, element] = (du[v, l, 1, element] - - surface_flux_values[v, l, 3, element] * - factor_1) - - # surface at +y - du[v, l, nnodes(dg), element] = (du[v, l, nnodes(dg), element] + - surface_flux_values[v, l, 4, element] * - factor_2) + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, j, element] *= factor + end end end - end - return nothing -end - -function apply_jacobian!(du, mesh::TreeMesh{2}, - equations, dg::DG, cache) - @unpack inverse_jacobian = cache.elements + return nothing + end - @threaded for element in eachelement(dg, cache) - factor = -inverse_jacobian[element] + # TODO: Taal dimension agnostic + function calc_sources!( + du, u, t, source_terms::Nothing, + equations::AbstractEquations{2}, dg::DG, cache + ) + return nothing + end - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - du[v, i, j, element] *= factor + function calc_sources!( + du, u, t, source_terms, + equations::AbstractEquations{2}, dg::DG, cache + ) + @unpack node_coordinates = cache.elements + + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, element) + x_local = get_node_coords( + node_coordinates, equations, dg, + i, j, element + ) + du_local = source_terms(u_local, x_local, t, equations) + add_to_node_vars!(du, du_local, equations, dg, i, j, element) end end - end - - return nothing -end -# TODO: Taal dimension agnostic -function calc_sources!(du, u, t, source_terms::Nothing, - equations::AbstractEquations{2}, dg::DG, cache) - return nothing -end - -function calc_sources!(du, u, t, source_terms, - equations::AbstractEquations{2}, dg::DG, cache) - @unpack node_coordinates = cache.elements - - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, element) - x_local = get_node_coords(node_coordinates, equations, dg, - i, j, element) - du_local = source_terms(u_local, x_local, t, equations) - add_to_node_vars!(du, du_local, equations, dg, i, j, element) - end + return nothing end - - return nothing -end end # @muladd diff --git a/src/solvers/dgsem_tree/dg_2d_compressible_euler.jl b/src/solvers/dgsem_tree/dg_2d_compressible_euler.jl index 50b1e8cb5b..a703ce8bd8 100644 --- a/src/solvers/dgsem_tree/dg_2d_compressible_euler.jl +++ b/src/solvers/dgsem_tree/dg_2d_compressible_euler.jl @@ -3,48 +3,54 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Calculate the vorticity on a single node using the derivative matrix from the polynomial basis of -# a DGSEM solver. `u` is the solution on the whole domain. -# This function is used for calculating acoustic source terms for coupled Euler-acoustics -# simulations. -function calc_vorticity_node(u, mesh::TreeMesh{2}, - equations::CompressibleEulerEquations2D, - dg::DGSEM, cache, i, j, element) - @unpack derivative_matrix = dg.basis - - v2_x = zero(eltype(u)) # derivative of v2 in x direction - for ii in eachnode(dg) - rho, _, rho_v2 = get_node_vars(u, equations, dg, ii, j, element) - v2 = rho_v2 / rho - v2_x = v2_x + derivative_matrix[i, ii] * v2 - end + #! format: noindent + + # Calculate the vorticity on a single node using the derivative matrix from the polynomial basis of + # a DGSEM solver. `u` is the solution on the whole domain. + # This function is used for calculating acoustic source terms for coupled Euler-acoustics + # simulations. + function calc_vorticity_node( + u, mesh::TreeMesh{2}, + equations::CompressibleEulerEquations2D, + dg::DGSEM, cache, i, j, element + ) + @unpack derivative_matrix = dg.basis + + v2_x = zero(eltype(u)) # derivative of v2 in x direction + for ii in eachnode(dg) + rho, _, rho_v2 = get_node_vars(u, equations, dg, ii, j, element) + v2 = rho_v2 / rho + v2_x = v2_x + derivative_matrix[i, ii] * v2 + end - v1_y = zero(eltype(u)) # derivative of v1 in y direction - for jj in eachnode(dg) - rho, rho_v1 = get_node_vars(u, equations, dg, i, jj, element) - v1 = rho_v1 / rho - v1_y = v1_y + derivative_matrix[j, jj] * v1 - end + v1_y = zero(eltype(u)) # derivative of v1 in y direction + for jj in eachnode(dg) + rho, rho_v1 = get_node_vars(u, equations, dg, i, jj, element) + v1 = rho_v1 / rho + v1_y = v1_y + derivative_matrix[j, jj] * v1 + end - return (v2_x - v1_y) * cache.elements.inverse_jacobian[element] -end + return (v2_x - v1_y) * cache.elements.inverse_jacobian[element] + end -# Convenience function for calculating the vorticity on the whole domain and storing it in a -# preallocated array -function calc_vorticity!(vorticity, u, mesh::TreeMesh{2}, - equations::CompressibleEulerEquations2D, - dg::DGSEM, cache) - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - vorticity[i, j, element] = calc_vorticity_node(u, mesh, equations, dg, - cache, i, j, element) + # Convenience function for calculating the vorticity on the whole domain and storing it in a + # preallocated array + function calc_vorticity!( + vorticity, u, mesh::TreeMesh{2}, + equations::CompressibleEulerEquations2D, + dg::DGSEM, cache + ) + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + vorticity[i, j, element] = calc_vorticity_node( + u, mesh, equations, dg, + cache, i, j, element + ) + end end - end - return nothing -end + return nothing + end end # muladd # From here on, this file contains specializations of DG methods on the @@ -64,26 +70,38 @@ end # muladd # We specialize on `PtrArray` since these will be returned by `Trixi.wrap_array` # if LoopVectorization.jl can handle the array types. This ensures that `@turbo` # works efficiently here. -@inline function flux_differencing_kernel!(_du::PtrArray, u_cons::PtrArray, - element, mesh::TreeMesh{2}, - nonconservative_terms::False, - equations::CompressibleEulerEquations2D, - volume_flux::typeof(flux_shima_etal_turbo), - dg::DGSEM, cache, alpha) +@inline function flux_differencing_kernel!( + _du::PtrArray, u_cons::PtrArray, + element, mesh::TreeMesh{2}, + nonconservative_terms::False, + equations::CompressibleEulerEquations2D, + volume_flux::typeof(flux_shima_etal_turbo), + dg::DGSEM, cache, alpha + ) @unpack derivative_split = dg.basis # Create a temporary array that will be used to store the RHS with permuted # indices `[i, j, v]` to allow using SIMD instructions. # `StrideArray`s with purely static dimensions do not allocate on the heap. - du = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., - StaticInt(nvariables(equations)))) + du = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., + StaticInt(nvariables(equations)), + ) + ) # Convert conserved to primitive variables on the given `element`. - u_prim = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), - ndims(mesh))..., - StaticInt(nvariables(equations)))) + u_prim = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple( + _ -> StaticInt(nnodes(dg)), + ndims(mesh) + )..., + StaticInt(nvariables(equations)), + ) + ) @turbo for j in eachnode(dg), i in eachnode(dg) rho = u_cons[1, i, j, element] @@ -105,18 +123,26 @@ end # muladd # At first, we create new temporary arrays with permuted memory layout to # allow using SIMD instructions along the first dimension (which is contiguous # in memory). - du_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - u_prim_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) + du_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + u_prim_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) u_prim_permuted[j, i, v] = u_prim[i, j, v] end @@ -167,8 +193,8 @@ end # muladd end @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) du[i, j, v] = du_permuted[j, i, v] end @@ -219,36 +245,48 @@ end # muladd # Finally, we add the temporary RHS computed here to the global RHS in the # given `element`. @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) _du[v, i, j, element] += du[i, j, v] end end -@inline function flux_differencing_kernel!(_du::PtrArray, u_cons::PtrArray, - element, mesh::TreeMesh{2}, - nonconservative_terms::False, - equations::CompressibleEulerEquations2D, - volume_flux::typeof(flux_ranocha_turbo), - dg::DGSEM, cache, alpha) +@inline function flux_differencing_kernel!( + _du::PtrArray, u_cons::PtrArray, + element, mesh::TreeMesh{2}, + nonconservative_terms::False, + equations::CompressibleEulerEquations2D, + volume_flux::typeof(flux_ranocha_turbo), + dg::DGSEM, cache, alpha + ) @unpack derivative_split = dg.basis # Create a temporary array that will be used to store the RHS with permuted # indices `[i, j, v]` to allow using SIMD instructions. # `StrideArray`s with purely static dimensions do not allocate on the heap. - du = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., - StaticInt(nvariables(equations)))) + du = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., + StaticInt(nvariables(equations)), + ) + ) # Convert conserved to primitive variables on the given `element`. In addition # to the usual primitive variables, we also compute logarithms of the density # and pressure to increase the performance of the required logarithmic mean # values. - u_prim = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), - ndims(mesh))..., - StaticInt(nvariables(equations) + 2))) # We also compute "+ 2" logs + u_prim = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple( + _ -> StaticInt(nnodes(dg)), + ndims(mesh) + )..., + StaticInt(nvariables(equations) + 2), + ) + ) # We also compute "+ 2" logs @turbo for j in eachnode(dg), i in eachnode(dg) rho = u_cons[1, i, j, element] @@ -272,18 +310,26 @@ end # At first, we create new temporary arrays with permuted memory layout to # allow using SIMD instructions along the first dimension (which is contiguous # in memory). - du_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - u_prim_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations) + 2))) + du_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + u_prim_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations) + 2), + ) + ) @turbo for v in indices(u_prim, 3), # v in eachvariable(equations) misses +2 logs - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) u_prim_permuted[j, i, v] = u_prim[i, j, v] end @@ -349,8 +395,8 @@ end f2 = f1 * v1_avg + p_avg f3 = f1 * v2_avg f4 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5 * (p_ll * v1_rr + p_rr * v1_ll) + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5 * (p_ll * v1_rr + p_rr * v1_ll) # Add scaled fluxes to RHS factor_i = alpha * derivative_split[i, ii] @@ -368,8 +414,8 @@ end end @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) du[i, j, v] = du_permuted[j, i, v] end @@ -433,8 +479,8 @@ end f2 = f1 * v1_avg f3 = f1 * v2_avg + p_avg f4 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5 * (p_ll * v2_rr + p_rr * v2_ll) + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5 * (p_ll * v2_rr + p_rr * v2_ll) # Add scaled fluxes to RHS factor_j = alpha * derivative_split[j, jj] @@ -454,8 +500,8 @@ end # Finally, we add the temporary RHS computed here to the global RHS in the # given `element`. @turbo for v in eachvariable(equations), - j in eachnode(dg), - i in eachnode(dg) + j in eachnode(dg), + i in eachnode(dg) _du[v, i, j, element] += du[i, j, v] end diff --git a/src/solvers/dgsem_tree/dg_2d_parabolic.jl b/src/solvers/dgsem_tree/dg_2d_parabolic.jl index f84a08cb69..c43c9d9610 100644 --- a/src/solvers/dgsem_tree/dg_2d_parabolic.jl +++ b/src/solvers/dgsem_tree/dg_2d_parabolic.jl @@ -3,952 +3,1156 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This file collects all methods that have been updated to work with parabolic systems of equations -# -# assumptions: parabolic terms are of the form div(f(u, grad(u))) and -# will be discretized first order form as follows: -# 1. compute grad(u) -# 2. compute f(u, grad(u)) -# 3. compute div(f(u, grad(u))) (i.e., the "regular" rhs! call) -# boundary conditions will be applied to both grad(u) and div(f(u, grad(u))). -function rhs_parabolic!(du, u, t, mesh::Union{TreeMesh{2}, TreeMesh{3}}, - equations_parabolic::AbstractEquationsParabolic, - initial_condition, boundary_conditions_parabolic, source_terms, - dg::DG, parabolic_scheme, cache, cache_parabolic) - @unpack viscous_container = cache_parabolic - @unpack u_transformed, gradients, flux_viscous = viscous_container - - # Convert conservative variables to a form more suitable for viscous flux calculations - @trixi_timeit timer() "transform variables" begin - transform_variables!(u_transformed, u, mesh, equations_parabolic, - dg, parabolic_scheme, cache, cache_parabolic) - end + #! format: noindent - # Compute the gradients of the transformed variables - @trixi_timeit timer() "calculate gradient" begin - calc_gradient!(gradients, u_transformed, t, mesh, equations_parabolic, - boundary_conditions_parabolic, dg, cache, cache_parabolic) - end + # This file collects all methods that have been updated to work with parabolic systems of equations + # + # assumptions: parabolic terms are of the form div(f(u, grad(u))) and + # will be discretized first order form as follows: + # 1. compute grad(u) + # 2. compute f(u, grad(u)) + # 3. compute div(f(u, grad(u))) (i.e., the "regular" rhs! call) + # boundary conditions will be applied to both grad(u) and div(f(u, grad(u))). + function rhs_parabolic!( + du, u, t, mesh::Union{TreeMesh{2}, TreeMesh{3}}, + equations_parabolic::AbstractEquationsParabolic, + initial_condition, boundary_conditions_parabolic, source_terms, + dg::DG, parabolic_scheme, cache, cache_parabolic + ) + @unpack viscous_container = cache_parabolic + @unpack u_transformed, gradients, flux_viscous = viscous_container + + # Convert conservative variables to a form more suitable for viscous flux calculations + @trixi_timeit timer() "transform variables" begin + transform_variables!( + u_transformed, u, mesh, equations_parabolic, + dg, parabolic_scheme, cache, cache_parabolic + ) + end - # Compute and store the viscous fluxes - @trixi_timeit timer() "calculate viscous fluxes" begin - calc_viscous_fluxes!(flux_viscous, gradients, u_transformed, mesh, - equations_parabolic, dg, cache, cache_parabolic) - end + # Compute the gradients of the transformed variables + @trixi_timeit timer() "calculate gradient" begin + calc_gradient!( + gradients, u_transformed, t, mesh, equations_parabolic, + boundary_conditions_parabolic, dg, cache, cache_parabolic + ) + end - # The remainder of this function is essentially a regular rhs! for parabolic - # equations (i.e., it computes the divergence of the viscous fluxes) - # - # OBS! In `calc_viscous_fluxes!`, the viscous flux values at the volume nodes of each element have - # been computed and stored in `fluxes_viscous`. In the following, we *reuse* (abuse) the - # `interfaces` and `boundaries` containers in `cache_parabolic` to interpolate and store the - # *fluxes* at the element surfaces, as opposed to interpolating and storing the *solution* (as it - # is done in the hyperbolic operator). That is, `interfaces.u`/`boundaries.u` store *viscous flux values* - # and *not the solution*. The advantage is that a) we do not need to allocate more storage, b) we - # do not need to recreate the existing data structure only with a different name, and c) we do not - # need to interpolate solutions *and* gradients to the surfaces. - - # TODO: parabolic; reconsider current data structure reuse strategy - - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, flux_viscous, mesh, equations_parabolic, dg, cache) - end + # Compute and store the viscous fluxes + @trixi_timeit timer() "calculate viscous fluxes" begin + calc_viscous_fluxes!( + flux_viscous, gradients, u_transformed, mesh, + equations_parabolic, dg, cache, cache_parabolic + ) + end - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache_parabolic, flux_viscous, mesh, equations_parabolic, - dg.surface_integral, dg, cache) - end + # The remainder of this function is essentially a regular rhs! for parabolic + # equations (i.e., it computes the divergence of the viscous fluxes) + # + # OBS! In `calc_viscous_fluxes!`, the viscous flux values at the volume nodes of each element have + # been computed and stored in `fluxes_viscous`. In the following, we *reuse* (abuse) the + # `interfaces` and `boundaries` containers in `cache_parabolic` to interpolate and store the + # *fluxes* at the element surfaces, as opposed to interpolating and storing the *solution* (as it + # is done in the hyperbolic operator). That is, `interfaces.u`/`boundaries.u` store *viscous flux values* + # and *not the solution*. The advantage is that a) we do not need to allocate more storage, b) we + # do not need to recreate the existing data structure only with a different name, and c) we do not + # need to interpolate solutions *and* gradients to the surfaces. + + # TODO: parabolic; reconsider current data structure reuse strategy + + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!(du, flux_viscous, mesh, equations_parabolic, dg, cache) + end - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache_parabolic.elements.surface_flux_values, mesh, - equations_parabolic, dg, cache_parabolic) - end + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache_parabolic, flux_viscous, mesh, equations_parabolic, + dg.surface_integral, dg, cache + ) + end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache_parabolic, flux_viscous, mesh, equations_parabolic, - dg.surface_integral, dg, cache) - end + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache_parabolic.elements.surface_flux_values, mesh, + equations_parabolic, dg, cache_parabolic + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux_divergence!(cache_parabolic, t, - boundary_conditions_parabolic, mesh, - equations_parabolic, - dg.surface_integral, dg) - end + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache_parabolic, flux_viscous, mesh, equations_parabolic, + dg.surface_integral, dg, cache + ) + end - # Prolong solution to mortars - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, flux_viscous, mesh, equations_parabolic, - dg.mortar, dg.surface_integral, dg) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux_divergence!( + cache_parabolic, t, + boundary_conditions_parabolic, mesh, + equations_parabolic, + dg.surface_integral, dg + ) + end - # Calculate mortar fluxes - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(cache_parabolic.elements.surface_flux_values, mesh, - equations_parabolic, - dg.mortar, dg.surface_integral, dg, cache) - end + # Prolong solution to mortars + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!( + cache, flux_viscous, mesh, equations_parabolic, + dg.mortar, dg.surface_integral, dg + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations_parabolic, - dg.surface_integral, dg, cache_parabolic) - end + # Calculate mortar fluxes + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!( + cache_parabolic.elements.surface_flux_values, mesh, + equations_parabolic, + dg.mortar, dg.surface_integral, dg, cache + ) + end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" begin - apply_jacobian_parabolic!(du, mesh, equations_parabolic, dg, cache_parabolic) - end + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations_parabolic, + dg.surface_integral, dg, cache_parabolic + ) + end - return nothing -end - -# Transform solution variables prior to taking the gradient -# (e.g., conservative to primitive variables). Defaults to doing nothing. -# TODO: can we avoid copying data? -function transform_variables!(u_transformed, u, mesh::Union{TreeMesh{2}, P4estMesh{2}}, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, parabolic_scheme, cache, cache_parabolic) - transformation = gradient_variable_transformation(equations_parabolic) - - @threaded for element in eachelement(dg, cache) - # Calculate volume terms in one element - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations_parabolic, dg, i, j, element) - u_transformed_node = transformation(u_node, equations_parabolic) - set_node_vars!(u_transformed, u_transformed_node, equations_parabolic, dg, - i, j, element) + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" begin + apply_jacobian_parabolic!(du, mesh, equations_parabolic, dg, cache_parabolic) end + + return nothing end -end - -# This is the version used when calculating the divergence of the viscous fluxes -function calc_volume_integral!(du, flux_viscous, - mesh::TreeMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - dg::DGSEM, cache) - @unpack derivative_dhat = dg.basis - flux_viscous_x, flux_viscous_y = flux_viscous - - @threaded for element in eachelement(dg, cache) - # Calculate volume terms in one element - for j in eachnode(dg), i in eachnode(dg) - flux_1_node = get_node_vars(flux_viscous_x, equations_parabolic, dg, i, j, - element) - flux_2_node = get_node_vars(flux_viscous_y, equations_parabolic, dg, i, j, - element) - - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[ii, i], flux_1_node, - equations_parabolic, dg, ii, j, element) - end - for jj in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[jj, j], flux_2_node, - equations_parabolic, dg, i, jj, element) + # Transform solution variables prior to taking the gradient + # (e.g., conservative to primitive variables). Defaults to doing nothing. + # TODO: can we avoid copying data? + function transform_variables!( + u_transformed, u, mesh::Union{TreeMesh{2}, P4estMesh{2}}, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, parabolic_scheme, cache, cache_parabolic + ) + transformation = gradient_variable_transformation(equations_parabolic) + + @threaded for element in eachelement(dg, cache) + # Calculate volume terms in one element + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations_parabolic, dg, i, j, element) + u_transformed_node = transformation(u_node, equations_parabolic) + set_node_vars!( + u_transformed, u_transformed_node, equations_parabolic, dg, + i, j, element + ) end end end - return nothing -end - -# This is the version used when calculating the divergence of the viscous fluxes -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache_parabolic, flux_viscous, - mesh::TreeMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - @unpack interfaces = cache_parabolic - @unpack orientations, neighbor_ids = interfaces - interfaces_u = interfaces.u - - flux_viscous_x, flux_viscous_y = flux_viscous - - @threaded for interface in eachinterface(dg, cache) - left_element = neighbor_ids[1, interface] - right_element = neighbor_ids[2, interface] - - if orientations[interface] == 1 - # interface in x-direction - for j in eachnode(dg), v in eachvariable(equations_parabolic) - # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! - interfaces_u[1, v, j, interface] = flux_viscous_x[v, nnodes(dg), j, - left_element] - interfaces_u[2, v, j, interface] = flux_viscous_x[v, 1, j, - right_element] - end - else # if orientations[interface] == 2 - # interface in y-direction - for i in eachnode(dg), v in eachvariable(equations_parabolic) - # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! - interfaces_u[1, v, i, interface] = flux_viscous_y[v, i, nnodes(dg), - left_element] - interfaces_u[2, v, i, interface] = flux_viscous_y[v, i, 1, - right_element] + # This is the version used when calculating the divergence of the viscous fluxes + function calc_volume_integral!( + du, flux_viscous, + mesh::TreeMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + dg::DGSEM, cache + ) + @unpack derivative_dhat = dg.basis + flux_viscous_x, flux_viscous_y = flux_viscous + + @threaded for element in eachelement(dg, cache) + # Calculate volume terms in one element + for j in eachnode(dg), i in eachnode(dg) + flux_1_node = get_node_vars( + flux_viscous_x, equations_parabolic, dg, i, j, + element + ) + flux_2_node = get_node_vars( + flux_viscous_y, equations_parabolic, dg, i, j, + element + ) + + for ii in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[ii, i], flux_1_node, + equations_parabolic, dg, ii, j, element + ) + end + + for jj in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[jj, j], flux_2_node, + equations_parabolic, dg, i, jj, element + ) + end end end + + return nothing end - return nothing -end + # This is the version used when calculating the divergence of the viscous fluxes + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache_parabolic, flux_viscous, + mesh::TreeMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + @unpack interfaces = cache_parabolic + @unpack orientations, neighbor_ids = interfaces + interfaces_u = interfaces.u + + flux_viscous_x, flux_viscous_y = flux_viscous + + @threaded for interface in eachinterface(dg, cache) + left_element = neighbor_ids[1, interface] + right_element = neighbor_ids[2, interface] + + if orientations[interface] == 1 + # interface in x-direction + for j in eachnode(dg), v in eachvariable(equations_parabolic) + # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! + interfaces_u[1, v, j, interface] = flux_viscous_x[ + v, nnodes(dg), j, + left_element, + ] + interfaces_u[2, v, j, interface] = flux_viscous_x[ + v, 1, j, + right_element, + ] + end + else # if orientations[interface] == 2 + # interface in y-direction + for i in eachnode(dg), v in eachvariable(equations_parabolic) + # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! + interfaces_u[1, v, i, interface] = flux_viscous_y[ + v, i, nnodes(dg), + left_element, + ] + interfaces_u[2, v, i, interface] = flux_viscous_y[ + v, i, 1, + right_element, + ] + end + end + end -# This is the version used when calculating the divergence of the viscous fluxes -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{2}, equations_parabolic, - dg::DG, cache_parabolic) - @unpack neighbor_ids, orientations = cache_parabolic.interfaces + return nothing + end - @threaded for interface in eachinterface(dg, cache_parabolic) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] + # This is the version used when calculating the divergence of the viscous fluxes + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{2}, equations_parabolic, + dg::DG, cache_parabolic + ) + @unpack neighbor_ids, orientations = cache_parabolic.interfaces - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 + @threaded for interface in eachinterface(dg, cache_parabolic) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] - for i in eachnode(dg) - # Get precomputed fluxes at interfaces - flux_ll, flux_rr = get_surface_node_vars(cache_parabolic.interfaces.u, - equations_parabolic, - dg, i, interface) + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 - # Compute interface flux as mean of left and right viscous fluxes - # TODO: parabolic; only BR1 at the moment - flux = 0.5f0 * (flux_ll + flux_rr) + for i in eachnode(dg) + # Get precomputed fluxes at interfaces + flux_ll, flux_rr = get_surface_node_vars( + cache_parabolic.interfaces.u, + equations_parabolic, + dg, i, interface + ) - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, left_direction, left_id] = flux[v] - surface_flux_values[v, i, right_direction, right_id] = flux[v] + # Compute interface flux as mean of left and right viscous fluxes + # TODO: parabolic; only BR1 at the moment + flux = 0.5f0 * (flux_ll + flux_rr) + + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, left_direction, left_id] = flux[v] + surface_flux_values[v, i, right_direction, right_id] = flux[v] + end end end + + return nothing end - return nothing -end - -# This is the version used when calculating the divergence of the viscous fluxes -function prolong2boundaries!(cache_parabolic, flux_viscous, - mesh::TreeMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - @unpack boundaries = cache_parabolic - @unpack orientations, neighbor_sides, neighbor_ids = boundaries - boundaries_u = boundaries.u - flux_viscous_x, flux_viscous_y = flux_viscous - - @threaded for boundary in eachboundary(dg, cache_parabolic) - element = neighbor_ids[boundary] - - if orientations[boundary] == 1 - # boundary in x-direction - if neighbor_sides[boundary] == 1 - # element in -x direction of boundary - for l in eachnode(dg), v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[1, v, l, boundary] = flux_viscous_x[v, nnodes(dg), l, - element] + # This is the version used when calculating the divergence of the viscous fluxes + function prolong2boundaries!( + cache_parabolic, flux_viscous, + mesh::TreeMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + @unpack boundaries = cache_parabolic + @unpack orientations, neighbor_sides, neighbor_ids = boundaries + boundaries_u = boundaries.u + flux_viscous_x, flux_viscous_y = flux_viscous + + @threaded for boundary in eachboundary(dg, cache_parabolic) + element = neighbor_ids[boundary] + + if orientations[boundary] == 1 + # boundary in x-direction + if neighbor_sides[boundary] == 1 + # element in -x direction of boundary + for l in eachnode(dg), v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[1, v, l, boundary] = flux_viscous_x[ + v, nnodes(dg), l, + element, + ] + end + else # Element in +x direction of boundary + for l in eachnode(dg), v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[2, v, l, boundary] = flux_viscous_x[v, 1, l, element] + end end - else # Element in +x direction of boundary - for l in eachnode(dg), v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[2, v, l, boundary] = flux_viscous_x[v, 1, l, element] + else # if orientations[boundary] == 2 + # boundary in y-direction + if neighbor_sides[boundary] == 1 + # element in -y direction of boundary + for l in eachnode(dg), v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[1, v, l, boundary] = flux_viscous_y[ + v, l, nnodes(dg), + element, + ] + end + else + # element in +y direction of boundary + for l in eachnode(dg), v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[2, v, l, boundary] = flux_viscous_y[v, l, 1, element] + end end end - else # if orientations[boundary] == 2 - # boundary in y-direction - if neighbor_sides[boundary] == 1 - # element in -y direction of boundary - for l in eachnode(dg), v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[1, v, l, boundary] = flux_viscous_y[v, l, nnodes(dg), - element] - end - else - # element in +y direction of boundary - for l in eachnode(dg), v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[2, v, l, boundary] = flux_viscous_y[v, l, 1, element] - end + end + + return nothing + end + + function calc_viscous_fluxes!( + flux_viscous, + gradients, u_transformed, + mesh::Union{TreeMesh{2}, P4estMesh{2}}, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, cache, cache_parabolic + ) + gradients_x, gradients_y = gradients + flux_viscous_x, flux_viscous_y = flux_viscous # output arrays + + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + # Get solution and gradients + u_node = get_node_vars( + u_transformed, equations_parabolic, dg, i, j, + element + ) + gradients_1_node = get_node_vars( + gradients_x, equations_parabolic, dg, i, j, + element + ) + gradients_2_node = get_node_vars( + gradients_y, equations_parabolic, dg, i, j, + element + ) + + # Calculate viscous flux and store each component for later use + flux_viscous_node_x = flux( + u_node, (gradients_1_node, gradients_2_node), 1, + equations_parabolic + ) + flux_viscous_node_y = flux( + u_node, (gradients_1_node, gradients_2_node), 2, + equations_parabolic + ) + set_node_vars!( + flux_viscous_x, flux_viscous_node_x, equations_parabolic, dg, + i, j, element + ) + set_node_vars!( + flux_viscous_y, flux_viscous_node_y, equations_parabolic, dg, + i, j, element + ) end end end - return nothing -end - -function calc_viscous_fluxes!(flux_viscous, - gradients, u_transformed, - mesh::Union{TreeMesh{2}, P4estMesh{2}}, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, cache, cache_parabolic) - gradients_x, gradients_y = gradients - flux_viscous_x, flux_viscous_y = flux_viscous # output arrays - - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - # Get solution and gradients - u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, j, - element) - gradients_1_node = get_node_vars(gradients_x, equations_parabolic, dg, i, j, - element) - gradients_2_node = get_node_vars(gradients_y, equations_parabolic, dg, i, j, - element) - - # Calculate viscous flux and store each component for later use - flux_viscous_node_x = flux(u_node, (gradients_1_node, gradients_2_node), 1, - equations_parabolic) - flux_viscous_node_y = flux(u_node, (gradients_1_node, gradients_2_node), 2, - equations_parabolic) - set_node_vars!(flux_viscous_x, flux_viscous_node_x, equations_parabolic, dg, - i, j, element) - set_node_vars!(flux_viscous_y, flux_viscous_node_y, equations_parabolic, dg, - i, j, element) + # TODO: parabolic; decide if we should keep this, and if so, extend to 3D. + function get_unsigned_normal_vector_2d(direction) + if direction > 4 || direction < 1 + error("Direction = $direction; in 2D, direction should be 1, 2, 3, or 4.") + end + if direction == 1 || direction == 2 + return SVector(1.0, 0.0) + else + return SVector(0.0, 1.0) end end -end -# TODO: parabolic; decide if we should keep this, and if so, extend to 3D. -function get_unsigned_normal_vector_2d(direction) - if direction > 4 || direction < 1 - error("Direction = $direction; in 2D, direction should be 1, 2, 3, or 4.") + function calc_boundary_flux_gradients!( + cache, t, + boundary_conditions_parabolic::BoundaryConditionPeriodic, + mesh::Union{TreeMesh{2}, P4estMesh{2}}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + return nothing end - if direction == 1 || direction == 2 - return SVector(1.0, 0.0) - else - return SVector(0.0, 1.0) + + function calc_boundary_flux_divergence!( + cache, t, + boundary_conditions_parabolic::BoundaryConditionPeriodic, + mesh::Union{TreeMesh{2}, P4estMesh{2}}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + return nothing end -end - -function calc_boundary_flux_gradients!(cache, t, - boundary_conditions_parabolic::BoundaryConditionPeriodic, - mesh::Union{TreeMesh{2}, P4estMesh{2}}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - return nothing -end - -function calc_boundary_flux_divergence!(cache, t, - boundary_conditions_parabolic::BoundaryConditionPeriodic, - mesh::Union{TreeMesh{2}, P4estMesh{2}}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - return nothing -end - -function calc_boundary_flux_gradients!(cache, t, - boundary_conditions_parabolic::NamedTuple, - mesh::TreeMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - @unpack surface_flux_values = cache.elements - @unpack n_boundaries_per_direction = cache.boundaries - - # Calculate indices - lasts = accumulate(+, n_boundaries_per_direction) - firsts = lasts - n_boundaries_per_direction .+ 1 - - # Calc boundary fluxes in each direction - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[1], - equations_parabolic, surface_integral, dg, - cache, - 1, firsts[1], lasts[1]) - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[2], - equations_parabolic, surface_integral, dg, - cache, - 2, firsts[2], lasts[2]) - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[3], - equations_parabolic, surface_integral, dg, - cache, - 3, firsts[3], lasts[3]) - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[4], - equations_parabolic, surface_integral, dg, - cache, - 4, firsts[4], lasts[4]) -end -function calc_boundary_flux_by_direction_gradient!(surface_flux_values::AbstractArray{<:Any, - 4}, - t, - boundary_condition, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - direction, first_boundary, - last_boundary) - @unpack surface_flux = surface_integral - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] - for i in eachnode(dg) - # Get boundary flux - u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, i, boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - u_inner = u_ll - else # Element is on the right, boundary on the left - u_inner = u_rr + function calc_boundary_flux_gradients!( + cache, t, + boundary_conditions_parabolic::NamedTuple, + mesh::TreeMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + @unpack surface_flux_values = cache.elements + @unpack n_boundaries_per_direction = cache.boundaries + + # Calculate indices + lasts = accumulate(+, n_boundaries_per_direction) + firsts = lasts - n_boundaries_per_direction .+ 1 + + # Calc boundary fluxes in each direction + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[1], + equations_parabolic, surface_integral, dg, + cache, + 1, firsts[1], lasts[1] + ) + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[2], + equations_parabolic, surface_integral, dg, + cache, + 2, firsts[2], lasts[2] + ) + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[3], + equations_parabolic, surface_integral, dg, + cache, + 3, firsts[3], lasts[3] + ) + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[4], + equations_parabolic, surface_integral, dg, + cache, + 4, firsts[4], lasts[4] + ) + end + function calc_boundary_flux_by_direction_gradient!( + surface_flux_values::AbstractArray{ + <:Any, + 4, + }, + t, + boundary_condition, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + direction, first_boundary, + last_boundary + ) + @unpack surface_flux = surface_integral + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] + + for i in eachnode(dg) + # Get boundary flux + u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, i, boundary) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + u_inner = u_ll + else # Element is on the right, boundary on the left + u_inner = u_rr + end + + # TODO: revisit if we want more general boundary treatments. + # This assumes the gradient numerical flux at the boundary is the gradient variable, + # which is consistent with BR1, LDG. + flux_inner = u_inner + + x = get_node_coords(node_coordinates, equations_parabolic, dg, i, boundary) + flux = boundary_condition( + flux_inner, u_inner, + get_unsigned_normal_vector_2d(direction), + x, t, Gradient(), equations_parabolic + ) + + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, direction, neighbor] = flux[v] + end end + end - # TODO: revisit if we want more general boundary treatments. - # This assumes the gradient numerical flux at the boundary is the gradient variable, - # which is consistent with BR1, LDG. - flux_inner = u_inner + return nothing + end - x = get_node_coords(node_coordinates, equations_parabolic, dg, i, boundary) - flux = boundary_condition(flux_inner, u_inner, - get_unsigned_normal_vector_2d(direction), - x, t, Gradient(), equations_parabolic) + function calc_boundary_flux_divergence!( + cache, t, + boundary_conditions_parabolic::NamedTuple, + mesh::TreeMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + @unpack surface_flux_values = cache.elements + @unpack n_boundaries_per_direction = cache.boundaries + + # Calculate indices + lasts = accumulate(+, n_boundaries_per_direction) + firsts = lasts - n_boundaries_per_direction .+ 1 + + # Calc boundary fluxes in each direction + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[1], + equations_parabolic, surface_integral, + dg, cache, + 1, firsts[1], lasts[1] + ) + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[2], + equations_parabolic, surface_integral, + dg, cache, + 2, firsts[2], lasts[2] + ) + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[3], + equations_parabolic, surface_integral, + dg, cache, + 3, firsts[3], lasts[3] + ) + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[4], + equations_parabolic, surface_integral, + dg, cache, + 4, firsts[4], lasts[4] + ) + end + function calc_boundary_flux_by_direction_divergence!( + surface_flux_values::AbstractArray{ + <:Any, + 4, + }, + t, + boundary_condition, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + direction, first_boundary, + last_boundary + ) + @unpack surface_flux = surface_integral + + # Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction") + # of the viscous flux, as computed in `prolong2boundaries!` + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, direction, neighbor] = flux[v] + for i in eachnode(dg) + # Get viscous boundary fluxes + flux_ll, flux_rr = get_surface_node_vars( + u, equations_parabolic, dg, i, + boundary + ) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + flux_inner = flux_ll + else # Element is on the right, boundary on the left + flux_inner = flux_rr + end + + x = get_node_coords(node_coordinates, equations_parabolic, dg, i, boundary) + + # TODO: add a field in `cache.boundaries` for gradient information. + # Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information. + # This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion2D and + # NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion2D as of 2022-6-27. + # It will not work with implementations which utilize `u_inner` to impose boundary conditions. + flux = boundary_condition( + flux_inner, nothing, + get_unsigned_normal_vector_2d(direction), + x, t, Divergence(), equations_parabolic + ) + + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, direction, neighbor] = flux[v] + end end end + + return nothing end - return nothing -end - -function calc_boundary_flux_divergence!(cache, t, - boundary_conditions_parabolic::NamedTuple, - mesh::TreeMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - @unpack surface_flux_values = cache.elements - @unpack n_boundaries_per_direction = cache.boundaries - - # Calculate indices - lasts = accumulate(+, n_boundaries_per_direction) - firsts = lasts - n_boundaries_per_direction .+ 1 - - # Calc boundary fluxes in each direction - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[1], - equations_parabolic, surface_integral, - dg, cache, - 1, firsts[1], lasts[1]) - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[2], - equations_parabolic, surface_integral, - dg, cache, - 2, firsts[2], lasts[2]) - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[3], - equations_parabolic, surface_integral, - dg, cache, - 3, firsts[3], lasts[3]) - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[4], - equations_parabolic, surface_integral, - dg, cache, - 4, firsts[4], lasts[4]) -end -function calc_boundary_flux_by_direction_divergence!(surface_flux_values::AbstractArray{<:Any, - 4}, - t, - boundary_condition, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - direction, first_boundary, - last_boundary) - @unpack surface_flux = surface_integral - - # Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction") - # of the viscous flux, as computed in `prolong2boundaries!` - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] + # `cache` is the hyperbolic cache, i.e., in particular not `cache_parabolic`. + # This is because mortar handling is done in the (hyperbolic) `cache`. + # Specialization `flux_viscous::Vector{Array{uEltype, 4}}` needed since + #`prolong2mortars!` in dg_2d.jl is used for both purely hyperbolic and + # hyperbolic-parabolic systems. + function prolong2mortars!( + cache, flux_viscous::Vector{Array{uEltype, 4}}, + mesh::TreeMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + mortar_l2::LobattoLegendreMortarL2, surface_integral, + dg::DGSEM + ) where {uEltype <: Real} + flux_viscous_x, flux_viscous_y = flux_viscous + @threaded for mortar in eachmortar(dg, cache) + large_element = cache.mortars.neighbor_ids[3, mortar] + upper_element = cache.mortars.neighbor_ids[2, mortar] + lower_element = cache.mortars.neighbor_ids[1, mortar] + + # Copy solution small to small + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + for l in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper[2, v, l, mortar] = flux_viscous_x[ + v, 1, l, + upper_element, + ] + cache.mortars.u_lower[2, v, l, mortar] = flux_viscous_x[ + v, 1, l, + lower_element, + ] + end + end + else + # L2 mortars in y-direction + for l in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper[2, v, l, mortar] = flux_viscous_y[ + v, l, 1, + upper_element, + ] + cache.mortars.u_lower[2, v, l, mortar] = flux_viscous_y[ + v, l, 1, + lower_element, + ] + end + end + end + else # large_sides[mortar] == 2 -> small elements on left side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + for l in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper[1, v, l, mortar] = flux_viscous_x[ + v, + nnodes(dg), + l, + upper_element, + ] + cache.mortars.u_lower[1, v, l, mortar] = flux_viscous_x[ + v, + nnodes(dg), + l, + lower_element, + ] + end + end + else + # L2 mortars in y-direction + for l in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper[1, v, l, mortar] = flux_viscous_y[ + v, l, + nnodes(dg), + upper_element, + ] + cache.mortars.u_lower[1, v, l, mortar] = flux_viscous_y[ + v, l, + nnodes(dg), + lower_element, + ] + end + end + end + end - for i in eachnode(dg) - # Get viscous boundary fluxes - flux_ll, flux_rr = get_surface_node_vars(u, equations_parabolic, dg, i, - boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - flux_inner = flux_ll - else # Element is on the right, boundary on the left - flux_inner = flux_rr + # Interpolate large element face data to small interface locations + if cache.mortars.large_sides[mortar] == 1 # -> large element on left side + leftright = 1 + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(flux_viscous_x, :, nnodes(dg), :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large + ) + else + # L2 mortars in y-direction + u_large = view(flux_viscous_y, :, :, nnodes(dg), large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large + ) + end + else # large_sides[mortar] == 2 -> large element on right side + leftright = 2 + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(flux_viscous_x, :, 1, :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large + ) + else + # L2 mortars in y-direction + u_large = view(flux_viscous_y, :, :, 1, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large + ) + end end + end + + return nothing + end - x = get_node_coords(node_coordinates, equations_parabolic, dg, i, boundary) + # NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms. + # Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for + # hyperbolic terms with conserved terms only, i.e., no nonconservative terms. + function calc_mortar_flux!( + surface_flux_values, + mesh::TreeMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u_lower, u_upper, orientations = cache.mortars + @unpack fstar_upper_threaded, fstar_lower_threaded = cache + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar_upper = fstar_upper_threaded[Threads.threadid()] + fstar_lower = fstar_lower_threaded[Threads.threadid()] + + # Calculate fluxes + orientation = orientations[mortar] + calc_fstar!( + fstar_upper, equations_parabolic, surface_flux, dg, u_upper, mortar, + orientation + ) + calc_fstar!( + fstar_lower, equations_parabolic, surface_flux, dg, u_lower, mortar, + orientation + ) + + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations_parabolic, mortar_l2, dg, cache, + mortar, fstar_upper, fstar_lower + ) + end - # TODO: add a field in `cache.boundaries` for gradient information. - # Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information. - # This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion2D and - # NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion2D as of 2022-6-27. - # It will not work with implementations which utilize `u_inner` to impose boundary conditions. - flux = boundary_condition(flux_inner, nothing, - get_unsigned_normal_vector_2d(direction), - x, t, Divergence(), equations_parabolic) + return nothing + end + + @inline function calc_fstar!( + destination::AbstractArray{<:Any, 2}, + equations_parabolic::AbstractEquationsParabolic, + surface_flux, dg::DGSEM, + u_interfaces, interface, orientation + ) + for i in eachnode(dg) + # Call pointwise two-point numerical flux function + u_ll, u_rr = get_surface_node_vars( + u_interfaces, equations_parabolic, dg, i, + interface + ) + # TODO: parabolic; only BR1 at the moment + flux = 0.5f0 * (u_ll + u_rr) # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, direction, neighbor] = flux[v] - end + set_node_vars!(destination, flux, equations_parabolic, dg, i) end + + return nothing end - return nothing -end - -# `cache` is the hyperbolic cache, i.e., in particular not `cache_parabolic`. -# This is because mortar handling is done in the (hyperbolic) `cache`. -# Specialization `flux_viscous::Vector{Array{uEltype, 4}}` needed since -#`prolong2mortars!` in dg_2d.jl is used for both purely hyperbolic and -# hyperbolic-parabolic systems. -function prolong2mortars!(cache, flux_viscous::Vector{Array{uEltype, 4}}, - mesh::TreeMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - mortar_l2::LobattoLegendreMortarL2, surface_integral, - dg::DGSEM) where {uEltype <: Real} - flux_viscous_x, flux_viscous_y = flux_viscous - @threaded for mortar in eachmortar(dg, cache) + @inline function mortar_fluxes_to_elements!( + surface_flux_values, + mesh::TreeMesh{2}, + equations_parabolic::AbstractEquationsParabolic, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, + mortar, fstar_upper, fstar_lower + ) large_element = cache.mortars.neighbor_ids[3, mortar] upper_element = cache.mortars.neighbor_ids[2, mortar] lower_element = cache.mortars.neighbor_ids[1, mortar] - # Copy solution small to small + # Copy flux small to small if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - for l in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper[2, v, l, mortar] = flux_viscous_x[v, 1, l, - upper_element] - cache.mortars.u_lower[2, v, l, mortar] = flux_viscous_x[v, 1, l, - lower_element] - end - end + direction = 1 else # L2 mortars in y-direction - for l in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper[2, v, l, mortar] = flux_viscous_y[v, l, 1, - upper_element] - cache.mortars.u_lower[2, v, l, mortar] = flux_viscous_y[v, l, 1, - lower_element] - end - end + direction = 3 end else # large_sides[mortar] == 2 -> small elements on left side if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - for l in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper[1, v, l, mortar] = flux_viscous_x[v, - nnodes(dg), - l, - upper_element] - cache.mortars.u_lower[1, v, l, mortar] = flux_viscous_x[v, - nnodes(dg), - l, - lower_element] - end - end + direction = 2 else # L2 mortars in y-direction - for l in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper[1, v, l, mortar] = flux_viscous_y[v, l, - nnodes(dg), - upper_element] - cache.mortars.u_lower[1, v, l, mortar] = flux_viscous_y[v, l, - nnodes(dg), - lower_element] - end - end + direction = 4 end end + surface_flux_values[:, :, direction, upper_element] .= fstar_upper + surface_flux_values[:, :, direction, lower_element] .= fstar_lower - # Interpolate large element face data to small interface locations + # Project small fluxes to large element if cache.mortars.large_sides[mortar] == 1 # -> large element on left side - leftright = 1 if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - u_large = view(flux_viscous_x, :, nnodes(dg), :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large) + direction = 2 else # L2 mortars in y-direction - u_large = view(flux_viscous_y, :, :, nnodes(dg), large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large) + direction = 4 end else # large_sides[mortar] == 2 -> large element on right side - leftright = 2 if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - u_large = view(flux_viscous_x, :, 1, :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large) + direction = 1 else # L2 mortars in y-direction - u_large = view(flux_viscous_y, :, :, 1, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large) + direction = 3 end end - end - return nothing -end - -# NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms. -# Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for -# hyperbolic terms with conserved terms only, i.e., no nonconservative terms. -function calc_mortar_flux!(surface_flux_values, - mesh::TreeMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u_lower, u_upper, orientations = cache.mortars - @unpack fstar_upper_threaded, fstar_lower_threaded = cache - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar_upper = fstar_upper_threaded[Threads.threadid()] - fstar_lower = fstar_lower_threaded[Threads.threadid()] - - # Calculate fluxes - orientation = orientations[mortar] - calc_fstar!(fstar_upper, equations_parabolic, surface_flux, dg, u_upper, mortar, - orientation) - calc_fstar!(fstar_lower, equations_parabolic, surface_flux, dg, u_lower, mortar, - orientation) - - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations_parabolic, mortar_l2, dg, cache, - mortar, fstar_upper, fstar_lower) + # TODO: Taal performance + # for v in eachvariable(equations) + # # The code below is semantically equivalent to + # # surface_flux_values[v, :, direction, large_element] .= + # # (mortar_l2.reverse_upper * fstar_upper[v, :] + mortar_l2.reverse_lower * fstar_lower[v, :]) + # # but faster and does not allocate. + # # Note that `true * some_float == some_float` in Julia, i.e. `true` acts as + # # a universal `one`. Hence, the second `mul!` means "add the matrix-vector + # # product to the current value of the destination". + # @views mul!(surface_flux_values[v, :, direction, large_element], + # mortar_l2.reverse_upper, fstar_upper[v, :]) + # @views mul!(surface_flux_values[v, :, direction, large_element], + # mortar_l2.reverse_lower, fstar_lower[v, :], true, true) + # end + # The code above could be replaced by the following code. However, the relative efficiency + # depends on the types of fstar_upper/fstar_lower and dg.l2mortar_reverse_upper. + # Using StaticArrays for both makes the code above faster for common test cases. + multiply_dimensionwise!( + view(surface_flux_values, :, :, direction, large_element), + mortar_l2.reverse_upper, fstar_upper, + mortar_l2.reverse_lower, fstar_lower + ) + + return nothing end - return nothing -end - -@inline function calc_fstar!(destination::AbstractArray{<:Any, 2}, - equations_parabolic::AbstractEquationsParabolic, - surface_flux, dg::DGSEM, - u_interfaces, interface, orientation) - for i in eachnode(dg) - # Call pointwise two-point numerical flux function - u_ll, u_rr = get_surface_node_vars(u_interfaces, equations_parabolic, dg, i, - interface) - # TODO: parabolic; only BR1 at the moment - flux = 0.5f0 * (u_ll + u_rr) - - # Copy flux to left and right element storage - set_node_vars!(destination, flux, equations_parabolic, dg, i) - end - - return nothing -end - -@inline function mortar_fluxes_to_elements!(surface_flux_values, - mesh::TreeMesh{2}, - equations_parabolic::AbstractEquationsParabolic, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, - mortar, fstar_upper, fstar_lower) - large_element = cache.mortars.neighbor_ids[3, mortar] - upper_element = cache.mortars.neighbor_ids[2, mortar] - lower_element = cache.mortars.neighbor_ids[1, mortar] - - # Copy flux small to small - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 1 - else - # L2 mortars in y-direction - direction = 3 + # Calculate the gradient of the transformed variables + function calc_gradient!( + gradients, u_transformed, t, + mesh::TreeMesh{2}, equations_parabolic, + boundary_conditions_parabolic, dg::DG, cache, cache_parabolic + ) + gradients_x, gradients_y = gradients + + # Reset du + @trixi_timeit timer() "reset gradients" begin + reset_du!(gradients_x, dg, cache) + reset_du!(gradients_y, dg, cache) end - else # large_sides[mortar] == 2 -> small elements on left side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 2 - else - # L2 mortars in y-direction - direction = 4 - end - end - surface_flux_values[:, :, direction, upper_element] .= fstar_upper - surface_flux_values[:, :, direction, lower_element] .= fstar_lower - - # Project small fluxes to large element - if cache.mortars.large_sides[mortar] == 1 # -> large element on left side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 2 - else - # L2 mortars in y-direction - direction = 4 - end - else # large_sides[mortar] == 2 -> large element on right side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 1 - else - # L2 mortars in y-direction - direction = 3 - end - end - - # TODO: Taal performance - # for v in eachvariable(equations) - # # The code below is semantically equivalent to - # # surface_flux_values[v, :, direction, large_element] .= - # # (mortar_l2.reverse_upper * fstar_upper[v, :] + mortar_l2.reverse_lower * fstar_lower[v, :]) - # # but faster and does not allocate. - # # Note that `true * some_float == some_float` in Julia, i.e. `true` acts as - # # a universal `one`. Hence, the second `mul!` means "add the matrix-vector - # # product to the current value of the destination". - # @views mul!(surface_flux_values[v, :, direction, large_element], - # mortar_l2.reverse_upper, fstar_upper[v, :]) - # @views mul!(surface_flux_values[v, :, direction, large_element], - # mortar_l2.reverse_lower, fstar_lower[v, :], true, true) - # end - # The code above could be replaced by the following code. However, the relative efficiency - # depends on the types of fstar_upper/fstar_lower and dg.l2mortar_reverse_upper. - # Using StaticArrays for both makes the code above faster for common test cases. - multiply_dimensionwise!(view(surface_flux_values, :, :, direction, large_element), - mortar_l2.reverse_upper, fstar_upper, - mortar_l2.reverse_lower, fstar_lower) - - return nothing -end - -# Calculate the gradient of the transformed variables -function calc_gradient!(gradients, u_transformed, t, - mesh::TreeMesh{2}, equations_parabolic, - boundary_conditions_parabolic, dg::DG, cache, cache_parabolic) - gradients_x, gradients_y = gradients - - # Reset du - @trixi_timeit timer() "reset gradients" begin - reset_du!(gradients_x, dg, cache) - reset_du!(gradients_y, dg, cache) - end - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - @unpack derivative_dhat = dg.basis - @threaded for element in eachelement(dg, cache) - - # Calculate volume terms in one element - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, j, - element) - for ii in eachnode(dg) - multiply_add_to_node_vars!(gradients_x, derivative_dhat[ii, i], - u_node, equations_parabolic, dg, ii, j, - element) - end + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + @unpack derivative_dhat = dg.basis + @threaded for element in eachelement(dg, cache) + + # Calculate volume terms in one element + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars( + u_transformed, equations_parabolic, dg, i, j, + element + ) + + for ii in eachnode(dg) + multiply_add_to_node_vars!( + gradients_x, derivative_dhat[ii, i], + u_node, equations_parabolic, dg, ii, j, + element + ) + end - for jj in eachnode(dg) - multiply_add_to_node_vars!(gradients_y, derivative_dhat[jj, j], - u_node, equations_parabolic, dg, i, jj, - element) + for jj in eachnode(dg) + multiply_add_to_node_vars!( + gradients_y, derivative_dhat[jj, j], + u_node, equations_parabolic, dg, i, jj, + element + ) + end end end end - end - - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache_parabolic, u_transformed, mesh, equations_parabolic, - dg.surface_integral, dg) - end - - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - @unpack surface_flux_values = cache_parabolic.elements - @unpack neighbor_ids, orientations = cache_parabolic.interfaces - - @threaded for interface in eachinterface(dg, cache_parabolic) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] - - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 - for i in eachnode(dg) - # Call pointwise Riemann solver - u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u, - equations_parabolic, dg, i, - interface) - flux = 0.5f0 * (u_ll + u_rr) + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache_parabolic, u_transformed, mesh, equations_parabolic, + dg.surface_integral, dg + ) + end - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, left_direction, left_id] = flux[v] - surface_flux_values[v, i, right_direction, right_id] = flux[v] + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + @unpack surface_flux_values = cache_parabolic.elements + @unpack neighbor_ids, orientations = cache_parabolic.interfaces + + @threaded for interface in eachinterface(dg, cache_parabolic) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 + + for i in eachnode(dg) + # Call pointwise Riemann solver + u_ll, u_rr = get_surface_node_vars( + cache_parabolic.interfaces.u, + equations_parabolic, dg, i, + interface + ) + flux = 0.5f0 * (u_ll + u_rr) + + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, left_direction, left_id] = flux[v] + surface_flux_values[v, i, right_direction, right_id] = flux[v] + end end end end - end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache_parabolic, u_transformed, mesh, equations_parabolic, - dg.surface_integral, dg) - end + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache_parabolic, u_transformed, mesh, equations_parabolic, + dg.surface_integral, dg + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux_gradients!(cache_parabolic, t, - boundary_conditions_parabolic, mesh, - equations_parabolic, - dg.surface_integral, dg) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux_gradients!( + cache_parabolic, t, + boundary_conditions_parabolic, mesh, + equations_parabolic, + dg.surface_integral, dg + ) + end - # Prolong solution to mortars - # NOTE: This re-uses the implementation for hyperbolic terms in "dg_2d.jl" - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u_transformed, mesh, equations_parabolic, - dg.mortar, dg.surface_integral, dg) - end + # Prolong solution to mortars + # NOTE: This re-uses the implementation for hyperbolic terms in "dg_2d.jl" + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!( + cache, u_transformed, mesh, equations_parabolic, + dg.mortar, dg.surface_integral, dg + ) + end - # Calculate mortar fluxes - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(surface_flux_values, - mesh, - equations_parabolic, - dg.mortar, dg.surface_integral, dg, cache) - end + # Calculate mortar fluxes + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!( + surface_flux_values, + mesh, + equations_parabolic, + dg.mortar, dg.surface_integral, dg, cache + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - @unpack boundary_interpolation = dg.basis - @unpack surface_flux_values = cache_parabolic.elements - - # Note that all fluxes have been computed with outward-pointing normal vectors. - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` to let `@muladd` turn these - # into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] - @threaded for element in eachelement(dg, cache) - for l in eachnode(dg) - for v in eachvariable(equations_parabolic) - # surface at -x - gradients_x[v, 1, l, element] = (gradients_x[v, 1, l, element] - - surface_flux_values[v, l, 1, - element] * - factor_1) - - # surface at +x - gradients_x[v, nnodes(dg), l, element] = (gradients_x[v, nnodes(dg), - l, element] + - surface_flux_values[v, l, - 2, - element] * - factor_2) - - # surface at -y - gradients_y[v, l, 1, element] = (gradients_y[v, l, 1, element] - - surface_flux_values[v, l, 3, - element] * - factor_1) - - # surface at +y - gradients_y[v, l, nnodes(dg), element] = (gradients_y[v, l, - nnodes(dg), - element] + - surface_flux_values[v, l, - 4, - element] * - factor_2) + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + @unpack boundary_interpolation = dg.basis + @unpack surface_flux_values = cache_parabolic.elements + + # Note that all fluxes have been computed with outward-pointing normal vectors. + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg) + for v in eachvariable(equations_parabolic) + # surface at -x + gradients_x[v, 1, l, element] = ( + gradients_x[v, 1, l, element] - + surface_flux_values[ + v, l, 1, + element, + ] * + factor_1 + ) + + # surface at +x + gradients_x[v, nnodes(dg), l, element] = ( + gradients_x[ + v, nnodes(dg), + l, element, + ] + + surface_flux_values[ + v, l, + 2, + element, + ] * + factor_2 + ) + + # surface at -y + gradients_y[v, l, 1, element] = ( + gradients_y[v, l, 1, element] - + surface_flux_values[ + v, l, 3, + element, + ] * + factor_1 + ) + + # surface at +y + gradients_y[v, l, nnodes(dg), element] = ( + gradients_y[ + v, l, + nnodes(dg), + element, + ] + + surface_flux_values[ + v, l, + 4, + element, + ] * + factor_2 + ) + end end end end - end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" begin - apply_jacobian_parabolic!(gradients_x, mesh, equations_parabolic, dg, - cache_parabolic) - apply_jacobian_parabolic!(gradients_y, mesh, equations_parabolic, dg, - cache_parabolic) - end + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" begin + apply_jacobian_parabolic!( + gradients_x, mesh, equations_parabolic, dg, + cache_parabolic + ) + apply_jacobian_parabolic!( + gradients_y, mesh, equations_parabolic, dg, + cache_parabolic + ) + end - return nothing -end + return nothing + end -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache_parabolic(mesh::TreeMesh{2}, - equations_hyperbolic::AbstractEquations, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, parabolic_scheme, RealT, uEltype) - # Get cells for which an element needs to be created (i.e. all leaf cells) - leaf_cell_ids = local_leaf_cells(mesh.tree) + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache_parabolic( + mesh::TreeMesh{2}, + equations_hyperbolic::AbstractEquations, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, parabolic_scheme, RealT, uEltype + ) + # Get cells for which an element needs to be created (i.e. all leaf cells) + leaf_cell_ids = local_leaf_cells(mesh.tree) - elements = init_elements(leaf_cell_ids, mesh, equations_hyperbolic, dg.basis, RealT, - uEltype) + elements = init_elements( + leaf_cell_ids, mesh, equations_hyperbolic, dg.basis, RealT, + uEltype + ) - interfaces = init_interfaces(leaf_cell_ids, mesh, elements) + interfaces = init_interfaces(leaf_cell_ids, mesh, elements) - boundaries = init_boundaries(leaf_cell_ids, mesh, elements) + boundaries = init_boundaries(leaf_cell_ids, mesh, elements) - # mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) + # mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) - viscous_container = init_viscous_container_2d(nvariables(equations_hyperbolic), - nnodes(elements), nelements(elements), - uEltype) + viscous_container = init_viscous_container_2d( + nvariables(equations_hyperbolic), + nnodes(elements), nelements(elements), + uEltype + ) - # cache = (; elements, interfaces, boundaries, mortars) - cache = (; elements, interfaces, boundaries, viscous_container) + # cache = (; elements, interfaces, boundaries, mortars) + cache = (; elements, interfaces, boundaries, viscous_container) - # Add specialized parts of the cache required to compute the mortars etc. - # cache = (;cache..., create_cache(mesh, equations_parabolic, dg.mortar, uEltype)...) + # Add specialized parts of the cache required to compute the mortars etc. + # cache = (;cache..., create_cache(mesh, equations_parabolic, dg.mortar, uEltype)...) - return cache -end + return cache + end -# Needed to *not* flip the sign of the inverse Jacobian. -# This is because the parabolic fluxes are assumed to be of the form -# `du/dt + df/dx = dg/dx + source(x,t)`, -# where f(u) is the inviscid flux and g(u) is the viscous flux. -function apply_jacobian_parabolic!(du, mesh::TreeMesh{2}, - equations::AbstractEquationsParabolic, dg::DG, cache) - @unpack inverse_jacobian = cache.elements + # Needed to *not* flip the sign of the inverse Jacobian. + # This is because the parabolic fluxes are assumed to be of the form + # `du/dt + df/dx = dg/dx + source(x,t)`, + # where f(u) is the inviscid flux and g(u) is the viscous flux. + function apply_jacobian_parabolic!( + du, mesh::TreeMesh{2}, + equations::AbstractEquationsParabolic, dg::DG, cache + ) + @unpack inverse_jacobian = cache.elements - @threaded for element in eachelement(dg, cache) - factor = inverse_jacobian[element] + @threaded for element in eachelement(dg, cache) + factor = inverse_jacobian[element] - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - du[v, i, j, element] *= factor + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, j, element] *= factor + end end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_tree/dg_2d_parallel.jl b/src/solvers/dgsem_tree/dg_2d_parallel.jl index d7b76f5773..f379a71e33 100644 --- a/src/solvers/dgsem_tree/dg_2d_parallel.jl +++ b/src/solvers/dgsem_tree/dg_2d_parallel.jl @@ -3,854 +3,1006 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# everything related to a DG semidiscretization in 2D using MPI, -# currently limited to Lobatto-Legendre nodes - -# TODO: MPI dimension agnostic -mutable struct MPICache{uEltype <: Real} - mpi_neighbor_ranks::Vector{Int} - mpi_neighbor_interfaces::Vector{Vector{Int}} - mpi_neighbor_mortars::Vector{Vector{Int}} - mpi_send_buffers::Vector{Vector{uEltype}} - mpi_recv_buffers::Vector{Vector{uEltype}} - mpi_send_requests::Vector{MPI.Request} - mpi_recv_requests::Vector{MPI.Request} - n_elements_by_rank::OffsetArray{Int, 1, Array{Int, 1}} - n_elements_global::Int - first_element_global_id::Int -end - -function MPICache(uEltype) - # MPI communication "just works" for bitstypes only - if !isbitstype(uEltype) - throw(ArgumentError("MPICache only supports bitstypes, $uEltype is not a bitstype.")) + #! format: noindent + + # everything related to a DG semidiscretization in 2D using MPI, + # currently limited to Lobatto-Legendre nodes + + # TODO: MPI dimension agnostic + mutable struct MPICache{uEltype <: Real} + mpi_neighbor_ranks::Vector{Int} + mpi_neighbor_interfaces::Vector{Vector{Int}} + mpi_neighbor_mortars::Vector{Vector{Int}} + mpi_send_buffers::Vector{Vector{uEltype}} + mpi_recv_buffers::Vector{Vector{uEltype}} + mpi_send_requests::Vector{MPI.Request} + mpi_recv_requests::Vector{MPI.Request} + n_elements_by_rank::OffsetArray{Int, 1, Array{Int, 1}} + n_elements_global::Int + first_element_global_id::Int end - mpi_neighbor_ranks = Vector{Int}(undef, 0) - mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, 0) - mpi_neighbor_mortars = Vector{Vector{Int}}(undef, 0) - mpi_send_buffers = Vector{Vector{uEltype}}(undef, 0) - mpi_recv_buffers = Vector{Vector{uEltype}}(undef, 0) - mpi_send_requests = Vector{MPI.Request}(undef, 0) - mpi_recv_requests = Vector{MPI.Request}(undef, 0) - n_elements_by_rank = OffsetArray(Vector{Int}(undef, 0), 0:-1) - n_elements_global = 0 - first_element_global_id = 0 - - MPICache{uEltype}(mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars, - mpi_send_buffers, mpi_recv_buffers, - mpi_send_requests, mpi_recv_requests, - n_elements_by_rank, n_elements_global, - first_element_global_id) -end -@inline Base.eltype(::MPICache{uEltype}) where {uEltype} = uEltype - -# TODO: MPI dimension agnostic -function start_mpi_receive!(mpi_cache::MPICache) - for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks) - mpi_cache.mpi_recv_requests[index] = MPI.Irecv!(mpi_cache.mpi_recv_buffers[index], - rank, rank, mpi_comm()) - end - - return nothing -end -# TODO: MPI dimension agnostic -function start_mpi_send!(mpi_cache::MPICache, mesh, equations, dg, cache) - data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1) - - for rank in 1:length(mpi_cache.mpi_neighbor_ranks) - send_buffer = mpi_cache.mpi_send_buffers[rank] + function MPICache(uEltype) + # MPI communication "just works" for bitstypes only + if !isbitstype(uEltype) + throw(ArgumentError("MPICache only supports bitstypes, $uEltype is not a bitstype.")) + end + mpi_neighbor_ranks = Vector{Int}(undef, 0) + mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, 0) + mpi_neighbor_mortars = Vector{Vector{Int}}(undef, 0) + mpi_send_buffers = Vector{Vector{uEltype}}(undef, 0) + mpi_recv_buffers = Vector{Vector{uEltype}}(undef, 0) + mpi_send_requests = Vector{MPI.Request}(undef, 0) + mpi_recv_requests = Vector{MPI.Request}(undef, 0) + n_elements_by_rank = OffsetArray(Vector{Int}(undef, 0), 0:-1) + n_elements_global = 0 + first_element_global_id = 0 + + MPICache{uEltype}( + mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars, + mpi_send_buffers, mpi_recv_buffers, + mpi_send_requests, mpi_recv_requests, + n_elements_by_rank, n_elements_global, + first_element_global_id + ) + end + @inline Base.eltype(::MPICache{uEltype}) where {uEltype} = uEltype + + # TODO: MPI dimension agnostic + function start_mpi_receive!(mpi_cache::MPICache) + for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks) + mpi_cache.mpi_recv_requests[index] = MPI.Irecv!( + mpi_cache.mpi_recv_buffers[index], + rank, rank, mpi_comm() + ) + end - for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[rank]) - first = (index - 1) * data_size + 1 - last = (index - 1) * data_size + data_size + return nothing + end - if cache.mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction - @views send_buffer[first:last] .= vec(cache.mpi_interfaces.u[2, :, :, - interface]) - else # local element in negative direction - @views send_buffer[first:last] .= vec(cache.mpi_interfaces.u[1, :, :, - interface]) + # TODO: MPI dimension agnostic + function start_mpi_send!(mpi_cache::MPICache, mesh, equations, dg, cache) + data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1) + + for rank in 1:length(mpi_cache.mpi_neighbor_ranks) + send_buffer = mpi_cache.mpi_send_buffers[rank] + + for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[rank]) + first = (index - 1) * data_size + 1 + last = (index - 1) * data_size + data_size + + if cache.mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction + @views send_buffer[first:last] .= vec( + cache.mpi_interfaces.u[ + 2, :, :, + interface, + ] + ) + else # local element in negative direction + @views send_buffer[first:last] .= vec( + cache.mpi_interfaces.u[ + 1, :, :, + interface, + ] + ) + end end - end - # Each mortar has a total size of 4 * data_size, set everything to NaN first and overwrite the - # parts where local data exists - interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[rank]) * - data_size - mortars_data_size = length(mpi_cache.mpi_neighbor_mortars[rank]) * 4 * data_size - send_buffer[(interfaces_data_size + 1):(interfaces_data_size + mortars_data_size)] .= NaN - - for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[rank]) - # First and last indices in the send buffer for mortar data obtained from local element - # in a given position - index_base = interfaces_data_size + (index - 1) * 4 * data_size - indices = ( - # first, last for local element in position 1 (lower element) - (index_base + 1, - index_base + 1 * data_size), - # first, last for local element in position 2 (upper element) - (index_base + 1 * data_size + 1, - index_base + 2 * data_size), - # firsts, lasts for local element in position 3 (large element) - (index_base + 2 * data_size + 1, + # Each mortar has a total size of 4 * data_size, set everything to NaN first and overwrite the + # parts where local data exists + interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[rank]) * + data_size + mortars_data_size = length(mpi_cache.mpi_neighbor_mortars[rank]) * 4 * data_size + send_buffer[(interfaces_data_size + 1):(interfaces_data_size + mortars_data_size)] .= NaN + + for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[rank]) + # First and last indices in the send buffer for mortar data obtained from local element + # in a given position + index_base = interfaces_data_size + (index - 1) * 4 * data_size + indices = ( + # first, last for local element in position 1 (lower element) + ( + index_base + 1, + index_base + 1 * data_size, + ), + # first, last for local element in position 2 (upper element) + ( + index_base + 1 * data_size + 1, + index_base + 2 * data_size, + ), + # firsts, lasts for local element in position 3 (large element) + ( + index_base + 2 * data_size + 1, index_base + 3 * data_size, index_base + 3 * data_size + 1, - index_base + 4 * data_size)) - - for position in cache.mpi_mortars.local_neighbor_positions[mortar] - # Determine whether the data belongs to the left or right side - if cache.mpi_mortars.large_sides[mortar] == 1 # large element on left side - if position in (1, 2) # small element - leftright = 2 - else # large element - leftright = 1 + index_base + 4 * data_size, + ), + ) + + for position in cache.mpi_mortars.local_neighbor_positions[mortar] + # Determine whether the data belongs to the left or right side + if cache.mpi_mortars.large_sides[mortar] == 1 # large element on left side + if position in (1, 2) # small element + leftright = 2 + else # large element + leftright = 1 + end + else # large element on right side + if position in (1, 2) # small element + leftright = 1 + else # large element + leftright = 2 + end end - else # large element on right side - if position in (1, 2) # small element - leftright = 1 + # copy data to buffer + if position == 1 # lower element + first, last = indices[position] + @views send_buffer[first:last] .= vec( + cache.mpi_mortars.u_lower[ + leftright, + :, + :, + mortar, + ] + ) + elseif position == 2 # upper element + first, last = indices[position] + @views send_buffer[first:last] .= vec( + cache.mpi_mortars.u_upper[ + leftright, + :, + :, + mortar, + ] + ) else # large element - leftright = 2 + first_lower, last_lower, first_upper, last_upper = indices[position] + @views send_buffer[first_lower:last_lower] .= vec( + cache.mpi_mortars.u_lower[ + leftright, + :, + :, + mortar, + ] + ) + @views send_buffer[first_upper:last_upper] .= vec( + cache.mpi_mortars.u_upper[ + leftright, + :, + :, + mortar, + ] + ) end end - # copy data to buffer - if position == 1 # lower element - first, last = indices[position] - @views send_buffer[first:last] .= vec(cache.mpi_mortars.u_lower[leftright, - :, - :, - mortar]) - elseif position == 2 # upper element - first, last = indices[position] - @views send_buffer[first:last] .= vec(cache.mpi_mortars.u_upper[leftright, - :, - :, - mortar]) - else # large element - first_lower, last_lower, first_upper, last_upper = indices[position] - @views send_buffer[first_lower:last_lower] .= vec(cache.mpi_mortars.u_lower[leftright, - :, - :, - mortar]) - @views send_buffer[first_upper:last_upper] .= vec(cache.mpi_mortars.u_upper[leftright, - :, - :, - mortar]) - end end end - end - # Start sending - for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks) - mpi_cache.mpi_send_requests[index] = MPI.Isend(mpi_cache.mpi_send_buffers[index], - rank, mpi_rank(), mpi_comm()) - end + # Start sending + for (index, rank) in enumerate(mpi_cache.mpi_neighbor_ranks) + mpi_cache.mpi_send_requests[index] = MPI.Isend( + mpi_cache.mpi_send_buffers[index], + rank, mpi_rank(), mpi_comm() + ) + end - return nothing -end + return nothing + end -# TODO: MPI dimension agnostic -function finish_mpi_send!(mpi_cache::MPICache) - MPI.Waitall(mpi_cache.mpi_send_requests, MPI.Status) -end + # TODO: MPI dimension agnostic + function finish_mpi_send!(mpi_cache::MPICache) + MPI.Waitall(mpi_cache.mpi_send_requests, MPI.Status) + end -# TODO: MPI dimension agnostic -function finish_mpi_receive!(mpi_cache::MPICache, mesh, equations, dg, cache) - data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1) + # TODO: MPI dimension agnostic + function finish_mpi_receive!(mpi_cache::MPICache, mesh, equations, dg, cache) + data_size = nvariables(equations) * nnodes(dg)^(ndims(mesh) - 1) - # Start receiving and unpack received data until all communication is finished - data = MPI.Waitany(mpi_cache.mpi_recv_requests) - while data !== nothing - recv_buffer = mpi_cache.mpi_recv_buffers[data] + # Start receiving and unpack received data until all communication is finished + data = MPI.Waitany(mpi_cache.mpi_recv_requests) + while data !== nothing + recv_buffer = mpi_cache.mpi_recv_buffers[data] - for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[data]) - first = (index - 1) * data_size + 1 - last = (index - 1) * data_size + data_size + for (index, interface) in enumerate(mpi_cache.mpi_neighbor_interfaces[data]) + first = (index - 1) * data_size + 1 + last = (index - 1) * data_size + data_size - if cache.mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction - @views vec(cache.mpi_interfaces.u[1, :, :, interface]) .= recv_buffer[first:last] - else # local element in negative direction - @views vec(cache.mpi_interfaces.u[2, :, :, interface]) .= recv_buffer[first:last] + if cache.mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction + @views vec(cache.mpi_interfaces.u[1, :, :, interface]) .= recv_buffer[first:last] + else # local element in negative direction + @views vec(cache.mpi_interfaces.u[2, :, :, interface]) .= recv_buffer[first:last] + end end - end - interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[data]) * - data_size - for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[data]) - # First and last indices in the receive buffer for mortar data obtained from remote element - # in a given position - index_base = interfaces_data_size + (index - 1) * 4 * data_size - indices = ( - # first, last for local element in position 1 (lower element) - (index_base + 1, - index_base + 1 * data_size), - # first, last for local element in position 2 (upper element) - (index_base + 1 * data_size + 1, - index_base + 2 * data_size), - # firsts, lasts for local element in position 3 (large element) - (index_base + 2 * data_size + 1, + interfaces_data_size = length(mpi_cache.mpi_neighbor_interfaces[data]) * + data_size + for (index, mortar) in enumerate(mpi_cache.mpi_neighbor_mortars[data]) + # First and last indices in the receive buffer for mortar data obtained from remote element + # in a given position + index_base = interfaces_data_size + (index - 1) * 4 * data_size + indices = ( + # first, last for local element in position 1 (lower element) + ( + index_base + 1, + index_base + 1 * data_size, + ), + # first, last for local element in position 2 (upper element) + ( + index_base + 1 * data_size + 1, + index_base + 2 * data_size, + ), + # firsts, lasts for local element in position 3 (large element) + ( + index_base + 2 * data_size + 1, index_base + 3 * data_size, index_base + 3 * data_size + 1, - index_base + 4 * data_size)) - - for position in 1:3 - # Skip if received data for `pos` is NaN as no real data has been sent for the - # corresponding element - if isnan(recv_buffer[Base.first(indices[position])]) - continue - end - - # Determine whether the received data belongs to the left or right side - if cache.mpi_mortars.large_sides[mortar] == 1 # large element on left side - if position in (1, 2) # small element - leftright = 2 - else # large element - leftright = 1 + index_base + 4 * data_size, + ), + ) + + for position in 1:3 + # Skip if received data for `pos` is NaN as no real data has been sent for the + # corresponding element + if isnan(recv_buffer[Base.first(indices[position])]) + continue end - else # large element on right side - if position in (1, 2) # small element - leftright = 1 - else # large element - leftright = 2 + + # Determine whether the received data belongs to the left or right side + if cache.mpi_mortars.large_sides[mortar] == 1 # large element on left side + if position in (1, 2) # small element + leftright = 2 + else # large element + leftright = 1 + end + else # large element on right side + if position in (1, 2) # small element + leftright = 1 + else # large element + leftright = 2 + end end - end - if position == 1 # lower element data has been received - first, last = indices[position] - @views vec(cache.mpi_mortars.u_lower[leftright, :, :, mortar]) .= recv_buffer[first:last] - elseif position == 2 # upper element data has been received - first, last = indices[position] - @views vec(cache.mpi_mortars.u_upper[leftright, :, :, mortar]) .= recv_buffer[first:last] - else # large element data has been received - first_lower, last_lower, first_upper, last_upper = indices[position] - @views vec(cache.mpi_mortars.u_lower[leftright, :, :, mortar]) .= recv_buffer[first_lower:last_lower] - @views vec(cache.mpi_mortars.u_upper[leftright, :, :, mortar]) .= recv_buffer[first_upper:last_upper] + if position == 1 # lower element data has been received + first, last = indices[position] + @views vec(cache.mpi_mortars.u_lower[leftright, :, :, mortar]) .= recv_buffer[first:last] + elseif position == 2 # upper element data has been received + first, last = indices[position] + @views vec(cache.mpi_mortars.u_upper[leftright, :, :, mortar]) .= recv_buffer[first:last] + else # large element data has been received + first_lower, last_lower, first_upper, last_upper = indices[position] + @views vec(cache.mpi_mortars.u_lower[leftright, :, :, mortar]) .= recv_buffer[first_lower:last_lower] + @views vec(cache.mpi_mortars.u_upper[leftright, :, :, mortar]) .= recv_buffer[first_upper:last_upper] + end end end + + data = MPI.Waitany(mpi_cache.mpi_recv_requests) end - data = MPI.Waitany(mpi_cache.mpi_recv_requests) + return nothing end - return nothing -end - -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::ParallelTreeMesh{2}, equations, - dg::DG, RealT, ::Type{uEltype}) where {uEltype <: Real} - # Get cells for which an element needs to be created (i.e. all leaf cells) - leaf_cell_ids = local_leaf_cells(mesh.tree) - - elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype) - - interfaces = init_interfaces(leaf_cell_ids, mesh, elements) - - mpi_interfaces = init_mpi_interfaces(leaf_cell_ids, mesh, elements) - - boundaries = init_boundaries(leaf_cell_ids, mesh, elements) - - mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) - - mpi_mortars = init_mpi_mortars(leaf_cell_ids, mesh, elements, dg.mortar) - - mpi_cache = init_mpi_cache(mesh, elements, mpi_interfaces, mpi_mortars, - nvariables(equations), nnodes(dg), uEltype) - - cache = (; elements, interfaces, mpi_interfaces, boundaries, mortars, mpi_mortars, - mpi_cache) - - # Add specialized parts of the cache required to compute the volume integral etc. - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) - - return cache -end - -function init_mpi_cache(mesh, elements, mpi_interfaces, mpi_mortars, nvars, nnodes, - uEltype) - mpi_cache = MPICache(uEltype) - - init_mpi_cache!(mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, nvars, - nnodes, uEltype) - return mpi_cache -end - -function init_mpi_cache!(mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, nvars, - nnodes, uEltype) - mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars = init_mpi_neighbor_connectivity(elements, - mpi_interfaces, - mpi_mortars, - mesh) - - mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests = init_mpi_data_structures(mpi_neighbor_interfaces, - mpi_neighbor_mortars, - ndims(mesh), - nvars, - nnodes, - uEltype) - - # Determine local and total number of elements - n_elements_by_rank = Vector{Int}(undef, mpi_nranks()) - n_elements_by_rank[mpi_rank() + 1] = nelements(elements) - MPI.Allgather!(MPI.UBuffer(n_elements_by_rank, 1), mpi_comm()) - n_elements_by_rank = OffsetArray(n_elements_by_rank, 0:(mpi_nranks() - 1)) - n_elements_global = MPI.Allreduce(nelements(elements), +, mpi_comm()) - @assert n_elements_global==sum(n_elements_by_rank) "error in total number of elements" - - # Determine the global element id of the first element - first_element_global_id = MPI.Exscan(nelements(elements), +, mpi_comm()) - if mpi_isroot() - # With Exscan, the result on the first rank is undefined - first_element_global_id = 1 - else - # On all other ranks we need to add one, since Julia has one-based indices - first_element_global_id += 1 - end - # TODO reuse existing structures - @pack! mpi_cache = mpi_neighbor_ranks, mpi_neighbor_interfaces, - mpi_neighbor_mortars, - mpi_send_buffers, mpi_recv_buffers, - mpi_send_requests, mpi_recv_requests, - n_elements_by_rank, n_elements_global, - first_element_global_id -end - -# Initialize connectivity between MPI neighbor ranks -function init_mpi_neighbor_connectivity(elements, mpi_interfaces, mpi_mortars, - mesh::TreeMesh2D) - tree = mesh.tree - - # Determine neighbor ranks and sides for MPI interfaces - neighbor_ranks_interface = fill(-1, nmpiinterfaces(mpi_interfaces)) - # The global interface id is the smaller of the (globally unique) neighbor cell ids, multiplied by - # number of directions (2 * ndims) plus direction minus one - global_interface_ids = fill(-1, nmpiinterfaces(mpi_interfaces)) - for interface_id in 1:nmpiinterfaces(mpi_interfaces) - orientation = mpi_interfaces.orientations[interface_id] - remote_side = mpi_interfaces.remote_sides[interface_id] - # Direction is from local cell to remote cell - if orientation == 1 # MPI interface in x-direction - if remote_side == 1 # remote cell on the "left" of MPI interface - direction = 1 - else # remote cell on the "right" of MPI interface - direction = 2 - end - else # MPI interface in y-direction - if remote_side == 1 # remote cell on the "left" of MPI interface - direction = 3 - else # remote cell on the "right" of MPI interface - direction = 4 - end - end - local_neighbor_id = mpi_interfaces.local_neighbor_ids[interface_id] - local_cell_id = elements.cell_ids[local_neighbor_id] - remote_cell_id = tree.neighbor_ids[direction, local_cell_id] - neighbor_ranks_interface[interface_id] = tree.mpi_ranks[remote_cell_id] - if local_cell_id < remote_cell_id - global_interface_ids[interface_id] = 2 * ndims(tree) * local_cell_id + - direction - 1 - else - global_interface_ids[interface_id] = (2 * ndims(tree) * remote_cell_id + - opposite_direction(direction) - 1) - end + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::ParallelTreeMesh{2}, equations, + dg::DG, RealT, ::Type{uEltype} + ) where {uEltype <: Real} + # Get cells for which an element needs to be created (i.e. all leaf cells) + leaf_cell_ids = local_leaf_cells(mesh.tree) + + elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype) + + interfaces = init_interfaces(leaf_cell_ids, mesh, elements) + + mpi_interfaces = init_mpi_interfaces(leaf_cell_ids, mesh, elements) + + boundaries = init_boundaries(leaf_cell_ids, mesh, elements) + + mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) + + mpi_mortars = init_mpi_mortars(leaf_cell_ids, mesh, elements, dg.mortar) + + mpi_cache = init_mpi_cache( + mesh, elements, mpi_interfaces, mpi_mortars, + nvariables(equations), nnodes(dg), uEltype + ) + + cache = (; + elements, interfaces, mpi_interfaces, boundaries, mortars, mpi_mortars, + mpi_cache, + ) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) + + return cache end - # Determine neighbor ranks for MPI mortars - neighbor_ranks_mortar = Vector{Vector{Int}}(undef, nmpimortars(mpi_mortars)) - # The global mortar id is the (globally unique) large cell id, multiplied by - # number of directions (2 * ndims) plus direction minus one where - # direction = 1 for mortars in x-direction where large element is left - # direction = 2 for mortars in x-direction where large element is right - # direction = 3 for mortars in y-direction where large element is left - # direction = 4 for mortars in y-direction where large element is right - global_mortar_ids = fill(-1, nmpimortars(mpi_mortars)) - for mortar in 1:nmpimortars(mpi_mortars) - neighbor_ranks_mortar[mortar] = Vector{Int}() - - orientation = mpi_mortars.orientations[mortar] - large_side = mpi_mortars.large_sides[mortar] - direction = (orientation - 1) * 2 + large_side - - local_neighbor_ids = mpi_mortars.local_neighbor_ids[mortar] - local_neighbor_positions = mpi_mortars.local_neighbor_positions[mortar] - if 3 in local_neighbor_positions # large element is on this rank - large_element_id = local_neighbor_ids[findfirst(pos -> pos == 3, - local_neighbor_positions)] - large_cell_id = elements.cell_ids[large_element_id] - else # large element is remote - cell_id = elements.cell_ids[first(local_neighbor_ids)] - large_cell_id = tree.neighbor_ids[direction, tree.parent_ids[cell_id]] - end + function init_mpi_cache( + mesh, elements, mpi_interfaces, mpi_mortars, nvars, nnodes, + uEltype + ) + mpi_cache = MPICache(uEltype) + + init_mpi_cache!( + mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, nvars, + nnodes, uEltype + ) + return mpi_cache + end - neighbor_cell_id = tree.neighbor_ids[opposite_direction(direction), - large_cell_id] - if direction == 1 - lower_cell_id = tree.child_ids[1, neighbor_cell_id] - upper_cell_id = tree.child_ids[3, neighbor_cell_id] - elseif direction == 2 - lower_cell_id = tree.child_ids[2, neighbor_cell_id] - upper_cell_id = tree.child_ids[4, neighbor_cell_id] - elseif direction == 3 - lower_cell_id = tree.child_ids[1, neighbor_cell_id] - upper_cell_id = tree.child_ids[2, neighbor_cell_id] + function init_mpi_cache!( + mpi_cache, mesh, elements, mpi_interfaces, mpi_mortars, nvars, + nnodes, uEltype + ) + mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars = init_mpi_neighbor_connectivity( + elements, + mpi_interfaces, + mpi_mortars, + mesh + ) + + mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests = init_mpi_data_structures( + mpi_neighbor_interfaces, + mpi_neighbor_mortars, + ndims(mesh), + nvars, + nnodes, + uEltype + ) + + # Determine local and total number of elements + n_elements_by_rank = Vector{Int}(undef, mpi_nranks()) + n_elements_by_rank[mpi_rank() + 1] = nelements(elements) + MPI.Allgather!(MPI.UBuffer(n_elements_by_rank, 1), mpi_comm()) + n_elements_by_rank = OffsetArray(n_elements_by_rank, 0:(mpi_nranks() - 1)) + n_elements_global = MPI.Allreduce(nelements(elements), +, mpi_comm()) + @assert n_elements_global == sum(n_elements_by_rank) "error in total number of elements" + + # Determine the global element id of the first element + first_element_global_id = MPI.Exscan(nelements(elements), +, mpi_comm()) + if mpi_isroot() + # With Exscan, the result on the first rank is undefined + first_element_global_id = 1 else - lower_cell_id = tree.child_ids[3, neighbor_cell_id] - upper_cell_id = tree.child_ids[4, neighbor_cell_id] + # On all other ranks we need to add one, since Julia has one-based indices + first_element_global_id += 1 end + # TODO reuse existing structures + @pack! mpi_cache = mpi_neighbor_ranks, mpi_neighbor_interfaces, + mpi_neighbor_mortars, + mpi_send_buffers, mpi_recv_buffers, + mpi_send_requests, mpi_recv_requests, + n_elements_by_rank, n_elements_global, + first_element_global_id + end - for cell_id in (lower_cell_id, upper_cell_id, large_cell_id) - if !is_own_cell(tree, cell_id) - neighbor_rank = tree.mpi_ranks[cell_id] - if !(neighbor_rank in neighbor_ranks_mortar[mortar]) - push!(neighbor_ranks_mortar[mortar], neighbor_rank) + # Initialize connectivity between MPI neighbor ranks + function init_mpi_neighbor_connectivity( + elements, mpi_interfaces, mpi_mortars, + mesh::TreeMesh2D + ) + tree = mesh.tree + + # Determine neighbor ranks and sides for MPI interfaces + neighbor_ranks_interface = fill(-1, nmpiinterfaces(mpi_interfaces)) + # The global interface id is the smaller of the (globally unique) neighbor cell ids, multiplied by + # number of directions (2 * ndims) plus direction minus one + global_interface_ids = fill(-1, nmpiinterfaces(mpi_interfaces)) + for interface_id in 1:nmpiinterfaces(mpi_interfaces) + orientation = mpi_interfaces.orientations[interface_id] + remote_side = mpi_interfaces.remote_sides[interface_id] + # Direction is from local cell to remote cell + if orientation == 1 # MPI interface in x-direction + if remote_side == 1 # remote cell on the "left" of MPI interface + direction = 1 + else # remote cell on the "right" of MPI interface + direction = 2 end + else # MPI interface in y-direction + if remote_side == 1 # remote cell on the "left" of MPI interface + direction = 3 + else # remote cell on the "right" of MPI interface + direction = 4 + end + end + local_neighbor_id = mpi_interfaces.local_neighbor_ids[interface_id] + local_cell_id = elements.cell_ids[local_neighbor_id] + remote_cell_id = tree.neighbor_ids[direction, local_cell_id] + neighbor_ranks_interface[interface_id] = tree.mpi_ranks[remote_cell_id] + if local_cell_id < remote_cell_id + global_interface_ids[interface_id] = 2 * ndims(tree) * local_cell_id + + direction - 1 + else + global_interface_ids[interface_id] = ( + 2 * ndims(tree) * remote_cell_id + + opposite_direction(direction) - 1 + ) end end - global_mortar_ids[mortar] = 2 * ndims(tree) * large_cell_id + direction - 1 - end + # Determine neighbor ranks for MPI mortars + neighbor_ranks_mortar = Vector{Vector{Int}}(undef, nmpimortars(mpi_mortars)) + # The global mortar id is the (globally unique) large cell id, multiplied by + # number of directions (2 * ndims) plus direction minus one where + # direction = 1 for mortars in x-direction where large element is left + # direction = 2 for mortars in x-direction where large element is right + # direction = 3 for mortars in y-direction where large element is left + # direction = 4 for mortars in y-direction where large element is right + global_mortar_ids = fill(-1, nmpimortars(mpi_mortars)) + for mortar in 1:nmpimortars(mpi_mortars) + neighbor_ranks_mortar[mortar] = Vector{Int}() + + orientation = mpi_mortars.orientations[mortar] + large_side = mpi_mortars.large_sides[mortar] + direction = (orientation - 1) * 2 + large_side + + local_neighbor_ids = mpi_mortars.local_neighbor_ids[mortar] + local_neighbor_positions = mpi_mortars.local_neighbor_positions[mortar] + if 3 in local_neighbor_positions # large element is on this rank + large_element_id = local_neighbor_ids[ + findfirst( + pos -> pos == 3, + local_neighbor_positions + ), + ] + large_cell_id = elements.cell_ids[large_element_id] + else # large element is remote + cell_id = elements.cell_ids[first(local_neighbor_ids)] + large_cell_id = tree.neighbor_ids[direction, tree.parent_ids[cell_id]] + end - # Get sorted, unique neighbor ranks - mpi_neighbor_ranks = vcat(neighbor_ranks_interface, neighbor_ranks_mortar...) |> - sort |> unique - - # Sort interfaces by global interface id - p = sortperm(global_interface_ids) - neighbor_ranks_interface .= neighbor_ranks_interface[p] - interface_ids = collect(1:nmpiinterfaces(mpi_interfaces))[p] - - # Sort mortars by global mortar id - p = sortperm(global_mortar_ids) - neighbor_ranks_mortar .= neighbor_ranks_mortar[p] - mortar_ids = collect(1:nmpimortars(mpi_mortars))[p] - - # For each neighbor rank, init connectivity data structures - mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) - mpi_neighbor_mortars = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) - for (index, rank) in enumerate(mpi_neighbor_ranks) - mpi_neighbor_interfaces[index] = interface_ids[findall(x -> (x == rank), - neighbor_ranks_interface)] - mpi_neighbor_mortars[index] = mortar_ids[findall(x -> (rank in x), - neighbor_ranks_mortar)] - end + neighbor_cell_id = tree.neighbor_ids[ + opposite_direction(direction), + large_cell_id, + ] + if direction == 1 + lower_cell_id = tree.child_ids[1, neighbor_cell_id] + upper_cell_id = tree.child_ids[3, neighbor_cell_id] + elseif direction == 2 + lower_cell_id = tree.child_ids[2, neighbor_cell_id] + upper_cell_id = tree.child_ids[4, neighbor_cell_id] + elseif direction == 3 + lower_cell_id = tree.child_ids[1, neighbor_cell_id] + upper_cell_id = tree.child_ids[2, neighbor_cell_id] + else + lower_cell_id = tree.child_ids[3, neighbor_cell_id] + upper_cell_id = tree.child_ids[4, neighbor_cell_id] + end - # Sanity checks that we counted all interfaces exactly once - @assert sum(length(v) for v in mpi_neighbor_interfaces) == - nmpiinterfaces(mpi_interfaces) + for cell_id in (lower_cell_id, upper_cell_id, large_cell_id) + if !is_own_cell(tree, cell_id) + neighbor_rank = tree.mpi_ranks[cell_id] + if !(neighbor_rank in neighbor_ranks_mortar[mortar]) + push!(neighbor_ranks_mortar[mortar], neighbor_rank) + end + end + end - return mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars -end + global_mortar_ids[mortar] = 2 * ndims(tree) * large_cell_id + direction - 1 + end -function rhs!(du, u, t, - mesh::Union{ParallelTreeMesh{2}, ParallelP4estMesh{2}, - ParallelT8codeMesh{2}}, equations, - initial_condition, boundary_conditions, source_terms::Source, - dg::DG, cache) where {Source} - # Start to receive MPI data - @trixi_timeit timer() "start MPI receive" start_mpi_receive!(cache.mpi_cache) + # Get sorted, unique neighbor ranks + mpi_neighbor_ranks = vcat(neighbor_ranks_interface, neighbor_ranks_mortar...) |> + sort |> unique + + # Sort interfaces by global interface id + p = sortperm(global_interface_ids) + neighbor_ranks_interface .= neighbor_ranks_interface[p] + interface_ids = collect(1:nmpiinterfaces(mpi_interfaces))[p] + + # Sort mortars by global mortar id + p = sortperm(global_mortar_ids) + neighbor_ranks_mortar .= neighbor_ranks_mortar[p] + mortar_ids = collect(1:nmpimortars(mpi_mortars))[p] + + # For each neighbor rank, init connectivity data structures + mpi_neighbor_interfaces = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) + mpi_neighbor_mortars = Vector{Vector{Int}}(undef, length(mpi_neighbor_ranks)) + for (index, rank) in enumerate(mpi_neighbor_ranks) + mpi_neighbor_interfaces[index] = interface_ids[ + findall( + x -> (x == rank), + neighbor_ranks_interface + ), + ] + mpi_neighbor_mortars[index] = mortar_ids[ + findall( + x -> (rank in x), + neighbor_ranks_mortar + ), + ] + end - # Prolong solution to MPI interfaces - @trixi_timeit timer() "prolong2mpiinterfaces" begin - prolong2mpiinterfaces!(cache, u, mesh, equations, dg.surface_integral, dg) - end + # Sanity checks that we counted all interfaces exactly once + @assert sum(length(v) for v in mpi_neighbor_interfaces) == + nmpiinterfaces(mpi_interfaces) - # Prolong solution to MPI mortars - @trixi_timeit timer() "prolong2mpimortars" begin - prolong2mpimortars!(cache, u, mesh, equations, - dg.mortar, dg.surface_integral, dg) + return mpi_neighbor_ranks, mpi_neighbor_interfaces, mpi_neighbor_mortars end - # Start to send MPI data - @trixi_timeit timer() "start MPI send" begin - start_mpi_send!(cache.mpi_cache, mesh, equations, dg, cache) - end + function rhs!( + du, u, t, + mesh::Union{ + ParallelTreeMesh{2}, ParallelP4estMesh{2}, + ParallelT8codeMesh{2}, + }, equations, + initial_condition, boundary_conditions, source_terms::Source, + dg::DG, cache + ) where {Source} + # Start to receive MPI data + @trixi_timeit timer() "start MPI receive" start_mpi_receive!(cache.mpi_cache) + + # Prolong solution to MPI interfaces + @trixi_timeit timer() "prolong2mpiinterfaces" begin + prolong2mpiinterfaces!(cache, u, mesh, equations, dg.surface_integral, dg) + end - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + # Prolong solution to MPI mortars + @trixi_timeit timer() "prolong2mpimortars" begin + prolong2mpimortars!( + cache, u, mesh, equations, + dg.mortar, dg.surface_integral, dg + ) + end - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) - end + # Start to send MPI data + @trixi_timeit timer() "start MPI send" begin + start_mpi_send!(cache.mpi_cache, mesh, equations, dg, cache) + end - # Prolong solution to interfaces - # TODO: Taal decide order of arguments, consistent vs. modified cache first? - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache, u, mesh, equations, - dg.surface_integral, dg) - end + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg, cache) - end + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache, u, mesh, equations, - dg.surface_integral, dg) - end + # Prolong solution to interfaces + # TODO: Taal decide order of arguments, consistent vs. modified cache first? + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations, - dg.surface_integral, dg) - end + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg, cache + ) + end - # Prolong solution to mortars - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u, mesh, equations, - dg.mortar, dg.surface_integral, dg) - end + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end - # Calculate mortar fluxes - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.mortar, dg.surface_integral, dg, cache) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, t, boundary_conditions, mesh, equations, + dg.surface_integral, dg + ) + end - # Finish to receive MPI data - @trixi_timeit timer() "finish MPI receive" begin - finish_mpi_receive!(cache.mpi_cache, mesh, equations, dg, cache) - end + # Prolong solution to mortars + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!( + cache, u, mesh, equations, + dg.mortar, dg.surface_integral, dg + ) + end - # Calculate MPI interface fluxes - @trixi_timeit timer() "MPI interface flux" begin - calc_mpi_interface_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg, cache) - end + # Calculate mortar fluxes + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.mortar, dg.surface_integral, dg, cache + ) + end - # Calculate MPI mortar fluxes - @trixi_timeit timer() "MPI mortar flux" begin - calc_mpi_mortar_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.mortar, dg.surface_integral, dg, cache) - end + # Finish to receive MPI data + @trixi_timeit timer() "finish MPI receive" begin + finish_mpi_receive!(cache.mpi_cache, mesh, equations, dg, cache) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) - end + # Calculate MPI interface fluxes + @trixi_timeit timer() "MPI interface flux" begin + calc_mpi_interface_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg, cache + ) + end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + # Calculate MPI mortar fluxes + @trixi_timeit timer() "MPI mortar flux" begin + calc_mpi_mortar_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.mortar, dg.surface_integral, dg, cache + ) + end - # Calculate source terms - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, equations, dg, cache) - end + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end - # Finish to send MPI data - @trixi_timeit timer() "finish MPI send" finish_mpi_send!(cache.mpi_cache) + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) - return nothing -end + # Calculate source terms + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, equations, dg, cache) + end -function prolong2mpiinterfaces!(cache, u, - mesh::ParallelTreeMesh{2}, - equations, surface_integral, dg::DG) - @unpack mpi_interfaces = cache + # Finish to send MPI data + @trixi_timeit timer() "finish MPI send" finish_mpi_send!(cache.mpi_cache) - @threaded for interface in eachmpiinterface(dg, cache) - local_element = mpi_interfaces.local_neighbor_ids[interface] + return nothing + end - if mpi_interfaces.orientations[interface] == 1 # interface in x-direction - if mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction - for j in eachnode(dg), v in eachvariable(equations) - mpi_interfaces.u[2, v, j, interface] = u[v, 1, j, local_element] - end - else # local element in negative direction - for j in eachnode(dg), v in eachvariable(equations) - mpi_interfaces.u[1, v, j, interface] = u[v, nnodes(dg), j, - local_element] - end - end - else # interface in y-direction - if mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction - for i in eachnode(dg), v in eachvariable(equations) - mpi_interfaces.u[2, v, i, interface] = u[v, i, 1, local_element] + function prolong2mpiinterfaces!( + cache, u, + mesh::ParallelTreeMesh{2}, + equations, surface_integral, dg::DG + ) + @unpack mpi_interfaces = cache + + @threaded for interface in eachmpiinterface(dg, cache) + local_element = mpi_interfaces.local_neighbor_ids[interface] + + if mpi_interfaces.orientations[interface] == 1 # interface in x-direction + if mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction + for j in eachnode(dg), v in eachvariable(equations) + mpi_interfaces.u[2, v, j, interface] = u[v, 1, j, local_element] + end + else # local element in negative direction + for j in eachnode(dg), v in eachvariable(equations) + mpi_interfaces.u[1, v, j, interface] = u[ + v, nnodes(dg), j, + local_element, + ] + end end - else # local element in negative direction - for i in eachnode(dg), v in eachvariable(equations) - mpi_interfaces.u[1, v, i, interface] = u[v, i, nnodes(dg), - local_element] + else # interface in y-direction + if mpi_interfaces.remote_sides[interface] == 1 # local element in positive direction + for i in eachnode(dg), v in eachvariable(equations) + mpi_interfaces.u[2, v, i, interface] = u[v, i, 1, local_element] + end + else # local element in negative direction + for i in eachnode(dg), v in eachvariable(equations) + mpi_interfaces.u[1, v, i, interface] = u[ + v, i, nnodes(dg), + local_element, + ] + end end end end - end - - return nothing -end - -function prolong2mpimortars!(cache, u, - mesh::ParallelTreeMesh{2}, equations, - mortar_l2::LobattoLegendreMortarL2, surface_integral, - dg::DGSEM) - @unpack mpi_mortars = cache - @threaded for mortar in eachmpimortar(dg, cache) - local_neighbor_ids = mpi_mortars.local_neighbor_ids[mortar] - local_neighbor_positions = mpi_mortars.local_neighbor_positions[mortar] + return nothing + end - for (element, position) in zip(local_neighbor_ids, local_neighbor_positions) - if position in (1, 2) # Current element is small - # Copy solution small to small - if mpi_mortars.large_sides[mortar] == 1 # -> small elements on right side - if mpi_mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - if position == 1 - for l in eachnode(dg) - for v in eachvariable(equations) - mpi_mortars.u_lower[2, v, l, mortar] = u[v, 1, l, - element] + function prolong2mpimortars!( + cache, u, + mesh::ParallelTreeMesh{2}, equations, + mortar_l2::LobattoLegendreMortarL2, surface_integral, + dg::DGSEM + ) + @unpack mpi_mortars = cache + + @threaded for mortar in eachmpimortar(dg, cache) + local_neighbor_ids = mpi_mortars.local_neighbor_ids[mortar] + local_neighbor_positions = mpi_mortars.local_neighbor_positions[mortar] + + for (element, position) in zip(local_neighbor_ids, local_neighbor_positions) + if position in (1, 2) # Current element is small + # Copy solution small to small + if mpi_mortars.large_sides[mortar] == 1 # -> small elements on right side + if mpi_mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + if position == 1 + for l in eachnode(dg) + for v in eachvariable(equations) + mpi_mortars.u_lower[2, v, l, mortar] = u[ + v, 1, l, + element, + ] + end end - end - else # position == 2 - for l in eachnode(dg) - for v in eachvariable(equations) - mpi_mortars.u_upper[2, v, l, mortar] = u[v, 1, l, - element] + else # position == 2 + for l in eachnode(dg) + for v in eachvariable(equations) + mpi_mortars.u_upper[2, v, l, mortar] = u[ + v, 1, l, + element, + ] + end end end - end - else - # L2 mortars in y-direction - if position == 1 - for l in eachnode(dg) - for v in eachvariable(equations) - mpi_mortars.u_lower[2, v, l, mortar] = u[v, l, 1, - element] + else + # L2 mortars in y-direction + if position == 1 + for l in eachnode(dg) + for v in eachvariable(equations) + mpi_mortars.u_lower[2, v, l, mortar] = u[ + v, l, 1, + element, + ] + end end - end - else # position == 2 - for l in eachnode(dg) - for v in eachvariable(equations) - mpi_mortars.u_upper[2, v, l, mortar] = u[v, l, 1, - element] + else # position == 2 + for l in eachnode(dg) + for v in eachvariable(equations) + mpi_mortars.u_upper[2, v, l, mortar] = u[ + v, l, 1, + element, + ] + end end end end - end - else # large_sides[mortar] == 2 -> small elements on left side - if mpi_mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - if position == 1 - for l in eachnode(dg) - for v in eachvariable(equations) - mpi_mortars.u_lower[1, v, l, mortar] = u[v, - nnodes(dg), - l, element] + else # large_sides[mortar] == 2 -> small elements on left side + if mpi_mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + if position == 1 + for l in eachnode(dg) + for v in eachvariable(equations) + mpi_mortars.u_lower[1, v, l, mortar] = u[ + v, + nnodes(dg), + l, element, + ] + end end - end - else # position == 2 - for l in eachnode(dg) - for v in eachvariable(equations) - mpi_mortars.u_upper[1, v, l, mortar] = u[v, - nnodes(dg), - l, element] + else # position == 2 + for l in eachnode(dg) + for v in eachvariable(equations) + mpi_mortars.u_upper[1, v, l, mortar] = u[ + v, + nnodes(dg), + l, element, + ] + end end end - end - else - # L2 mortars in y-direction - if position == 1 - for l in eachnode(dg) - for v in eachvariable(equations) - mpi_mortars.u_lower[1, v, l, mortar] = u[v, l, - nnodes(dg), - element] + else + # L2 mortars in y-direction + if position == 1 + for l in eachnode(dg) + for v in eachvariable(equations) + mpi_mortars.u_lower[1, v, l, mortar] = u[ + v, l, + nnodes(dg), + element, + ] + end end - end - else # position == 2 - for l in eachnode(dg) - for v in eachvariable(equations) - mpi_mortars.u_upper[1, v, l, mortar] = u[v, l, - nnodes(dg), - element] + else # position == 2 + for l in eachnode(dg) + for v in eachvariable(equations) + mpi_mortars.u_upper[1, v, l, mortar] = u[ + v, l, + nnodes(dg), + element, + ] + end end end end end - end - else # position == 3 -> current element is large - # Interpolate large element face data to small interface locations - if mpi_mortars.large_sides[mortar] == 1 # -> large element on left side - leftright = 1 - if mpi_mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - u_large = view(u, :, nnodes(dg), :, element) - element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright, - mortar, u_large) - else - # L2 mortars in y-direction - u_large = view(u, :, :, nnodes(dg), element) - element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright, - mortar, u_large) - end - else # large_sides[mortar] == 2 -> large element on right side - leftright = 2 - if mpi_mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - u_large = view(u, :, 1, :, element) - element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright, - mortar, u_large) - else - # L2 mortars in y-direction - u_large = view(u, :, :, 1, element) - element_solutions_to_mortars!(mpi_mortars, mortar_l2, leftright, - mortar, u_large) + else # position == 3 -> current element is large + # Interpolate large element face data to small interface locations + if mpi_mortars.large_sides[mortar] == 1 # -> large element on left side + leftright = 1 + if mpi_mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(u, :, nnodes(dg), :, element) + element_solutions_to_mortars!( + mpi_mortars, mortar_l2, leftright, + mortar, u_large + ) + else + # L2 mortars in y-direction + u_large = view(u, :, :, nnodes(dg), element) + element_solutions_to_mortars!( + mpi_mortars, mortar_l2, leftright, + mortar, u_large + ) + end + else # large_sides[mortar] == 2 -> large element on right side + leftright = 2 + if mpi_mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(u, :, 1, :, element) + element_solutions_to_mortars!( + mpi_mortars, mortar_l2, leftright, + mortar, u_large + ) + else + # L2 mortars in y-direction + u_large = view(u, :, :, 1, element) + element_solutions_to_mortars!( + mpi_mortars, mortar_l2, leftright, + mortar, u_large + ) + end end end end end + + return nothing end - return nothing -end - -function calc_mpi_interface_flux!(surface_flux_values, - mesh::ParallelTreeMesh{2}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u, local_neighbor_ids, orientations, remote_sides = cache.mpi_interfaces - - @threaded for interface in eachmpiinterface(dg, cache) - # Get local neighboring element - element = local_neighbor_ids[interface] - - # Determine interface direction with respect to element: - if orientations[interface] == 1 # interface in x-direction - if remote_sides[interface] == 1 # local element in positive direction - direction = 1 - else # local element in negative direction - direction = 2 - end - else # interface in y-direction - if remote_sides[interface] == 1 # local element in positive direction - direction = 3 - else # local element in negative direction - direction = 4 + function calc_mpi_interface_flux!( + surface_flux_values, + mesh::ParallelTreeMesh{2}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u, local_neighbor_ids, orientations, remote_sides = cache.mpi_interfaces + + @threaded for interface in eachmpiinterface(dg, cache) + # Get local neighboring element + element = local_neighbor_ids[interface] + + # Determine interface direction with respect to element: + if orientations[interface] == 1 # interface in x-direction + if remote_sides[interface] == 1 # local element in positive direction + direction = 1 + else # local element in negative direction + direction = 2 + end + else # interface in y-direction + if remote_sides[interface] == 1 # local element in positive direction + direction = 3 + else # local element in negative direction + direction = 4 + end end - end - for i in eachnode(dg) - # Call pointwise Riemann solver - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface) - flux = surface_flux(u_ll, u_rr, orientations[interface], equations) + for i in eachnode(dg) + # Call pointwise Riemann solver + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface) + flux = surface_flux(u_ll, u_rr, orientations[interface], equations) - # Copy flux to local element storage - for v in eachvariable(equations) - surface_flux_values[v, i, direction, element] = flux[v] + # Copy flux to local element storage + for v in eachvariable(equations) + surface_flux_values[v, i, direction, element] = flux[v] + end end end + + return nothing end - return nothing -end - -function calc_mpi_mortar_flux!(surface_flux_values, - mesh::ParallelTreeMesh{2}, - nonconservative_terms::False, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u_lower, u_upper, orientations = cache.mpi_mortars - @unpack fstar_upper_threaded, fstar_lower_threaded = cache - - @threaded for mortar in eachmpimortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar_upper = fstar_upper_threaded[Threads.threadid()] - fstar_lower = fstar_lower_threaded[Threads.threadid()] - - # Calculate fluxes - orientation = orientations[mortar] - calc_fstar!(fstar_upper, equations, surface_flux, dg, u_upper, mortar, - orientation) - calc_fstar!(fstar_lower, equations, surface_flux, dg, u_lower, mortar, - orientation) - - mpi_mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, - mortar, fstar_upper, fstar_lower) + function calc_mpi_mortar_flux!( + surface_flux_values, + mesh::ParallelTreeMesh{2}, + nonconservative_terms::False, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u_lower, u_upper, orientations = cache.mpi_mortars + @unpack fstar_upper_threaded, fstar_lower_threaded = cache + + @threaded for mortar in eachmpimortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar_upper = fstar_upper_threaded[Threads.threadid()] + fstar_lower = fstar_lower_threaded[Threads.threadid()] + + # Calculate fluxes + orientation = orientations[mortar] + calc_fstar!( + fstar_upper, equations, surface_flux, dg, u_upper, mortar, + orientation + ) + calc_fstar!( + fstar_lower, equations, surface_flux, dg, u_lower, mortar, + orientation + ) + + mpi_mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, + mortar, fstar_upper, fstar_lower + ) + end + + return nothing end - return nothing -end - -@inline function mpi_mortar_fluxes_to_elements!(surface_flux_values, - mesh::ParallelTreeMesh{2}, equations, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, - mortar, fstar_upper, fstar_lower) - local_neighbor_ids = cache.mpi_mortars.local_neighbor_ids[mortar] - local_neighbor_positions = cache.mpi_mortars.local_neighbor_positions[mortar] - - for (element, position) in zip(local_neighbor_ids, local_neighbor_positions) - if position in (1, 2) # Current element is small - # Copy flux small to small - if cache.mpi_mortars.large_sides[mortar] == 1 # -> small elements on right side - if cache.mpi_mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 1 - else - # L2 mortars in y-direction - direction = 3 - end - else # large_sides[mortar] == 2 -> small elements on left side - if cache.mpi_mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 2 - else - # L2 mortars in y-direction - direction = 4 + @inline function mpi_mortar_fluxes_to_elements!( + surface_flux_values, + mesh::ParallelTreeMesh{2}, equations, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, + mortar, fstar_upper, fstar_lower + ) + local_neighbor_ids = cache.mpi_mortars.local_neighbor_ids[mortar] + local_neighbor_positions = cache.mpi_mortars.local_neighbor_positions[mortar] + + for (element, position) in zip(local_neighbor_ids, local_neighbor_positions) + if position in (1, 2) # Current element is small + # Copy flux small to small + if cache.mpi_mortars.large_sides[mortar] == 1 # -> small elements on right side + if cache.mpi_mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + direction = 1 + else + # L2 mortars in y-direction + direction = 3 + end + else # large_sides[mortar] == 2 -> small elements on left side + if cache.mpi_mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + direction = 2 + else + # L2 mortars in y-direction + direction = 4 + end end - end - if position == 1 - surface_flux_values[:, :, direction, element] .= fstar_lower - elseif position == 2 - surface_flux_values[:, :, direction, element] .= fstar_upper - end - else # position == 3 -> current element is large - # Project small fluxes to large element - if cache.mpi_mortars.large_sides[mortar] == 1 # -> large element on left side - if cache.mpi_mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 2 - else - # L2 mortars in y-direction - direction = 4 + if position == 1 + surface_flux_values[:, :, direction, element] .= fstar_lower + elseif position == 2 + surface_flux_values[:, :, direction, element] .= fstar_upper end - else # large_sides[mortar] == 2 -> large element on right side - if cache.mpi_mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 1 - else - # L2 mortars in y-direction - direction = 3 + else # position == 3 -> current element is large + # Project small fluxes to large element + if cache.mpi_mortars.large_sides[mortar] == 1 # -> large element on left side + if cache.mpi_mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + direction = 2 + else + # L2 mortars in y-direction + direction = 4 + end + else # large_sides[mortar] == 2 -> large element on right side + if cache.mpi_mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + direction = 1 + else + # L2 mortars in y-direction + direction = 3 + end end - end - multiply_dimensionwise!(view(surface_flux_values, :, :, direction, element), - mortar_l2.reverse_upper, fstar_upper, - mortar_l2.reverse_lower, fstar_lower) + multiply_dimensionwise!( + view(surface_flux_values, :, :, direction, element), + mortar_l2.reverse_upper, fstar_upper, + mortar_l2.reverse_lower, fstar_lower + ) + end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl index c27327be8a..4d8e3c8572 100644 --- a/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl +++ b/src/solvers/dgsem_tree/dg_2d_subcell_limiters.jl @@ -3,498 +3,612 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -function create_cache(mesh::Union{TreeMesh{2}, StructuredMesh{2}, P4estMesh{2}}, - equations, volume_integral::VolumeIntegralSubcellLimiting, - dg::DG, uEltype) - cache = create_cache(mesh, equations, - VolumeIntegralPureLGLFiniteVolume(volume_integral.volume_flux_fv), - dg, uEltype) - - A3dp1_x = Array{uEltype, 3} - A3dp1_y = Array{uEltype, 3} - A3d = Array{uEltype, 3} - A4d = Array{uEltype, 4} - - fhat1_L_threaded = A3dp1_x[A3dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg)) for _ in 1:Threads.nthreads()] - fhat2_L_threaded = A3dp1_y[A3dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1) for _ in 1:Threads.nthreads()] - fhat1_R_threaded = A3dp1_x[A3dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg)) for _ in 1:Threads.nthreads()] - fhat2_R_threaded = A3dp1_y[A3dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1) for _ in 1:Threads.nthreads()] - flux_temp_threaded = A3d[A3d(undef, nvariables(equations), nnodes(dg), nnodes(dg)) - for _ in 1:Threads.nthreads()] - fhat_temp_threaded = A3d[A3d(undef, nvariables(equations), nnodes(dg), - nnodes(dg)) - for _ in 1:Threads.nthreads()] - antidiffusive_fluxes = Trixi.ContainerAntidiffusiveFlux2D{uEltype}(0, - nvariables(equations), - nnodes(dg)) - - if have_nonconservative_terms(equations) == true - flux_nonconservative_temp_threaded = A4d[A4d(undef, nvariables(equations), - n_nonconservative_terms(equations), - nnodes(dg), nnodes(dg)) - for _ in 1:Threads.nthreads()] - fhat_nonconservative_temp_threaded = A4d[A4d(undef, nvariables(equations), - n_nonconservative_terms(equations), - nnodes(dg), nnodes(dg)) - for _ in 1:Threads.nthreads()] - phi_threaded = A4d[A4d(undef, nvariables(equations), - n_nonconservative_terms(equations), - nnodes(dg), nnodes(dg)) - for _ in 1:Threads.nthreads()] - cache = (; cache..., flux_nonconservative_temp_threaded, - fhat_nonconservative_temp_threaded, phi_threaded) - end + #! format: noindent + + function create_cache( + mesh::Union{TreeMesh{2}, StructuredMesh{2}, P4estMesh{2}}, + equations, volume_integral::VolumeIntegralSubcellLimiting, + dg::DG, uEltype + ) + cache = create_cache( + mesh, equations, + VolumeIntegralPureLGLFiniteVolume(volume_integral.volume_flux_fv), + dg, uEltype + ) + + A3dp1_x = Array{uEltype, 3} + A3dp1_y = Array{uEltype, 3} + A3d = Array{uEltype, 3} + A4d = Array{uEltype, 4} + + fhat1_L_threaded = A3dp1_x[ + A3dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg) + ) for _ in 1:Threads.nthreads() + ] + fhat2_L_threaded = A3dp1_y[ + A3dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1 + ) for _ in 1:Threads.nthreads() + ] + fhat1_R_threaded = A3dp1_x[ + A3dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg) + ) for _ in 1:Threads.nthreads() + ] + fhat2_R_threaded = A3dp1_y[ + A3dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1 + ) for _ in 1:Threads.nthreads() + ] + flux_temp_threaded = A3d[ + A3d(undef, nvariables(equations), nnodes(dg), nnodes(dg)) + for _ in 1:Threads.nthreads() + ] + fhat_temp_threaded = A3d[ + A3d( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + antidiffusive_fluxes = Trixi.ContainerAntidiffusiveFlux2D{uEltype}( + 0, + nvariables(equations), + nnodes(dg) + ) + + if have_nonconservative_terms(equations) == true + flux_nonconservative_temp_threaded = A4d[ + A4d( + undef, nvariables(equations), + n_nonconservative_terms(equations), + nnodes(dg), nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + fhat_nonconservative_temp_threaded = A4d[ + A4d( + undef, nvariables(equations), + n_nonconservative_terms(equations), + nnodes(dg), nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + phi_threaded = A4d[ + A4d( + undef, nvariables(equations), + n_nonconservative_terms(equations), + nnodes(dg), nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + cache = (; + cache..., flux_nonconservative_temp_threaded, + fhat_nonconservative_temp_threaded, phi_threaded, + ) + end - return (; cache..., antidiffusive_fluxes, + return (; + cache..., antidiffusive_fluxes, fhat1_L_threaded, fhat2_L_threaded, fhat1_R_threaded, fhat2_R_threaded, - flux_temp_threaded, fhat_temp_threaded) -end - -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - P4estMesh{2}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralSubcellLimiting, - dg::DGSEM, cache) - @unpack limiter = volume_integral - - @threaded for element in eachelement(dg, cache) - subcell_limiting_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - volume_integral, limiter, - dg, cache) - end -end - -@inline function subcell_limiting_kernel!(du, u, element, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - P4estMesh{2}}, - nonconservative_terms, equations, - volume_integral, limiter::SubcellLimiterIDP, - dg::DGSEM, cache) - @unpack inverse_weights = dg.basis - @unpack volume_flux_dg, volume_flux_fv = volume_integral - - # high-order DG fluxes - @unpack fhat1_L_threaded, fhat1_R_threaded, fhat2_L_threaded, fhat2_R_threaded = cache - - fhat1_L = fhat1_L_threaded[Threads.threadid()] - fhat1_R = fhat1_R_threaded[Threads.threadid()] - fhat2_L = fhat2_L_threaded[Threads.threadid()] - fhat2_R = fhat2_R_threaded[Threads.threadid()] - calcflux_fhat!(fhat1_L, fhat1_R, fhat2_L, fhat2_R, u, mesh, - nonconservative_terms, equations, volume_flux_dg, dg, element, - cache) - - # low-order FV fluxes - @unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded = cache - - fstar1_L = fstar1_L_threaded[Threads.threadid()] - fstar2_L = fstar2_L_threaded[Threads.threadid()] - fstar1_R = fstar1_R_threaded[Threads.threadid()] - fstar2_R = fstar2_R_threaded[Threads.threadid()] - calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, mesh, - nonconservative_terms, equations, volume_flux_fv, dg, element, - cache) - - # antidiffusive flux - calcflux_antidiffusive!(fhat1_L, fhat1_R, fhat2_L, fhat2_R, - fstar1_L, fstar1_R, fstar2_L, fstar2_R, - u, mesh, nonconservative_terms, equations, limiter, dg, - element, cache) - - # Calculate volume integral contribution of low-order FV flux - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - du[v, i, j, element] += inverse_weights[i] * - (fstar1_L[v, i + 1, j] - fstar1_R[v, i, j]) + - inverse_weights[j] * - (fstar2_L[v, i, j + 1] - fstar2_R[v, i, j]) - end + flux_temp_threaded, fhat_temp_threaded, + ) end - return nothing -end - -# Calculate the DG staggered volume fluxes `fhat` in subcell FV-form inside the element -# (**without non-conservative terms**). -# -# See also `flux_differencing_kernel!`. -@inline function calcflux_fhat!(fhat1_L, fhat1_R, fhat2_L, fhat2_R, u, - mesh::TreeMesh{2}, nonconservative_terms::False, - equations, - volume_flux, dg::DGSEM, element, cache) - @unpack weights, derivative_split = dg.basis - @unpack flux_temp_threaded = cache - - flux_temp = flux_temp_threaded[Threads.threadid()] - - # The FV-form fluxes are calculated in a recursive manner, i.e.: - # fhat_(0,1) = w_0 * FVol_0, - # fhat_(j,j+1) = fhat_(j-1,j) + w_j * FVol_j, for j=1,...,N-1, - # with the split form volume fluxes FVol_j = -2 * sum_i=0^N D_ji f*_(j,i). - - # To use the symmetry of the `volume_flux`, the split form volume flux is precalculated - # like in `calc_volume_integral!` for the `VolumeIntegralFluxDifferencing` - # and saved in in `flux_temp`. - - # Split form volume flux in orientation 1: x direction - flux_temp .= zero(eltype(flux_temp)) - - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - # All diagonal entries of `derivative_split` are zero. Thus, we can skip - # the computation of the diagonal terms. In addition, we use the symmetry - # of the `volume_flux` to save half of the possible two-point flux - # computations. - for ii in (i + 1):nnodes(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, element) - flux1 = volume_flux(u_node, u_node_ii, 1, equations) - multiply_add_to_node_vars!(flux_temp, derivative_split[i, ii], flux1, - equations, dg, i, j) - multiply_add_to_node_vars!(flux_temp, derivative_split[ii, i], flux1, - equations, dg, ii, j) + function calc_volume_integral!( + du, u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + P4estMesh{2}, + }, + nonconservative_terms, equations, + volume_integral::VolumeIntegralSubcellLimiting, + dg::DGSEM, cache + ) + @unpack limiter = volume_integral + + @threaded for element in eachelement(dg, cache) + subcell_limiting_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + volume_integral, limiter, + dg, cache + ) end end - # FV-form flux `fhat` in x direction - fhat1_L[:, 1, :] .= zero(eltype(fhat1_L)) - fhat1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_L)) - fhat1_R[:, 1, :] .= zero(eltype(fhat1_R)) - fhat1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_R)) + @inline function subcell_limiting_kernel!( + du, u, element, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + P4estMesh{2}, + }, + nonconservative_terms, equations, + volume_integral, limiter::SubcellLimiterIDP, + dg::DGSEM, cache + ) + @unpack inverse_weights = dg.basis + @unpack volume_flux_dg, volume_flux_fv = volume_integral + + # high-order DG fluxes + @unpack fhat1_L_threaded, fhat1_R_threaded, fhat2_L_threaded, fhat2_R_threaded = cache + + fhat1_L = fhat1_L_threaded[Threads.threadid()] + fhat1_R = fhat1_R_threaded[Threads.threadid()] + fhat2_L = fhat2_L_threaded[Threads.threadid()] + fhat2_R = fhat2_R_threaded[Threads.threadid()] + calcflux_fhat!( + fhat1_L, fhat1_R, fhat2_L, fhat2_R, u, mesh, + nonconservative_terms, equations, volume_flux_dg, dg, element, + cache + ) + + # low-order FV fluxes + @unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded = cache + + fstar1_L = fstar1_L_threaded[Threads.threadid()] + fstar2_L = fstar2_L_threaded[Threads.threadid()] + fstar1_R = fstar1_R_threaded[Threads.threadid()] + fstar2_R = fstar2_R_threaded[Threads.threadid()] + calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, u, mesh, + nonconservative_terms, equations, volume_flux_fv, dg, element, + cache + ) + + # antidiffusive flux + calcflux_antidiffusive!( + fhat1_L, fhat1_R, fhat2_L, fhat2_R, + fstar1_L, fstar1_R, fstar2_L, fstar2_R, + u, mesh, nonconservative_terms, equations, limiter, dg, + element, cache + ) + + # Calculate volume integral contribution of low-order FV flux + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, j, element] += inverse_weights[i] * + (fstar1_L[v, i + 1, j] - fstar1_R[v, i, j]) + + inverse_weights[j] * + (fstar2_L[v, i, j + 1] - fstar2_R[v, i, j]) + end + end - for j in eachnode(dg), i in 1:(nnodes(dg) - 1), v in eachvariable(equations) - fhat1_L[v, i + 1, j] = fhat1_L[v, i, j] + weights[i] * flux_temp[v, i, j] - fhat1_R[v, i + 1, j] = fhat1_L[v, i + 1, j] + return nothing end - # Split form volume flux in orientation 2: y direction - flux_temp .= zero(eltype(flux_temp)) - - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - for jj in (j + 1):nnodes(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, element) - flux2 = volume_flux(u_node, u_node_jj, 2, equations) - multiply_add_to_node_vars!(flux_temp, derivative_split[j, jj], flux2, - equations, dg, i, j) - multiply_add_to_node_vars!(flux_temp, derivative_split[jj, j], flux2, - equations, dg, i, jj) + # Calculate the DG staggered volume fluxes `fhat` in subcell FV-form inside the element + # (**without non-conservative terms**). + # + # See also `flux_differencing_kernel!`. + @inline function calcflux_fhat!( + fhat1_L, fhat1_R, fhat2_L, fhat2_R, u, + mesh::TreeMesh{2}, nonconservative_terms::False, + equations, + volume_flux, dg::DGSEM, element, cache + ) + @unpack weights, derivative_split = dg.basis + @unpack flux_temp_threaded = cache + + flux_temp = flux_temp_threaded[Threads.threadid()] + + # The FV-form fluxes are calculated in a recursive manner, i.e.: + # fhat_(0,1) = w_0 * FVol_0, + # fhat_(j,j+1) = fhat_(j-1,j) + w_j * FVol_j, for j=1,...,N-1, + # with the split form volume fluxes FVol_j = -2 * sum_i=0^N D_ji f*_(j,i). + + # To use the symmetry of the `volume_flux`, the split form volume flux is precalculated + # like in `calc_volume_integral!` for the `VolumeIntegralFluxDifferencing` + # and saved in in `flux_temp`. + + # Split form volume flux in orientation 1: x direction + flux_temp .= zero(eltype(flux_temp)) + + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # All diagonal entries of `derivative_split` are zero. Thus, we can skip + # the computation of the diagonal terms. In addition, we use the symmetry + # of the `volume_flux` to save half of the possible two-point flux + # computations. + for ii in (i + 1):nnodes(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, element) + flux1 = volume_flux(u_node, u_node_ii, 1, equations) + multiply_add_to_node_vars!( + flux_temp, derivative_split[i, ii], flux1, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + flux_temp, derivative_split[ii, i], flux1, + equations, dg, ii, j + ) + end end - end - # FV-form flux `fhat` in y direction - fhat2_L[:, :, 1] .= zero(eltype(fhat2_L)) - fhat2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_L)) - fhat2_R[:, :, 1] .= zero(eltype(fhat2_R)) - fhat2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_R)) + # FV-form flux `fhat` in x direction + fhat1_L[:, 1, :] .= zero(eltype(fhat1_L)) + fhat1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_L)) + fhat1_R[:, 1, :] .= zero(eltype(fhat1_R)) + fhat1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_R)) - for j in 1:(nnodes(dg) - 1), i in eachnode(dg), v in eachvariable(equations) - fhat2_L[v, i, j + 1] = fhat2_L[v, i, j] + weights[j] * flux_temp[v, i, j] - fhat2_R[v, i, j + 1] = fhat2_L[v, i, j + 1] - end + for j in eachnode(dg), i in 1:(nnodes(dg) - 1), v in eachvariable(equations) + fhat1_L[v, i + 1, j] = fhat1_L[v, i, j] + weights[i] * flux_temp[v, i, j] + fhat1_R[v, i + 1, j] = fhat1_L[v, i + 1, j] + end - return nothing -end - -# Calculate the DG staggered volume fluxes `fhat` in subcell FV-form inside the element -# (**with non-conservative terms**). -# -# See also `flux_differencing_kernel!`. -# -# The calculation of the non-conservative staggered "fluxes" requires non-conservative -# terms that can be written as a product of local and a symmetric contributions. See, e.g., -# -# - Rueda-Ramírez, Gassner (2023). A Flux-Differencing Formula for Split-Form Summation By Parts -# Discretizations of Non-Conservative Systems. https://arxiv.org/pdf/2211.14009.pdf. -# -@inline function calcflux_fhat!(fhat1_L, fhat1_R, fhat2_L, fhat2_R, u, - mesh::TreeMesh{2}, nonconservative_terms::True, - equations, - volume_flux, dg::DGSEM, element, cache) - @unpack weights, derivative_split = dg.basis - @unpack flux_temp_threaded, flux_nonconservative_temp_threaded = cache - @unpack fhat_temp_threaded, fhat_nonconservative_temp_threaded, phi_threaded = cache - - volume_flux_cons, volume_flux_noncons = volume_flux - - flux_temp = flux_temp_threaded[Threads.threadid()] - flux_noncons_temp = flux_nonconservative_temp_threaded[Threads.threadid()] - - fhat_temp = fhat_temp_threaded[Threads.threadid()] - fhat_noncons_temp = fhat_nonconservative_temp_threaded[Threads.threadid()] - phi = phi_threaded[Threads.threadid()] - - # The FV-form fluxes are calculated in a recursive manner, i.e.: - # fhat_(0,1) = w_0 * FVol_0, - # fhat_(j,j+1) = fhat_(j-1,j) + w_j * FVol_j, for j=1,...,N-1, - # with the split form volume fluxes FVol_j = -2 * sum_i=0^N D_ji f*_(j,i). - - # To use the symmetry of the `volume_flux`, the split form volume flux is precalculated - # like in `calc_volume_integral!` for the `VolumeIntegralFluxDifferencing` - # and saved in in `flux_temp`. - - # Split form volume flux in orientation 1: x direction - flux_temp .= zero(eltype(flux_temp)) - flux_noncons_temp .= zero(eltype(flux_noncons_temp)) - - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - - # All diagonal entries of `derivative_split` are zero. Thus, we can skip - # the computation of the diagonal terms. In addition, we use the symmetry - # of `volume_flux_cons` and `volume_flux_noncons` to save half of the possible two-point flux - # computations. - for ii in (i + 1):nnodes(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, element) - flux1 = volume_flux_cons(u_node, u_node_ii, 1, equations) - multiply_add_to_node_vars!(flux_temp, derivative_split[i, ii], flux1, - equations, dg, i, j) - multiply_add_to_node_vars!(flux_temp, derivative_split[ii, i], flux1, - equations, dg, ii, j) - for noncons in 1:n_nonconservative_terms(equations) - # We multiply by 0.5 because that is done in other parts of Trixi - flux1_noncons = volume_flux_noncons(u_node, u_node_ii, 1, equations, - NonConservativeSymmetric(), noncons) - multiply_add_to_node_vars!(flux_noncons_temp, - 0.5f0 * derivative_split[i, ii], - flux1_noncons, - equations, dg, noncons, i, j) - multiply_add_to_node_vars!(flux_noncons_temp, - 0.5f0 * derivative_split[ii, i], - flux1_noncons, - equations, dg, noncons, ii, j) + # Split form volume flux in orientation 2: y direction + flux_temp .= zero(eltype(flux_temp)) + + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + for jj in (j + 1):nnodes(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, element) + flux2 = volume_flux(u_node, u_node_jj, 2, equations) + multiply_add_to_node_vars!( + flux_temp, derivative_split[j, jj], flux2, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + flux_temp, derivative_split[jj, j], flux2, + equations, dg, i, jj + ) end end - end - # FV-form flux `fhat` in x direction - fhat1_L[:, 1, :] .= zero(eltype(fhat1_L)) - fhat1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_L)) - fhat1_R[:, 1, :] .= zero(eltype(fhat1_R)) - fhat1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_R)) - - fhat_temp[:, 1, :] .= zero(eltype(fhat1_L)) - fhat_noncons_temp[:, :, 1, :] .= zero(eltype(fhat1_L)) - - # Compute local contribution to non-conservative flux - for j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, element) - for noncons in 1:n_nonconservative_terms(equations) - set_node_vars!(phi, - volume_flux_noncons(u_local, 1, equations, - NonConservativeLocal(), noncons), - equations, dg, noncons, i, j) + # FV-form flux `fhat` in y direction + fhat2_L[:, :, 1] .= zero(eltype(fhat2_L)) + fhat2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_L)) + fhat2_R[:, :, 1] .= zero(eltype(fhat2_R)) + fhat2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_R)) + + for j in 1:(nnodes(dg) - 1), i in eachnode(dg), v in eachvariable(equations) + fhat2_L[v, i, j + 1] = fhat2_L[v, i, j] + weights[j] * flux_temp[v, i, j] + fhat2_R[v, i, j + 1] = fhat2_L[v, i, j + 1] end + + return nothing end - for j in eachnode(dg), i in 1:(nnodes(dg) - 1) - # Conservative part - for v in eachvariable(equations) - value = fhat_temp[v, i, j] + weights[i] * flux_temp[v, i, j] - fhat_temp[v, i + 1, j] = value - fhat1_L[v, i + 1, j] = value - fhat1_R[v, i + 1, j] = value + # Calculate the DG staggered volume fluxes `fhat` in subcell FV-form inside the element + # (**with non-conservative terms**). + # + # See also `flux_differencing_kernel!`. + # + # The calculation of the non-conservative staggered "fluxes" requires non-conservative + # terms that can be written as a product of local and a symmetric contributions. See, e.g., + # + # - Rueda-Ramírez, Gassner (2023). A Flux-Differencing Formula for Split-Form Summation By Parts + # Discretizations of Non-Conservative Systems. https://arxiv.org/pdf/2211.14009.pdf. + # + @inline function calcflux_fhat!( + fhat1_L, fhat1_R, fhat2_L, fhat2_R, u, + mesh::TreeMesh{2}, nonconservative_terms::True, + equations, + volume_flux, dg::DGSEM, element, cache + ) + @unpack weights, derivative_split = dg.basis + @unpack flux_temp_threaded, flux_nonconservative_temp_threaded = cache + @unpack fhat_temp_threaded, fhat_nonconservative_temp_threaded, phi_threaded = cache + + volume_flux_cons, volume_flux_noncons = volume_flux + + flux_temp = flux_temp_threaded[Threads.threadid()] + flux_noncons_temp = flux_nonconservative_temp_threaded[Threads.threadid()] + + fhat_temp = fhat_temp_threaded[Threads.threadid()] + fhat_noncons_temp = fhat_nonconservative_temp_threaded[Threads.threadid()] + phi = phi_threaded[Threads.threadid()] + + # The FV-form fluxes are calculated in a recursive manner, i.e.: + # fhat_(0,1) = w_0 * FVol_0, + # fhat_(j,j+1) = fhat_(j-1,j) + w_j * FVol_j, for j=1,...,N-1, + # with the split form volume fluxes FVol_j = -2 * sum_i=0^N D_ji f*_(j,i). + + # To use the symmetry of the `volume_flux`, the split form volume flux is precalculated + # like in `calc_volume_integral!` for the `VolumeIntegralFluxDifferencing` + # and saved in in `flux_temp`. + + # Split form volume flux in orientation 1: x direction + flux_temp .= zero(eltype(flux_temp)) + flux_noncons_temp .= zero(eltype(flux_noncons_temp)) + + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + + # All diagonal entries of `derivative_split` are zero. Thus, we can skip + # the computation of the diagonal terms. In addition, we use the symmetry + # of `volume_flux_cons` and `volume_flux_noncons` to save half of the possible two-point flux + # computations. + for ii in (i + 1):nnodes(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, element) + flux1 = volume_flux_cons(u_node, u_node_ii, 1, equations) + multiply_add_to_node_vars!( + flux_temp, derivative_split[i, ii], flux1, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + flux_temp, derivative_split[ii, i], flux1, + equations, dg, ii, j + ) + for noncons in 1:n_nonconservative_terms(equations) + # We multiply by 0.5 because that is done in other parts of Trixi + flux1_noncons = volume_flux_noncons( + u_node, u_node_ii, 1, equations, + NonConservativeSymmetric(), noncons + ) + multiply_add_to_node_vars!( + flux_noncons_temp, + 0.5f0 * derivative_split[i, ii], + flux1_noncons, + equations, dg, noncons, i, j + ) + multiply_add_to_node_vars!( + flux_noncons_temp, + 0.5f0 * derivative_split[ii, i], + flux1_noncons, + equations, dg, noncons, ii, j + ) + end + end end - # Nonconservative part - for noncons in 1:n_nonconservative_terms(equations), - v in eachvariable(equations) - value = fhat_noncons_temp[v, noncons, i, j] + - weights[i] * flux_noncons_temp[v, noncons, i, j] - fhat_noncons_temp[v, noncons, i + 1, j] = value + # FV-form flux `fhat` in x direction + fhat1_L[:, 1, :] .= zero(eltype(fhat1_L)) + fhat1_L[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_L)) + fhat1_R[:, 1, :] .= zero(eltype(fhat1_R)) + fhat1_R[:, nnodes(dg) + 1, :] .= zero(eltype(fhat1_R)) - fhat1_L[v, i + 1, j] = fhat1_L[v, i + 1, j] + phi[v, noncons, i, j] * value - fhat1_R[v, i + 1, j] = fhat1_R[v, i + 1, j] + - phi[v, noncons, i + 1, j] * value - end - end + fhat_temp[:, 1, :] .= zero(eltype(fhat1_L)) + fhat_noncons_temp[:, :, 1, :] .= zero(eltype(fhat1_L)) - # Split form volume flux in orientation 2: y direction - flux_temp .= zero(eltype(flux_temp)) - flux_noncons_temp .= zero(eltype(flux_noncons_temp)) - - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - for jj in (j + 1):nnodes(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, element) - flux2 = volume_flux_cons(u_node, u_node_jj, 2, equations) - multiply_add_to_node_vars!(flux_temp, derivative_split[j, jj], flux2, - equations, dg, i, j) - multiply_add_to_node_vars!(flux_temp, derivative_split[jj, j], flux2, - equations, dg, i, jj) + # Compute local contribution to non-conservative flux + for j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, element) for noncons in 1:n_nonconservative_terms(equations) - # We multiply by 0.5 because that is done in other parts of Trixi - flux2_noncons = volume_flux_noncons(u_node, u_node_jj, 2, equations, - NonConservativeSymmetric(), noncons) - multiply_add_to_node_vars!(flux_noncons_temp, - 0.5 * derivative_split[j, jj], - flux2_noncons, - equations, dg, noncons, i, j) - multiply_add_to_node_vars!(flux_noncons_temp, - 0.5 * derivative_split[jj, j], - flux2_noncons, - equations, dg, noncons, i, jj) + set_node_vars!( + phi, + volume_flux_noncons( + u_local, 1, equations, + NonConservativeLocal(), noncons + ), + equations, dg, noncons, i, j + ) end end - end - # FV-form flux `fhat` in y direction - fhat2_L[:, :, 1] .= zero(eltype(fhat2_L)) - fhat2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_L)) - fhat2_R[:, :, 1] .= zero(eltype(fhat2_R)) - fhat2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_R)) - - fhat_temp[:, :, 1] .= zero(eltype(fhat1_L)) - fhat_noncons_temp[:, :, :, 1] .= zero(eltype(fhat1_L)) - - # Compute local contribution to non-conservative flux - for j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, element) - for noncons in 1:n_nonconservative_terms(equations) - set_node_vars!(phi, - volume_flux_noncons(u_local, 2, equations, - NonConservativeLocal(), noncons), - equations, dg, noncons, i, j) + for j in eachnode(dg), i in 1:(nnodes(dg) - 1) + # Conservative part + for v in eachvariable(equations) + value = fhat_temp[v, i, j] + weights[i] * flux_temp[v, i, j] + fhat_temp[v, i + 1, j] = value + fhat1_L[v, i + 1, j] = value + fhat1_R[v, i + 1, j] = value + end + # Nonconservative part + for noncons in 1:n_nonconservative_terms(equations), + v in eachvariable(equations) + + value = fhat_noncons_temp[v, noncons, i, j] + + weights[i] * flux_noncons_temp[v, noncons, i, j] + fhat_noncons_temp[v, noncons, i + 1, j] = value + + fhat1_L[v, i + 1, j] = fhat1_L[v, i + 1, j] + phi[v, noncons, i, j] * value + fhat1_R[v, i + 1, j] = fhat1_R[v, i + 1, j] + + phi[v, noncons, i + 1, j] * value + end end - end - for j in 1:(nnodes(dg) - 1), i in eachnode(dg) - # Conservative part - for v in eachvariable(equations) - value = fhat_temp[v, i, j] + weights[j] * flux_temp[v, i, j] - fhat_temp[v, i, j + 1] = value - fhat2_L[v, i, j + 1] = value - fhat2_R[v, i, j + 1] = value + # Split form volume flux in orientation 2: y direction + flux_temp .= zero(eltype(flux_temp)) + flux_noncons_temp .= zero(eltype(flux_noncons_temp)) + + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + for jj in (j + 1):nnodes(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, element) + flux2 = volume_flux_cons(u_node, u_node_jj, 2, equations) + multiply_add_to_node_vars!( + flux_temp, derivative_split[j, jj], flux2, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + flux_temp, derivative_split[jj, j], flux2, + equations, dg, i, jj + ) + for noncons in 1:n_nonconservative_terms(equations) + # We multiply by 0.5 because that is done in other parts of Trixi + flux2_noncons = volume_flux_noncons( + u_node, u_node_jj, 2, equations, + NonConservativeSymmetric(), noncons + ) + multiply_add_to_node_vars!( + flux_noncons_temp, + 0.5 * derivative_split[j, jj], + flux2_noncons, + equations, dg, noncons, i, j + ) + multiply_add_to_node_vars!( + flux_noncons_temp, + 0.5 * derivative_split[jj, j], + flux2_noncons, + equations, dg, noncons, i, jj + ) + end + end + end + + # FV-form flux `fhat` in y direction + fhat2_L[:, :, 1] .= zero(eltype(fhat2_L)) + fhat2_L[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_L)) + fhat2_R[:, :, 1] .= zero(eltype(fhat2_R)) + fhat2_R[:, :, nnodes(dg) + 1] .= zero(eltype(fhat2_R)) + + fhat_temp[:, :, 1] .= zero(eltype(fhat1_L)) + fhat_noncons_temp[:, :, :, 1] .= zero(eltype(fhat1_L)) + + # Compute local contribution to non-conservative flux + for j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, element) + for noncons in 1:n_nonconservative_terms(equations) + set_node_vars!( + phi, + volume_flux_noncons( + u_local, 2, equations, + NonConservativeLocal(), noncons + ), + equations, dg, noncons, i, j + ) + end end - # Nonconservative part - for noncons in 1:n_nonconservative_terms(equations), - v in eachvariable(equations) - value = fhat_noncons_temp[v, noncons, i, j] + + for j in 1:(nnodes(dg) - 1), i in eachnode(dg) + # Conservative part + for v in eachvariable(equations) + value = fhat_temp[v, i, j] + weights[j] * flux_temp[v, i, j] + fhat_temp[v, i, j + 1] = value + fhat2_L[v, i, j + 1] = value + fhat2_R[v, i, j + 1] = value + end + # Nonconservative part + for noncons in 1:n_nonconservative_terms(equations), + v in eachvariable(equations) + + value = fhat_noncons_temp[v, noncons, i, j] + weights[j] * flux_noncons_temp[v, noncons, i, j] - fhat_noncons_temp[v, noncons, i, j + 1] = value + fhat_noncons_temp[v, noncons, i, j + 1] = value - fhat2_L[v, i, j + 1] = fhat2_L[v, i, j + 1] + phi[v, noncons, i, j] * value - fhat2_R[v, i, j + 1] = fhat2_R[v, i, j + 1] + - phi[v, noncons, i, j + 1] * value + fhat2_L[v, i, j + 1] = fhat2_L[v, i, j + 1] + phi[v, noncons, i, j] * value + fhat2_R[v, i, j + 1] = fhat2_R[v, i, j + 1] + + phi[v, noncons, i, j + 1] * value + end end + + return nothing end - return nothing -end - -# Calculate the antidiffusive flux `antidiffusive_flux` as the subtraction between `fhat` and `fstar` for conservative systems. -@inline function calcflux_antidiffusive!(fhat1_L, fhat1_R, fhat2_L, fhat2_R, - fstar1_L, fstar1_R, fstar2_L, fstar2_R, - u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - P4estMesh{2}}, - nonconservative_terms::False, equations, - limiter::SubcellLimiterIDP, dg, element, cache) - @unpack antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R = cache.antidiffusive_fluxes - - for j in eachnode(dg), i in 2:nnodes(dg) - for v in eachvariable(equations) - antidiffusive_flux1_L[v, i, j, element] = fhat1_L[v, i, j] - - fstar1_L[v, i, j] - antidiffusive_flux1_R[v, i, j, element] = antidiffusive_flux1_L[v, i, j, - element] + # Calculate the antidiffusive flux `antidiffusive_flux` as the subtraction between `fhat` and `fstar` for conservative systems. + @inline function calcflux_antidiffusive!( + fhat1_L, fhat1_R, fhat2_L, fhat2_R, + fstar1_L, fstar1_R, fstar2_L, fstar2_R, + u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + P4estMesh{2}, + }, + nonconservative_terms::False, equations, + limiter::SubcellLimiterIDP, dg, element, cache + ) + @unpack antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R = cache.antidiffusive_fluxes + + for j in eachnode(dg), i in 2:nnodes(dg) + for v in eachvariable(equations) + antidiffusive_flux1_L[v, i, j, element] = fhat1_L[v, i, j] - + fstar1_L[v, i, j] + antidiffusive_flux1_R[v, i, j, element] = antidiffusive_flux1_L[ + v, i, j, + element, + ] + end end - end - for j in 2:nnodes(dg), i in eachnode(dg) - for v in eachvariable(equations) - antidiffusive_flux2_L[v, i, j, element] = fhat2_L[v, i, j] - - fstar2_L[v, i, j] - antidiffusive_flux2_R[v, i, j, element] = antidiffusive_flux2_L[v, i, j, - element] + for j in 2:nnodes(dg), i in eachnode(dg) + for v in eachvariable(equations) + antidiffusive_flux2_L[v, i, j, element] = fhat2_L[v, i, j] - + fstar2_L[v, i, j] + antidiffusive_flux2_R[v, i, j, element] = antidiffusive_flux2_L[ + v, i, j, + element, + ] + end end + + antidiffusive_flux1_L[:, 1, :, element] .= zero(eltype(antidiffusive_flux1_L)) + antidiffusive_flux1_L[:, nnodes(dg) + 1, :, element] .= zero(eltype(antidiffusive_flux1_L)) + antidiffusive_flux1_R[:, 1, :, element] .= zero(eltype(antidiffusive_flux1_R)) + antidiffusive_flux1_R[:, nnodes(dg) + 1, :, element] .= zero(eltype(antidiffusive_flux1_R)) + + antidiffusive_flux2_L[:, :, 1, element] .= zero(eltype(antidiffusive_flux2_L)) + antidiffusive_flux2_L[:, :, nnodes(dg) + 1, element] .= zero(eltype(antidiffusive_flux2_L)) + antidiffusive_flux2_R[:, :, 1, element] .= zero(eltype(antidiffusive_flux2_R)) + antidiffusive_flux2_R[:, :, nnodes(dg) + 1, element] .= zero(eltype(antidiffusive_flux2_R)) + + return nothing end - antidiffusive_flux1_L[:, 1, :, element] .= zero(eltype(antidiffusive_flux1_L)) - antidiffusive_flux1_L[:, nnodes(dg) + 1, :, element] .= zero(eltype(antidiffusive_flux1_L)) - antidiffusive_flux1_R[:, 1, :, element] .= zero(eltype(antidiffusive_flux1_R)) - antidiffusive_flux1_R[:, nnodes(dg) + 1, :, element] .= zero(eltype(antidiffusive_flux1_R)) - - antidiffusive_flux2_L[:, :, 1, element] .= zero(eltype(antidiffusive_flux2_L)) - antidiffusive_flux2_L[:, :, nnodes(dg) + 1, element] .= zero(eltype(antidiffusive_flux2_L)) - antidiffusive_flux2_R[:, :, 1, element] .= zero(eltype(antidiffusive_flux2_R)) - antidiffusive_flux2_R[:, :, nnodes(dg) + 1, element] .= zero(eltype(antidiffusive_flux2_R)) - - return nothing -end - -# Calculate the antidiffusive flux `antidiffusive_flux` as the subtraction between `fhat` and `fstar` for conservative systems. -@inline function calcflux_antidiffusive!(fhat1_L, fhat1_R, fhat2_L, fhat2_R, - fstar1_L, fstar1_R, fstar2_L, fstar2_R, - u, - mesh::Union{TreeMesh{2}, StructuredMesh{2}, - P4estMesh{2}}, - nonconservative_terms::True, equations, - limiter::SubcellLimiterIDP, dg, element, cache) - @unpack antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R = cache.antidiffusive_fluxes - - for j in eachnode(dg), i in 2:nnodes(dg) - for v in eachvariable(equations) - antidiffusive_flux1_L[v, i, j, element] = fhat1_L[v, i, j] - - fstar1_L[v, i, j] - antidiffusive_flux1_R[v, i, j, element] = fhat1_R[v, i, j] - - fstar1_R[v, i, j] + # Calculate the antidiffusive flux `antidiffusive_flux` as the subtraction between `fhat` and `fstar` for conservative systems. + @inline function calcflux_antidiffusive!( + fhat1_L, fhat1_R, fhat2_L, fhat2_R, + fstar1_L, fstar1_R, fstar2_L, fstar2_R, + u, + mesh::Union{ + TreeMesh{2}, StructuredMesh{2}, + P4estMesh{2}, + }, + nonconservative_terms::True, equations, + limiter::SubcellLimiterIDP, dg, element, cache + ) + @unpack antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R = cache.antidiffusive_fluxes + + for j in eachnode(dg), i in 2:nnodes(dg) + for v in eachvariable(equations) + antidiffusive_flux1_L[v, i, j, element] = fhat1_L[v, i, j] - + fstar1_L[v, i, j] + antidiffusive_flux1_R[v, i, j, element] = fhat1_R[v, i, j] - + fstar1_R[v, i, j] + end end - end - for j in 2:nnodes(dg), i in eachnode(dg) - for v in eachvariable(equations) - antidiffusive_flux2_L[v, i, j, element] = fhat2_L[v, i, j] - - fstar2_L[v, i, j] - antidiffusive_flux2_R[v, i, j, element] = fhat2_R[v, i, j] - - fstar2_R[v, i, j] + for j in 2:nnodes(dg), i in eachnode(dg) + for v in eachvariable(equations) + antidiffusive_flux2_L[v, i, j, element] = fhat2_L[v, i, j] - + fstar2_L[v, i, j] + antidiffusive_flux2_R[v, i, j, element] = fhat2_R[v, i, j] - + fstar2_R[v, i, j] + end end + + antidiffusive_flux1_L[:, 1, :, element] .= zero(eltype(antidiffusive_flux1_L)) + antidiffusive_flux1_L[:, nnodes(dg) + 1, :, element] .= zero(eltype(antidiffusive_flux1_L)) + antidiffusive_flux1_R[:, 1, :, element] .= zero(eltype(antidiffusive_flux1_R)) + antidiffusive_flux1_R[:, nnodes(dg) + 1, :, element] .= zero(eltype(antidiffusive_flux1_R)) + + antidiffusive_flux2_L[:, :, 1, element] .= zero(eltype(antidiffusive_flux2_L)) + antidiffusive_flux2_L[:, :, nnodes(dg) + 1, element] .= zero(eltype(antidiffusive_flux2_L)) + antidiffusive_flux2_R[:, :, 1, element] .= zero(eltype(antidiffusive_flux2_R)) + antidiffusive_flux2_R[:, :, nnodes(dg) + 1, element] .= zero(eltype(antidiffusive_flux2_R)) + + return nothing end - antidiffusive_flux1_L[:, 1, :, element] .= zero(eltype(antidiffusive_flux1_L)) - antidiffusive_flux1_L[:, nnodes(dg) + 1, :, element] .= zero(eltype(antidiffusive_flux1_L)) - antidiffusive_flux1_R[:, 1, :, element] .= zero(eltype(antidiffusive_flux1_R)) - antidiffusive_flux1_R[:, nnodes(dg) + 1, :, element] .= zero(eltype(antidiffusive_flux1_R)) - - antidiffusive_flux2_L[:, :, 1, element] .= zero(eltype(antidiffusive_flux2_L)) - antidiffusive_flux2_L[:, :, nnodes(dg) + 1, element] .= zero(eltype(antidiffusive_flux2_L)) - antidiffusive_flux2_R[:, :, 1, element] .= zero(eltype(antidiffusive_flux2_R)) - antidiffusive_flux2_R[:, :, nnodes(dg) + 1, element] .= zero(eltype(antidiffusive_flux2_R)) - - return nothing -end - -""" - get_boundary_outer_state(u_inner, t, - boundary_condition::BoundaryConditionDirichlet, - orientation_or_normal, direction, - equations, dg, cache, indices...) -For subcell limiting, the calculation of local bounds for non-periodic domains requires the boundary -outer state. This function returns the boundary value for [`BoundaryConditionDirichlet`](@ref) at -time `t` and for node with spatial indices `indices` at the boundary with `orientation_or_normal` -and `direction`. - -Should be used together with [`TreeMesh`](@ref) or [`StructuredMesh`](@ref). - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -@inline function get_boundary_outer_state(u_inner, t, - boundary_condition::BoundaryConditionDirichlet, - orientation_or_normal, direction, - equations, dg, cache, indices...) - (; node_coordinates) = cache.elements - - x = get_node_coords(node_coordinates, equations, dg, indices...) - u_outer = boundary_condition.boundary_value_function(x, t, equations) - - return u_outer -end + """ + get_boundary_outer_state(u_inner, t, + boundary_condition::BoundaryConditionDirichlet, + orientation_or_normal, direction, + equations, dg, cache, indices...) + For subcell limiting, the calculation of local bounds for non-periodic domains requires the boundary + outer state. This function returns the boundary value for [`BoundaryConditionDirichlet`](@ref) at + time `t` and for node with spatial indices `indices` at the boundary with `orientation_or_normal` + and `direction`. + + Should be used together with [`TreeMesh`](@ref) or [`StructuredMesh`](@ref). + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + @inline function get_boundary_outer_state( + u_inner, t, + boundary_condition::BoundaryConditionDirichlet, + orientation_or_normal, direction, + equations, dg, cache, indices... + ) + (; node_coordinates) = cache.elements + + x = get_node_coords(node_coordinates, equations, dg, indices...) + u_outer = boundary_condition.boundary_value_function(x, t, equations) + + return u_outer + end end # @muladd diff --git a/src/solvers/dgsem_tree/dg_3d.jl b/src/solvers/dgsem_tree/dg_3d.jl index acd90cee09..b926450a95 100644 --- a/src/solvers/dgsem_tree/dg_3d.jl +++ b/src/solvers/dgsem_tree/dg_3d.jl @@ -3,1405 +3,1827 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# everything related to a DG semidiscretization in 3D, -# currently limited to Lobatto-Legendre nodes - -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::TreeMesh{3}, equations, - dg::DG, RealT, uEltype) - # Get cells for which an element needs to be created (i.e. all leaf cells) - leaf_cell_ids = local_leaf_cells(mesh.tree) - - elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype) - - interfaces = init_interfaces(leaf_cell_ids, mesh, elements) - - boundaries = init_boundaries(leaf_cell_ids, mesh, elements) - - mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) - - cache = (; elements, interfaces, boundaries, mortars) - - # Add specialized parts of the cache required to compute the volume integral etc. - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) - - return cache -end - -# The methods below are specialized on the volume integral type -# and called from the basic `create_cache` method at the top. -function create_cache(mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - equations, volume_integral::VolumeIntegralFluxDifferencing, - dg::DG, uEltype) - NamedTuple() -end - -function create_cache(mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - equations, - volume_integral::VolumeIntegralShockCapturingHG, dg::DG, uEltype) - element_ids_dg = Int[] - element_ids_dgfv = Int[] - - cache = create_cache(mesh, equations, - VolumeIntegralFluxDifferencing(volume_integral.volume_flux_dg), - dg, uEltype) - - A4dp1_x = Array{uEltype, 4} - A4dp1_y = Array{uEltype, 4} - A4dp1_z = Array{uEltype, 4} - fstar1_L_threaded = A4dp1_x[A4dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg), nnodes(dg)) - for _ in 1:Threads.nthreads()] - fstar1_R_threaded = A4dp1_x[A4dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg), nnodes(dg)) - for _ in 1:Threads.nthreads()] - fstar2_L_threaded = A4dp1_y[A4dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1, nnodes(dg)) - for _ in 1:Threads.nthreads()] - fstar2_R_threaded = A4dp1_y[A4dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1, nnodes(dg)) - for _ in 1:Threads.nthreads()] - fstar3_L_threaded = A4dp1_z[A4dp1_z(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg) + 1) - for _ in 1:Threads.nthreads()] - fstar3_R_threaded = A4dp1_z[A4dp1_z(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg) + 1) - for _ in 1:Threads.nthreads()] - - return (; cache..., element_ids_dg, element_ids_dgfv, fstar1_L_threaded, - fstar1_R_threaded, - fstar2_L_threaded, fstar2_R_threaded, fstar3_L_threaded, fstar3_R_threaded) -end - -function create_cache(mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, equations, - volume_integral::VolumeIntegralPureLGLFiniteVolume, dg::DG, - uEltype) - A4dp1_x = Array{uEltype, 4} - A4dp1_y = Array{uEltype, 4} - A4dp1_z = Array{uEltype, 4} - fstar1_L_threaded = A4dp1_x[A4dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg), nnodes(dg)) - for _ in 1:Threads.nthreads()] - fstar1_R_threaded = A4dp1_x[A4dp1_x(undef, nvariables(equations), nnodes(dg) + 1, - nnodes(dg), nnodes(dg)) - for _ in 1:Threads.nthreads()] - fstar2_L_threaded = A4dp1_y[A4dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1, nnodes(dg)) - for _ in 1:Threads.nthreads()] - fstar2_R_threaded = A4dp1_y[A4dp1_y(undef, nvariables(equations), nnodes(dg), - nnodes(dg) + 1, nnodes(dg)) - for _ in 1:Threads.nthreads()] - fstar3_L_threaded = A4dp1_z[A4dp1_z(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg) + 1) - for _ in 1:Threads.nthreads()] - fstar3_R_threaded = A4dp1_z[A4dp1_z(undef, nvariables(equations), nnodes(dg), - nnodes(dg), nnodes(dg) + 1) - for _ in 1:Threads.nthreads()] - - return (; fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, - fstar2_R_threaded, - fstar3_L_threaded, fstar3_R_threaded) -end - -# The methods below are specialized on the mortar type -# and called from the basic `create_cache` method at the top. -function create_cache(mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - equations, mortar_l2::LobattoLegendreMortarL2, uEltype) - # TODO: Taal compare performance of different types - A3d = Array{uEltype, 3} - fstar_upper_left_threaded = A3d[A3d(undef, nvariables(equations), nnodes(mortar_l2), - nnodes(mortar_l2)) - for _ in 1:Threads.nthreads()] - fstar_upper_right_threaded = A3d[A3d(undef, nvariables(equations), - nnodes(mortar_l2), nnodes(mortar_l2)) - for _ in 1:Threads.nthreads()] - fstar_lower_left_threaded = A3d[A3d(undef, nvariables(equations), nnodes(mortar_l2), - nnodes(mortar_l2)) - for _ in 1:Threads.nthreads()] - fstar_lower_right_threaded = A3d[A3d(undef, nvariables(equations), - nnodes(mortar_l2), nnodes(mortar_l2)) - for _ in 1:Threads.nthreads()] - fstar_tmp1_threaded = A3d[A3d(undef, nvariables(equations), nnodes(mortar_l2), - nnodes(mortar_l2)) - for _ in 1:Threads.nthreads()] - - (; fstar_upper_left_threaded, fstar_upper_right_threaded, - fstar_lower_left_threaded, fstar_lower_right_threaded, - fstar_tmp1_threaded) -end - -# TODO: Taal discuss/refactor timer, allowing users to pass a custom timer? - -function rhs!(du, u, t, - mesh::Union{TreeMesh{3}, P4estMesh{3}, T8codeMesh{3}}, equations, - initial_condition, boundary_conditions, source_terms::Source, - dg::DG, cache) where {Source} - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) - end + #! format: noindent - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache, u, mesh, equations, - dg.surface_integral, dg) - end + # everything related to a DG semidiscretization in 3D, + # currently limited to Lobatto-Legendre nodes - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg, cache) - end + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::TreeMesh{3}, equations, + dg::DG, RealT, uEltype + ) + # Get cells for which an element needs to be created (i.e. all leaf cells) + leaf_cell_ids = local_leaf_cells(mesh.tree) + + elements = init_elements(leaf_cell_ids, mesh, equations, dg.basis, RealT, uEltype) + + interfaces = init_interfaces(leaf_cell_ids, mesh, elements) - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache, u, mesh, equations, - dg.surface_integral, dg) + boundaries = init_boundaries(leaf_cell_ids, mesh, elements) + + mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) + + cache = (; elements, interfaces, boundaries, mortars) + + # Add specialized parts of the cache required to compute the volume integral etc. + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + cache = (; cache..., create_cache(mesh, equations, dg.mortar, uEltype)...) + + return cache end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations, - dg.surface_integral, dg) + # The methods below are specialized on the volume integral type + # and called from the basic `create_cache` method at the top. + function create_cache( + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + equations, volume_integral::VolumeIntegralFluxDifferencing, + dg::DG, uEltype + ) + NamedTuple() end - # Prolong solution to mortars - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u, mesh, equations, - dg.mortar, dg.surface_integral, dg) + function create_cache( + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + equations, + volume_integral::VolumeIntegralShockCapturingHG, dg::DG, uEltype + ) + element_ids_dg = Int[] + element_ids_dgfv = Int[] + + cache = create_cache( + mesh, equations, + VolumeIntegralFluxDifferencing(volume_integral.volume_flux_dg), + dg, uEltype + ) + + A4dp1_x = Array{uEltype, 4} + A4dp1_y = Array{uEltype, 4} + A4dp1_z = Array{uEltype, 4} + fstar1_L_threaded = A4dp1_x[ + A4dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg), nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + fstar1_R_threaded = A4dp1_x[ + A4dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg), nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + fstar2_L_threaded = A4dp1_y[ + A4dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1, nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + fstar2_R_threaded = A4dp1_y[ + A4dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1, nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + fstar3_L_threaded = A4dp1_z[ + A4dp1_z( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + 1 + ) + for _ in 1:Threads.nthreads() + ] + fstar3_R_threaded = A4dp1_z[ + A4dp1_z( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + 1 + ) + for _ in 1:Threads.nthreads() + ] + + return (; + cache..., element_ids_dg, element_ids_dgfv, fstar1_L_threaded, + fstar1_R_threaded, + fstar2_L_threaded, fstar2_R_threaded, fstar3_L_threaded, fstar3_R_threaded, + ) end - # Calculate mortar fluxes - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.mortar, dg.surface_integral, dg, cache) + function create_cache( + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, equations, + volume_integral::VolumeIntegralPureLGLFiniteVolume, dg::DG, + uEltype + ) + A4dp1_x = Array{uEltype, 4} + A4dp1_y = Array{uEltype, 4} + A4dp1_z = Array{uEltype, 4} + fstar1_L_threaded = A4dp1_x[ + A4dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg), nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + fstar1_R_threaded = A4dp1_x[ + A4dp1_x( + undef, nvariables(equations), nnodes(dg) + 1, + nnodes(dg), nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + fstar2_L_threaded = A4dp1_y[ + A4dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1, nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + fstar2_R_threaded = A4dp1_y[ + A4dp1_y( + undef, nvariables(equations), nnodes(dg), + nnodes(dg) + 1, nnodes(dg) + ) + for _ in 1:Threads.nthreads() + ] + fstar3_L_threaded = A4dp1_z[ + A4dp1_z( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + 1 + ) + for _ in 1:Threads.nthreads() + ] + fstar3_R_threaded = A4dp1_z[ + A4dp1_z( + undef, nvariables(equations), nnodes(dg), + nnodes(dg), nnodes(dg) + 1 + ) + for _ in 1:Threads.nthreads() + ] + + return (; + fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, + fstar2_R_threaded, + fstar3_L_threaded, fstar3_R_threaded, + ) end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) + # The methods below are specialized on the mortar type + # and called from the basic `create_cache` method at the top. + function create_cache( + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + equations, mortar_l2::LobattoLegendreMortarL2, uEltype + ) + # TODO: Taal compare performance of different types + A3d = Array{uEltype, 3} + fstar_upper_left_threaded = A3d[ + A3d( + undef, nvariables(equations), nnodes(mortar_l2), + nnodes(mortar_l2) + ) + for _ in 1:Threads.nthreads() + ] + fstar_upper_right_threaded = A3d[ + A3d( + undef, nvariables(equations), + nnodes(mortar_l2), nnodes(mortar_l2) + ) + for _ in 1:Threads.nthreads() + ] + fstar_lower_left_threaded = A3d[ + A3d( + undef, nvariables(equations), nnodes(mortar_l2), + nnodes(mortar_l2) + ) + for _ in 1:Threads.nthreads() + ] + fstar_lower_right_threaded = A3d[ + A3d( + undef, nvariables(equations), + nnodes(mortar_l2), nnodes(mortar_l2) + ) + for _ in 1:Threads.nthreads() + ] + fstar_tmp1_threaded = A3d[ + A3d( + undef, nvariables(equations), nnodes(mortar_l2), + nnodes(mortar_l2) + ) + for _ in 1:Threads.nthreads() + ] + + (; + fstar_upper_left_threaded, fstar_upper_right_threaded, + fstar_lower_left_threaded, fstar_lower_right_threaded, + fstar_tmp1_threaded, + ) end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + # TODO: Taal discuss/refactor timer, allowing users to pass a custom timer? + + function rhs!( + du, u, t, + mesh::Union{TreeMesh{3}, P4estMesh{3}, T8codeMesh{3}}, equations, + initial_condition, boundary_conditions, source_terms::Source, + dg::DG, cache + ) where {Source} + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - # Calculate source terms - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, equations, dg, cache) - end + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end + + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg, cache + ) + end + + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end + + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, t, boundary_conditions, mesh, equations, + dg.surface_integral, dg + ) + end + + # Prolong solution to mortars + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!( + cache, u, mesh, equations, + dg.mortar, dg.surface_integral, dg + ) + end + + # Calculate mortar fluxes + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.mortar, dg.surface_integral, dg, cache + ) + end - return nothing -end - -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralWeakForm, - dg::DGSEM, cache) - @threaded for element in eachelement(dg, cache) - weak_form_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - dg, cache) + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end + + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + + # Calculate source terms + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, equations, dg, cache) + end + + return nothing end - return nothing -end + function calc_volume_integral!( + du, u, + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms, equations, + volume_integral::VolumeIntegralWeakForm, + dg::DGSEM, cache + ) + @threaded for element in eachelement(dg, cache) + weak_form_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + dg, cache + ) + end -#= + return nothing + end + + #= `weak_form_kernel!` is only implemented for conserved terms as non-conservative terms should always be discretized in conjunction with a flux-splitting scheme, see `flux_differencing_kernel!`. This treatment is required to achieve, e.g., entropy-stability or well-balancedness. See also https://github.com/trixi-framework/Trixi.jl/issues/1671#issuecomment-1765644064 =# -@inline function weak_form_kernel!(du, u, - element, mesh::TreeMesh{3}, - nonconservative_terms::False, equations, - dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_dhat = dg.basis - - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - - flux1 = flux(u_node, 1, equations) - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[ii, i], flux1, - equations, dg, ii, j, k, element) - end + @inline function weak_form_kernel!( + du, u, + element, mesh::TreeMesh{3}, + nonconservative_terms::False, equations, + dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_dhat = dg.basis - flux2 = flux(u_node, 2, equations) - for jj in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[jj, j], flux2, - equations, dg, i, jj, k, element) - end + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + + flux1 = flux(u_node, 1, equations) + for ii in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[ii, i], flux1, + equations, dg, ii, j, k, element + ) + end + + flux2 = flux(u_node, 2, equations) + for jj in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[jj, j], flux2, + equations, dg, i, jj, k, element + ) + end - flux3 = flux(u_node, 3, equations) - for kk in eachnode(dg) - multiply_add_to_node_vars!(du, alpha * derivative_dhat[kk, k], flux3, - equations, dg, i, j, kk, element) + flux3 = flux(u_node, 3, equations) + for kk in eachnode(dg) + multiply_add_to_node_vars!( + du, alpha * derivative_dhat[kk, k], flux3, + equations, dg, i, j, kk, element + ) + end end - end - return nothing -end - -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralFluxDifferencing, - dg::DGSEM, cache) - @threaded for element in eachelement(dg, cache) - flux_differencing_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - volume_integral.volume_flux, dg, cache) + return nothing end -end - -@inline function flux_differencing_kernel!(du, u, - element, mesh::TreeMesh{3}, - nonconservative_terms::False, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_split = dg.basis - - # Calculate volume integral in one element - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - - # All diagonal entries of `derivative_split` are zero. Thus, we can skip - # the computation of the diagonal terms. In addition, we use the symmetry - # of the `volume_flux` to save half of the possible two-point flux - # computations. - - # x direction - for ii in (i + 1):nnodes(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, k, element) - flux1 = volume_flux(u_node, u_node_ii, 1, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[i, ii], flux1, - equations, dg, i, j, k, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[ii, i], flux1, - equations, dg, ii, j, k, element) - end - # y direction - for jj in (j + 1):nnodes(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, k, element) - flux2 = volume_flux(u_node, u_node_jj, 2, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[j, jj], flux2, - equations, dg, i, j, k, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[jj, j], flux2, - equations, dg, i, jj, k, element) + function calc_volume_integral!( + du, u, + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms, equations, + volume_integral::VolumeIntegralFluxDifferencing, + dg::DGSEM, cache + ) + @threaded for element in eachelement(dg, cache) + flux_differencing_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + volume_integral.volume_flux, dg, cache + ) end + end - # z direction - for kk in (k + 1):nnodes(dg) - u_node_kk = get_node_vars(u, equations, dg, i, j, kk, element) - flux3 = volume_flux(u_node, u_node_kk, 3, equations) - multiply_add_to_node_vars!(du, alpha * derivative_split[k, kk], flux3, - equations, dg, i, j, k, element) - multiply_add_to_node_vars!(du, alpha * derivative_split[kk, k], flux3, - equations, dg, i, j, kk, element) + @inline function flux_differencing_kernel!( + du, u, + element, mesh::TreeMesh{3}, + nonconservative_terms::False, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_split = dg.basis + + # Calculate volume integral in one element + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + + # All diagonal entries of `derivative_split` are zero. Thus, we can skip + # the computation of the diagonal terms. In addition, we use the symmetry + # of the `volume_flux` to save half of the possible two-point flux + # computations. + + # x direction + for ii in (i + 1):nnodes(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, k, element) + flux1 = volume_flux(u_node, u_node_ii, 1, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[i, ii], flux1, + equations, dg, i, j, k, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[ii, i], flux1, + equations, dg, ii, j, k, element + ) + end + + # y direction + for jj in (j + 1):nnodes(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, k, element) + flux2 = volume_flux(u_node, u_node_jj, 2, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[j, jj], flux2, + equations, dg, i, j, k, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[jj, j], flux2, + equations, dg, i, jj, k, element + ) + end + + # z direction + for kk in (k + 1):nnodes(dg) + u_node_kk = get_node_vars(u, equations, dg, i, j, kk, element) + flux3 = volume_flux(u_node, u_node_kk, 3, equations) + multiply_add_to_node_vars!( + du, alpha * derivative_split[k, kk], flux3, + equations, dg, i, j, k, element + ) + multiply_add_to_node_vars!( + du, alpha * derivative_split[kk, k], flux3, + equations, dg, i, j, kk, element + ) + end end end -end - -@inline function flux_differencing_kernel!(du, u, - element, mesh::TreeMesh{3}, - nonconservative_terms::True, equations, - volume_flux, dg::DGSEM, cache, alpha = true) - # true * [some floating point value] == [exactly the same floating point value] - # This can (hopefully) be optimized away due to constant propagation. - @unpack derivative_split = dg.basis - symmetric_flux, nonconservative_flux = volume_flux - - # Apply the symmetric flux as usual - flux_differencing_kernel!(du, u, element, mesh, False(), equations, symmetric_flux, - dg, cache, alpha) - - # Calculate the remaining volume terms using the nonsymmetric generalized flux - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, k, element) - - # The diagonal terms are zero since the diagonal of `derivative_split` - # is zero. We ignore this for now. - - # x direction - integral_contribution = zero(u_node) - for ii in eachnode(dg) - u_node_ii = get_node_vars(u, equations, dg, ii, j, k, element) - noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations) - integral_contribution = integral_contribution + - derivative_split[i, ii] * noncons_flux1 + + @inline function flux_differencing_kernel!( + du, u, + element, mesh::TreeMesh{3}, + nonconservative_terms::True, equations, + volume_flux, dg::DGSEM, cache, alpha = true + ) + # true * [some floating point value] == [exactly the same floating point value] + # This can (hopefully) be optimized away due to constant propagation. + @unpack derivative_split = dg.basis + symmetric_flux, nonconservative_flux = volume_flux + + # Apply the symmetric flux as usual + flux_differencing_kernel!( + du, u, element, mesh, False(), equations, symmetric_flux, + dg, cache, alpha + ) + + # Calculate the remaining volume terms using the nonsymmetric generalized flux + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, k, element) + + # The diagonal terms are zero since the diagonal of `derivative_split` + # is zero. We ignore this for now. + + # x direction + integral_contribution = zero(u_node) + for ii in eachnode(dg) + u_node_ii = get_node_vars(u, equations, dg, ii, j, k, element) + noncons_flux1 = nonconservative_flux(u_node, u_node_ii, 1, equations) + integral_contribution = integral_contribution + + derivative_split[i, ii] * noncons_flux1 + end + + # y direction + for jj in eachnode(dg) + u_node_jj = get_node_vars(u, equations, dg, i, jj, k, element) + noncons_flux2 = nonconservative_flux(u_node, u_node_jj, 2, equations) + integral_contribution = integral_contribution + + derivative_split[j, jj] * noncons_flux2 + end + + # z direction + for kk in eachnode(dg) + u_node_kk = get_node_vars(u, equations, dg, i, j, kk, element) + noncons_flux3 = nonconservative_flux(u_node, u_node_kk, 3, equations) + integral_contribution = integral_contribution + + derivative_split[k, kk] * noncons_flux3 + end + + # The factor 0.5 cancels the factor 2 in the flux differencing form + multiply_add_to_node_vars!( + du, alpha * 0.5f0, integral_contribution, equations, + dg, i, j, k, element + ) end + end - # y direction - for jj in eachnode(dg) - u_node_jj = get_node_vars(u, equations, dg, i, jj, k, element) - noncons_flux2 = nonconservative_flux(u_node, u_node_jj, 2, equations) - integral_contribution = integral_contribution + - derivative_split[j, jj] * noncons_flux2 + # TODO: Taal dimension agnostic + function calc_volume_integral!( + du, u, + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms, equations, + volume_integral::VolumeIntegralShockCapturingHG, + dg::DGSEM, cache + ) + @unpack element_ids_dg, element_ids_dgfv = cache + @unpack volume_flux_dg, volume_flux_fv, indicator = volume_integral + + # Calculate blending factors α: u = u_DG * (1 - α) + u_FV * α + alpha = @trixi_timeit timer() "blending factors" indicator( + u, mesh, equations, dg, + cache + ) + + # Determine element ids for DG-only and blended DG-FV volume integral + pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, dg, cache) + + # Loop over pure DG elements + @trixi_timeit timer() "pure DG" @threaded for idx_element in eachindex(element_ids_dg) + element = element_ids_dg[idx_element] + flux_differencing_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + volume_flux_dg, dg, cache + ) end - # z direction - for kk in eachnode(dg) - u_node_kk = get_node_vars(u, equations, dg, i, j, kk, element) - noncons_flux3 = nonconservative_flux(u_node, u_node_kk, 3, equations) - integral_contribution = integral_contribution + - derivative_split[k, kk] * noncons_flux3 + # Loop over blended DG-FV elements + @trixi_timeit timer() "blended DG-FV" @threaded for idx_element in eachindex(element_ids_dgfv) + element = element_ids_dgfv[idx_element] + alpha_element = alpha[element] + + # Calculate DG volume integral contribution + flux_differencing_kernel!( + du, u, element, mesh, + nonconservative_terms, equations, + volume_flux_dg, dg, cache, 1 - alpha_element + ) + + # Calculate FV volume integral contribution + fv_kernel!( + du, u, mesh, nonconservative_terms, equations, volume_flux_fv, + dg, cache, element, alpha_element + ) end - # The factor 0.5 cancels the factor 2 in the flux differencing form - multiply_add_to_node_vars!(du, alpha * 0.5f0, integral_contribution, equations, - dg, i, j, k, element) - end -end - -# TODO: Taal dimension agnostic -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralShockCapturingHG, - dg::DGSEM, cache) - @unpack element_ids_dg, element_ids_dgfv = cache - @unpack volume_flux_dg, volume_flux_fv, indicator = volume_integral - - # Calculate blending factors α: u = u_DG * (1 - α) + u_FV * α - alpha = @trixi_timeit timer() "blending factors" indicator(u, mesh, equations, dg, - cache) - - # Determine element ids for DG-only and blended DG-FV volume integral - pure_and_blended_element_ids!(element_ids_dg, element_ids_dgfv, alpha, dg, cache) - - # Loop over pure DG elements - @trixi_timeit timer() "pure DG" @threaded for idx_element in eachindex(element_ids_dg) - element = element_ids_dg[idx_element] - flux_differencing_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - volume_flux_dg, dg, cache) + return nothing end - # Loop over blended DG-FV elements - @trixi_timeit timer() "blended DG-FV" @threaded for idx_element in eachindex(element_ids_dgfv) - element = element_ids_dgfv[idx_element] - alpha_element = alpha[element] + # TODO: Taal dimension agnostic + function calc_volume_integral!( + du, u, + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms, equations, + volume_integral::VolumeIntegralPureLGLFiniteVolume, + dg::DGSEM, cache + ) + @unpack volume_flux_fv = volume_integral + + # Calculate LGL FV volume integral + @threaded for element in eachelement(dg, cache) + fv_kernel!( + du, u, mesh, nonconservative_terms, equations, volume_flux_fv, + dg, cache, element, true + ) + end + + return nothing + end - # Calculate DG volume integral contribution - flux_differencing_kernel!(du, u, element, mesh, - nonconservative_terms, equations, - volume_flux_dg, dg, cache, 1 - alpha_element) + @inline function fv_kernel!( + du, u, + mesh::Union{ + TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, + T8codeMesh{3}, + }, + nonconservative_terms, equations, + volume_flux_fv, dg::DGSEM, cache, element, alpha = true + ) + @unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded, fstar3_L_threaded, fstar3_R_threaded = cache + @unpack inverse_weights = dg.basis + + # Calculate FV two-point fluxes + fstar1_L = fstar1_L_threaded[Threads.threadid()] + fstar2_L = fstar2_L_threaded[Threads.threadid()] + fstar3_L = fstar3_L_threaded[Threads.threadid()] + fstar1_R = fstar1_R_threaded[Threads.threadid()] + fstar2_R = fstar2_R_threaded[Threads.threadid()] + fstar3_R = fstar3_R_threaded[Threads.threadid()] + + calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, fstar3_R, u, + mesh, nonconservative_terms, equations, volume_flux_fv, dg, element, + cache + ) # Calculate FV volume integral contribution - fv_kernel!(du, u, mesh, nonconservative_terms, equations, volume_flux_fv, - dg, cache, element, alpha_element) - end + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, j, k, element] += ( + alpha * + ( + inverse_weights[i] * + ( + fstar1_L[v, i + 1, j, k] - + fstar1_R[v, i, j, k] + ) + + inverse_weights[j] * + ( + fstar2_L[v, i, j + 1, k] - + fstar2_R[v, i, j, k] + ) + + inverse_weights[k] * + ( + fstar3_L[v, i, j, k + 1] - + fstar3_R[v, i, j, k] + ) + ) + ) + end + end - return nothing -end - -# TODO: Taal dimension agnostic -function calc_volume_integral!(du, u, - mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms, equations, - volume_integral::VolumeIntegralPureLGLFiniteVolume, - dg::DGSEM, cache) - @unpack volume_flux_fv = volume_integral - - # Calculate LGL FV volume integral - @threaded for element in eachelement(dg, cache) - fv_kernel!(du, u, mesh, nonconservative_terms, equations, volume_flux_fv, - dg, cache, element, true) + return nothing end - return nothing -end - -@inline function fv_kernel!(du, u, - mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}, - T8codeMesh{3}}, - nonconservative_terms, equations, - volume_flux_fv, dg::DGSEM, cache, element, alpha = true) - @unpack fstar1_L_threaded, fstar1_R_threaded, fstar2_L_threaded, fstar2_R_threaded, fstar3_L_threaded, fstar3_R_threaded = cache - @unpack inverse_weights = dg.basis - - # Calculate FV two-point fluxes - fstar1_L = fstar1_L_threaded[Threads.threadid()] - fstar2_L = fstar2_L_threaded[Threads.threadid()] - fstar3_L = fstar3_L_threaded[Threads.threadid()] - fstar1_R = fstar1_R_threaded[Threads.threadid()] - fstar2_R = fstar2_R_threaded[Threads.threadid()] - fstar3_R = fstar3_R_threaded[Threads.threadid()] - - calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, fstar3_R, u, - mesh, nonconservative_terms, equations, volume_flux_fv, dg, element, - cache) - - # Calculate FV volume integral contribution - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - du[v, i, j, k, element] += (alpha * - (inverse_weights[i] * - (fstar1_L[v, i + 1, j, k] - - fstar1_R[v, i, j, k]) + - inverse_weights[j] * - (fstar2_L[v, i, j + 1, k] - - fstar2_R[v, i, j, k]) + - inverse_weights[k] * - (fstar3_L[v, i, j, k + 1] - - fstar3_R[v, i, j, k]))) + # Calculate the finite volume fluxes inside the elements (**without non-conservative terms**). + @inline function calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, + fstar3_R, u, + mesh::TreeMesh{3}, nonconservative_terms::False, + equations, + volume_flux_fv, dg::DGSEM, element, cache + ) + fstar1_L[:, 1, :, :] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1, :, :] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_R)) + + for k in eachnode(dg), j in eachnode(dg), i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, j, k, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction + set_node_vars!(fstar1_L, flux, equations, dg, i, j, k) + set_node_vars!(fstar1_R, flux, equations, dg, i, j, k) end - end - return nothing -end - -# Calculate the finite volume fluxes inside the elements (**without non-conservative terms**). -@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, - fstar3_R, u, - mesh::TreeMesh{3}, nonconservative_terms::False, - equations, - volume_flux_fv, dg::DGSEM, element, cache) - fstar1_L[:, 1, :, :] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1, :, :] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_R)) - - for k in eachnode(dg), j in eachnode(dg), i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, j, k, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) - flux = volume_flux_fv(u_ll, u_rr, 1, equations) # orientation 1: x direction - set_node_vars!(fstar1_L, flux, equations, dg, i, j, k) - set_node_vars!(fstar1_R, flux, equations, dg, i, j, k) - end + fstar2_L[:, :, 1, :] .= zero(eltype(fstar2_L)) + fstar2_L[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_L)) + fstar2_R[:, :, 1, :] .= zero(eltype(fstar2_R)) + fstar2_R[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_R)) + + for k in eachnode(dg), j in 2:nnodes(dg), i in eachnode(dg) + u_ll = get_node_vars(u, equations, dg, i, j - 1, k, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + flux = volume_flux_fv(u_ll, u_rr, 2, equations) # orientation 2: y direction + set_node_vars!(fstar2_L, flux, equations, dg, i, j, k) + set_node_vars!(fstar2_R, flux, equations, dg, i, j, k) + end - fstar2_L[:, :, 1, :] .= zero(eltype(fstar2_L)) - fstar2_L[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_L)) - fstar2_R[:, :, 1, :] .= zero(eltype(fstar2_R)) - fstar2_R[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_R)) - - for k in eachnode(dg), j in 2:nnodes(dg), i in eachnode(dg) - u_ll = get_node_vars(u, equations, dg, i, j - 1, k, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) - flux = volume_flux_fv(u_ll, u_rr, 2, equations) # orientation 2: y direction - set_node_vars!(fstar2_L, flux, equations, dg, i, j, k) - set_node_vars!(fstar2_R, flux, equations, dg, i, j, k) - end + fstar3_L[:, :, :, 1] .= zero(eltype(fstar3_L)) + fstar3_L[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_L)) + fstar3_R[:, :, :, 1] .= zero(eltype(fstar3_R)) + fstar3_R[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_R)) + + for k in 2:nnodes(dg), j in eachnode(dg), i in eachnode(dg) + u_ll = get_node_vars(u, equations, dg, i, j, k - 1, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + flux = volume_flux_fv(u_ll, u_rr, 3, equations) # orientation 3: z direction + set_node_vars!(fstar3_L, flux, equations, dg, i, j, k) + set_node_vars!(fstar3_R, flux, equations, dg, i, j, k) + end - fstar3_L[:, :, :, 1] .= zero(eltype(fstar3_L)) - fstar3_L[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_L)) - fstar3_R[:, :, :, 1] .= zero(eltype(fstar3_R)) - fstar3_R[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_R)) - - for k in 2:nnodes(dg), j in eachnode(dg), i in eachnode(dg) - u_ll = get_node_vars(u, equations, dg, i, j, k - 1, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) - flux = volume_flux_fv(u_ll, u_rr, 3, equations) # orientation 3: z direction - set_node_vars!(fstar3_L, flux, equations, dg, i, j, k) - set_node_vars!(fstar3_R, flux, equations, dg, i, j, k) + return nothing end - return nothing -end - -# Calculate the finite volume fluxes inside the elements (**without non-conservative terms**). -@inline function calcflux_fv!(fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, - fstar3_R, u, - mesh::TreeMesh{3}, nonconservative_terms::True, equations, - volume_flux_fv, dg::DGSEM, element, cache) - volume_flux, nonconservative_flux = volume_flux_fv - - fstar1_L[:, 1, :, :] .= zero(eltype(fstar1_L)) - fstar1_L[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_L)) - fstar1_R[:, 1, :, :] .= zero(eltype(fstar1_R)) - fstar1_R[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_R)) - - for k in eachnode(dg), j in eachnode(dg), i in 2:nnodes(dg) - u_ll = get_node_vars(u, equations, dg, i - 1, j, k, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) - - # Compute conservative part - flux = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction - - # Compute nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - flux_L = flux + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations) - flux_R = flux + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations) - - set_node_vars!(fstar1_L, flux_L, equations, dg, i, j, k) - set_node_vars!(fstar1_R, flux_R, equations, dg, i, j, k) - end + # Calculate the finite volume fluxes inside the elements (**without non-conservative terms**). + @inline function calcflux_fv!( + fstar1_L, fstar1_R, fstar2_L, fstar2_R, fstar3_L, + fstar3_R, u, + mesh::TreeMesh{3}, nonconservative_terms::True, equations, + volume_flux_fv, dg::DGSEM, element, cache + ) + volume_flux, nonconservative_flux = volume_flux_fv + + fstar1_L[:, 1, :, :] .= zero(eltype(fstar1_L)) + fstar1_L[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_L)) + fstar1_R[:, 1, :, :] .= zero(eltype(fstar1_R)) + fstar1_R[:, nnodes(dg) + 1, :, :] .= zero(eltype(fstar1_R)) + + for k in eachnode(dg), j in eachnode(dg), i in 2:nnodes(dg) + u_ll = get_node_vars(u, equations, dg, i - 1, j, k, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) + + # Compute conservative part + flux = volume_flux(u_ll, u_rr, 1, equations) # orientation 1: x direction + + # Compute nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + flux_L = flux + 0.5f0 * nonconservative_flux(u_ll, u_rr, 1, equations) + flux_R = flux + 0.5f0 * nonconservative_flux(u_rr, u_ll, 1, equations) + + set_node_vars!(fstar1_L, flux_L, equations, dg, i, j, k) + set_node_vars!(fstar1_R, flux_R, equations, dg, i, j, k) + end - fstar2_L[:, :, 1, :] .= zero(eltype(fstar2_L)) - fstar2_L[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_L)) - fstar2_R[:, :, 1, :] .= zero(eltype(fstar2_R)) - fstar2_R[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_R)) + fstar2_L[:, :, 1, :] .= zero(eltype(fstar2_L)) + fstar2_L[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_L)) + fstar2_R[:, :, 1, :] .= zero(eltype(fstar2_R)) + fstar2_R[:, :, nnodes(dg) + 1, :] .= zero(eltype(fstar2_R)) - for k in eachnode(dg), j in 2:nnodes(dg), i in eachnode(dg) - u_ll = get_node_vars(u, equations, dg, i, j - 1, k, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) + for k in eachnode(dg), j in 2:nnodes(dg), i in eachnode(dg) + u_ll = get_node_vars(u, equations, dg, i, j - 1, k, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) - # Compute conservative part - flux = volume_flux(u_ll, u_rr, 2, equations) # orientation 2: y direction + # Compute conservative part + flux = volume_flux(u_ll, u_rr, 2, equations) # orientation 2: y direction - # Compute nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - flux_L = flux + 0.5f0 * nonconservative_flux(u_ll, u_rr, 2, equations) - flux_R = flux + 0.5f0 * nonconservative_flux(u_rr, u_ll, 2, equations) + # Compute nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + flux_L = flux + 0.5f0 * nonconservative_flux(u_ll, u_rr, 2, equations) + flux_R = flux + 0.5f0 * nonconservative_flux(u_rr, u_ll, 2, equations) - set_node_vars!(fstar2_L, flux_L, equations, dg, i, j, k) - set_node_vars!(fstar2_R, flux_R, equations, dg, i, j, k) - end + set_node_vars!(fstar2_L, flux_L, equations, dg, i, j, k) + set_node_vars!(fstar2_R, flux_R, equations, dg, i, j, k) + end - fstar3_L[:, :, :, 1] .= zero(eltype(fstar3_L)) - fstar3_L[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_L)) - fstar3_R[:, :, :, 1] .= zero(eltype(fstar3_R)) - fstar3_R[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_R)) + fstar3_L[:, :, :, 1] .= zero(eltype(fstar3_L)) + fstar3_L[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_L)) + fstar3_R[:, :, :, 1] .= zero(eltype(fstar3_R)) + fstar3_R[:, :, :, nnodes(dg) + 1] .= zero(eltype(fstar3_R)) - for k in 2:nnodes(dg), j in eachnode(dg), i in eachnode(dg) - u_ll = get_node_vars(u, equations, dg, i, j, k - 1, element) - u_rr = get_node_vars(u, equations, dg, i, j, k, element) + for k in 2:nnodes(dg), j in eachnode(dg), i in eachnode(dg) + u_ll = get_node_vars(u, equations, dg, i, j, k - 1, element) + u_rr = get_node_vars(u, equations, dg, i, j, k, element) - # Compute conservative part - flux = volume_flux(u_ll, u_rr, 3, equations) # orientation 3: z direction + # Compute conservative part + flux = volume_flux(u_ll, u_rr, 3, equations) # orientation 3: z direction - # Compute nonconservative part - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - flux_L = flux + 0.5f0 * nonconservative_flux(u_ll, u_rr, 3, equations) - flux_R = flux + 0.5f0 * nonconservative_flux(u_rr, u_ll, 3, equations) + # Compute nonconservative part + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + flux_L = flux + 0.5f0 * nonconservative_flux(u_ll, u_rr, 3, equations) + flux_R = flux + 0.5f0 * nonconservative_flux(u_rr, u_ll, 3, equations) - set_node_vars!(fstar3_L, flux_L, equations, dg, i, j, k) - set_node_vars!(fstar3_R, flux_R, equations, dg, i, j, k) + set_node_vars!(fstar3_L, flux_L, equations, dg, i, j, k) + set_node_vars!(fstar3_R, flux_R, equations, dg, i, j, k) + end + + return nothing end - return nothing -end - -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache, u, - mesh::TreeMesh{3}, equations, surface_integral, dg::DG) - @unpack interfaces = cache - @unpack orientations, neighbor_ids = interfaces - interfaces_u = interfaces.u - - @threaded for interface in eachinterface(dg, cache) - left_element = neighbor_ids[1, interface] - right_element = neighbor_ids[2, interface] - - if orientations[interface] == 1 - # interface in x-direction - for k in eachnode(dg), j in eachnode(dg), v in eachvariable(equations) - interfaces_u[1, v, j, k, interface] = u[v, nnodes(dg), j, k, - left_element] - interfaces_u[2, v, j, k, interface] = u[v, 1, j, k, right_element] - end - elseif orientations[interface] == 2 - # interface in y-direction - for k in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) - interfaces_u[1, v, i, k, interface] = u[v, i, nnodes(dg), k, - left_element] - interfaces_u[2, v, i, k, interface] = u[v, i, 1, k, right_element] - end - else # if orientations[interface] == 3 - # interface in z-direction - for j in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) - interfaces_u[1, v, i, j, interface] = u[v, i, j, nnodes(dg), - left_element] - interfaces_u[2, v, i, j, interface] = u[v, i, j, 1, right_element] + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache, u, + mesh::TreeMesh{3}, equations, surface_integral, dg::DG + ) + @unpack interfaces = cache + @unpack orientations, neighbor_ids = interfaces + interfaces_u = interfaces.u + + @threaded for interface in eachinterface(dg, cache) + left_element = neighbor_ids[1, interface] + right_element = neighbor_ids[2, interface] + + if orientations[interface] == 1 + # interface in x-direction + for k in eachnode(dg), j in eachnode(dg), v in eachvariable(equations) + interfaces_u[1, v, j, k, interface] = u[ + v, nnodes(dg), j, k, + left_element, + ] + interfaces_u[2, v, j, k, interface] = u[v, 1, j, k, right_element] + end + elseif orientations[interface] == 2 + # interface in y-direction + for k in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) + interfaces_u[1, v, i, k, interface] = u[ + v, i, nnodes(dg), k, + left_element, + ] + interfaces_u[2, v, i, k, interface] = u[v, i, 1, k, right_element] + end + else # if orientations[interface] == 3 + # interface in z-direction + for j in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) + interfaces_u[1, v, i, j, interface] = u[ + v, i, j, nnodes(dg), + left_element, + ] + interfaces_u[2, v, i, j, interface] = u[v, i, j, 1, right_element] + end end end - end - - return nothing -end - -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{3}, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u, neighbor_ids, orientations = cache.interfaces - - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - # orientation = 3: left -> 6, right -> 5 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 + return nothing + end - for j in eachnode(dg), i in eachnode(dg) - # Call pointwise Riemann solver - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, interface) - flux = surface_flux(u_ll, u_rr, orientations[interface], equations) + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{3}, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u, neighbor_ids, orientations = cache.interfaces + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + # orientation = 3: left -> 6, right -> 5 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 - # Copy flux to left and right element storage - for v in eachvariable(equations) - surface_flux_values[v, i, j, left_direction, left_id] = flux[v] - surface_flux_values[v, i, j, right_direction, right_id] = flux[v] + for j in eachnode(dg), i in eachnode(dg) + # Call pointwise Riemann solver + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, interface) + flux = surface_flux(u_ll, u_rr, orientations[interface], equations) + + # Copy flux to left and right element storage + for v in eachvariable(equations) + surface_flux_values[v, i, j, left_direction, left_id] = flux[v] + surface_flux_values[v, i, j, right_direction, right_id] = flux[v] + end end end end -end - -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{3}, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache) - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack u, neighbor_ids, orientations = cache.interfaces - - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] - - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - # orientation = 3: left -> 6, right -> 5 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 - for j in eachnode(dg), i in eachnode(dg) - # Call pointwise Riemann solver - orientation = orientations[interface] - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, interface) - flux = surface_flux(u_ll, u_rr, orientation, equations) + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{3}, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache + ) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack u, neighbor_ids, orientations = cache.interfaces + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + # orientation = 3: left -> 6, right -> 5 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 + + for j in eachnode(dg), i in eachnode(dg) + # Call pointwise Riemann solver + orientation = orientations[interface] + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, interface) + flux = surface_flux(u_ll, u_rr, orientation, equations) + + # Compute both nonconservative fluxes + noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations) + noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations) + + # Copy flux to left and right element storage + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[v, i, j, left_direction, left_id] = flux[v] + + 0.5f0 * + noncons_left[v] + surface_flux_values[v, i, j, right_direction, right_id] = flux[v] + + 0.5f0 * + noncons_right[v] + end + end + end - # Compute both nonconservative fluxes - noncons_left = nonconservative_flux(u_ll, u_rr, orientation, equations) - noncons_right = nonconservative_flux(u_rr, u_ll, orientation, equations) + return nothing + end - # Copy flux to left and right element storage - for v in eachvariable(equations) - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - surface_flux_values[v, i, j, left_direction, left_id] = flux[v] + - 0.5f0 * - noncons_left[v] - surface_flux_values[v, i, j, right_direction, right_id] = flux[v] + - 0.5f0 * - noncons_right[v] + function prolong2boundaries!( + cache, u, + mesh::TreeMesh{3}, equations, surface_integral, dg::DG + ) + @unpack boundaries = cache + @unpack orientations, neighbor_sides = boundaries + + @threaded for boundary in eachboundary(dg, cache) + element = boundaries.neighbor_ids[boundary] + + if orientations[boundary] == 1 + # boundary in x-direction + if neighbor_sides[boundary] == 1 + # element in -x direction of boundary + for k in eachnode(dg), j in eachnode(dg), v in eachvariable(equations) + boundaries.u[1, v, j, k, boundary] = u[v, nnodes(dg), j, k, element] + end + else # Element in +x direction of boundary + for k in eachnode(dg), j in eachnode(dg), v in eachvariable(equations) + boundaries.u[2, v, j, k, boundary] = u[v, 1, j, k, element] + end + end + elseif orientations[boundary] == 2 + # boundary in y-direction + if neighbor_sides[boundary] == 1 + # element in -y direction of boundary + for k in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) + boundaries.u[1, v, i, k, boundary] = u[v, i, nnodes(dg), k, element] + end + else + # element in +y direction of boundary + for k in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) + boundaries.u[2, v, i, k, boundary] = u[v, i, 1, k, element] + end + end + else #if orientations[boundary] == 3 + # boundary in z-direction + if neighbor_sides[boundary] == 1 + # element in -z direction of boundary + for j in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) + boundaries.u[1, v, i, j, boundary] = u[v, i, j, nnodes(dg), element] + end + else + # element in +z direction of boundary + for j in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) + boundaries.u[2, v, i, j, boundary] = u[v, i, j, 1, element] + end + end end end + + return nothing end - return nothing -end + # TODO: Taal dimension agnostic + function calc_boundary_flux!( + cache, t, boundary_condition::BoundaryConditionPeriodic, + mesh::TreeMesh{3}, equations, surface_integral, dg::DG + ) + @assert isempty(eachboundary(dg, cache)) + end -function prolong2boundaries!(cache, u, - mesh::TreeMesh{3}, equations, surface_integral, dg::DG) - @unpack boundaries = cache - @unpack orientations, neighbor_sides = boundaries + function calc_boundary_flux!( + cache, t, boundary_conditions::NamedTuple, + mesh::TreeMesh{3}, equations, surface_integral, dg::DG + ) + @unpack surface_flux_values = cache.elements + @unpack n_boundaries_per_direction = cache.boundaries + + # Calculate indices + lasts = accumulate(+, n_boundaries_per_direction) + firsts = lasts - n_boundaries_per_direction .+ 1 + + # Calc boundary fluxes in each direction + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[1], + equations, surface_integral, dg, cache, + 1, firsts[1], lasts[1] + ) + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[2], + equations, surface_integral, dg, cache, + 2, firsts[2], lasts[2] + ) + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[3], + equations, surface_integral, dg, cache, + 3, firsts[3], lasts[3] + ) + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[4], + equations, surface_integral, dg, cache, + 4, firsts[4], lasts[4] + ) + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[5], + equations, surface_integral, dg, cache, + 5, firsts[5], lasts[5] + ) + calc_boundary_flux_by_direction!( + surface_flux_values, t, boundary_conditions[6], + equations, surface_integral, dg, cache, + 6, firsts[6], lasts[6] + ) + end - @threaded for boundary in eachboundary(dg, cache) - element = boundaries.neighbor_ids[boundary] + function calc_boundary_flux_by_direction!( + surface_flux_values::AbstractArray{<:Any, 5}, + t, + boundary_condition, equations, + surface_integral, dg::DG, cache, + direction, first_boundary, last_boundary + ) + @unpack surface_flux = surface_integral + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - if orientations[boundary] == 1 - # boundary in x-direction - if neighbor_sides[boundary] == 1 - # element in -x direction of boundary - for k in eachnode(dg), j in eachnode(dg), v in eachvariable(equations) - boundaries.u[1, v, j, k, boundary] = u[v, nnodes(dg), j, k, element] + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] + + for j in eachnode(dg), i in eachnode(dg) + # Get boundary flux + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, boundary) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + u_inner = u_ll + else # Element is on the right, boundary on the left + u_inner = u_rr end - else # Element in +x direction of boundary - for k in eachnode(dg), j in eachnode(dg), v in eachvariable(equations) - boundaries.u[2, v, j, k, boundary] = u[v, 1, j, k, element] + x = get_node_coords(node_coordinates, equations, dg, i, j, boundary) + flux = boundary_condition( + u_inner, orientations[boundary], direction, x, t, + surface_flux, + equations + ) + + # Copy flux to left and right element storage + for v in eachvariable(equations) + surface_flux_values[v, i, j, direction, neighbor] = flux[v] end end - elseif orientations[boundary] == 2 - # boundary in y-direction - if neighbor_sides[boundary] == 1 - # element in -y direction of boundary - for k in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) - boundaries.u[1, v, i, k, boundary] = u[v, i, nnodes(dg), k, element] + end + + return nothing + end + + function prolong2mortars!( + cache, u, + mesh::TreeMesh{3}, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DGSEM + ) + # temporary buffer for projections + @unpack fstar_tmp1_threaded = cache + + @threaded for mortar in eachmortar(dg, cache) + fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] + + lower_left_element = cache.mortars.neighbor_ids[1, mortar] + lower_right_element = cache.mortars.neighbor_ids[2, mortar] + upper_left_element = cache.mortars.neighbor_ids[3, mortar] + upper_right_element = cache.mortars.neighbor_ids[4, mortar] + large_element = cache.mortars.neighbor_ids[5, mortar] + + # Copy solution small to small + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + for k in eachnode(dg), j in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper_left[2, v, j, k, mortar] = u[ + v, 1, j, k, + upper_left_element, + ] + cache.mortars.u_upper_right[2, v, j, k, mortar] = u[ + v, 1, j, k, + upper_right_element, + ] + cache.mortars.u_lower_left[2, v, j, k, mortar] = u[ + v, 1, j, k, + lower_left_element, + ] + cache.mortars.u_lower_right[2, v, j, k, mortar] = u[ + v, 1, j, k, + lower_right_element, + ] + end + end + elseif cache.mortars.orientations[mortar] == 2 + # L2 mortars in y-direction + for k in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper_left[2, v, i, k, mortar] = u[ + v, i, 1, k, + upper_left_element, + ] + cache.mortars.u_upper_right[2, v, i, k, mortar] = u[ + v, i, 1, k, + upper_right_element, + ] + cache.mortars.u_lower_left[2, v, i, k, mortar] = u[ + v, i, 1, k, + lower_left_element, + ] + cache.mortars.u_lower_right[2, v, i, k, mortar] = u[ + v, i, 1, k, + lower_right_element, + ] + end + end + else # orientations[mortar] == 3 + # L2 mortars in z-direction + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper_left[2, v, i, j, mortar] = u[ + v, i, j, 1, + upper_left_element, + ] + cache.mortars.u_upper_right[2, v, i, j, mortar] = u[ + v, i, j, 1, + upper_right_element, + ] + cache.mortars.u_lower_left[2, v, i, j, mortar] = u[ + v, i, j, 1, + lower_left_element, + ] + cache.mortars.u_lower_right[2, v, i, j, mortar] = u[ + v, i, j, 1, + lower_right_element, + ] + end + end end - else - # element in +y direction of boundary - for k in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) - boundaries.u[2, v, i, k, boundary] = u[v, i, 1, k, element] + else # large_sides[mortar] == 2 -> small elements on left side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + for k in eachnode(dg), j in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper_left[1, v, j, k, mortar] = u[ + v, + nnodes(dg), + j, k, + upper_left_element, + ] + cache.mortars.u_upper_right[1, v, j, k, mortar] = u[ + v, + nnodes(dg), + j, k, + upper_right_element, + ] + cache.mortars.u_lower_left[1, v, j, k, mortar] = u[ + v, + nnodes(dg), + j, k, + lower_left_element, + ] + cache.mortars.u_lower_right[1, v, j, k, mortar] = u[ + v, + nnodes(dg), + j, k, + lower_right_element, + ] + end + end + elseif cache.mortars.orientations[mortar] == 2 + # L2 mortars in y-direction + for k in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper_left[1, v, i, k, mortar] = u[ + v, i, + nnodes(dg), + k, + upper_left_element, + ] + cache.mortars.u_upper_right[1, v, i, k, mortar] = u[ + v, i, + nnodes(dg), + k, + upper_right_element, + ] + cache.mortars.u_lower_left[1, v, i, k, mortar] = u[ + v, i, + nnodes(dg), + k, + lower_left_element, + ] + cache.mortars.u_lower_right[1, v, i, k, mortar] = u[ + v, i, + nnodes(dg), + k, + lower_right_element, + ] + end + end + else # if cache.mortars.orientations[mortar] == 3 + # L2 mortars in z-direction + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + cache.mortars.u_upper_left[1, v, i, j, mortar] = u[ + v, i, j, + nnodes(dg), + upper_left_element, + ] + cache.mortars.u_upper_right[1, v, i, j, mortar] = u[ + v, i, j, + nnodes(dg), + upper_right_element, + ] + cache.mortars.u_lower_left[1, v, i, j, mortar] = u[ + v, i, j, + nnodes(dg), + lower_left_element, + ] + cache.mortars.u_lower_right[1, v, i, j, mortar] = u[ + v, i, j, + nnodes(dg), + lower_right_element, + ] + end + end end end - else #if orientations[boundary] == 3 - # boundary in z-direction - if neighbor_sides[boundary] == 1 - # element in -z direction of boundary - for j in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) - boundaries.u[1, v, i, j, boundary] = u[v, i, j, nnodes(dg), element] + + # Interpolate large element face data to small interface locations + if cache.mortars.large_sides[mortar] == 1 # -> large element on left side + leftright = 1 + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(u, :, nnodes(dg), :, :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + elseif cache.mortars.orientations[mortar] == 2 + # L2 mortars in y-direction + u_large = view(u, :, :, nnodes(dg), :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + else # cache.mortars.orientations[mortar] == 3 + # L2 mortars in z-direction + u_large = view(u, :, :, :, nnodes(dg), large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) end - else - # element in +z direction of boundary - for j in eachnode(dg), i in eachnode(dg), v in eachvariable(equations) - boundaries.u[2, v, i, j, boundary] = u[v, i, j, 1, element] + else # large_sides[mortar] == 2 -> large element on right side + leftright = 2 + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(u, :, 1, :, :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + elseif cache.mortars.orientations[mortar] == 2 + # L2 mortars in y-direction + u_large = view(u, :, :, 1, :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + else # cache.mortars.orientations[mortar] == 3 + # L2 mortars in z-direction + u_large = view(u, :, :, :, 1, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) end end end + + return nothing end - return nothing -end - -# TODO: Taal dimension agnostic -function calc_boundary_flux!(cache, t, boundary_condition::BoundaryConditionPeriodic, - mesh::TreeMesh{3}, equations, surface_integral, dg::DG) - @assert isempty(eachboundary(dg, cache)) -end - -function calc_boundary_flux!(cache, t, boundary_conditions::NamedTuple, - mesh::TreeMesh{3}, equations, surface_integral, dg::DG) - @unpack surface_flux_values = cache.elements - @unpack n_boundaries_per_direction = cache.boundaries - - # Calculate indices - lasts = accumulate(+, n_boundaries_per_direction) - firsts = lasts - n_boundaries_per_direction .+ 1 - - # Calc boundary fluxes in each direction - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[1], - equations, surface_integral, dg, cache, - 1, firsts[1], lasts[1]) - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[2], - equations, surface_integral, dg, cache, - 2, firsts[2], lasts[2]) - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[3], - equations, surface_integral, dg, cache, - 3, firsts[3], lasts[3]) - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[4], - equations, surface_integral, dg, cache, - 4, firsts[4], lasts[4]) - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[5], - equations, surface_integral, dg, cache, - 5, firsts[5], lasts[5]) - calc_boundary_flux_by_direction!(surface_flux_values, t, boundary_conditions[6], - equations, surface_integral, dg, cache, - 6, firsts[6], lasts[6]) -end - -function calc_boundary_flux_by_direction!(surface_flux_values::AbstractArray{<:Any, 5}, - t, - boundary_condition, equations, - surface_integral, dg::DG, cache, - direction, first_boundary, last_boundary) - @unpack surface_flux = surface_integral - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] + @inline function element_solutions_to_mortars!( + mortars, + mortar_l2::LobattoLegendreMortarL2, + leftright, mortar, + u_large::AbstractArray{<:Any, 3}, + fstar_tmp1 + ) + multiply_dimensionwise!( + view(mortars.u_upper_left, leftright, :, :, :, mortar), + mortar_l2.forward_lower, mortar_l2.forward_upper, u_large, + fstar_tmp1 + ) + multiply_dimensionwise!( + view(mortars.u_upper_right, leftright, :, :, :, mortar), + mortar_l2.forward_upper, mortar_l2.forward_upper, u_large, + fstar_tmp1 + ) + multiply_dimensionwise!( + view(mortars.u_lower_left, leftright, :, :, :, mortar), + mortar_l2.forward_lower, mortar_l2.forward_lower, u_large, + fstar_tmp1 + ) + multiply_dimensionwise!( + view(mortars.u_lower_right, leftright, :, :, :, mortar), + mortar_l2.forward_upper, mortar_l2.forward_lower, u_large, + fstar_tmp1 + ) + return nothing + end - for j in eachnode(dg), i in eachnode(dg) - # Get boundary flux - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - u_inner = u_ll - else # Element is on the right, boundary on the left - u_inner = u_rr - end - x = get_node_coords(node_coordinates, equations, dg, i, j, boundary) - flux = boundary_condition(u_inner, orientations[boundary], direction, x, t, - surface_flux, - equations) + function calc_mortar_flux!( + surface_flux_values, + mesh::TreeMesh{3}, + nonconservative_terms::False, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u_lower_left, u_lower_right, u_upper_left, u_upper_right, orientations = cache.mortars + @unpack ( + fstar_upper_left_threaded, fstar_upper_right_threaded, + fstar_lower_left_threaded, fstar_lower_right_threaded, + fstar_tmp1_threaded, + ) = cache + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar_upper_left = fstar_upper_left_threaded[Threads.threadid()] + fstar_upper_right = fstar_upper_right_threaded[Threads.threadid()] + fstar_lower_left = fstar_lower_left_threaded[Threads.threadid()] + fstar_lower_right = fstar_lower_right_threaded[Threads.threadid()] + fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] + + # Calculate fluxes + orientation = orientations[mortar] + calc_fstar!( + fstar_upper_left, equations, surface_flux, dg, u_upper_left, mortar, + orientation + ) + calc_fstar!( + fstar_upper_right, equations, surface_flux, dg, u_upper_right, + mortar, orientation + ) + calc_fstar!( + fstar_lower_left, equations, surface_flux, dg, u_lower_left, mortar, + orientation + ) + calc_fstar!( + fstar_lower_right, equations, surface_flux, dg, u_lower_right, + mortar, orientation + ) + + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, mortar, + fstar_upper_left, fstar_upper_right, + fstar_lower_left, fstar_lower_right, + fstar_tmp1 + ) + end - # Copy flux to left and right element storage - for v in eachvariable(equations) - surface_flux_values[v, i, j, direction, neighbor] = flux[v] + return nothing + end + + function calc_mortar_flux!( + surface_flux_values, + mesh::TreeMesh{3}, + nonconservative_terms::True, equations, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack u_lower_left, u_lower_right, u_upper_left, u_upper_right, orientations, large_sides = cache.mortars + @unpack ( + fstar_upper_left_threaded, fstar_upper_right_threaded, + fstar_lower_left_threaded, fstar_lower_right_threaded, + fstar_tmp1_threaded, + ) = cache + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar_upper_left = fstar_upper_left_threaded[Threads.threadid()] + fstar_upper_right = fstar_upper_right_threaded[Threads.threadid()] + fstar_lower_left = fstar_lower_left_threaded[Threads.threadid()] + fstar_lower_right = fstar_lower_right_threaded[Threads.threadid()] + fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] + + # Calculate fluxes + orientation = orientations[mortar] + calc_fstar!( + fstar_upper_left, equations, surface_flux, dg, u_upper_left, mortar, + orientation + ) + calc_fstar!( + fstar_upper_right, equations, surface_flux, dg, u_upper_right, + mortar, orientation + ) + calc_fstar!( + fstar_lower_left, equations, surface_flux, dg, u_lower_left, mortar, + orientation + ) + calc_fstar!( + fstar_lower_right, equations, surface_flux, dg, u_lower_right, + mortar, orientation + ) + + # Add nonconservative fluxes. + # These need to be adapted on the geometry (left/right) since the order of + # the arguments matters, based on the global SBP operator interpretation. + # The same interpretation (global SBP operators coupled discontinuously via + # central fluxes/SATs) explains why we need the factor 0.5. + # Alternatively, you can also follow the argumentation of Bohm et al. 2018 + # ("nonconservative diamond flux") + if large_sides[mortar] == 1 # -> small elements on right side + for j in eachnode(dg), i in eachnode(dg) + # Pull the left and right solutions + u_upper_left_ll, u_upper_left_rr = get_surface_node_vars( + u_upper_left, + equations, dg, + i, j, mortar + ) + u_upper_right_ll, u_upper_right_rr = get_surface_node_vars( + u_upper_right, + equations, + dg, i, j, + mortar + ) + u_lower_left_ll, u_lower_left_rr = get_surface_node_vars( + u_lower_left, + equations, dg, + i, j, mortar + ) + u_lower_right_ll, u_lower_right_rr = get_surface_node_vars( + u_lower_right, + equations, + dg, i, j, + mortar + ) + # Call pointwise nonconservative term + noncons_upper_left = nonconservative_flux( + u_upper_left_ll, + u_upper_left_rr, orientation, + equations + ) + noncons_upper_right = nonconservative_flux( + u_upper_right_ll, + u_upper_right_rr, + orientation, equations + ) + noncons_lower_left = nonconservative_flux( + u_lower_left_ll, + u_lower_left_rr, orientation, + equations + ) + noncons_lower_right = nonconservative_flux( + u_lower_right_ll, + u_lower_right_rr, + orientation, equations + ) + # Add to primary and secondary temporary storage + multiply_add_to_node_vars!( + fstar_upper_left, 0.5f0, noncons_upper_left, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + fstar_upper_right, 0.5f0, + noncons_upper_right, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + fstar_lower_left, 0.5f0, noncons_lower_left, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + fstar_lower_right, 0.5f0, + noncons_lower_right, + equations, dg, i, j + ) + end + else # large_sides[mortar] == 2 -> small elements on the left + for j in eachnode(dg), i in eachnode(dg) + # Pull the left and right solutions + u_upper_left_ll, u_upper_left_rr = get_surface_node_vars( + u_upper_left, + equations, dg, + i, j, mortar + ) + u_upper_right_ll, u_upper_right_rr = get_surface_node_vars( + u_upper_right, + equations, + dg, i, j, + mortar + ) + u_lower_left_ll, u_lower_left_rr = get_surface_node_vars( + u_lower_left, + equations, dg, + i, j, mortar + ) + u_lower_right_ll, u_lower_right_rr = get_surface_node_vars( + u_lower_right, + equations, + dg, i, j, + mortar + ) + # Call pointwise nonconservative term + noncons_upper_left = nonconservative_flux( + u_upper_left_rr, + u_upper_left_ll, orientation, + equations + ) + noncons_upper_right = nonconservative_flux( + u_upper_right_rr, + u_upper_right_ll, + orientation, equations + ) + noncons_lower_left = nonconservative_flux( + u_lower_left_rr, + u_lower_left_ll, orientation, + equations + ) + noncons_lower_right = nonconservative_flux( + u_lower_right_rr, + u_lower_right_ll, + orientation, equations + ) + # Add to primary and secondary temporary storage + multiply_add_to_node_vars!( + fstar_upper_left, 0.5f0, noncons_upper_left, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + fstar_upper_right, 0.5f0, + noncons_upper_right, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + fstar_lower_left, 0.5f0, noncons_lower_left, + equations, dg, i, j + ) + multiply_add_to_node_vars!( + fstar_lower_right, 0.5f0, + noncons_lower_right, + equations, dg, i, j + ) + end end + + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations, mortar_l2, dg, cache, mortar, + fstar_upper_left, fstar_upper_right, + fstar_lower_left, fstar_lower_right, + fstar_tmp1 + ) end + + return nothing end - return nothing -end + @inline function calc_fstar!( + destination::AbstractArray{<:Any, 3}, equations, + surface_flux, dg::DGSEM, + u_interfaces, interface, orientation + ) + for j in eachnode(dg), i in eachnode(dg) + # Call pointwise two-point numerical flux function + u_ll, u_rr = get_surface_node_vars(u_interfaces, equations, dg, i, j, interface) + flux = surface_flux(u_ll, u_rr, orientation, equations) -function prolong2mortars!(cache, u, - mesh::TreeMesh{3}, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DGSEM) - # temporary buffer for projections - @unpack fstar_tmp1_threaded = cache + # Copy flux to left and right element storage + set_node_vars!(destination, flux, equations, dg, i, j) + end - @threaded for mortar in eachmortar(dg, cache) - fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] + return nothing + end + @inline function mortar_fluxes_to_elements!( + surface_flux_values, + mesh::TreeMesh{3}, equations, + mortar_l2::LobattoLegendreMortarL2, + dg::DGSEM, cache, + mortar, + fstar_upper_left, fstar_upper_right, + fstar_lower_left, fstar_lower_right, + fstar_tmp1 + ) lower_left_element = cache.mortars.neighbor_ids[1, mortar] lower_right_element = cache.mortars.neighbor_ids[2, mortar] upper_left_element = cache.mortars.neighbor_ids[3, mortar] upper_right_element = cache.mortars.neighbor_ids[4, mortar] large_element = cache.mortars.neighbor_ids[5, mortar] - # Copy solution small to small + # Copy flux small to small if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - for k in eachnode(dg), j in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper_left[2, v, j, k, mortar] = u[v, 1, j, k, - upper_left_element] - cache.mortars.u_upper_right[2, v, j, k, mortar] = u[v, 1, j, k, - upper_right_element] - cache.mortars.u_lower_left[2, v, j, k, mortar] = u[v, 1, j, k, - lower_left_element] - cache.mortars.u_lower_right[2, v, j, k, mortar] = u[v, 1, j, k, - lower_right_element] - end - end + direction = 1 elseif cache.mortars.orientations[mortar] == 2 # L2 mortars in y-direction - for k in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper_left[2, v, i, k, mortar] = u[v, i, 1, k, - upper_left_element] - cache.mortars.u_upper_right[2, v, i, k, mortar] = u[v, i, 1, k, - upper_right_element] - cache.mortars.u_lower_left[2, v, i, k, mortar] = u[v, i, 1, k, - lower_left_element] - cache.mortars.u_lower_right[2, v, i, k, mortar] = u[v, i, 1, k, - lower_right_element] - end - end - else # orientations[mortar] == 3 + direction = 3 + else # if cache.mortars.orientations[mortar] == 3 # L2 mortars in z-direction - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper_left[2, v, i, j, mortar] = u[v, i, j, 1, - upper_left_element] - cache.mortars.u_upper_right[2, v, i, j, mortar] = u[v, i, j, 1, - upper_right_element] - cache.mortars.u_lower_left[2, v, i, j, mortar] = u[v, i, j, 1, - lower_left_element] - cache.mortars.u_lower_right[2, v, i, j, mortar] = u[v, i, j, 1, - lower_right_element] - end - end + direction = 5 end else # large_sides[mortar] == 2 -> small elements on left side if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - for k in eachnode(dg), j in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper_left[1, v, j, k, mortar] = u[v, - nnodes(dg), - j, k, - upper_left_element] - cache.mortars.u_upper_right[1, v, j, k, mortar] = u[v, - nnodes(dg), - j, k, - upper_right_element] - cache.mortars.u_lower_left[1, v, j, k, mortar] = u[v, - nnodes(dg), - j, k, - lower_left_element] - cache.mortars.u_lower_right[1, v, j, k, mortar] = u[v, - nnodes(dg), - j, k, - lower_right_element] - end - end + direction = 2 elseif cache.mortars.orientations[mortar] == 2 # L2 mortars in y-direction - for k in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper_left[1, v, i, k, mortar] = u[v, i, - nnodes(dg), - k, - upper_left_element] - cache.mortars.u_upper_right[1, v, i, k, mortar] = u[v, i, - nnodes(dg), - k, - upper_right_element] - cache.mortars.u_lower_left[1, v, i, k, mortar] = u[v, i, - nnodes(dg), - k, - lower_left_element] - cache.mortars.u_lower_right[1, v, i, k, mortar] = u[v, i, - nnodes(dg), - k, - lower_right_element] - end - end + direction = 4 else # if cache.mortars.orientations[mortar] == 3 # L2 mortars in z-direction - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - cache.mortars.u_upper_left[1, v, i, j, mortar] = u[v, i, j, - nnodes(dg), - upper_left_element] - cache.mortars.u_upper_right[1, v, i, j, mortar] = u[v, i, j, - nnodes(dg), - upper_right_element] - cache.mortars.u_lower_left[1, v, i, j, mortar] = u[v, i, j, - nnodes(dg), - lower_left_element] - cache.mortars.u_lower_right[1, v, i, j, mortar] = u[v, i, j, - nnodes(dg), - lower_right_element] - end - end + direction = 6 end end + surface_flux_values[:, :, :, direction, upper_left_element] .= fstar_upper_left + surface_flux_values[:, :, :, direction, upper_right_element] .= fstar_upper_right + surface_flux_values[:, :, :, direction, lower_left_element] .= fstar_lower_left + surface_flux_values[:, :, :, direction, lower_right_element] .= fstar_lower_right - # Interpolate large element face data to small interface locations - if cache.mortars.large_sides[mortar] == 1 # -> large element on left side - leftright = 1 + # Project small fluxes to large element + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - u_large = view(u, :, nnodes(dg), :, :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) + direction = 2 elseif cache.mortars.orientations[mortar] == 2 # L2 mortars in y-direction - u_large = view(u, :, :, nnodes(dg), :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) - else # cache.mortars.orientations[mortar] == 3 + direction = 4 + else # if cache.mortars.orientations[mortar] == 3 # L2 mortars in z-direction - u_large = view(u, :, :, :, nnodes(dg), large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) + direction = 6 end - else # large_sides[mortar] == 2 -> large element on right side - leftright = 2 + else # large_sides[mortar] == 2 -> small elements on left side if cache.mortars.orientations[mortar] == 1 # L2 mortars in x-direction - u_large = view(u, :, 1, :, :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) + direction = 1 elseif cache.mortars.orientations[mortar] == 2 # L2 mortars in y-direction - u_large = view(u, :, :, 1, :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) - else # cache.mortars.orientations[mortar] == 3 + direction = 3 + else # if cache.mortars.orientations[mortar] == 3 # L2 mortars in z-direction - u_large = view(u, :, :, :, 1, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) + direction = 5 end end - end - return nothing -end - -@inline function element_solutions_to_mortars!(mortars, - mortar_l2::LobattoLegendreMortarL2, - leftright, mortar, - u_large::AbstractArray{<:Any, 3}, - fstar_tmp1) - multiply_dimensionwise!(view(mortars.u_upper_left, leftright, :, :, :, mortar), - mortar_l2.forward_lower, mortar_l2.forward_upper, u_large, - fstar_tmp1) - multiply_dimensionwise!(view(mortars.u_upper_right, leftright, :, :, :, mortar), - mortar_l2.forward_upper, mortar_l2.forward_upper, u_large, - fstar_tmp1) - multiply_dimensionwise!(view(mortars.u_lower_left, leftright, :, :, :, mortar), - mortar_l2.forward_lower, mortar_l2.forward_lower, u_large, - fstar_tmp1) - multiply_dimensionwise!(view(mortars.u_lower_right, leftright, :, :, :, mortar), - mortar_l2.forward_upper, mortar_l2.forward_lower, u_large, - fstar_tmp1) - return nothing -end - -function calc_mortar_flux!(surface_flux_values, - mesh::TreeMesh{3}, - nonconservative_terms::False, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u_lower_left, u_lower_right, u_upper_left, u_upper_right, orientations = cache.mortars - @unpack (fstar_upper_left_threaded, fstar_upper_right_threaded, - fstar_lower_left_threaded, fstar_lower_right_threaded, - fstar_tmp1_threaded) = cache - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar_upper_left = fstar_upper_left_threaded[Threads.threadid()] - fstar_upper_right = fstar_upper_right_threaded[Threads.threadid()] - fstar_lower_left = fstar_lower_left_threaded[Threads.threadid()] - fstar_lower_right = fstar_lower_right_threaded[Threads.threadid()] - fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] - - # Calculate fluxes - orientation = orientations[mortar] - calc_fstar!(fstar_upper_left, equations, surface_flux, dg, u_upper_left, mortar, - orientation) - calc_fstar!(fstar_upper_right, equations, surface_flux, dg, u_upper_right, - mortar, orientation) - calc_fstar!(fstar_lower_left, equations, surface_flux, dg, u_lower_left, mortar, - orientation) - calc_fstar!(fstar_lower_right, equations, surface_flux, dg, u_lower_right, - mortar, orientation) - - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, mortar, - fstar_upper_left, fstar_upper_right, - fstar_lower_left, fstar_lower_right, - fstar_tmp1) + multiply_dimensionwise!( + view( + surface_flux_values, :, :, :, direction, + large_element + ), + mortar_l2.reverse_lower, mortar_l2.reverse_upper, + fstar_upper_left, fstar_tmp1 + ) + add_multiply_dimensionwise!( + view( + surface_flux_values, :, :, :, direction, + large_element + ), + mortar_l2.reverse_upper, mortar_l2.reverse_upper, + fstar_upper_right, fstar_tmp1 + ) + add_multiply_dimensionwise!( + view( + surface_flux_values, :, :, :, direction, + large_element + ), + mortar_l2.reverse_lower, mortar_l2.reverse_lower, + fstar_lower_left, fstar_tmp1 + ) + add_multiply_dimensionwise!( + view( + surface_flux_values, :, :, :, direction, + large_element + ), + mortar_l2.reverse_upper, mortar_l2.reverse_lower, + fstar_lower_right, fstar_tmp1 + ) + + return nothing end - return nothing -end - -function calc_mortar_flux!(surface_flux_values, - mesh::TreeMesh{3}, - nonconservative_terms::True, equations, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack u_lower_left, u_lower_right, u_upper_left, u_upper_right, orientations, large_sides = cache.mortars - @unpack (fstar_upper_left_threaded, fstar_upper_right_threaded, - fstar_lower_left_threaded, fstar_lower_right_threaded, - fstar_tmp1_threaded) = cache - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar_upper_left = fstar_upper_left_threaded[Threads.threadid()] - fstar_upper_right = fstar_upper_right_threaded[Threads.threadid()] - fstar_lower_left = fstar_lower_left_threaded[Threads.threadid()] - fstar_lower_right = fstar_lower_right_threaded[Threads.threadid()] - fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] - - # Calculate fluxes - orientation = orientations[mortar] - calc_fstar!(fstar_upper_left, equations, surface_flux, dg, u_upper_left, mortar, - orientation) - calc_fstar!(fstar_upper_right, equations, surface_flux, dg, u_upper_right, - mortar, orientation) - calc_fstar!(fstar_lower_left, equations, surface_flux, dg, u_lower_left, mortar, - orientation) - calc_fstar!(fstar_lower_right, equations, surface_flux, dg, u_lower_right, - mortar, orientation) - - # Add nonconservative fluxes. - # These need to be adapted on the geometry (left/right) since the order of - # the arguments matters, based on the global SBP operator interpretation. - # The same interpretation (global SBP operators coupled discontinuously via - # central fluxes/SATs) explains why we need the factor 0.5. - # Alternatively, you can also follow the argumentation of Bohm et al. 2018 - # ("nonconservative diamond flux") - if large_sides[mortar] == 1 # -> small elements on right side - for j in eachnode(dg), i in eachnode(dg) - # Pull the left and right solutions - u_upper_left_ll, u_upper_left_rr = get_surface_node_vars(u_upper_left, - equations, dg, - i, j, mortar) - u_upper_right_ll, u_upper_right_rr = get_surface_node_vars(u_upper_right, - equations, - dg, i, j, - mortar) - u_lower_left_ll, u_lower_left_rr = get_surface_node_vars(u_lower_left, - equations, dg, - i, j, mortar) - u_lower_right_ll, u_lower_right_rr = get_surface_node_vars(u_lower_right, - equations, - dg, i, j, - mortar) - # Call pointwise nonconservative term - noncons_upper_left = nonconservative_flux(u_upper_left_ll, - u_upper_left_rr, orientation, - equations) - noncons_upper_right = nonconservative_flux(u_upper_right_ll, - u_upper_right_rr, - orientation, equations) - noncons_lower_left = nonconservative_flux(u_lower_left_ll, - u_lower_left_rr, orientation, - equations) - noncons_lower_right = nonconservative_flux(u_lower_right_ll, - u_lower_right_rr, - orientation, equations) - # Add to primary and secondary temporary storage - multiply_add_to_node_vars!(fstar_upper_left, 0.5f0, noncons_upper_left, - equations, dg, i, j) - multiply_add_to_node_vars!(fstar_upper_right, 0.5f0, - noncons_upper_right, - equations, dg, i, j) - multiply_add_to_node_vars!(fstar_lower_left, 0.5f0, noncons_lower_left, - equations, dg, i, j) - multiply_add_to_node_vars!(fstar_lower_right, 0.5f0, - noncons_lower_right, - equations, dg, i, j) - end - else # large_sides[mortar] == 2 -> small elements on the left - for j in eachnode(dg), i in eachnode(dg) - # Pull the left and right solutions - u_upper_left_ll, u_upper_left_rr = get_surface_node_vars(u_upper_left, - equations, dg, - i, j, mortar) - u_upper_right_ll, u_upper_right_rr = get_surface_node_vars(u_upper_right, - equations, - dg, i, j, - mortar) - u_lower_left_ll, u_lower_left_rr = get_surface_node_vars(u_lower_left, - equations, dg, - i, j, mortar) - u_lower_right_ll, u_lower_right_rr = get_surface_node_vars(u_lower_right, - equations, - dg, i, j, - mortar) - # Call pointwise nonconservative term - noncons_upper_left = nonconservative_flux(u_upper_left_rr, - u_upper_left_ll, orientation, - equations) - noncons_upper_right = nonconservative_flux(u_upper_right_rr, - u_upper_right_ll, - orientation, equations) - noncons_lower_left = nonconservative_flux(u_lower_left_rr, - u_lower_left_ll, orientation, - equations) - noncons_lower_right = nonconservative_flux(u_lower_right_rr, - u_lower_right_ll, - orientation, equations) - # Add to primary and secondary temporary storage - multiply_add_to_node_vars!(fstar_upper_left, 0.5f0, noncons_upper_left, - equations, dg, i, j) - multiply_add_to_node_vars!(fstar_upper_right, 0.5f0, - noncons_upper_right, - equations, dg, i, j) - multiply_add_to_node_vars!(fstar_lower_left, 0.5f0, noncons_lower_left, - equations, dg, i, j) - multiply_add_to_node_vars!(fstar_lower_right, 0.5f0, - noncons_lower_right, - equations, dg, i, j) + function calc_surface_integral!( + du, u, mesh::Union{TreeMesh{3}, StructuredMesh{3}}, + equations, surface_integral, dg::DGSEM, cache + ) + @unpack boundary_interpolation = dg.basis + @unpack surface_flux_values = cache.elements + + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` and `-=` to let `@muladd` + # turn these into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for m in eachnode(dg), l in eachnode(dg) + for v in eachvariable(equations) + # surface at -x + du[v, 1, l, m, element] = ( + du[v, 1, l, m, element] - + surface_flux_values[v, l, m, 1, element] * + factor_1 + ) + + # surface at +x + du[v, nnodes(dg), l, m, element] = ( + du[v, nnodes(dg), l, m, element] + + surface_flux_values[ + v, l, m, 2, + element, + ] * + factor_2 + ) + + # surface at -y + du[v, l, 1, m, element] = ( + du[v, l, 1, m, element] - + surface_flux_values[v, l, m, 3, element] * + factor_1 + ) + + # surface at +y + du[v, l, nnodes(dg), m, element] = ( + du[v, l, nnodes(dg), m, element] + + surface_flux_values[ + v, l, m, 4, + element, + ] * + factor_2 + ) + + # surface at -z + du[v, l, m, 1, element] = ( + du[v, l, m, 1, element] - + surface_flux_values[v, l, m, 5, element] * + factor_1 + ) + + # surface at +z + du[v, l, m, nnodes(dg), element] = ( + du[v, l, m, nnodes(dg), element] + + surface_flux_values[ + v, l, m, 6, + element, + ] * + factor_2 + ) + end end end - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations, mortar_l2, dg, cache, mortar, - fstar_upper_left, fstar_upper_right, - fstar_lower_left, fstar_lower_right, - fstar_tmp1) + return nothing end - return nothing -end - -@inline function calc_fstar!(destination::AbstractArray{<:Any, 3}, equations, - surface_flux, dg::DGSEM, - u_interfaces, interface, orientation) - for j in eachnode(dg), i in eachnode(dg) - # Call pointwise two-point numerical flux function - u_ll, u_rr = get_surface_node_vars(u_interfaces, equations, dg, i, j, interface) - flux = surface_flux(u_ll, u_rr, orientation, equations) + function apply_jacobian!( + du, mesh::TreeMesh{3}, + equations, dg::DG, cache + ) + @unpack inverse_jacobian = cache.elements - # Copy flux to left and right element storage - set_node_vars!(destination, flux, equations, dg, i, j) - end + @threaded for element in eachelement(dg, cache) + factor = -inverse_jacobian[element] - return nothing -end - -@inline function mortar_fluxes_to_elements!(surface_flux_values, - mesh::TreeMesh{3}, equations, - mortar_l2::LobattoLegendreMortarL2, - dg::DGSEM, cache, - mortar, - fstar_upper_left, fstar_upper_right, - fstar_lower_left, fstar_lower_right, - fstar_tmp1) - lower_left_element = cache.mortars.neighbor_ids[1, mortar] - lower_right_element = cache.mortars.neighbor_ids[2, mortar] - upper_left_element = cache.mortars.neighbor_ids[3, mortar] - upper_right_element = cache.mortars.neighbor_ids[4, mortar] - large_element = cache.mortars.neighbor_ids[5, mortar] - - # Copy flux small to small - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 1 - elseif cache.mortars.orientations[mortar] == 2 - # L2 mortars in y-direction - direction = 3 - else # if cache.mortars.orientations[mortar] == 3 - # L2 mortars in z-direction - direction = 5 - end - else # large_sides[mortar] == 2 -> small elements on left side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 2 - elseif cache.mortars.orientations[mortar] == 2 - # L2 mortars in y-direction - direction = 4 - else # if cache.mortars.orientations[mortar] == 3 - # L2 mortars in z-direction - direction = 6 - end - end - surface_flux_values[:, :, :, direction, upper_left_element] .= fstar_upper_left - surface_flux_values[:, :, :, direction, upper_right_element] .= fstar_upper_right - surface_flux_values[:, :, :, direction, lower_left_element] .= fstar_lower_left - surface_flux_values[:, :, :, direction, lower_right_element] .= fstar_lower_right - - # Project small fluxes to large element - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 2 - elseif cache.mortars.orientations[mortar] == 2 - # L2 mortars in y-direction - direction = 4 - else # if cache.mortars.orientations[mortar] == 3 - # L2 mortars in z-direction - direction = 6 - end - else # large_sides[mortar] == 2 -> small elements on left side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - direction = 1 - elseif cache.mortars.orientations[mortar] == 2 - # L2 mortars in y-direction - direction = 3 - else # if cache.mortars.orientations[mortar] == 3 - # L2 mortars in z-direction - direction = 5 - end - end - - multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction, - large_element), - mortar_l2.reverse_lower, mortar_l2.reverse_upper, - fstar_upper_left, fstar_tmp1) - add_multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction, - large_element), - mortar_l2.reverse_upper, mortar_l2.reverse_upper, - fstar_upper_right, fstar_tmp1) - add_multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction, - large_element), - mortar_l2.reverse_lower, mortar_l2.reverse_lower, - fstar_lower_left, fstar_tmp1) - add_multiply_dimensionwise!(view(surface_flux_values, :, :, :, direction, - large_element), - mortar_l2.reverse_upper, mortar_l2.reverse_lower, - fstar_lower_right, fstar_tmp1) - - return nothing -end - -function calc_surface_integral!(du, u, mesh::Union{TreeMesh{3}, StructuredMesh{3}}, - equations, surface_integral, dg::DGSEM, cache) - @unpack boundary_interpolation = dg.basis - @unpack surface_flux_values = cache.elements - - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` and `-=` to let `@muladd` - # turn these into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] - @threaded for element in eachelement(dg, cache) - for m in eachnode(dg), l in eachnode(dg) - for v in eachvariable(equations) - # surface at -x - du[v, 1, l, m, element] = (du[v, 1, l, m, element] - - surface_flux_values[v, l, m, 1, element] * - factor_1) - - # surface at +x - du[v, nnodes(dg), l, m, element] = (du[v, nnodes(dg), l, m, element] + - surface_flux_values[v, l, m, 2, - element] * - factor_2) - - # surface at -y - du[v, l, 1, m, element] = (du[v, l, 1, m, element] - - surface_flux_values[v, l, m, 3, element] * - factor_1) - - # surface at +y - du[v, l, nnodes(dg), m, element] = (du[v, l, nnodes(dg), m, element] + - surface_flux_values[v, l, m, 4, - element] * - factor_2) - - # surface at -z - du[v, l, m, 1, element] = (du[v, l, m, 1, element] - - surface_flux_values[v, l, m, 5, element] * - factor_1) - - # surface at +z - du[v, l, m, nnodes(dg), element] = (du[v, l, m, nnodes(dg), element] + - surface_flux_values[v, l, m, 6, - element] * - factor_2) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, j, k, element] *= factor + end end end - end - return nothing -end - -function apply_jacobian!(du, mesh::TreeMesh{3}, - equations, dg::DG, cache) - @unpack inverse_jacobian = cache.elements + return nothing + end - @threaded for element in eachelement(dg, cache) - factor = -inverse_jacobian[element] + # TODO: Taal dimension agnostic + function calc_sources!( + du, u, t, source_terms::Nothing, + equations::AbstractEquations{3}, dg::DG, cache + ) + return nothing + end - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - du[v, i, j, k, element] *= factor + function calc_sources!( + du, u, t, source_terms, + equations::AbstractEquations{3}, dg::DG, cache + ) + @unpack node_coordinates = cache.elements + + @threaded for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, k, element) + x_local = get_node_coords( + node_coordinates, equations, dg, + i, j, k, element + ) + du_local = source_terms(u_local, x_local, t, equations) + add_to_node_vars!(du, du_local, equations, dg, i, j, k, element) end end - end - return nothing -end - -# TODO: Taal dimension agnostic -function calc_sources!(du, u, t, source_terms::Nothing, - equations::AbstractEquations{3}, dg::DG, cache) - return nothing -end - -function calc_sources!(du, u, t, source_terms, - equations::AbstractEquations{3}, dg::DG, cache) - @unpack node_coordinates = cache.elements - - @threaded for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, k, element) - x_local = get_node_coords(node_coordinates, equations, dg, - i, j, k, element) - du_local = source_terms(u_local, x_local, t, equations) - add_to_node_vars!(du, du_local, equations, dg, i, j, k, element) - end + return nothing end - - return nothing -end end # @muladd diff --git a/src/solvers/dgsem_tree/dg_3d_compressible_euler.jl b/src/solvers/dgsem_tree/dg_3d_compressible_euler.jl index ec3647ed64..4abcb4cf31 100644 --- a/src/solvers/dgsem_tree/dg_3d_compressible_euler.jl +++ b/src/solvers/dgsem_tree/dg_3d_compressible_euler.jl @@ -1,4 +1,3 @@ - # From here on, this file contains specializations of DG methods on the # TreeMesh3D to the compressible Euler equations. # @@ -16,26 +15,38 @@ # We specialize on `PtrArray` since these will be returned by `Trixi.wrap_array` # if LoopVectorization.jl can handle the array types. This ensures that `@turbo` # works efficiently here. -@inline function flux_differencing_kernel!(_du::PtrArray, u_cons::PtrArray, - element, mesh::TreeMesh{3}, - nonconservative_terms::False, - equations::CompressibleEulerEquations3D, - volume_flux::typeof(flux_shima_etal_turbo), - dg::DGSEM, cache, alpha) +@inline function flux_differencing_kernel!( + _du::PtrArray, u_cons::PtrArray, + element, mesh::TreeMesh{3}, + nonconservative_terms::False, + equations::CompressibleEulerEquations3D, + volume_flux::typeof(flux_shima_etal_turbo), + dg::DGSEM, cache, alpha + ) @unpack derivative_split = dg.basis # Create a temporary array that will be used to store the RHS with permuted # indices `[i, j, k, v]` to allow using SIMD instructions. # `StrideArray`s with purely static dimensions do not allocate on the heap. - du = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., - StaticInt(nvariables(equations)))) + du = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., + StaticInt(nvariables(equations)), + ) + ) # Convert conserved to primitive variables on the given `element`. - u_prim = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), - ndims(mesh))..., - StaticInt(nvariables(equations)))) + u_prim = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple( + _ -> StaticInt(nnodes(dg)), + ndims(mesh) + )..., + StaticInt(nvariables(equations)), + ) + ) @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) rho = u_cons[1, i, j, k, element] @@ -61,20 +72,28 @@ # At first, we create new temporary arrays with permuted memory layout to # allow using SIMD instructions along the first dimension (which is contiguous # in memory). - du_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - u_prim_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) + du_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + u_prim_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) u_prim_permuted[jk, i, v] = u_prim[i, j, k, v] @@ -132,9 +151,9 @@ end @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) du[i, j, k, v] = du_permuted[jk, i, v] @@ -195,13 +214,21 @@ # The memory layout is already optimal for SIMD vectorization in this loop. # We just squeeze the first two dimensions to make the code slightly faster. GC.@preserve u_prim begin - u_prim_reshaped = PtrArray(pointer(u_prim), - (StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - du_reshaped = PtrArray(pointer(du), - (StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) + u_prim_reshaped = PtrArray( + pointer(u_prim), + ( + StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + du_reshaped = PtrArray( + pointer(du), + ( + StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) for k in eachnode(dg), kk in (k + 1):nnodes(dg) @turbo for ij in Base.OneTo(nnodes(dg)^2) @@ -254,37 +281,49 @@ # Finally, we add the temporary RHS computed here to the global RHS in the # given `element`. @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) _du[v, i, j, k, element] += du[i, j, k, v] end end -@inline function flux_differencing_kernel!(_du::PtrArray, u_cons::PtrArray, - element, mesh::TreeMesh{3}, - nonconservative_terms::False, - equations::CompressibleEulerEquations3D, - volume_flux::typeof(flux_ranocha_turbo), - dg::DGSEM, cache, alpha) +@inline function flux_differencing_kernel!( + _du::PtrArray, u_cons::PtrArray, + element, mesh::TreeMesh{3}, + nonconservative_terms::False, + equations::CompressibleEulerEquations3D, + volume_flux::typeof(flux_ranocha_turbo), + dg::DGSEM, cache, alpha + ) @unpack derivative_split = dg.basis # Create a temporary array that will be used to store the RHS with permuted # indices `[i, j, k, v]` to allow using SIMD instructions. # `StrideArray`s with purely static dimensions do not allocate on the heap. - du = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., - StaticInt(nvariables(equations)))) + du = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple(_ -> StaticInt(nnodes(dg)), ndims(mesh))..., + StaticInt(nvariables(equations)), + ) + ) # Convert conserved to primitive variables on the given `element`. In addition # to the usual primitive variables, we also compute logarithms of the density # and pressure to increase the performance of the required logarithmic mean # values. - u_prim = StrideArray{eltype(u_cons)}(undef, - (ntuple(_ -> StaticInt(nnodes(dg)), - ndims(mesh))..., - StaticInt(nvariables(equations) + 2))) # We also compute "+ 2" logs + u_prim = StrideArray{eltype(u_cons)}( + undef, + ( + ntuple( + _ -> StaticInt(nnodes(dg)), + ndims(mesh) + )..., + StaticInt(nvariables(equations) + 2), + ) + ) # We also compute "+ 2" logs @turbo for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) rho = u_cons[1, i, j, k, element] @@ -312,20 +351,28 @@ end # At first, we create new temporary arrays with permuted memory layout to # allow using SIMD instructions along the first dimension (which is contiguous # in memory). - du_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) - - u_prim_permuted = StrideArray{eltype(u_cons)}(undef, - (StaticInt(nnodes(dg)^2), - StaticInt(nnodes(dg)), - StaticInt(nvariables(equations) + 2))) + du_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) + + u_prim_permuted = StrideArray{eltype(u_cons)}( + undef, + ( + StaticInt(nnodes(dg)^2), + StaticInt(nnodes(dg)), + StaticInt(nvariables(equations) + 2), + ) + ) @turbo for v in indices(u_prim, 4), # v in eachvariable(equations) misses +2 logs - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) u_prim_permuted[jk, i, v] = u_prim[i, j, k, v] @@ -396,8 +443,8 @@ end f3 = f1 * v2_avg f4 = f1 * v3_avg f5 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5 * (p_ll * v1_rr + p_rr * v1_ll) + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5 * (p_ll * v1_rr + p_rr * v1_ll) # Add scaled fluxes to RHS factor_i = alpha * derivative_split[i, ii] @@ -417,9 +464,9 @@ end end @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) jk = j + nnodes(dg) * (k - 1) du[i, j, k, v] = du_permuted[jk, i, v] @@ -490,8 +537,8 @@ end f3 = f1 * v2_avg + p_avg f4 = f1 * v3_avg f5 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5 * (p_ll * v2_rr + p_rr * v2_ll) + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5 * (p_ll * v2_rr + p_rr * v2_ll) # Add scaled fluxes to RHS factor_j = alpha * derivative_split[j, jj] @@ -514,13 +561,21 @@ end # The memory layout is already optimal for SIMD vectorization in this loop. # We just squeeze the first two dimensions to make the code slightly faster. GC.@preserve u_prim begin - u_prim_reshaped = PtrArray(pointer(u_prim), - (StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations) + 2))) - - du_reshaped = PtrArray(pointer(du), - (StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), - StaticInt(nvariables(equations)))) + u_prim_reshaped = PtrArray( + pointer(u_prim), + ( + StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations) + 2), + ) + ) + + du_reshaped = PtrArray( + pointer(du), + ( + StaticInt(nnodes(dg)^2), StaticInt(nnodes(dg)), + StaticInt(nvariables(equations)), + ) + ) for k in eachnode(dg), kk in (k + 1):nnodes(dg) @turbo for ij in Base.OneTo(nnodes(dg)^2) @@ -570,7 +625,7 @@ end special_path2 = (2 + z2 * (2 / 3 + z2 * (2 / 5 + 2 / 7 * z2))) / x2_plus_y2 regular_path2 = (log_y2 - log_x2) / y2_minus_x2 inv_rho_p_mean = p_ll * p_rr * - ifelse(z2 < 1.0e-4, special_path2, regular_path2) + ifelse(z2 < 1.0e-4, special_path2, regular_path2) v1_avg = 0.5 * (v1_ll + v1_rr) v2_avg = 0.5 * (v2_ll + v2_rr) @@ -584,8 +639,8 @@ end f3 = f1 * v2_avg f4 = f1 * v3_avg + p_avg f5 = f1 * - (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + - 0.5 * (p_ll * v3_rr + p_rr * v3_ll) + (velocity_square_avg + inv_rho_p_mean * equations.inv_gamma_minus_one) + + 0.5 * (p_ll * v3_rr + p_rr * v3_ll) # Add scaled fluxes to RHS factor_k = alpha * derivative_split[k, kk] @@ -608,9 +663,9 @@ end # Finally, we add the temporary RHS computed here to the global RHS in the # given `element`. @turbo for v in eachvariable(equations), - k in eachnode(dg), - j in eachnode(dg), - i in eachnode(dg) + k in eachnode(dg), + j in eachnode(dg), + i in eachnode(dg) _du[v, i, j, k, element] += du[i, j, k, v] end diff --git a/src/solvers/dgsem_tree/dg_3d_parabolic.jl b/src/solvers/dgsem_tree/dg_3d_parabolic.jl index 69956d5834..d209a5c42e 100644 --- a/src/solvers/dgsem_tree/dg_3d_parabolic.jl +++ b/src/solvers/dgsem_tree/dg_3d_parabolic.jl @@ -3,1034 +3,1316 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Transform solution variables prior to taking the gradient -# (e.g., conservative to primitive variables). Defaults to doing nothing. -# TODO: can we avoid copying data? -function transform_variables!(u_transformed, u, mesh::Union{TreeMesh{3}, P4estMesh{3}}, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, parabolic_scheme, cache, cache_parabolic) - transformation = gradient_variable_transformation(equations_parabolic) - - @threaded for element in eachelement(dg, cache) - # Calculate volume terms in one element - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations_parabolic, dg, i, j, k, element) - u_transformed_node = transformation(u_node, equations_parabolic) - set_node_vars!(u_transformed, u_transformed_node, equations_parabolic, dg, - i, j, k, element) + #! format: noindent + + # Transform solution variables prior to taking the gradient + # (e.g., conservative to primitive variables). Defaults to doing nothing. + # TODO: can we avoid copying data? + function transform_variables!( + u_transformed, u, mesh::Union{TreeMesh{3}, P4estMesh{3}}, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, parabolic_scheme, cache, cache_parabolic + ) + transformation = gradient_variable_transformation(equations_parabolic) + + @threaded for element in eachelement(dg, cache) + # Calculate volume terms in one element + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations_parabolic, dg, i, j, k, element) + u_transformed_node = transformation(u_node, equations_parabolic) + set_node_vars!( + u_transformed, u_transformed_node, equations_parabolic, dg, + i, j, k, element + ) + end end end -end - -# This is the version used when calculating the divergence of the viscous fluxes -function calc_volume_integral!(du, flux_viscous, - mesh::TreeMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - dg::DGSEM, cache) - @unpack derivative_dhat = dg.basis - flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous - - @threaded for element in eachelement(dg, cache) - # Calculate volume terms in one element - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - flux_1_node = get_node_vars(flux_viscous_x, equations_parabolic, dg, i, j, - k, element) - flux_2_node = get_node_vars(flux_viscous_y, equations_parabolic, dg, i, j, - k, element) - flux_3_node = get_node_vars(flux_viscous_z, equations_parabolic, dg, i, j, - k, element) - - for ii in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[ii, i], flux_1_node, - equations_parabolic, dg, ii, j, k, element) - end - for jj in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[jj, j], flux_2_node, - equations_parabolic, dg, i, jj, k, element) - end + # This is the version used when calculating the divergence of the viscous fluxes + function calc_volume_integral!( + du, flux_viscous, + mesh::TreeMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + dg::DGSEM, cache + ) + @unpack derivative_dhat = dg.basis + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for element in eachelement(dg, cache) + # Calculate volume terms in one element + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + flux_1_node = get_node_vars( + flux_viscous_x, equations_parabolic, dg, i, j, + k, element + ) + flux_2_node = get_node_vars( + flux_viscous_y, equations_parabolic, dg, i, j, + k, element + ) + flux_3_node = get_node_vars( + flux_viscous_z, equations_parabolic, dg, i, j, + k, element + ) - for kk in eachnode(dg) - multiply_add_to_node_vars!(du, derivative_dhat[kk, k], flux_3_node, - equations_parabolic, dg, i, j, kk, element) + for ii in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[ii, i], flux_1_node, + equations_parabolic, dg, ii, j, k, element + ) + end + + for jj in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[jj, j], flux_2_node, + equations_parabolic, dg, i, jj, k, element + ) + end + + for kk in eachnode(dg) + multiply_add_to_node_vars!( + du, derivative_dhat[kk, k], flux_3_node, + equations_parabolic, dg, i, j, kk, element + ) + end end end + + return nothing end - return nothing -end - -# This is the version used when calculating the divergence of the viscous fluxes -# We pass the `surface_integral` argument solely for dispatch -function prolong2interfaces!(cache_parabolic, flux_viscous, - mesh::TreeMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - @unpack interfaces = cache_parabolic - @unpack orientations, neighbor_ids = interfaces - interfaces_u = interfaces.u - - flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous - - @threaded for interface in eachinterface(dg, cache) - left_element = neighbor_ids[1, interface] - right_element = neighbor_ids[2, interface] - - if orientations[interface] == 1 - # interface in x-direction - for k in eachnode(dg), j in eachnode(dg), - v in eachvariable(equations_parabolic) - # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! - interfaces_u[1, v, j, k, interface] = flux_viscous_x[v, nnodes(dg), j, - k, left_element] - interfaces_u[2, v, j, k, interface] = flux_viscous_x[v, 1, j, k, - right_element] - end - elseif orientations[interface] == 2 - # interface in y-direction - for k in eachnode(dg), i in eachnode(dg), - v in eachvariable(equations_parabolic) - # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! - interfaces_u[1, v, i, k, interface] = flux_viscous_y[v, i, nnodes(dg), - k, left_element] - interfaces_u[2, v, i, k, interface] = flux_viscous_y[v, i, 1, k, - right_element] - end - else # if orientations[interface] == 3 - # interface in z-direction - for j in eachnode(dg), i in eachnode(dg), - v in eachvariable(equations_parabolic) - # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! - interfaces_u[1, v, i, j, interface] = flux_viscous_z[v, i, j, - nnodes(dg), - left_element] - interfaces_u[2, v, i, j, interface] = flux_viscous_z[v, i, j, 1, - right_element] + # This is the version used when calculating the divergence of the viscous fluxes + # We pass the `surface_integral` argument solely for dispatch + function prolong2interfaces!( + cache_parabolic, flux_viscous, + mesh::TreeMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + @unpack interfaces = cache_parabolic + @unpack orientations, neighbor_ids = interfaces + interfaces_u = interfaces.u + + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for interface in eachinterface(dg, cache) + left_element = neighbor_ids[1, interface] + right_element = neighbor_ids[2, interface] + + if orientations[interface] == 1 + # interface in x-direction + for k in eachnode(dg), j in eachnode(dg), + v in eachvariable(equations_parabolic) + # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! + interfaces_u[1, v, j, k, interface] = flux_viscous_x[ + v, nnodes(dg), j, + k, left_element, + ] + interfaces_u[2, v, j, k, interface] = flux_viscous_x[ + v, 1, j, k, + right_element, + ] + end + elseif orientations[interface] == 2 + # interface in y-direction + for k in eachnode(dg), i in eachnode(dg), + v in eachvariable(equations_parabolic) + # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! + interfaces_u[1, v, i, k, interface] = flux_viscous_y[ + v, i, nnodes(dg), + k, left_element, + ] + interfaces_u[2, v, i, k, interface] = flux_viscous_y[ + v, i, 1, k, + right_element, + ] + end + else # if orientations[interface] == 3 + # interface in z-direction + for j in eachnode(dg), i in eachnode(dg), + v in eachvariable(equations_parabolic) + # OBS! `interfaces_u` stores the interpolated *fluxes* and *not the solution*! + interfaces_u[1, v, i, j, interface] = flux_viscous_z[ + v, i, j, + nnodes(dg), + left_element, + ] + interfaces_u[2, v, i, j, interface] = flux_viscous_z[ + v, i, j, 1, + right_element, + ] + end end end - end - return nothing -end + return nothing + end -# This is the version used when calculating the divergence of the viscous fluxes -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{3}, equations_parabolic, - dg::DG, cache_parabolic) - @unpack neighbor_ids, orientations = cache_parabolic.interfaces + # This is the version used when calculating the divergence of the viscous fluxes + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{3}, equations_parabolic, + dg::DG, cache_parabolic + ) + @unpack neighbor_ids, orientations = cache_parabolic.interfaces - @threaded for interface in eachinterface(dg, cache_parabolic) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] + @threaded for interface in eachinterface(dg, cache_parabolic) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - # orientation = 3: left -> 6, right -> 5 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + # orientation = 3: left -> 6, right -> 5 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 - for j in eachnode(dg), i in eachnode(dg) - # Get precomputed fluxes at interfaces - flux_ll, flux_rr = get_surface_node_vars(cache_parabolic.interfaces.u, - equations_parabolic, - dg, i, j, interface) + for j in eachnode(dg), i in eachnode(dg) + # Get precomputed fluxes at interfaces + flux_ll, flux_rr = get_surface_node_vars( + cache_parabolic.interfaces.u, + equations_parabolic, + dg, i, j, interface + ) - # Compute interface flux as mean of left and right viscous fluxes - # TODO: parabolic; only BR1 at the moment - flux = 0.5f0 * (flux_ll + flux_rr) + # Compute interface flux as mean of left and right viscous fluxes + # TODO: parabolic; only BR1 at the moment + flux = 0.5f0 * (flux_ll + flux_rr) - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, j, left_direction, left_id] = flux[v] - surface_flux_values[v, i, j, right_direction, right_id] = flux[v] + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, j, left_direction, left_id] = flux[v] + surface_flux_values[v, i, j, right_direction, right_id] = flux[v] + end end end + + return nothing end - return nothing -end - -# This is the version used when calculating the divergence of the viscous fluxes -function prolong2boundaries!(cache_parabolic, flux_viscous, - mesh::TreeMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache) - @unpack boundaries = cache_parabolic - @unpack orientations, neighbor_sides, neighbor_ids = boundaries - boundaries_u = boundaries.u - flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous - - @threaded for boundary in eachboundary(dg, cache_parabolic) - element = neighbor_ids[boundary] - - if orientations[boundary] == 1 - # boundary in x-direction - if neighbor_sides[boundary] == 1 - # element in -x direction of boundary - for k in eachnode(dg), j in eachnode(dg), - v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[1, v, j, k, boundary] = flux_viscous_x[v, nnodes(dg), - j, k, element] - end - else # Element in +x direction of boundary - for k in eachnode(dg), j in eachnode(dg), - v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[2, v, j, k, boundary] = flux_viscous_x[v, 1, j, k, - element] + # This is the version used when calculating the divergence of the viscous fluxes + function prolong2boundaries!( + cache_parabolic, flux_viscous, + mesh::TreeMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache + ) + @unpack boundaries = cache_parabolic + @unpack orientations, neighbor_sides, neighbor_ids = boundaries + boundaries_u = boundaries.u + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + + @threaded for boundary in eachboundary(dg, cache_parabolic) + element = neighbor_ids[boundary] + + if orientations[boundary] == 1 + # boundary in x-direction + if neighbor_sides[boundary] == 1 + # element in -x direction of boundary + for k in eachnode(dg), j in eachnode(dg), + v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[1, v, j, k, boundary] = flux_viscous_x[ + v, nnodes(dg), + j, k, element, + ] + end + else # Element in +x direction of boundary + for k in eachnode(dg), j in eachnode(dg), + v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[2, v, j, k, boundary] = flux_viscous_x[ + v, 1, j, k, + element, + ] + end end - end - elseif orientations[boundary] == 2 - # boundary in y-direction - if neighbor_sides[boundary] == 1 - # element in -y direction of boundary - for k in eachnode(dg), i in eachnode(dg), - v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[1, v, i, k, boundary] = flux_viscous_y[v, i, - nnodes(dg), k, - element] + elseif orientations[boundary] == 2 + # boundary in y-direction + if neighbor_sides[boundary] == 1 + # element in -y direction of boundary + for k in eachnode(dg), i in eachnode(dg), + v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[1, v, i, k, boundary] = flux_viscous_y[ + v, i, + nnodes(dg), k, + element, + ] + end + else + # element in +y direction of boundary + for k in eachnode(dg), i in eachnode(dg), + v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[2, v, i, k, boundary] = flux_viscous_y[ + v, i, 1, k, + element, + ] + end end - else - # element in +y direction of boundary - for k in eachnode(dg), i in eachnode(dg), - v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[2, v, i, k, boundary] = flux_viscous_y[v, i, 1, k, - element] + else # if orientations[boundary] == 3 + # boundary in z-direction + if neighbor_sides[boundary] == 1 + # element in -z direction of boundary + for j in eachnode(dg), i in eachnode(dg), + v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[1, v, i, j, boundary] = flux_viscous_z[ + v, i, j, + nnodes(dg), + element, + ] + end + else + # element in +z direction of boundary + for j in eachnode(dg), i in eachnode(dg), + v in eachvariable(equations_parabolic) + # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! + boundaries_u[2, v, i, j, boundary] = flux_viscous_z[ + v, i, j, 1, + element, + ] + end end end - else # if orientations[boundary] == 3 - # boundary in z-direction - if neighbor_sides[boundary] == 1 - # element in -z direction of boundary - for j in eachnode(dg), i in eachnode(dg), - v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[1, v, i, j, boundary] = flux_viscous_z[v, i, j, - nnodes(dg), - element] - end - else - # element in +z direction of boundary - for j in eachnode(dg), i in eachnode(dg), - v in eachvariable(equations_parabolic) - # OBS! `boundaries_u` stores the interpolated *fluxes* and *not the solution*! - boundaries_u[2, v, i, j, boundary] = flux_viscous_z[v, i, j, 1, - element] - end + end + + return nothing + end + + function calc_viscous_fluxes!( + flux_viscous, + gradients, u_transformed, + mesh::Union{TreeMesh{3}, P4estMesh{3}}, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, cache, cache_parabolic + ) + gradients_x, gradients_y, gradients_z = gradients + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous # output arrays + + @threaded for element in eachelement(dg, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + # Get solution and gradients + u_node = get_node_vars( + u_transformed, equations_parabolic, dg, i, j, k, + element + ) + gradients_1_node = get_node_vars( + gradients_x, equations_parabolic, dg, i, j, + k, element + ) + gradients_2_node = get_node_vars( + gradients_y, equations_parabolic, dg, i, j, + k, element + ) + gradients_3_node = get_node_vars( + gradients_z, equations_parabolic, dg, i, j, + k, element + ) + + # Calculate viscous flux and store each component for later use + flux_viscous_node_x = flux( + u_node, + ( + gradients_1_node, gradients_2_node, + gradients_3_node, + ), 1, equations_parabolic + ) + flux_viscous_node_y = flux( + u_node, + ( + gradients_1_node, gradients_2_node, + gradients_3_node, + ), 2, equations_parabolic + ) + flux_viscous_node_z = flux( + u_node, + ( + gradients_1_node, gradients_2_node, + gradients_3_node, + ), 3, equations_parabolic + ) + set_node_vars!( + flux_viscous_x, flux_viscous_node_x, equations_parabolic, dg, + i, j, k, element + ) + set_node_vars!( + flux_viscous_y, flux_viscous_node_y, equations_parabolic, dg, + i, j, k, element + ) + set_node_vars!( + flux_viscous_z, flux_viscous_node_z, equations_parabolic, dg, + i, j, k, element + ) end end end - return nothing -end - -function calc_viscous_fluxes!(flux_viscous, - gradients, u_transformed, - mesh::Union{TreeMesh{3}, P4estMesh{3}}, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, cache, cache_parabolic) - gradients_x, gradients_y, gradients_z = gradients - flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous # output arrays - - @threaded for element in eachelement(dg, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - # Get solution and gradients - u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, j, k, - element) - gradients_1_node = get_node_vars(gradients_x, equations_parabolic, dg, i, j, - k, element) - gradients_2_node = get_node_vars(gradients_y, equations_parabolic, dg, i, j, - k, element) - gradients_3_node = get_node_vars(gradients_z, equations_parabolic, dg, i, j, - k, element) - - # Calculate viscous flux and store each component for later use - flux_viscous_node_x = flux(u_node, - (gradients_1_node, gradients_2_node, - gradients_3_node), 1, equations_parabolic) - flux_viscous_node_y = flux(u_node, - (gradients_1_node, gradients_2_node, - gradients_3_node), 2, equations_parabolic) - flux_viscous_node_z = flux(u_node, - (gradients_1_node, gradients_2_node, - gradients_3_node), 3, equations_parabolic) - set_node_vars!(flux_viscous_x, flux_viscous_node_x, equations_parabolic, dg, - i, j, k, element) - set_node_vars!(flux_viscous_y, flux_viscous_node_y, equations_parabolic, dg, - i, j, k, element) - set_node_vars!(flux_viscous_z, flux_viscous_node_z, equations_parabolic, dg, - i, j, k, element) + # TODO: parabolic; decide if we should keep this. + function get_unsigned_normal_vector_3d(direction) + if direction > 6 || direction < 1 + error("Direction = $direction; in 3D, direction should be 1, 2, 3, 4, 5, or 6.") + end + if direction == 1 || direction == 2 + return SVector(1.0, 0.0, 0.0) + elseif direction == 3 || direction == 4 + return SVector(0.0, 1.0, 0.0) + else + return SVector(0.0, 0.0, 1.0) end end -end -# TODO: parabolic; decide if we should keep this. -function get_unsigned_normal_vector_3d(direction) - if direction > 6 || direction < 1 - error("Direction = $direction; in 3D, direction should be 1, 2, 3, 4, 5, or 6.") + function calc_boundary_flux_gradients!( + cache, t, + boundary_conditions_parabolic::BoundaryConditionPeriodic, + mesh::Union{TreeMesh{3}, P4estMesh{3}}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + return nothing end - if direction == 1 || direction == 2 - return SVector(1.0, 0.0, 0.0) - elseif direction == 3 || direction == 4 - return SVector(0.0, 1.0, 0.0) - else - return SVector(0.0, 0.0, 1.0) + + function calc_boundary_flux_divergence!( + cache, t, + boundary_conditions_parabolic::BoundaryConditionPeriodic, + mesh::Union{TreeMesh{3}, P4estMesh{3}}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + return nothing end -end - -function calc_boundary_flux_gradients!(cache, t, - boundary_conditions_parabolic::BoundaryConditionPeriodic, - mesh::Union{TreeMesh{3}, P4estMesh{3}}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - return nothing -end - -function calc_boundary_flux_divergence!(cache, t, - boundary_conditions_parabolic::BoundaryConditionPeriodic, - mesh::Union{TreeMesh{3}, P4estMesh{3}}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - return nothing -end - -function calc_boundary_flux_gradients!(cache, t, - boundary_conditions_parabolic::NamedTuple, - mesh::TreeMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - @unpack surface_flux_values = cache.elements - @unpack n_boundaries_per_direction = cache.boundaries - - # Calculate indices - lasts = accumulate(+, n_boundaries_per_direction) - firsts = lasts - n_boundaries_per_direction .+ 1 - - # Calc boundary fluxes in each direction - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[1], - equations_parabolic, surface_integral, dg, - cache, - 1, firsts[1], lasts[1]) - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[2], - equations_parabolic, surface_integral, dg, - cache, - 2, firsts[2], lasts[2]) - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[3], - equations_parabolic, surface_integral, dg, - cache, - 3, firsts[3], lasts[3]) - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[4], - equations_parabolic, surface_integral, dg, - cache, - 4, firsts[4], lasts[4]) - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[5], - equations_parabolic, surface_integral, dg, - cache, - 5, firsts[5], lasts[5]) - calc_boundary_flux_by_direction_gradient!(surface_flux_values, t, - boundary_conditions_parabolic[6], - equations_parabolic, surface_integral, dg, - cache, - 6, firsts[6], lasts[6]) -end - -function calc_boundary_flux_by_direction_gradient!(surface_flux_values::AbstractArray{<:Any, - 5}, - t, - boundary_condition, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - direction, first_boundary, - last_boundary) - @unpack surface_flux = surface_integral - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] - for j in eachnode(dg), i in eachnode(dg) - # Get boundary flux - u_ll, u_rr = get_surface_node_vars(u, equations_parabolic, dg, i, j, - boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - u_inner = u_ll - else # Element is on the right, boundary on the left - u_inner = u_rr - end + function calc_boundary_flux_gradients!( + cache, t, + boundary_conditions_parabolic::NamedTuple, + mesh::TreeMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + @unpack surface_flux_values = cache.elements + @unpack n_boundaries_per_direction = cache.boundaries + + # Calculate indices + lasts = accumulate(+, n_boundaries_per_direction) + firsts = lasts - n_boundaries_per_direction .+ 1 + + # Calc boundary fluxes in each direction + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[1], + equations_parabolic, surface_integral, dg, + cache, + 1, firsts[1], lasts[1] + ) + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[2], + equations_parabolic, surface_integral, dg, + cache, + 2, firsts[2], lasts[2] + ) + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[3], + equations_parabolic, surface_integral, dg, + cache, + 3, firsts[3], lasts[3] + ) + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[4], + equations_parabolic, surface_integral, dg, + cache, + 4, firsts[4], lasts[4] + ) + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[5], + equations_parabolic, surface_integral, dg, + cache, + 5, firsts[5], lasts[5] + ) + calc_boundary_flux_by_direction_gradient!( + surface_flux_values, t, + boundary_conditions_parabolic[6], + equations_parabolic, surface_integral, dg, + cache, + 6, firsts[6], lasts[6] + ) + end - # TODO: revisit if we want more general boundary treatments. - # This assumes the gradient numerical flux at the boundary is the gradient variable, - # which is consistent with BR1, LDG. - flux_inner = u_inner + function calc_boundary_flux_by_direction_gradient!( + surface_flux_values::AbstractArray{ + <:Any, + 5, + }, + t, + boundary_condition, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + direction, first_boundary, + last_boundary + ) + @unpack surface_flux = surface_integral + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] - x = get_node_coords(node_coordinates, equations_parabolic, dg, i, j, - boundary) - flux = boundary_condition(flux_inner, u_inner, - get_unsigned_normal_vector_3d(direction), - x, t, Gradient(), equations_parabolic) + for j in eachnode(dg), i in eachnode(dg) + # Get boundary flux + u_ll, u_rr = get_surface_node_vars( + u, equations_parabolic, dg, i, j, + boundary + ) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + u_inner = u_ll + else # Element is on the right, boundary on the left + u_inner = u_rr + end - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, j, direction, neighbor] = flux[v] + # TODO: revisit if we want more general boundary treatments. + # This assumes the gradient numerical flux at the boundary is the gradient variable, + # which is consistent with BR1, LDG. + flux_inner = u_inner + + x = get_node_coords( + node_coordinates, equations_parabolic, dg, i, j, + boundary + ) + flux = boundary_condition( + flux_inner, u_inner, + get_unsigned_normal_vector_3d(direction), + x, t, Gradient(), equations_parabolic + ) + + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, j, direction, neighbor] = flux[v] + end end end - end - return nothing -end - -function calc_boundary_flux_divergence!(cache, t, - boundary_conditions_parabolic::NamedTuple, - mesh::TreeMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG) - @unpack surface_flux_values = cache.elements - @unpack n_boundaries_per_direction = cache.boundaries - - # Calculate indices - lasts = accumulate(+, n_boundaries_per_direction) - firsts = lasts - n_boundaries_per_direction .+ 1 - - # Calc boundary fluxes in each direction - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[1], - equations_parabolic, surface_integral, - dg, cache, - 1, firsts[1], lasts[1]) - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[2], - equations_parabolic, surface_integral, - dg, cache, - 2, firsts[2], lasts[2]) - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[3], - equations_parabolic, surface_integral, - dg, cache, - 3, firsts[3], lasts[3]) - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[4], - equations_parabolic, surface_integral, - dg, cache, - 4, firsts[4], lasts[4]) - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[5], - equations_parabolic, surface_integral, - dg, cache, - 5, firsts[5], lasts[5]) - calc_boundary_flux_by_direction_divergence!(surface_flux_values, t, - boundary_conditions_parabolic[6], - equations_parabolic, surface_integral, - dg, cache, - 6, firsts[6], lasts[6]) -end -function calc_boundary_flux_by_direction_divergence!(surface_flux_values::AbstractArray{<:Any, - 5}, - t, - boundary_condition, - equations_parabolic::AbstractEquationsParabolic, - surface_integral, dg::DG, cache, - direction, first_boundary, - last_boundary) - @unpack surface_flux = surface_integral - - # Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction") - # of the viscous flux, as computed in `prolong2boundaries!` - @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries - - @threaded for boundary in first_boundary:last_boundary - # Get neighboring element - neighbor = neighbor_ids[boundary] + return nothing + end - for j in eachnode(dg), i in eachnode(dg) - # Get viscous boundary fluxes - flux_ll, flux_rr = get_surface_node_vars(u, equations_parabolic, dg, i, j, - boundary) - if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right - flux_inner = flux_ll - else # Element is on the right, boundary on the left - flux_inner = flux_rr - end + function calc_boundary_flux_divergence!( + cache, t, + boundary_conditions_parabolic::NamedTuple, + mesh::TreeMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG + ) + @unpack surface_flux_values = cache.elements + @unpack n_boundaries_per_direction = cache.boundaries + + # Calculate indices + lasts = accumulate(+, n_boundaries_per_direction) + firsts = lasts - n_boundaries_per_direction .+ 1 + + # Calc boundary fluxes in each direction + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[1], + equations_parabolic, surface_integral, + dg, cache, + 1, firsts[1], lasts[1] + ) + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[2], + equations_parabolic, surface_integral, + dg, cache, + 2, firsts[2], lasts[2] + ) + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[3], + equations_parabolic, surface_integral, + dg, cache, + 3, firsts[3], lasts[3] + ) + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[4], + equations_parabolic, surface_integral, + dg, cache, + 4, firsts[4], lasts[4] + ) + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[5], + equations_parabolic, surface_integral, + dg, cache, + 5, firsts[5], lasts[5] + ) + calc_boundary_flux_by_direction_divergence!( + surface_flux_values, t, + boundary_conditions_parabolic[6], + equations_parabolic, surface_integral, + dg, cache, + 6, firsts[6], lasts[6] + ) + end + function calc_boundary_flux_by_direction_divergence!( + surface_flux_values::AbstractArray{ + <:Any, + 5, + }, + t, + boundary_condition, + equations_parabolic::AbstractEquationsParabolic, + surface_integral, dg::DG, cache, + direction, first_boundary, + last_boundary + ) + @unpack surface_flux = surface_integral + + # Note: cache.boundaries.u contains the unsigned normal component (using "orientation", not "direction") + # of the viscous flux, as computed in `prolong2boundaries!` + @unpack u, neighbor_ids, neighbor_sides, node_coordinates, orientations = cache.boundaries + + @threaded for boundary in first_boundary:last_boundary + # Get neighboring element + neighbor = neighbor_ids[boundary] - x = get_node_coords(node_coordinates, equations_parabolic, dg, i, j, - boundary) + for j in eachnode(dg), i in eachnode(dg) + # Get viscous boundary fluxes + flux_ll, flux_rr = get_surface_node_vars( + u, equations_parabolic, dg, i, j, + boundary + ) + if neighbor_sides[boundary] == 1 # Element is on the left, boundary on the right + flux_inner = flux_ll + else # Element is on the right, boundary on the left + flux_inner = flux_rr + end - # TODO: add a field in `cache.boundaries` for gradient information. UPDATE THIS COMMENT - # Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information. - # This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion3D and - # NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion3D as of 2022-6-27. - # It will not work with implementations which utilize `u_inner` to impose boundary conditions. - flux = boundary_condition(flux_inner, nothing, - get_unsigned_normal_vector_3d(direction), - x, t, Divergence(), equations_parabolic) + x = get_node_coords( + node_coordinates, equations_parabolic, dg, i, j, + boundary + ) + + # TODO: add a field in `cache.boundaries` for gradient information. UPDATE THIS COMMENT + # Here, we pass in `u_inner = nothing` since we overwrite cache.boundaries.u with gradient information. + # This currently works with Dirichlet/Neuman boundary conditions for LaplaceDiffusion3D and + # NoSlipWall/Adiabatic boundary conditions for CompressibleNavierStokesDiffusion3D as of 2022-6-27. + # It will not work with implementations which utilize `u_inner` to impose boundary conditions. + flux = boundary_condition( + flux_inner, nothing, + get_unsigned_normal_vector_3d(direction), + x, t, Divergence(), equations_parabolic + ) - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, j, direction, neighbor] = flux[v] + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, j, direction, neighbor] = flux[v] + end end end + + return nothing end - return nothing -end - -# `cache` is the hyperbolic cache, i.e., in particular not `cache_parabolic`. -# This is because mortar handling is done in the (hyperbolic) `cache`. -# Specialization `flux_viscous::Vector{Array{uEltype, 4}}` needed since -#`prolong2mortars!` in dg_2d.jl is used for both purely hyperbolic and -# hyperbolic-parabolic systems. -function prolong2mortars!(cache, - flux_viscous::Vector{Array{uEltype, 5}}, - mesh::TreeMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DGSEM) where {uEltype <: Real} - # temporary buffer for projections - @unpack fstar_tmp1_threaded = cache - - flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous - @threaded for mortar in eachmortar(dg, cache) - fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] - - lower_left_element = cache.mortars.neighbor_ids[1, mortar] - lower_right_element = cache.mortars.neighbor_ids[2, mortar] - upper_left_element = cache.mortars.neighbor_ids[3, mortar] - upper_right_element = cache.mortars.neighbor_ids[4, mortar] - large_element = cache.mortars.neighbor_ids[5, mortar] - - # Copy solution small to small - if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - for k in eachnode(dg), j in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper_left[2, v, j, k, mortar] = flux_viscous_x[v, - 1, - j, - k, - upper_left_element] - cache.mortars.u_upper_right[2, v, j, k, mortar] = flux_viscous_x[v, - 1, - j, - k, - upper_right_element] - cache.mortars.u_lower_left[2, v, j, k, mortar] = flux_viscous_x[v, - 1, - j, - k, - lower_left_element] - cache.mortars.u_lower_right[2, v, j, k, mortar] = flux_viscous_x[v, - 1, - j, - k, - lower_right_element] + # `cache` is the hyperbolic cache, i.e., in particular not `cache_parabolic`. + # This is because mortar handling is done in the (hyperbolic) `cache`. + # Specialization `flux_viscous::Vector{Array{uEltype, 4}}` needed since + #`prolong2mortars!` in dg_2d.jl is used for both purely hyperbolic and + # hyperbolic-parabolic systems. + function prolong2mortars!( + cache, + flux_viscous::Vector{Array{uEltype, 5}}, + mesh::TreeMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DGSEM + ) where {uEltype <: Real} + # temporary buffer for projections + @unpack fstar_tmp1_threaded = cache + + flux_viscous_x, flux_viscous_y, flux_viscous_z = flux_viscous + @threaded for mortar in eachmortar(dg, cache) + fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] + + lower_left_element = cache.mortars.neighbor_ids[1, mortar] + lower_right_element = cache.mortars.neighbor_ids[2, mortar] + upper_left_element = cache.mortars.neighbor_ids[3, mortar] + upper_right_element = cache.mortars.neighbor_ids[4, mortar] + large_element = cache.mortars.neighbor_ids[5, mortar] + + # Copy solution small to small + if cache.mortars.large_sides[mortar] == 1 # -> small elements on right side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + for k in eachnode(dg), j in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper_left[2, v, j, k, mortar] = flux_viscous_x[ + v, + 1, + j, + k, + upper_left_element, + ] + cache.mortars.u_upper_right[2, v, j, k, mortar] = flux_viscous_x[ + v, + 1, + j, + k, + upper_right_element, + ] + cache.mortars.u_lower_left[2, v, j, k, mortar] = flux_viscous_x[ + v, + 1, + j, + k, + lower_left_element, + ] + cache.mortars.u_lower_right[2, v, j, k, mortar] = flux_viscous_x[ + v, + 1, + j, + k, + lower_right_element, + ] + end end - end - elseif cache.mortars.orientations[mortar] == 2 - # L2 mortars in y-direction - for k in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper_left[2, v, i, k, mortar] = flux_viscous_y[v, - i, - 1, - k, - upper_left_element] - cache.mortars.u_upper_right[2, v, i, k, mortar] = flux_viscous_y[v, - i, - 1, - k, - upper_right_element] - cache.mortars.u_lower_left[2, v, i, k, mortar] = flux_viscous_y[v, - i, - 1, - k, - lower_left_element] - cache.mortars.u_lower_right[2, v, i, k, mortar] = flux_viscous_y[v, - i, - 1, - k, - lower_right_element] + elseif cache.mortars.orientations[mortar] == 2 + # L2 mortars in y-direction + for k in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper_left[2, v, i, k, mortar] = flux_viscous_y[ + v, + i, + 1, + k, + upper_left_element, + ] + cache.mortars.u_upper_right[2, v, i, k, mortar] = flux_viscous_y[ + v, + i, + 1, + k, + upper_right_element, + ] + cache.mortars.u_lower_left[2, v, i, k, mortar] = flux_viscous_y[ + v, + i, + 1, + k, + lower_left_element, + ] + cache.mortars.u_lower_right[2, v, i, k, mortar] = flux_viscous_y[ + v, + i, + 1, + k, + lower_right_element, + ] + end end - end - else # orientations[mortar] == 3 - # L2 mortars in z-direction - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper_left[2, v, i, j, mortar] = flux_viscous_z[v, - i, - j, - 1, - upper_left_element] - cache.mortars.u_upper_right[2, v, i, j, mortar] = flux_viscous_z[v, - i, - j, - 1, - upper_right_element] - cache.mortars.u_lower_left[2, v, i, j, mortar] = flux_viscous_z[v, - i, - j, - 1, - lower_left_element] - cache.mortars.u_lower_right[2, v, i, j, mortar] = flux_viscous_z[v, - i, - j, - 1, - lower_right_element] + else # orientations[mortar] == 3 + # L2 mortars in z-direction + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper_left[2, v, i, j, mortar] = flux_viscous_z[ + v, + i, + j, + 1, + upper_left_element, + ] + cache.mortars.u_upper_right[2, v, i, j, mortar] = flux_viscous_z[ + v, + i, + j, + 1, + upper_right_element, + ] + cache.mortars.u_lower_left[2, v, i, j, mortar] = flux_viscous_z[ + v, + i, + j, + 1, + lower_left_element, + ] + cache.mortars.u_lower_right[2, v, i, j, mortar] = flux_viscous_z[ + v, + i, + j, + 1, + lower_right_element, + ] + end end end - end - else # large_sides[mortar] == 2 -> small elements on left side - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - for k in eachnode(dg), j in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper_left[1, v, j, k, mortar] = flux_viscous_x[v, - nnodes(dg), - j, - k, - upper_left_element] - cache.mortars.u_upper_right[1, v, j, k, mortar] = flux_viscous_x[v, - nnodes(dg), - j, - k, - upper_right_element] - cache.mortars.u_lower_left[1, v, j, k, mortar] = flux_viscous_x[v, - nnodes(dg), - j, - k, - lower_left_element] - cache.mortars.u_lower_right[1, v, j, k, mortar] = flux_viscous_x[v, - nnodes(dg), - j, - k, - lower_right_element] + else # large_sides[mortar] == 2 -> small elements on left side + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + for k in eachnode(dg), j in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper_left[1, v, j, k, mortar] = flux_viscous_x[ + v, + nnodes(dg), + j, + k, + upper_left_element, + ] + cache.mortars.u_upper_right[1, v, j, k, mortar] = flux_viscous_x[ + v, + nnodes(dg), + j, + k, + upper_right_element, + ] + cache.mortars.u_lower_left[1, v, j, k, mortar] = flux_viscous_x[ + v, + nnodes(dg), + j, + k, + lower_left_element, + ] + cache.mortars.u_lower_right[1, v, j, k, mortar] = flux_viscous_x[ + v, + nnodes(dg), + j, + k, + lower_right_element, + ] + end end - end - elseif cache.mortars.orientations[mortar] == 2 - # L2 mortars in y-direction - for k in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper_left[1, v, i, k, mortar] = flux_viscous_y[v, - i, - nnodes(dg), - k, - upper_left_element] - cache.mortars.u_upper_right[1, v, i, k, mortar] = flux_viscous_y[v, - i, - nnodes(dg), - k, - upper_right_element] - cache.mortars.u_lower_left[1, v, i, k, mortar] = flux_viscous_y[v, - i, - nnodes(dg), - k, - lower_left_element] - cache.mortars.u_lower_right[1, v, i, k, mortar] = flux_viscous_y[v, - i, - nnodes(dg), - k, - lower_right_element] + elseif cache.mortars.orientations[mortar] == 2 + # L2 mortars in y-direction + for k in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper_left[1, v, i, k, mortar] = flux_viscous_y[ + v, + i, + nnodes(dg), + k, + upper_left_element, + ] + cache.mortars.u_upper_right[1, v, i, k, mortar] = flux_viscous_y[ + v, + i, + nnodes(dg), + k, + upper_right_element, + ] + cache.mortars.u_lower_left[1, v, i, k, mortar] = flux_viscous_y[ + v, + i, + nnodes(dg), + k, + lower_left_element, + ] + cache.mortars.u_lower_right[1, v, i, k, mortar] = flux_viscous_y[ + v, + i, + nnodes(dg), + k, + lower_right_element, + ] + end end - end - else # if cache.mortars.orientations[mortar] == 3 - # L2 mortars in z-direction - for j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations_parabolic) - cache.mortars.u_upper_left[1, v, i, j, mortar] = flux_viscous_z[v, - i, - j, - nnodes(dg), - upper_left_element] - cache.mortars.u_upper_right[1, v, i, j, mortar] = flux_viscous_z[v, - i, - j, - nnodes(dg), - upper_right_element] - cache.mortars.u_lower_left[1, v, i, j, mortar] = flux_viscous_z[v, - i, - j, - nnodes(dg), - lower_left_element] - cache.mortars.u_lower_right[1, v, i, j, mortar] = flux_viscous_z[v, - i, - j, - nnodes(dg), - lower_right_element] + else # if cache.mortars.orientations[mortar] == 3 + # L2 mortars in z-direction + for j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations_parabolic) + cache.mortars.u_upper_left[1, v, i, j, mortar] = flux_viscous_z[ + v, + i, + j, + nnodes(dg), + upper_left_element, + ] + cache.mortars.u_upper_right[1, v, i, j, mortar] = flux_viscous_z[ + v, + i, + j, + nnodes(dg), + upper_right_element, + ] + cache.mortars.u_lower_left[1, v, i, j, mortar] = flux_viscous_z[ + v, + i, + j, + nnodes(dg), + lower_left_element, + ] + cache.mortars.u_lower_right[1, v, i, j, mortar] = flux_viscous_z[ + v, + i, + j, + nnodes(dg), + lower_right_element, + ] + end end end end - end - # Interpolate large element face data to small interface locations - if cache.mortars.large_sides[mortar] == 1 # -> large element on left side - leftright = 1 - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - u_large = view(flux_viscous_x, :, nnodes(dg), :, :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) - elseif cache.mortars.orientations[mortar] == 2 - # L2 mortars in y-direction - u_large = view(flux_viscous_y, :, :, nnodes(dg), :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) - else # cache.mortars.orientations[mortar] == 3 - # L2 mortars in z-direction - u_large = view(flux_viscous_z, :, :, :, nnodes(dg), large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) - end - else # large_sides[mortar] == 2 -> large element on right side - leftright = 2 - if cache.mortars.orientations[mortar] == 1 - # L2 mortars in x-direction - u_large = view(flux_viscous_x, :, 1, :, :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) - elseif cache.mortars.orientations[mortar] == 2 - # L2 mortars in y-direction - u_large = view(flux_viscous_y, :, :, 1, :, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) - else # cache.mortars.orientations[mortar] == 3 - # L2 mortars in z-direction - u_large = view(flux_viscous_z, :, :, :, 1, large_element) - element_solutions_to_mortars!(cache.mortars, mortar_l2, leftright, - mortar, u_large, fstar_tmp1) + # Interpolate large element face data to small interface locations + if cache.mortars.large_sides[mortar] == 1 # -> large element on left side + leftright = 1 + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(flux_viscous_x, :, nnodes(dg), :, :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + elseif cache.mortars.orientations[mortar] == 2 + # L2 mortars in y-direction + u_large = view(flux_viscous_y, :, :, nnodes(dg), :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + else # cache.mortars.orientations[mortar] == 3 + # L2 mortars in z-direction + u_large = view(flux_viscous_z, :, :, :, nnodes(dg), large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + end + else # large_sides[mortar] == 2 -> large element on right side + leftright = 2 + if cache.mortars.orientations[mortar] == 1 + # L2 mortars in x-direction + u_large = view(flux_viscous_x, :, 1, :, :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + elseif cache.mortars.orientations[mortar] == 2 + # L2 mortars in y-direction + u_large = view(flux_viscous_y, :, :, 1, :, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + else # cache.mortars.orientations[mortar] == 3 + # L2 mortars in z-direction + u_large = view(flux_viscous_z, :, :, :, 1, large_element) + element_solutions_to_mortars!( + cache.mortars, mortar_l2, leftright, + mortar, u_large, fstar_tmp1 + ) + end end end - end - return nothing -end - -# NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms. -# Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for -# hyperbolic terms with conserved terms only, i.e., no nonconservative terms. -function calc_mortar_flux!(surface_flux_values, - mesh::TreeMesh{3}, - equations_parabolic::AbstractEquationsParabolic, - mortar_l2::LobattoLegendreMortarL2, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u_lower_left, u_lower_right, u_upper_left, u_upper_right, orientations = cache.mortars - @unpack (fstar_upper_left_threaded, fstar_upper_right_threaded, - fstar_lower_left_threaded, fstar_lower_right_threaded, - fstar_tmp1_threaded) = cache - - @threaded for mortar in eachmortar(dg, cache) - # Choose thread-specific pre-allocated container - fstar_upper_left = fstar_upper_left_threaded[Threads.threadid()] - fstar_upper_right = fstar_upper_right_threaded[Threads.threadid()] - fstar_lower_left = fstar_lower_left_threaded[Threads.threadid()] - fstar_lower_right = fstar_lower_right_threaded[Threads.threadid()] - fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] - - # Calculate fluxes - orientation = orientations[mortar] - calc_fstar!(fstar_upper_left, equations_parabolic, surface_flux, dg, - u_upper_left, mortar, - orientation) - calc_fstar!(fstar_upper_right, equations_parabolic, surface_flux, dg, - u_upper_right, - mortar, orientation) - calc_fstar!(fstar_lower_left, equations_parabolic, surface_flux, dg, - u_lower_left, mortar, - orientation) - calc_fstar!(fstar_lower_right, equations_parabolic, surface_flux, dg, - u_lower_right, - mortar, orientation) - - mortar_fluxes_to_elements!(surface_flux_values, - mesh, equations_parabolic, mortar_l2, dg, cache, - mortar, - fstar_upper_left, fstar_upper_right, - fstar_lower_left, fstar_lower_right, - fstar_tmp1) + return nothing end - return nothing -end - -@inline function calc_fstar!(destination::AbstractArray{<:Any, 3}, - equations_parabolic::AbstractEquationsParabolic, - surface_flux, dg::DGSEM, - u_interfaces, interface, orientation) - for j in eachnode(dg), i in eachnode(dg) - # Call pointwise two-point numerical flux function - u_ll, u_rr = get_surface_node_vars(u_interfaces, equations_parabolic, dg, i, j, - interface) - # TODO: parabolic; only BR1 at the moment - flux = 0.5f0 * (u_ll + u_rr) - - # Copy flux to left and right element storage - set_node_vars!(destination, flux, equations_parabolic, dg, i, j) + # NOTE: Use analogy to "calc_mortar_flux!" for hyperbolic eqs with no nonconservative terms. + # Reasoning: "calc_interface_flux!" for parabolic part is implemented as the version for + # hyperbolic terms with conserved terms only, i.e., no nonconservative terms. + function calc_mortar_flux!( + surface_flux_values, + mesh::TreeMesh{3}, + equations_parabolic::AbstractEquationsParabolic, + mortar_l2::LobattoLegendreMortarL2, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u_lower_left, u_lower_right, u_upper_left, u_upper_right, orientations = cache.mortars + @unpack ( + fstar_upper_left_threaded, fstar_upper_right_threaded, + fstar_lower_left_threaded, fstar_lower_right_threaded, + fstar_tmp1_threaded, + ) = cache + + @threaded for mortar in eachmortar(dg, cache) + # Choose thread-specific pre-allocated container + fstar_upper_left = fstar_upper_left_threaded[Threads.threadid()] + fstar_upper_right = fstar_upper_right_threaded[Threads.threadid()] + fstar_lower_left = fstar_lower_left_threaded[Threads.threadid()] + fstar_lower_right = fstar_lower_right_threaded[Threads.threadid()] + fstar_tmp1 = fstar_tmp1_threaded[Threads.threadid()] + + # Calculate fluxes + orientation = orientations[mortar] + calc_fstar!( + fstar_upper_left, equations_parabolic, surface_flux, dg, + u_upper_left, mortar, + orientation + ) + calc_fstar!( + fstar_upper_right, equations_parabolic, surface_flux, dg, + u_upper_right, + mortar, orientation + ) + calc_fstar!( + fstar_lower_left, equations_parabolic, surface_flux, dg, + u_lower_left, mortar, + orientation + ) + calc_fstar!( + fstar_lower_right, equations_parabolic, surface_flux, dg, + u_lower_right, + mortar, orientation + ) + + mortar_fluxes_to_elements!( + surface_flux_values, + mesh, equations_parabolic, mortar_l2, dg, cache, + mortar, + fstar_upper_left, fstar_upper_right, + fstar_lower_left, fstar_lower_right, + fstar_tmp1 + ) + end + + return nothing end - return nothing -end + @inline function calc_fstar!( + destination::AbstractArray{<:Any, 3}, + equations_parabolic::AbstractEquationsParabolic, + surface_flux, dg::DGSEM, + u_interfaces, interface, orientation + ) + for j in eachnode(dg), i in eachnode(dg) + # Call pointwise two-point numerical flux function + u_ll, u_rr = get_surface_node_vars( + u_interfaces, equations_parabolic, dg, i, j, + interface + ) + # TODO: parabolic; only BR1 at the moment + flux = 0.5f0 * (u_ll + u_rr) -# Calculate the gradient of the transformed variables -function calc_gradient!(gradients, u_transformed, t, - mesh::TreeMesh{3}, equations_parabolic, - boundary_conditions_parabolic, dg::DG, cache, cache_parabolic) - gradients_x, gradients_y, gradients_z = gradients + # Copy flux to left and right element storage + set_node_vars!(destination, flux, equations_parabolic, dg, i, j) + end - # Reset du - @trixi_timeit timer() "reset gradients" begin - reset_du!(gradients_x, dg, cache) - reset_du!(gradients_y, dg, cache) - reset_du!(gradients_z, dg, cache) + return nothing end - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - @unpack derivative_dhat = dg.basis - @threaded for element in eachelement(dg, cache) - - # Calculate volume terms in one element - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u_transformed, equations_parabolic, dg, i, j, k, - element) + # Calculate the gradient of the transformed variables + function calc_gradient!( + gradients, u_transformed, t, + mesh::TreeMesh{3}, equations_parabolic, + boundary_conditions_parabolic, dg::DG, cache, cache_parabolic + ) + gradients_x, gradients_y, gradients_z = gradients + + # Reset du + @trixi_timeit timer() "reset gradients" begin + reset_du!(gradients_x, dg, cache) + reset_du!(gradients_y, dg, cache) + reset_du!(gradients_z, dg, cache) + end - for ii in eachnode(dg) - multiply_add_to_node_vars!(gradients_x, derivative_dhat[ii, i], - u_node, equations_parabolic, dg, ii, j, - k, element) - end + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + @unpack derivative_dhat = dg.basis + @threaded for element in eachelement(dg, cache) + + # Calculate volume terms in one element + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars( + u_transformed, equations_parabolic, dg, i, j, k, + element + ) + + for ii in eachnode(dg) + multiply_add_to_node_vars!( + gradients_x, derivative_dhat[ii, i], + u_node, equations_parabolic, dg, ii, j, + k, element + ) + end - for jj in eachnode(dg) - multiply_add_to_node_vars!(gradients_y, derivative_dhat[jj, j], - u_node, equations_parabolic, dg, i, jj, - k, element) - end + for jj in eachnode(dg) + multiply_add_to_node_vars!( + gradients_y, derivative_dhat[jj, j], + u_node, equations_parabolic, dg, i, jj, + k, element + ) + end - for kk in eachnode(dg) - multiply_add_to_node_vars!(gradients_z, derivative_dhat[kk, k], - u_node, equations_parabolic, dg, i, j, - kk, element) + for kk in eachnode(dg) + multiply_add_to_node_vars!( + gradients_z, derivative_dhat[kk, k], + u_node, equations_parabolic, dg, i, j, + kk, element + ) + end end end end - end - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache_parabolic, u_transformed, mesh, equations_parabolic, - dg.surface_integral, dg) - end + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache_parabolic, u_transformed, mesh, equations_parabolic, + dg.surface_integral, dg + ) + end - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - @unpack surface_flux_values = cache_parabolic.elements - @unpack neighbor_ids, orientations = cache_parabolic.interfaces + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + @unpack surface_flux_values = cache_parabolic.elements + @unpack neighbor_ids, orientations = cache_parabolic.interfaces - @threaded for interface in eachinterface(dg, cache_parabolic) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] + @threaded for interface in eachinterface(dg, cache_parabolic) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - # orientation = 3: left -> 6, right -> 5 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 - - for j in eachnode(dg), i in eachnode(dg) - # Call pointwise Riemann solver - u_ll, u_rr = get_surface_node_vars(cache_parabolic.interfaces.u, - equations_parabolic, dg, i, j, - interface) - flux = 0.5f0 * (u_ll + u_rr) + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + # orientation = 3: left -> 6, right -> 5 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 - # Copy flux to left and right element storage - for v in eachvariable(equations_parabolic) - surface_flux_values[v, i, j, left_direction, left_id] = flux[v] - surface_flux_values[v, i, j, right_direction, right_id] = flux[v] + for j in eachnode(dg), i in eachnode(dg) + # Call pointwise Riemann solver + u_ll, u_rr = get_surface_node_vars( + cache_parabolic.interfaces.u, + equations_parabolic, dg, i, j, + interface + ) + flux = 0.5f0 * (u_ll + u_rr) + + # Copy flux to left and right element storage + for v in eachvariable(equations_parabolic) + surface_flux_values[v, i, j, left_direction, left_id] = flux[v] + surface_flux_values[v, i, j, right_direction, right_id] = flux[v] + end end end end - end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache_parabolic, u_transformed, mesh, equations_parabolic, - dg.surface_integral, dg) - end + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache_parabolic, u_transformed, mesh, equations_parabolic, + dg.surface_integral, dg + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux_gradients!(cache_parabolic, t, boundary_conditions_parabolic, - mesh, equations_parabolic, - dg.surface_integral, dg) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux_gradients!( + cache_parabolic, t, boundary_conditions_parabolic, + mesh, equations_parabolic, + dg.surface_integral, dg + ) + end - # Prolong solution to mortars - # NOTE: This re-uses the implementation for hyperbolic terms in "dg_3d.jl" - @trixi_timeit timer() "prolong2mortars" begin - prolong2mortars!(cache, u_transformed, mesh, equations_parabolic, - dg.mortar, dg.surface_integral, dg) - end + # Prolong solution to mortars + # NOTE: This re-uses the implementation for hyperbolic terms in "dg_3d.jl" + @trixi_timeit timer() "prolong2mortars" begin + prolong2mortars!( + cache, u_transformed, mesh, equations_parabolic, + dg.mortar, dg.surface_integral, dg + ) + end - # Calculate mortar fluxes - @trixi_timeit timer() "mortar flux" begin - calc_mortar_flux!(surface_flux_values, - mesh, - equations_parabolic, - dg.mortar, dg.surface_integral, dg, cache) - end + # Calculate mortar fluxes + @trixi_timeit timer() "mortar flux" begin + calc_mortar_flux!( + surface_flux_values, + mesh, + equations_parabolic, + dg.mortar, dg.surface_integral, dg, cache + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - @unpack boundary_interpolation = dg.basis - @unpack surface_flux_values = cache_parabolic.elements - - # Note that all fluxes have been computed with outward-pointing normal vectors. - # Access the factors only once before beginning the loop to increase performance. - # We also use explicit assignments instead of `+=` to let `@muladd` turn these - # into FMAs (see comment at the top of the file). - factor_1 = boundary_interpolation[1, 1] - factor_2 = boundary_interpolation[nnodes(dg), 2] - @threaded for element in eachelement(dg, cache) - for m in eachnode(dg), l in eachnode(dg) - for v in eachvariable(equations_parabolic) - # surface at -x - gradients_x[v, 1, l, m, element] = (gradients_x[v, 1, l, m, - element] - - surface_flux_values[v, l, m, 1, - element] * - factor_1) - - # surface at +x - gradients_x[v, nnodes(dg), l, m, element] = (gradients_x[v, - nnodes(dg), - l, m, - element] + - surface_flux_values[v, - l, - m, - 2, - element] * - factor_2) - - # surface at -y - gradients_y[v, l, 1, m, element] = (gradients_y[v, l, 1, m, - element] - - surface_flux_values[v, l, m, 3, - element] * - factor_1) - - # surface at +y - gradients_y[v, l, nnodes(dg), m, element] = (gradients_y[v, l, - nnodes(dg), - m, - element] + - surface_flux_values[v, - l, - m, - 4, - element] * - factor_2) - - # surface at -z - gradients_z[v, l, m, 1, element] = (gradients_z[v, l, m, 1, - element] - - surface_flux_values[v, l, m, 5, - element] * - factor_1) - - # surface at +z - gradients_z[v, l, m, nnodes(dg), element] = (gradients_z[v, l, m, - nnodes(dg), - element] + - surface_flux_values[v, - l, - m, - 6, - element] * - factor_2) + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + @unpack boundary_interpolation = dg.basis + @unpack surface_flux_values = cache_parabolic.elements + + # Note that all fluxes have been computed with outward-pointing normal vectors. + # Access the factors only once before beginning the loop to increase performance. + # We also use explicit assignments instead of `+=` to let `@muladd` turn these + # into FMAs (see comment at the top of the file). + factor_1 = boundary_interpolation[1, 1] + factor_2 = boundary_interpolation[nnodes(dg), 2] + @threaded for element in eachelement(dg, cache) + for m in eachnode(dg), l in eachnode(dg) + for v in eachvariable(equations_parabolic) + # surface at -x + gradients_x[v, 1, l, m, element] = ( + gradients_x[ + v, 1, l, m, + element, + ] - + surface_flux_values[ + v, l, m, 1, + element, + ] * + factor_1 + ) + + # surface at +x + gradients_x[v, nnodes(dg), l, m, element] = ( + gradients_x[ + v, + nnodes(dg), + l, m, + element, + ] + + surface_flux_values[ + v, + l, + m, + 2, + element, + ] * + factor_2 + ) + + # surface at -y + gradients_y[v, l, 1, m, element] = ( + gradients_y[ + v, l, 1, m, + element, + ] - + surface_flux_values[ + v, l, m, 3, + element, + ] * + factor_1 + ) + + # surface at +y + gradients_y[v, l, nnodes(dg), m, element] = ( + gradients_y[ + v, l, + nnodes(dg), + m, + element, + ] + + surface_flux_values[ + v, + l, + m, + 4, + element, + ] * + factor_2 + ) + + # surface at -z + gradients_z[v, l, m, 1, element] = ( + gradients_z[ + v, l, m, 1, + element, + ] - + surface_flux_values[ + v, l, m, 5, + element, + ] * + factor_1 + ) + + # surface at +z + gradients_z[v, l, m, nnodes(dg), element] = ( + gradients_z[ + v, l, m, + nnodes(dg), + element, + ] + + surface_flux_values[ + v, + l, + m, + 6, + element, + ] * + factor_2 + ) + end end end end - end - # Apply Jacobian from mapping to reference element - @trixi_timeit timer() "Jacobian" begin - apply_jacobian_parabolic!(gradients_x, mesh, equations_parabolic, dg, - cache_parabolic) - apply_jacobian_parabolic!(gradients_y, mesh, equations_parabolic, dg, - cache_parabolic) - apply_jacobian_parabolic!(gradients_z, mesh, equations_parabolic, dg, - cache_parabolic) - end + # Apply Jacobian from mapping to reference element + @trixi_timeit timer() "Jacobian" begin + apply_jacobian_parabolic!( + gradients_x, mesh, equations_parabolic, dg, + cache_parabolic + ) + apply_jacobian_parabolic!( + gradients_y, mesh, equations_parabolic, dg, + cache_parabolic + ) + apply_jacobian_parabolic!( + gradients_z, mesh, equations_parabolic, dg, + cache_parabolic + ) + end - return nothing -end + return nothing + end -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache_parabolic(mesh::TreeMesh{3}, - equations_hyperbolic::AbstractEquations, - equations_parabolic::AbstractEquationsParabolic, - dg::DG, parabolic_scheme, RealT, uEltype) - # Get cells for which an element needs to be created (i.e. all leaf cells) - leaf_cell_ids = local_leaf_cells(mesh.tree) + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache_parabolic( + mesh::TreeMesh{3}, + equations_hyperbolic::AbstractEquations, + equations_parabolic::AbstractEquationsParabolic, + dg::DG, parabolic_scheme, RealT, uEltype + ) + # Get cells for which an element needs to be created (i.e. all leaf cells) + leaf_cell_ids = local_leaf_cells(mesh.tree) - elements = init_elements(leaf_cell_ids, mesh, equations_hyperbolic, dg.basis, RealT, - uEltype) + elements = init_elements( + leaf_cell_ids, mesh, equations_hyperbolic, dg.basis, RealT, + uEltype + ) - interfaces = init_interfaces(leaf_cell_ids, mesh, elements) + interfaces = init_interfaces(leaf_cell_ids, mesh, elements) - boundaries = init_boundaries(leaf_cell_ids, mesh, elements) + boundaries = init_boundaries(leaf_cell_ids, mesh, elements) - # mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) + # mortars = init_mortars(leaf_cell_ids, mesh, elements, dg.mortar) - viscous_container = init_viscous_container_3d(nvariables(equations_hyperbolic), - nnodes(elements), nelements(elements), - uEltype) + viscous_container = init_viscous_container_3d( + nvariables(equations_hyperbolic), + nnodes(elements), nelements(elements), + uEltype + ) - # cache = (; elements, interfaces, boundaries, mortars) - cache = (; elements, interfaces, boundaries, viscous_container) + # cache = (; elements, interfaces, boundaries, mortars) + cache = (; elements, interfaces, boundaries, viscous_container) - # Add specialized parts of the cache required to compute the mortars etc. - # cache = (;cache..., create_cache(mesh, equations_parabolic, dg.mortar, uEltype)...) + # Add specialized parts of the cache required to compute the mortars etc. + # cache = (;cache..., create_cache(mesh, equations_parabolic, dg.mortar, uEltype)...) - return cache -end + return cache + end -# Needed to *not* flip the sign of the inverse Jacobian. -# This is because the parabolic fluxes are assumed to be of the form -# `du/dt + df/dx = dg/dx + source(x,t)`, -# where f(u) is the inviscid flux and g(u) is the viscous flux. -function apply_jacobian_parabolic!(du, mesh::TreeMesh{3}, - equations::AbstractEquationsParabolic, - dg::DG, cache) - @unpack inverse_jacobian = cache.elements + # Needed to *not* flip the sign of the inverse Jacobian. + # This is because the parabolic fluxes are assumed to be of the form + # `du/dt + df/dx = dg/dx + source(x,t)`, + # where f(u) is the inviscid flux and g(u) is the viscous flux. + function apply_jacobian_parabolic!( + du, mesh::TreeMesh{3}, + equations::AbstractEquationsParabolic, + dg::DG, cache + ) + @unpack inverse_jacobian = cache.elements - @threaded for element in eachelement(dg, cache) - factor = inverse_jacobian[element] + @threaded for element in eachelement(dg, cache) + factor = inverse_jacobian[element] - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - for v in eachvariable(equations) - du[v, i, j, k, element] *= factor + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + for v in eachvariable(equations) + du[v, i, j, k, element] *= factor + end end end - end - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_tree/dg_parallel.jl b/src/solvers/dgsem_tree/dg_parallel.jl index c614fe0d0e..c0b218cec4 100644 --- a/src/solvers/dgsem_tree/dg_parallel.jl +++ b/src/solvers/dgsem_tree/dg_parallel.jl @@ -3,32 +3,38 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Initialize MPI data structures. This works for both the -# `TreeMesh` and the `P4estMesh` and is dimension-agnostic. -function init_mpi_data_structures(mpi_neighbor_interfaces, mpi_neighbor_mortars, n_dims, - nvars, n_nodes, uEltype) - data_size = nvars * n_nodes^(n_dims - 1) - n_small_elements = 2^(n_dims - 1) - mpi_send_buffers = Vector{Vector{uEltype}}(undef, length(mpi_neighbor_interfaces)) - mpi_recv_buffers = Vector{Vector{uEltype}}(undef, length(mpi_neighbor_interfaces)) - for index in 1:length(mpi_neighbor_interfaces) - mpi_send_buffers[index] = Vector{uEltype}(undef, - length(mpi_neighbor_interfaces[index]) * - data_size + - length(mpi_neighbor_mortars[index]) * - n_small_elements * 2 * data_size) - mpi_recv_buffers[index] = Vector{uEltype}(undef, - length(mpi_neighbor_interfaces[index]) * - data_size + - length(mpi_neighbor_mortars[index]) * - n_small_elements * 2 * data_size) - end + # Initialize MPI data structures. This works for both the + # `TreeMesh` and the `P4estMesh` and is dimension-agnostic. + function init_mpi_data_structures( + mpi_neighbor_interfaces, mpi_neighbor_mortars, n_dims, + nvars, n_nodes, uEltype + ) + data_size = nvars * n_nodes^(n_dims - 1) + n_small_elements = 2^(n_dims - 1) + mpi_send_buffers = Vector{Vector{uEltype}}(undef, length(mpi_neighbor_interfaces)) + mpi_recv_buffers = Vector{Vector{uEltype}}(undef, length(mpi_neighbor_interfaces)) + for index in 1:length(mpi_neighbor_interfaces) + mpi_send_buffers[index] = Vector{uEltype}( + undef, + length(mpi_neighbor_interfaces[index]) * + data_size + + length(mpi_neighbor_mortars[index]) * + n_small_elements * 2 * data_size + ) + mpi_recv_buffers[index] = Vector{uEltype}( + undef, + length(mpi_neighbor_interfaces[index]) * + data_size + + length(mpi_neighbor_mortars[index]) * + n_small_elements * 2 * data_size + ) + end - mpi_send_requests = Vector{MPI.Request}(undef, length(mpi_neighbor_interfaces)) - mpi_recv_requests = Vector{MPI.Request}(undef, length(mpi_neighbor_interfaces)) + mpi_send_requests = Vector{MPI.Request}(undef, length(mpi_neighbor_interfaces)) + mpi_recv_requests = Vector{MPI.Request}(undef, length(mpi_neighbor_interfaces)) - return mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests -end + return mpi_send_buffers, mpi_recv_buffers, mpi_send_requests, mpi_recv_requests + end end # muladd diff --git a/src/solvers/dgsem_tree/indicators.jl b/src/solvers/dgsem_tree/indicators.jl index 323c1236c2..7dd234b98b 100644 --- a/src/solvers/dgsem_tree/indicators.jl +++ b/src/solvers/dgsem_tree/indicators.jl @@ -3,260 +3,290 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -abstract type AbstractIndicator end - -function create_cache(typ::Type{IndicatorType}, - semi) where {IndicatorType <: AbstractIndicator} - create_cache(typ, mesh_equations_solver_cache(semi)...) -end - -function get_element_variables!(element_variables, indicator::AbstractIndicator, - ::VolumeIntegralShockCapturingHG) - element_variables[:indicator_shock_capturing] = indicator.cache.alpha - return nothing -end - -""" - IndicatorHennemannGassner(equations::AbstractEquations, basis; - alpha_max=0.5, - alpha_min=0.001, - alpha_smooth=true, - variable) - IndicatorHennemannGassner(semi::AbstractSemidiscretization; - alpha_max=0.5, - alpha_min=0.001, - alpha_smooth=true, - variable) - -Indicator used for shock-capturing (when passing the `equations` and the `basis`) -or adaptive mesh refinement (AMR, when passing the `semi`). - -See also [`VolumeIntegralShockCapturingHG`](@ref). - -## References - -- Hennemann, Gassner (2020) - "A provably entropy stable subcell shock capturing approach for high order split form DG" - [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) -""" -struct IndicatorHennemannGassner{RealT <: Real, Variable, Cache} <: AbstractIndicator - alpha_max::RealT - alpha_min::RealT - alpha_smooth::Bool - variable::Variable - cache::Cache -end - -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function IndicatorHennemannGassner(equations::AbstractEquations, basis; - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable) - alpha_max, alpha_min = promote(alpha_max, alpha_min) - cache = create_cache(IndicatorHennemannGassner, equations, basis) - IndicatorHennemannGassner{typeof(alpha_max), typeof(variable), typeof(cache)}(alpha_max, - alpha_min, - alpha_smooth, - variable, - cache) -end - -# this method is used when the indicator is constructed as for AMR -function IndicatorHennemannGassner(semi::AbstractSemidiscretization; - alpha_max = 0.5, - alpha_min = 0.001, - alpha_smooth = true, - variable) - alpha_max, alpha_min = promote(alpha_max, alpha_min) - cache = create_cache(IndicatorHennemannGassner, semi) - IndicatorHennemannGassner{typeof(alpha_max), typeof(variable), typeof(cache)}(alpha_max, - alpha_min, - alpha_smooth, - variable, - cache) -end - -function Base.show(io::IO, indicator::IndicatorHennemannGassner) - @nospecialize indicator # reduce precompilation time - - print(io, "IndicatorHennemannGassner(") - print(io, indicator.variable) - print(io, ", alpha_max=", indicator.alpha_max) - print(io, ", alpha_min=", indicator.alpha_min) - print(io, ", alpha_smooth=", indicator.alpha_smooth) - print(io, ")") -end - -function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorHennemannGassner) - @nospecialize indicator # reduce precompilation time - setup = [ - "indicator variable" => indicator.variable, - "max. α" => indicator.alpha_max, - "min. α" => indicator.alpha_min, - "smooth α" => (indicator.alpha_smooth ? "yes" : "no"), - ] - summary_box(io, "IndicatorHennemannGassner", setup) -end - -function (indicator_hg::IndicatorHennemannGassner)(u, mesh, equations, dg::DGSEM, cache; - kwargs...) - @unpack alpha_smooth = indicator_hg - @unpack alpha, alpha_tmp = indicator_hg.cache - # TODO: Taal refactor, when to `resize!` stuff changed possibly by AMR? - # Shall we implement `resize!(semi::AbstractSemidiscretization, new_size)` - # or just `resize!` whenever we call the relevant methods as we do now? - resize!(alpha, nelements(dg, cache)) - if alpha_smooth - resize!(alpha_tmp, nelements(dg, cache)) + #! format: noindent + + abstract type AbstractIndicator end + + function create_cache( + typ::Type{IndicatorType}, + semi + ) where {IndicatorType <: AbstractIndicator} + create_cache(typ, mesh_equations_solver_cache(semi)...) end - # magic parameters - # TODO: Are there better values for Float32? - RealT = real(dg) - threshold = 0.5f0 * 10^(convert(RealT, -1.8) * nnodes(dg)^convert(RealT, 0.25)) - o_0001 = convert(RealT, 0.0001) - parameter_s = log((1 - o_0001) / o_0001) - - @threaded for element in eachelement(dg, cache) - # This is dispatched by mesh dimension. - # Use this function barrier and unpack inside to avoid passing closures to - # Polyester.jl with `@batch` (`@threaded`). - # Otherwise, `@threaded` does not work here with Julia ARM on macOS. - # See https://github.com/JuliaSIMD/Polyester.jl/issues/88. - calc_indicator_hennemann_gassner!(indicator_hg, threshold, parameter_s, u, - element, mesh, equations, dg, cache) + function get_element_variables!( + element_variables, indicator::AbstractIndicator, + ::VolumeIntegralShockCapturingHG + ) + element_variables[:indicator_shock_capturing] = indicator.cache.alpha + return nothing end - if alpha_smooth - apply_smoothing!(mesh, alpha, alpha_tmp, dg, cache) + """ + IndicatorHennemannGassner(equations::AbstractEquations, basis; + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable) + IndicatorHennemannGassner(semi::AbstractSemidiscretization; + alpha_max=0.5, + alpha_min=0.001, + alpha_smooth=true, + variable) + + Indicator used for shock-capturing (when passing the `equations` and the `basis`) + or adaptive mesh refinement (AMR, when passing the `semi`). + + See also [`VolumeIntegralShockCapturingHG`](@ref). + + ## References + + - Hennemann, Gassner (2020) + "A provably entropy stable subcell shock capturing approach for high order split form DG" + [arXiv: 2008.12044](https://arxiv.org/abs/2008.12044) + """ + struct IndicatorHennemannGassner{RealT <: Real, Variable, Cache} <: AbstractIndicator + alpha_max::RealT + alpha_min::RealT + alpha_smooth::Bool + variable::Variable + cache::Cache end - return alpha -end - -""" - IndicatorLöhner (equivalent to IndicatorLoehner) - - IndicatorLöhner(equations::AbstractEquations, basis; - f_wave=0.2, variable) - IndicatorLöhner(semi::AbstractSemidiscretization; - f_wave=0.2, variable) - -AMR indicator adapted from a FEM indicator by Löhner (1987), also used in the -FLASH code as standard AMR indicator. -The indicator estimates a weighted second derivative of a specified variable locally. - -When constructed to be used for AMR, pass the `semi`. Pass the `equations`, -and `basis` if this indicator should be used for shock capturing. - -## References - -- Löhner (1987) - "An adaptive finite element scheme for transient problems in CFD" - [doi: 10.1016/0045-7825(87)90098-3](https://doi.org/10.1016/0045-7825(87)90098-3) -- [https://flash.rochester.edu/site/flashcode/user_support/flash4_ug_4p62/node59.html#SECTION05163100000000000000](https://flash.rochester.edu/site/flashcode/user_support/flash4_ug_4p62/node59.html#SECTION05163100000000000000) -""" -struct IndicatorLöhner{RealT <: Real, Variable, Cache} <: AbstractIndicator - f_wave::RealT # TODO: Taal documentation - variable::Variable - cache::Cache -end - -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function IndicatorLöhner(equations::AbstractEquations, basis; - f_wave = 0.2, variable) - cache = create_cache(IndicatorLöhner, equations, basis) - IndicatorLöhner{typeof(f_wave), typeof(variable), typeof(cache)}(f_wave, variable, - cache) -end - -# this method is used when the indicator is constructed as for AMR -function IndicatorLöhner(semi::AbstractSemidiscretization; - f_wave = 0.2, variable) - cache = create_cache(IndicatorLöhner, semi) - IndicatorLöhner{typeof(f_wave), typeof(variable), typeof(cache)}(f_wave, variable, - cache) -end - -function Base.show(io::IO, indicator::IndicatorLöhner) - @nospecialize indicator # reduce precompilation time - - print(io, "IndicatorLöhner(") - print(io, "f_wave=", indicator.f_wave, ", variable=", indicator.variable, ")") -end - -function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorLöhner) - @nospecialize indicator # reduce precompilation time - - if get(io, :compact, false) - show(io, indicator) - else - setup = [ - "indicator variable" => indicator.variable, - "f_wave" => indicator.f_wave, - ] - summary_box(io, "IndicatorLöhner", setup) + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function IndicatorHennemannGassner( + equations::AbstractEquations, basis; + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable + ) + alpha_max, alpha_min = promote(alpha_max, alpha_min) + cache = create_cache(IndicatorHennemannGassner, equations, basis) + IndicatorHennemannGassner{typeof(alpha_max), typeof(variable), typeof(cache)}( + alpha_max, + alpha_min, + alpha_smooth, + variable, + cache + ) end -end - -const IndicatorLoehner = IndicatorLöhner - -# dirty Löhner estimate, direction by direction, assuming constant nodes -@inline function local_löhner_estimate(um::Real, u0::Real, up::Real, - löhner::IndicatorLöhner) - num = abs(up - 2 * u0 + um) - den = abs(up - u0) + abs(u0 - um) + - löhner.f_wave * (abs(up) + 2 * abs(u0) + abs(um)) - return num / den -end - -""" - IndicatorMax(equations::AbstractEquations, basis; variable) - IndicatorMax(semi::AbstractSemidiscretization; variable) - -A simple indicator returning the maximum of `variable` in an element. -When constructed to be used for AMR, pass the `semi`. Pass the `equations`, -and `basis` if this indicator should be used for shock capturing. -""" -struct IndicatorMax{Variable, Cache <: NamedTuple} <: AbstractIndicator - variable::Variable - cache::Cache -end - -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function IndicatorMax(equations::AbstractEquations, basis; - variable) - cache = create_cache(IndicatorMax, equations, basis) - IndicatorMax{typeof(variable), typeof(cache)}(variable, cache) -end - -# this method is used when the indicator is constructed as for AMR -function IndicatorMax(semi::AbstractSemidiscretization; - variable) - cache = create_cache(IndicatorMax, semi) - return IndicatorMax{typeof(variable), typeof(cache)}(variable, cache) -end - -function Base.show(io::IO, indicator::IndicatorMax) - @nospecialize indicator # reduce precompilation time - - print(io, "IndicatorMax(") - print(io, "variable=", indicator.variable, ")") -end - -function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorMax) - @nospecialize indicator # reduce precompilation time - - if get(io, :compact, false) - show(io, indicator) - else + + # this method is used when the indicator is constructed as for AMR + function IndicatorHennemannGassner( + semi::AbstractSemidiscretization; + alpha_max = 0.5, + alpha_min = 0.001, + alpha_smooth = true, + variable + ) + alpha_max, alpha_min = promote(alpha_max, alpha_min) + cache = create_cache(IndicatorHennemannGassner, semi) + IndicatorHennemannGassner{typeof(alpha_max), typeof(variable), typeof(cache)}( + alpha_max, + alpha_min, + alpha_smooth, + variable, + cache + ) + end + + function Base.show(io::IO, indicator::IndicatorHennemannGassner) + @nospecialize indicator # reduce precompilation time + + print(io, "IndicatorHennemannGassner(") + print(io, indicator.variable) + print(io, ", alpha_max=", indicator.alpha_max) + print(io, ", alpha_min=", indicator.alpha_min) + print(io, ", alpha_smooth=", indicator.alpha_smooth) + print(io, ")") + end + + function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorHennemannGassner) + @nospecialize indicator # reduce precompilation time setup = [ "indicator variable" => indicator.variable, + "max. α" => indicator.alpha_max, + "min. α" => indicator.alpha_min, + "smooth α" => (indicator.alpha_smooth ? "yes" : "no"), ] - summary_box(io, "IndicatorMax", setup) + summary_box(io, "IndicatorHennemannGassner", setup) + end + + function (indicator_hg::IndicatorHennemannGassner)( + u, mesh, equations, dg::DGSEM, cache; + kwargs... + ) + @unpack alpha_smooth = indicator_hg + @unpack alpha, alpha_tmp = indicator_hg.cache + # TODO: Taal refactor, when to `resize!` stuff changed possibly by AMR? + # Shall we implement `resize!(semi::AbstractSemidiscretization, new_size)` + # or just `resize!` whenever we call the relevant methods as we do now? + resize!(alpha, nelements(dg, cache)) + if alpha_smooth + resize!(alpha_tmp, nelements(dg, cache)) + end + + # magic parameters + # TODO: Are there better values for Float32? + RealT = real(dg) + threshold = 0.5f0 * 10^(convert(RealT, -1.8) * nnodes(dg)^convert(RealT, 0.25)) + o_0001 = convert(RealT, 0.0001) + parameter_s = log((1 - o_0001) / o_0001) + + @threaded for element in eachelement(dg, cache) + # This is dispatched by mesh dimension. + # Use this function barrier and unpack inside to avoid passing closures to + # Polyester.jl with `@batch` (`@threaded`). + # Otherwise, `@threaded` does not work here with Julia ARM on macOS. + # See https://github.com/JuliaSIMD/Polyester.jl/issues/88. + calc_indicator_hennemann_gassner!( + indicator_hg, threshold, parameter_s, u, + element, mesh, equations, dg, cache + ) + end + + if alpha_smooth + apply_smoothing!(mesh, alpha, alpha_tmp, dg, cache) + end + + return alpha + end + + """ + IndicatorLöhner (equivalent to IndicatorLoehner) + + IndicatorLöhner(equations::AbstractEquations, basis; + f_wave=0.2, variable) + IndicatorLöhner(semi::AbstractSemidiscretization; + f_wave=0.2, variable) + + AMR indicator adapted from a FEM indicator by Löhner (1987), also used in the + FLASH code as standard AMR indicator. + The indicator estimates a weighted second derivative of a specified variable locally. + + When constructed to be used for AMR, pass the `semi`. Pass the `equations`, + and `basis` if this indicator should be used for shock capturing. + + ## References + + - Löhner (1987) + "An adaptive finite element scheme for transient problems in CFD" + [doi: 10.1016/0045-7825(87)90098-3](https://doi.org/10.1016/0045-7825(87)90098-3) + - [https://flash.rochester.edu/site/flashcode/user_support/flash4_ug_4p62/node59.html#SECTION05163100000000000000](https://flash.rochester.edu/site/flashcode/user_support/flash4_ug_4p62/node59.html#SECTION05163100000000000000) + """ + struct IndicatorLöhner{RealT <: Real, Variable, Cache} <: AbstractIndicator + f_wave::RealT # TODO: Taal documentation + variable::Variable + cache::Cache + end + + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function IndicatorLöhner( + equations::AbstractEquations, basis; + f_wave = 0.2, variable + ) + cache = create_cache(IndicatorLöhner, equations, basis) + IndicatorLöhner{typeof(f_wave), typeof(variable), typeof(cache)}( + f_wave, variable, + cache + ) + end + + # this method is used when the indicator is constructed as for AMR + function IndicatorLöhner( + semi::AbstractSemidiscretization; + f_wave = 0.2, variable + ) + cache = create_cache(IndicatorLöhner, semi) + IndicatorLöhner{typeof(f_wave), typeof(variable), typeof(cache)}( + f_wave, variable, + cache + ) + end + + function Base.show(io::IO, indicator::IndicatorLöhner) + @nospecialize indicator # reduce precompilation time + + print(io, "IndicatorLöhner(") + print(io, "f_wave=", indicator.f_wave, ", variable=", indicator.variable, ")") + end + + function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorLöhner) + @nospecialize indicator # reduce precompilation time + + if get(io, :compact, false) + show(io, indicator) + else + setup = [ + "indicator variable" => indicator.variable, + "f_wave" => indicator.f_wave, + ] + summary_box(io, "IndicatorLöhner", setup) + end + end + + const IndicatorLoehner = IndicatorLöhner + + # dirty Löhner estimate, direction by direction, assuming constant nodes + @inline function local_löhner_estimate( + um::Real, u0::Real, up::Real, + löhner::IndicatorLöhner + ) + num = abs(up - 2 * u0 + um) + den = abs(up - u0) + abs(u0 - um) + + löhner.f_wave * (abs(up) + 2 * abs(u0) + abs(um)) + return num / den + end + + """ + IndicatorMax(equations::AbstractEquations, basis; variable) + IndicatorMax(semi::AbstractSemidiscretization; variable) + + A simple indicator returning the maximum of `variable` in an element. + When constructed to be used for AMR, pass the `semi`. Pass the `equations`, + and `basis` if this indicator should be used for shock capturing. + """ + struct IndicatorMax{Variable, Cache <: NamedTuple} <: AbstractIndicator + variable::Variable + cache::Cache + end + + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function IndicatorMax( + equations::AbstractEquations, basis; + variable + ) + cache = create_cache(IndicatorMax, equations, basis) + IndicatorMax{typeof(variable), typeof(cache)}(variable, cache) + end + + # this method is used when the indicator is constructed as for AMR + function IndicatorMax( + semi::AbstractSemidiscretization; + variable + ) + cache = create_cache(IndicatorMax, semi) + return IndicatorMax{typeof(variable), typeof(cache)}(variable, cache) + end + + function Base.show(io::IO, indicator::IndicatorMax) + @nospecialize indicator # reduce precompilation time + + print(io, "IndicatorMax(") + print(io, "variable=", indicator.variable, ")") + end + + function Base.show(io::IO, ::MIME"text/plain", indicator::IndicatorMax) + @nospecialize indicator # reduce precompilation time + + if get(io, :compact, false) + show(io, indicator) + else + setup = [ + "indicator variable" => indicator.variable, + ] + summary_box(io, "IndicatorMax", setup) + end end -end end # @muladd diff --git a/src/solvers/dgsem_tree/indicators_1d.jl b/src/solvers/dgsem_tree/indicators_1d.jl index 9d11d05edc..ee74c1b9b6 100644 --- a/src/solvers/dgsem_tree/indicators_1d.jl +++ b/src/solvers/dgsem_tree/indicators_1d.jl @@ -3,197 +3,219 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorHennemannGassner}, - equations::AbstractEquations{1}, basis::LobattoLegendreBasis) - alpha = Vector{real(basis)}() - alpha_tmp = similar(alpha) - - A = Array{real(basis), ndims(equations)} - indicator_threaded = [A(undef, nnodes(basis)) for _ in 1:Threads.nthreads()] - modal_threaded = [A(undef, nnodes(basis)) for _ in 1:Threads.nthreads()] - - return (; alpha, alpha_tmp, indicator_threaded, modal_threaded) -end - -# this method is used when the indicator is constructed as for AMR -function create_cache(typ::Type{IndicatorHennemannGassner}, mesh, - equations::AbstractEquations{1}, dg::DGSEM, cache) - create_cache(typ, equations, dg.basis) -end - -# Use this function barrier and unpack inside to avoid passing closures to Polyester.jl -# with @batch (@threaded). -# Otherwise, @threaded does not work here with Julia ARM on macOS. -# See https://github.com/JuliaSIMD/Polyester.jl/issues/88. -@inline function calc_indicator_hennemann_gassner!(indicator_hg, threshold, parameter_s, - u, - element, mesh::AbstractMesh{1}, - equations, dg, cache) - @unpack alpha_max, alpha_min, alpha_smooth, variable = indicator_hg - @unpack alpha, alpha_tmp, indicator_threaded, modal_threaded = indicator_hg.cache - - indicator = indicator_threaded[Threads.threadid()] - modal = modal_threaded[Threads.threadid()] - - # Calculate indicator variables at Gauss-Lobatto nodes - for i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, element) - indicator[i] = indicator_hg.variable(u_local, equations) - end + #! format: noindent - # Convert to modal representation - multiply_scalar_dimensionwise!(modal, dg.basis.inverse_vandermonde_legendre, - indicator) + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function create_cache( + ::Type{IndicatorHennemannGassner}, + equations::AbstractEquations{1}, basis::LobattoLegendreBasis + ) + alpha = Vector{real(basis)}() + alpha_tmp = similar(alpha) - # Calculate total energies for all modes, without highest, without two highest - total_energy = zero(eltype(modal)) - for i in 1:nnodes(dg) - total_energy += modal[i]^2 - end - total_energy_clip1 = zero(eltype(modal)) - for i in 1:(nnodes(dg) - 1) - total_energy_clip1 += modal[i]^2 - end - total_energy_clip2 = zero(eltype(modal)) - for i in 1:(nnodes(dg) - 2) - total_energy_clip2 += modal[i]^2 - end + A = Array{real(basis), ndims(equations)} + indicator_threaded = [A(undef, nnodes(basis)) for _ in 1:Threads.nthreads()] + modal_threaded = [A(undef, nnodes(basis)) for _ in 1:Threads.nthreads()] - # Calculate energy in higher modes - if !(iszero(total_energy)) - energy_frac_1 = (total_energy - total_energy_clip1) / total_energy - else - energy_frac_1 = zero(total_energy) - end - if !(iszero(total_energy_clip1)) - energy_frac_2 = (total_energy_clip1 - total_energy_clip2) / total_energy_clip1 - else - energy_frac_2 = zero(total_energy_clip1) + return (; alpha, alpha_tmp, indicator_threaded, modal_threaded) end - energy = max(energy_frac_1, energy_frac_2) - alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold))) - - # Take care of the case close to pure DG - if alpha_element < alpha_min - alpha_element = zero(alpha_element) + # this method is used when the indicator is constructed as for AMR + function create_cache( + typ::Type{IndicatorHennemannGassner}, mesh, + equations::AbstractEquations{1}, dg::DGSEM, cache + ) + create_cache(typ, equations, dg.basis) end - # Take care of the case close to pure FV - if alpha_element > 1 - alpha_min - alpha_element = one(alpha_element) - end + # Use this function barrier and unpack inside to avoid passing closures to Polyester.jl + # with @batch (@threaded). + # Otherwise, @threaded does not work here with Julia ARM on macOS. + # See https://github.com/JuliaSIMD/Polyester.jl/issues/88. + @inline function calc_indicator_hennemann_gassner!( + indicator_hg, threshold, parameter_s, + u, + element, mesh::AbstractMesh{1}, + equations, dg, cache + ) + @unpack alpha_max, alpha_min, alpha_smooth, variable = indicator_hg + @unpack alpha, alpha_tmp, indicator_threaded, modal_threaded = indicator_hg.cache - # Clip the maximum amount of FV allowed - alpha[element] = min(alpha_max, alpha_element) -end - -# Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha -function apply_smoothing!(mesh::Union{TreeMesh{1}, P4estMesh{1}}, alpha, alpha_tmp, dg, - cache) - # Copy alpha values such that smoothing is indpedenent of the element access order - alpha_tmp .= alpha - - # Loop over interfaces - for interface in eachinterface(dg, cache) - # Get neighboring element ids - left = cache.interfaces.neighbor_ids[1, interface] - right = cache.interfaces.neighbor_ids[2, interface] - - # Apply smoothing - alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[right], alpha[left]) - alpha[right] = max(alpha_tmp[right], 0.5f0 * alpha_tmp[left], alpha[right]) - end -end + indicator = indicator_threaded[Threads.threadid()] + modal = modal_threaded[Threads.threadid()] -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorLöhner}, equations::AbstractEquations{1}, - basis::LobattoLegendreBasis) - alpha = Vector{real(basis)}() + # Calculate indicator variables at Gauss-Lobatto nodes + for i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, element) + indicator[i] = indicator_hg.variable(u_local, equations) + end - A = Array{real(basis), ndims(equations)} - indicator_threaded = [A(undef, nnodes(basis)) for _ in 1:Threads.nthreads()] + # Convert to modal representation + multiply_scalar_dimensionwise!( + modal, dg.basis.inverse_vandermonde_legendre, + indicator + ) - return (; alpha, indicator_threaded) -end + # Calculate total energies for all modes, without highest, without two highest + total_energy = zero(eltype(modal)) + for i in 1:nnodes(dg) + total_energy += modal[i]^2 + end + total_energy_clip1 = zero(eltype(modal)) + for i in 1:(nnodes(dg) - 1) + total_energy_clip1 += modal[i]^2 + end + total_energy_clip2 = zero(eltype(modal)) + for i in 1:(nnodes(dg) - 2) + total_energy_clip2 += modal[i]^2 + end -# this method is used when the indicator is constructed as for AMR -function create_cache(typ::Type{IndicatorLöhner}, mesh, equations::AbstractEquations{1}, - dg::DGSEM, cache) - create_cache(typ, equations, dg.basis) -end + # Calculate energy in higher modes + if !(iszero(total_energy)) + energy_frac_1 = (total_energy - total_energy_clip1) / total_energy + else + energy_frac_1 = zero(total_energy) + end + if !(iszero(total_energy_clip1)) + energy_frac_2 = (total_energy_clip1 - total_energy_clip2) / total_energy_clip1 + else + energy_frac_2 = zero(total_energy_clip1) + end + energy = max(energy_frac_1, energy_frac_2) -function (löhner::IndicatorLöhner)(u::AbstractArray{<:Any, 3}, - mesh, equations, dg::DGSEM, cache; - kwargs...) - @assert nnodes(dg)>=3 "IndicatorLöhner only works for nnodes >= 3 (polydeg > 1)" - @unpack alpha, indicator_threaded = löhner.cache - resize!(alpha, nelements(dg, cache)) + alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold))) - @threaded for element in eachelement(dg, cache) - indicator = indicator_threaded[Threads.threadid()] + # Take care of the case close to pure DG + if alpha_element < alpha_min + alpha_element = zero(alpha_element) + end - # Calculate indicator variables at Gauss-Lobatto nodes - for i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, element) - indicator[i] = löhner.variable(u_local, equations) + # Take care of the case close to pure FV + if alpha_element > 1 - alpha_min + alpha_element = one(alpha_element) end - estimate = zero(real(dg)) - for i in 2:(nnodes(dg) - 1) - # x direction - u0 = indicator[i] - up = indicator[i + 1] - um = indicator[i - 1] - estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) + # Clip the maximum amount of FV allowed + alpha[element] = min(alpha_max, alpha_element) + end + + # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha + function apply_smoothing!( + mesh::Union{TreeMesh{1}, P4estMesh{1}}, alpha, alpha_tmp, dg, + cache + ) + # Copy alpha values such that smoothing is indpedenent of the element access order + alpha_tmp .= alpha + + # Loop over interfaces + for interface in eachinterface(dg, cache) + # Get neighboring element ids + left = cache.interfaces.neighbor_ids[1, interface] + right = cache.interfaces.neighbor_ids[2, interface] + + # Apply smoothing + alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[right], alpha[left]) + alpha[right] = max(alpha_tmp[right], 0.5f0 * alpha_tmp[left], alpha[right]) end + end + + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function create_cache( + ::Type{IndicatorLöhner}, equations::AbstractEquations{1}, + basis::LobattoLegendreBasis + ) + alpha = Vector{real(basis)}() - # use the maximum as DG element indicator - alpha[element] = estimate + A = Array{real(basis), ndims(equations)} + indicator_threaded = [A(undef, nnodes(basis)) for _ in 1:Threads.nthreads()] + + return (; alpha, indicator_threaded) end - return alpha -end + # this method is used when the indicator is constructed as for AMR + function create_cache( + typ::Type{IndicatorLöhner}, mesh, equations::AbstractEquations{1}, + dg::DGSEM, cache + ) + create_cache(typ, equations, dg.basis) + end -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorMax}, equations::AbstractEquations{1}, - basis::LobattoLegendreBasis) - alpha = Vector{real(basis)}() + function (löhner::IndicatorLöhner)( + u::AbstractArray{<:Any, 3}, + mesh, equations, dg::DGSEM, cache; + kwargs... + ) + @assert nnodes(dg) >= 3 "IndicatorLöhner only works for nnodes >= 3 (polydeg > 1)" + @unpack alpha, indicator_threaded = löhner.cache + resize!(alpha, nelements(dg, cache)) + + @threaded for element in eachelement(dg, cache) + indicator = indicator_threaded[Threads.threadid()] + + # Calculate indicator variables at Gauss-Lobatto nodes + for i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, element) + indicator[i] = löhner.variable(u_local, equations) + end + + estimate = zero(real(dg)) + for i in 2:(nnodes(dg) - 1) + # x direction + u0 = indicator[i] + up = indicator[i + 1] + um = indicator[i - 1] + estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) + end + + # use the maximum as DG element indicator + alpha[element] = estimate + end - A = Array{real(basis), ndims(equations)} - indicator_threaded = [A(undef, nnodes(basis)) for _ in 1:Threads.nthreads()] + return alpha + end - return (; alpha, indicator_threaded) -end + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function create_cache( + ::Type{IndicatorMax}, equations::AbstractEquations{1}, + basis::LobattoLegendreBasis + ) + alpha = Vector{real(basis)}() -# this method is used when the indicator is constructed as for AMR -function create_cache(typ::Type{IndicatorMax}, mesh, equations::AbstractEquations{1}, - dg::DGSEM, cache) - cache = create_cache(typ, equations, dg.basis) -end + A = Array{real(basis), ndims(equations)} + indicator_threaded = [A(undef, nnodes(basis)) for _ in 1:Threads.nthreads()] -function (indicator_max::IndicatorMax)(u::AbstractArray{<:Any, 3}, - mesh, equations, dg::DGSEM, cache; - kwargs...) - @unpack alpha, indicator_threaded = indicator_max.cache - resize!(alpha, nelements(dg, cache)) - indicator_variable = indicator_max.variable + return (; alpha, indicator_threaded) + end - @threaded for element in eachelement(dg, cache) - indicator = indicator_threaded[Threads.threadid()] + # this method is used when the indicator is constructed as for AMR + function create_cache( + typ::Type{IndicatorMax}, mesh, equations::AbstractEquations{1}, + dg::DGSEM, cache + ) + cache = create_cache(typ, equations, dg.basis) + end - # Calculate indicator variables at Gauss-Lobatto nodes - for i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, element) - indicator[i] = indicator_variable(u_local, equations) + function (indicator_max::IndicatorMax)( + u::AbstractArray{<:Any, 3}, + mesh, equations, dg::DGSEM, cache; + kwargs... + ) + @unpack alpha, indicator_threaded = indicator_max.cache + resize!(alpha, nelements(dg, cache)) + indicator_variable = indicator_max.variable + + @threaded for element in eachelement(dg, cache) + indicator = indicator_threaded[Threads.threadid()] + + # Calculate indicator variables at Gauss-Lobatto nodes + for i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, element) + indicator[i] = indicator_variable(u_local, equations) + end + + alpha[element] = maximum(indicator) end - alpha[element] = maximum(indicator) + return alpha end - - return alpha -end end # @muladd diff --git a/src/solvers/dgsem_tree/indicators_2d.jl b/src/solvers/dgsem_tree/indicators_2d.jl index 6ef65c4e9d..995a6710ec 100644 --- a/src/solvers/dgsem_tree/indicators_2d.jl +++ b/src/solvers/dgsem_tree/indicators_2d.jl @@ -3,230 +3,262 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorHennemannGassner}, - equations::AbstractEquations{2}, basis::LobattoLegendreBasis) - alpha = Vector{real(basis)}() - alpha_tmp = similar(alpha) - - A = Array{real(basis), ndims(equations)} - indicator_threaded = [A(undef, nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] - modal_threaded = [A(undef, nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] - modal_tmp1_threaded = [A(undef, nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] - - return (; alpha, alpha_tmp, indicator_threaded, modal_threaded, modal_tmp1_threaded) -end - -# this method is used when the indicator is constructed as for AMR -function create_cache(typ::Type{IndicatorHennemannGassner}, mesh, - equations::AbstractEquations{2}, dg::DGSEM, cache) - create_cache(typ, equations, dg.basis) -end - -# Use this function barrier and unpack inside to avoid passing closures to Polyester.jl -# with @batch (@threaded). -# Otherwise, @threaded does not work here with Julia ARM on macOS. -# See https://github.com/JuliaSIMD/Polyester.jl/issues/88. -@inline function calc_indicator_hennemann_gassner!(indicator_hg, threshold, parameter_s, - u, - element, mesh::AbstractMesh{2}, - equations, dg, cache) - @unpack alpha_max, alpha_min, alpha_smooth, variable = indicator_hg - @unpack alpha, alpha_tmp, indicator_threaded, modal_threaded, - modal_tmp1_threaded = indicator_hg.cache - - indicator = indicator_threaded[Threads.threadid()] - modal = modal_threaded[Threads.threadid()] - modal_tmp1 = modal_tmp1_threaded[Threads.threadid()] - - # Calculate indicator variables at Gauss-Lobatto nodes - for j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, element) - indicator[i, j] = indicator_hg.variable(u_local, equations) + #! format: noindent + + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function create_cache( + ::Type{IndicatorHennemannGassner}, + equations::AbstractEquations{2}, basis::LobattoLegendreBasis + ) + alpha = Vector{real(basis)}() + alpha_tmp = similar(alpha) + + A = Array{real(basis), ndims(equations)} + indicator_threaded = [ + A(undef, nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] + modal_threaded = [ + A(undef, nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] + modal_tmp1_threaded = [ + A(undef, nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] + + return (; alpha, alpha_tmp, indicator_threaded, modal_threaded, modal_tmp1_threaded) end - # Convert to modal representation - multiply_scalar_dimensionwise!(modal, dg.basis.inverse_vandermonde_legendre, - indicator, modal_tmp1) - - # Calculate total energies for all modes, without highest, without two highest - total_energy = zero(eltype(modal)) - for j in 1:nnodes(dg), i in 1:nnodes(dg) - total_energy += modal[i, j]^2 - end - total_energy_clip1 = zero(eltype(modal)) - for j in 1:(nnodes(dg) - 1), i in 1:(nnodes(dg) - 1) - total_energy_clip1 += modal[i, j]^2 - end - total_energy_clip2 = zero(eltype(modal)) - for j in 1:(nnodes(dg) - 2), i in 1:(nnodes(dg) - 2) - total_energy_clip2 += modal[i, j]^2 - end - - # Calculate energy in higher modes - if !(iszero(total_energy)) - energy_frac_1 = (total_energy - total_energy_clip1) / total_energy - else - energy_frac_1 = zero(total_energy) - end - if !(iszero(total_energy_clip1)) - energy_frac_2 = (total_energy_clip1 - total_energy_clip2) / total_energy_clip1 - else - energy_frac_2 = zero(total_energy_clip1) + # this method is used when the indicator is constructed as for AMR + function create_cache( + typ::Type{IndicatorHennemannGassner}, mesh, + equations::AbstractEquations{2}, dg::DGSEM, cache + ) + create_cache(typ, equations, dg.basis) end - energy = max(energy_frac_1, energy_frac_2) - alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold))) + # Use this function barrier and unpack inside to avoid passing closures to Polyester.jl + # with @batch (@threaded). + # Otherwise, @threaded does not work here with Julia ARM on macOS. + # See https://github.com/JuliaSIMD/Polyester.jl/issues/88. + @inline function calc_indicator_hennemann_gassner!( + indicator_hg, threshold, parameter_s, + u, + element, mesh::AbstractMesh{2}, + equations, dg, cache + ) + @unpack alpha_max, alpha_min, alpha_smooth, variable = indicator_hg + @unpack alpha, alpha_tmp, indicator_threaded, modal_threaded, + modal_tmp1_threaded = indicator_hg.cache - # Take care of the case close to pure DG - if alpha_element < alpha_min - alpha_element = zero(alpha_element) - end - - # Take care of the case close to pure FV - if alpha_element > 1 - alpha_min - alpha_element = one(alpha_element) - end - - # Clip the maximum amount of FV allowed - alpha[element] = min(alpha_max, alpha_element) -end - -# Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha -function apply_smoothing!(mesh::Union{TreeMesh{2}, P4estMesh{2}, T8codeMesh{2}}, alpha, - alpha_tmp, dg, - cache) - # Copy alpha values such that smoothing is indpedenent of the element access order - alpha_tmp .= alpha - - # Loop over interfaces - for interface in eachinterface(dg, cache) - # Get neighboring element ids - left = cache.interfaces.neighbor_ids[1, interface] - right = cache.interfaces.neighbor_ids[2, interface] - - # Apply smoothing - alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[right], alpha[left]) - alpha[right] = max(alpha_tmp[right], 0.5f0 * alpha_tmp[left], alpha[right]) - end + indicator = indicator_threaded[Threads.threadid()] + modal = modal_threaded[Threads.threadid()] + modal_tmp1 = modal_tmp1_threaded[Threads.threadid()] - # Loop over L2 mortars - for mortar in eachmortar(dg, cache) - # Get neighboring element ids - lower = cache.mortars.neighbor_ids[1, mortar] - upper = cache.mortars.neighbor_ids[2, mortar] - large = cache.mortars.neighbor_ids[3, mortar] - - # Apply smoothing - alpha[lower] = max(alpha_tmp[lower], 0.5f0 * alpha_tmp[large], alpha[lower]) - alpha[upper] = max(alpha_tmp[upper], 0.5f0 * alpha_tmp[large], alpha[upper]) - alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[lower], alpha[large]) - alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[upper], alpha[large]) - end + # Calculate indicator variables at Gauss-Lobatto nodes + for j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, element) + indicator[i, j] = indicator_hg.variable(u_local, equations) + end - return alpha -end + # Convert to modal representation + multiply_scalar_dimensionwise!( + modal, dg.basis.inverse_vandermonde_legendre, + indicator, modal_tmp1 + ) -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorLöhner}, equations::AbstractEquations{2}, - basis::LobattoLegendreBasis) - alpha = Vector{real(basis)}() + # Calculate total energies for all modes, without highest, without two highest + total_energy = zero(eltype(modal)) + for j in 1:nnodes(dg), i in 1:nnodes(dg) + total_energy += modal[i, j]^2 + end + total_energy_clip1 = zero(eltype(modal)) + for j in 1:(nnodes(dg) - 1), i in 1:(nnodes(dg) - 1) + total_energy_clip1 += modal[i, j]^2 + end + total_energy_clip2 = zero(eltype(modal)) + for j in 1:(nnodes(dg) - 2), i in 1:(nnodes(dg) - 2) + total_energy_clip2 += modal[i, j]^2 + end - A = Array{real(basis), ndims(equations)} - indicator_threaded = [A(undef, nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] + # Calculate energy in higher modes + if !(iszero(total_energy)) + energy_frac_1 = (total_energy - total_energy_clip1) / total_energy + else + energy_frac_1 = zero(total_energy) + end + if !(iszero(total_energy_clip1)) + energy_frac_2 = (total_energy_clip1 - total_energy_clip2) / total_energy_clip1 + else + energy_frac_2 = zero(total_energy_clip1) + end + energy = max(energy_frac_1, energy_frac_2) - return (; alpha, indicator_threaded) -end + alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold))) -# this method is used when the indicator is constructed as for AMR -function create_cache(typ::Type{IndicatorLöhner}, mesh, equations::AbstractEquations{2}, - dg::DGSEM, cache) - create_cache(typ, equations, dg.basis) -end + # Take care of the case close to pure DG + if alpha_element < alpha_min + alpha_element = zero(alpha_element) + end -function (löhner::IndicatorLöhner)(u::AbstractArray{<:Any, 4}, - mesh, equations, dg::DGSEM, cache; - kwargs...) - @assert nnodes(dg)>=3 "IndicatorLöhner only works for nnodes >= 3 (polydeg > 1)" - @unpack alpha, indicator_threaded = löhner.cache - resize!(alpha, nelements(dg, cache)) + # Take care of the case close to pure FV + if alpha_element > 1 - alpha_min + alpha_element = one(alpha_element) + end - @threaded for element in eachelement(dg, cache) - indicator = indicator_threaded[Threads.threadid()] + # Clip the maximum amount of FV allowed + alpha[element] = min(alpha_max, alpha_element) + end - # Calculate indicator variables at Gauss-Lobatto nodes - for j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, element) - indicator[i, j] = löhner.variable(u_local, equations) + # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha + function apply_smoothing!( + mesh::Union{TreeMesh{2}, P4estMesh{2}, T8codeMesh{2}}, alpha, + alpha_tmp, dg, + cache + ) + # Copy alpha values such that smoothing is indpedenent of the element access order + alpha_tmp .= alpha + + # Loop over interfaces + for interface in eachinterface(dg, cache) + # Get neighboring element ids + left = cache.interfaces.neighbor_ids[1, interface] + right = cache.interfaces.neighbor_ids[2, interface] + + # Apply smoothing + alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[right], alpha[left]) + alpha[right] = max(alpha_tmp[right], 0.5f0 * alpha_tmp[left], alpha[right]) end - estimate = zero(real(dg)) - for j in eachnode(dg), i in 2:(nnodes(dg) - 1) - # x direction - u0 = indicator[i, j] - up = indicator[i + 1, j] - um = indicator[i - 1, j] - estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) + # Loop over L2 mortars + for mortar in eachmortar(dg, cache) + # Get neighboring element ids + lower = cache.mortars.neighbor_ids[1, mortar] + upper = cache.mortars.neighbor_ids[2, mortar] + large = cache.mortars.neighbor_ids[3, mortar] + + # Apply smoothing + alpha[lower] = max(alpha_tmp[lower], 0.5f0 * alpha_tmp[large], alpha[lower]) + alpha[upper] = max(alpha_tmp[upper], 0.5f0 * alpha_tmp[large], alpha[upper]) + alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[lower], alpha[large]) + alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[upper], alpha[large]) end - for j in 2:(nnodes(dg) - 1), i in eachnode(dg) - # y direction - u0 = indicator[i, j] - up = indicator[i, j + 1] - um = indicator[i, j - 1] - estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) - end + return alpha + end + + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function create_cache( + ::Type{IndicatorLöhner}, equations::AbstractEquations{2}, + basis::LobattoLegendreBasis + ) + alpha = Vector{real(basis)}() + + A = Array{real(basis), ndims(equations)} + indicator_threaded = [ + A(undef, nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] - # use the maximum as DG element indicator - alpha[element] = estimate + return (; alpha, indicator_threaded) end - return alpha -end + # this method is used when the indicator is constructed as for AMR + function create_cache( + typ::Type{IndicatorLöhner}, mesh, equations::AbstractEquations{2}, + dg::DGSEM, cache + ) + create_cache(typ, equations, dg.basis) + end -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorMax}, equations::AbstractEquations{2}, - basis::LobattoLegendreBasis) - alpha = Vector{real(basis)}() + function (löhner::IndicatorLöhner)( + u::AbstractArray{<:Any, 4}, + mesh, equations, dg::DGSEM, cache; + kwargs... + ) + @assert nnodes(dg) >= 3 "IndicatorLöhner only works for nnodes >= 3 (polydeg > 1)" + @unpack alpha, indicator_threaded = löhner.cache + resize!(alpha, nelements(dg, cache)) + + @threaded for element in eachelement(dg, cache) + indicator = indicator_threaded[Threads.threadid()] + + # Calculate indicator variables at Gauss-Lobatto nodes + for j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, element) + indicator[i, j] = löhner.variable(u_local, equations) + end + + estimate = zero(real(dg)) + for j in eachnode(dg), i in 2:(nnodes(dg) - 1) + # x direction + u0 = indicator[i, j] + up = indicator[i + 1, j] + um = indicator[i - 1, j] + estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) + end + + for j in 2:(nnodes(dg) - 1), i in eachnode(dg) + # y direction + u0 = indicator[i, j] + up = indicator[i, j + 1] + um = indicator[i, j - 1] + estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) + end + + # use the maximum as DG element indicator + alpha[element] = estimate + end - A = Array{real(basis), ndims(equations)} - indicator_threaded = [A(undef, nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] + return alpha + end - return (; alpha, indicator_threaded) -end + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function create_cache( + ::Type{IndicatorMax}, equations::AbstractEquations{2}, + basis::LobattoLegendreBasis + ) + alpha = Vector{real(basis)}() -# this method is used when the indicator is constructed as for AMR -function create_cache(typ::Type{IndicatorMax}, mesh, equations::AbstractEquations{2}, - dg::DGSEM, cache) - cache = create_cache(typ, equations, dg.basis) -end + A = Array{real(basis), ndims(equations)} + indicator_threaded = [ + A(undef, nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] -function (indicator_max::IndicatorMax)(u::AbstractArray{<:Any, 4}, - mesh, equations, dg::DGSEM, cache; - kwargs...) - @unpack alpha, indicator_threaded = indicator_max.cache - resize!(alpha, nelements(dg, cache)) - indicator_variable = indicator_max.variable + return (; alpha, indicator_threaded) + end - @threaded for element in eachelement(dg, cache) - indicator = indicator_threaded[Threads.threadid()] + # this method is used when the indicator is constructed as for AMR + function create_cache( + typ::Type{IndicatorMax}, mesh, equations::AbstractEquations{2}, + dg::DGSEM, cache + ) + cache = create_cache(typ, equations, dg.basis) + end - # Calculate indicator variables at Gauss-Lobatto nodes - for j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, element) - indicator[i, j] = indicator_variable(u_local, equations) + function (indicator_max::IndicatorMax)( + u::AbstractArray{<:Any, 4}, + mesh, equations, dg::DGSEM, cache; + kwargs... + ) + @unpack alpha, indicator_threaded = indicator_max.cache + resize!(alpha, nelements(dg, cache)) + indicator_variable = indicator_max.variable + + @threaded for element in eachelement(dg, cache) + indicator = indicator_threaded[Threads.threadid()] + + # Calculate indicator variables at Gauss-Lobatto nodes + for j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, element) + indicator[i, j] = indicator_variable(u_local, equations) + end + + alpha[element] = maximum(indicator) end - alpha[element] = maximum(indicator) + return alpha end - - return alpha -end end # @muladd diff --git a/src/solvers/dgsem_tree/indicators_3d.jl b/src/solvers/dgsem_tree/indicators_3d.jl index 6d4ee63618..aba9ba2000 100644 --- a/src/solvers/dgsem_tree/indicators_3d.jl +++ b/src/solvers/dgsem_tree/indicators_3d.jl @@ -3,256 +3,308 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorHennemannGassner}, - equations::AbstractEquations{3}, basis::LobattoLegendreBasis) - alpha = Vector{real(basis)}() - alpha_tmp = similar(alpha) - - A = Array{real(basis), ndims(equations)} - indicator_threaded = [A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] - modal_threaded = [A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] - modal_tmp1_threaded = [A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] - modal_tmp2_threaded = [A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] - - return (; alpha, alpha_tmp, indicator_threaded, modal_threaded, modal_tmp1_threaded, - modal_tmp2_threaded) -end - -# this method is used when the indicator is constructed as for AMR -function create_cache(typ::Type{IndicatorHennemannGassner}, mesh, - equations::AbstractEquations{3}, dg::DGSEM, cache) - create_cache(typ, equations, dg.basis) -end - -# Use this function barrier and unpack inside to avoid passing closures to Polyester.jl -# with @batch (@threaded). -# Otherwise, @threaded does not work here with Julia ARM on macOS. -# See https://github.com/JuliaSIMD/Polyester.jl/issues/88. -@inline function calc_indicator_hennemann_gassner!(indicator_hg, threshold, parameter_s, - u, - element, mesh::AbstractMesh{3}, - equations, dg, cache) - @unpack alpha_max, alpha_min, alpha_smooth, variable = indicator_hg - @unpack alpha, alpha_tmp, indicator_threaded, modal_threaded, - modal_tmp1_threaded, modal_tmp2_threaded = indicator_hg.cache - - indicator = indicator_threaded[Threads.threadid()] - modal = modal_threaded[Threads.threadid()] - modal_tmp1 = modal_tmp1_threaded[Threads.threadid()] - modal_tmp2 = modal_tmp2_threaded[Threads.threadid()] - - # Calculate indicator variables at Gauss-Lobatto nodes - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, k, element) - indicator[i, j, k] = indicator_hg.variable(u_local, equations) + #! format: noindent + + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function create_cache( + ::Type{IndicatorHennemannGassner}, + equations::AbstractEquations{3}, basis::LobattoLegendreBasis + ) + alpha = Vector{real(basis)}() + alpha_tmp = similar(alpha) + + A = Array{real(basis), ndims(equations)} + indicator_threaded = [ + A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] + modal_threaded = [ + A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] + modal_tmp1_threaded = [ + A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] + modal_tmp2_threaded = [ + A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] + + return (; + alpha, alpha_tmp, indicator_threaded, modal_threaded, modal_tmp1_threaded, + modal_tmp2_threaded, + ) end - # Convert to modal representation - multiply_scalar_dimensionwise!(modal, dg.basis.inverse_vandermonde_legendre, - indicator, modal_tmp1, modal_tmp2) - - # Calculate total energies for all modes, without highest, without two highest - total_energy = zero(eltype(modal)) - for k in 1:nnodes(dg), j in 1:nnodes(dg), i in 1:nnodes(dg) - total_energy += modal[i, j, k]^2 - end - total_energy_clip1 = zero(eltype(modal)) - for k in 1:(nnodes(dg) - 1), j in 1:(nnodes(dg) - 1), i in 1:(nnodes(dg) - 1) - total_energy_clip1 += modal[i, j, k]^2 - end - total_energy_clip2 = zero(eltype(modal)) - for k in 1:(nnodes(dg) - 2), j in 1:(nnodes(dg) - 2), i in 1:(nnodes(dg) - 2) - total_energy_clip2 += modal[i, j, k]^2 + # this method is used when the indicator is constructed as for AMR + function create_cache( + typ::Type{IndicatorHennemannGassner}, mesh, + equations::AbstractEquations{3}, dg::DGSEM, cache + ) + create_cache(typ, equations, dg.basis) end - # Calculate energy in higher modes - if !(iszero(total_energy)) - energy_frac_1 = (total_energy - total_energy_clip1) / total_energy - else - energy_frac_1 = zero(total_energy) - end - if !(iszero(total_energy_clip1)) - energy_frac_2 = (total_energy_clip1 - total_energy_clip2) / total_energy_clip1 - else - energy_frac_2 = zero(total_energy_clip1) - end - energy = max(energy_frac_1, energy_frac_2) + # Use this function barrier and unpack inside to avoid passing closures to Polyester.jl + # with @batch (@threaded). + # Otherwise, @threaded does not work here with Julia ARM on macOS. + # See https://github.com/JuliaSIMD/Polyester.jl/issues/88. + @inline function calc_indicator_hennemann_gassner!( + indicator_hg, threshold, parameter_s, + u, + element, mesh::AbstractMesh{3}, + equations, dg, cache + ) + @unpack alpha_max, alpha_min, alpha_smooth, variable = indicator_hg + @unpack alpha, alpha_tmp, indicator_threaded, modal_threaded, + modal_tmp1_threaded, modal_tmp2_threaded = indicator_hg.cache - alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold))) + indicator = indicator_threaded[Threads.threadid()] + modal = modal_threaded[Threads.threadid()] + modal_tmp1 = modal_tmp1_threaded[Threads.threadid()] + modal_tmp2 = modal_tmp2_threaded[Threads.threadid()] - # Take care of the case close to pure DG - if alpha_element < alpha_min - alpha_element = zero(alpha_element) - end + # Calculate indicator variables at Gauss-Lobatto nodes + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, k, element) + indicator[i, j, k] = indicator_hg.variable(u_local, equations) + end - # Take care of the case close to pure FV - if alpha_element > 1 - alpha_min - alpha_element = one(alpha_element) - end + # Convert to modal representation + multiply_scalar_dimensionwise!( + modal, dg.basis.inverse_vandermonde_legendre, + indicator, modal_tmp1, modal_tmp2 + ) - # Clip the maximum amount of FV allowed - alpha[element] = min(alpha_max, alpha_element) -end + # Calculate total energies for all modes, without highest, without two highest + total_energy = zero(eltype(modal)) + for k in 1:nnodes(dg), j in 1:nnodes(dg), i in 1:nnodes(dg) + total_energy += modal[i, j, k]^2 + end + total_energy_clip1 = zero(eltype(modal)) + for k in 1:(nnodes(dg) - 1), j in 1:(nnodes(dg) - 1), i in 1:(nnodes(dg) - 1) + total_energy_clip1 += modal[i, j, k]^2 + end + total_energy_clip2 = zero(eltype(modal)) + for k in 1:(nnodes(dg) - 2), j in 1:(nnodes(dg) - 2), i in 1:(nnodes(dg) - 2) + total_energy_clip2 += modal[i, j, k]^2 + end -function apply_smoothing!(mesh::Union{TreeMesh{3}, P4estMesh{3}, T8codeMesh{3}}, alpha, - alpha_tmp, dg, - cache) + # Calculate energy in higher modes + if !(iszero(total_energy)) + energy_frac_1 = (total_energy - total_energy_clip1) / total_energy + else + energy_frac_1 = zero(total_energy) + end + if !(iszero(total_energy_clip1)) + energy_frac_2 = (total_energy_clip1 - total_energy_clip2) / total_energy_clip1 + else + energy_frac_2 = zero(total_energy_clip1) + end + energy = max(energy_frac_1, energy_frac_2) - # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha - # Copy alpha values such that smoothing is indpedenent of the element access order - alpha_tmp .= alpha + alpha_element = 1 / (1 + exp(-parameter_s / threshold * (energy - threshold))) - # Loop over interfaces - for interface in eachinterface(dg, cache) - # Get neighboring element ids - left = cache.interfaces.neighbor_ids[1, interface] - right = cache.interfaces.neighbor_ids[2, interface] + # Take care of the case close to pure DG + if alpha_element < alpha_min + alpha_element = zero(alpha_element) + end - # Apply smoothing - alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[right], alpha[left]) - alpha[right] = max(alpha_tmp[right], 0.5f0 * alpha_tmp[left], alpha[right]) - end + # Take care of the case close to pure FV + if alpha_element > 1 - alpha_min + alpha_element = one(alpha_element) + end - # Loop over L2 mortars - for mortar in eachmortar(dg, cache) - # Get neighboring element ids - lower_left = cache.mortars.neighbor_ids[1, mortar] - lower_right = cache.mortars.neighbor_ids[2, mortar] - upper_left = cache.mortars.neighbor_ids[3, mortar] - upper_right = cache.mortars.neighbor_ids[4, mortar] - large = cache.mortars.neighbor_ids[5, mortar] - - # Apply smoothing - alpha[lower_left] = max(alpha_tmp[lower_left], 0.5f0 * alpha_tmp[large], - alpha[lower_left]) - alpha[lower_right] = max(alpha_tmp[lower_right], 0.5f0 * alpha_tmp[large], - alpha[lower_right]) - alpha[upper_left] = max(alpha_tmp[upper_left], 0.5f0 * alpha_tmp[large], - alpha[upper_left]) - alpha[upper_right] = max(alpha_tmp[upper_right], 0.5f0 * alpha_tmp[large], - alpha[upper_right]) - - alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[lower_left], - alpha[large]) - alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[lower_right], - alpha[large]) - alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[upper_left], - alpha[large]) - alpha[large] = max(alpha_tmp[large], 0.5f0 * alpha_tmp[upper_right], - alpha[large]) + # Clip the maximum amount of FV allowed + alpha[element] = min(alpha_max, alpha_element) end -end - -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorLöhner}, equations::AbstractEquations{3}, - basis::LobattoLegendreBasis) - alpha = Vector{real(basis)}() - - A = Array{real(basis), ndims(equations)} - indicator_threaded = [A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] - - return (; alpha, indicator_threaded) -end - -# this method is used when the indicator is constructed as for AMR -function create_cache(typ::Type{IndicatorLöhner}, mesh, equations::AbstractEquations{3}, - dg::DGSEM, cache) - create_cache(typ, equations, dg.basis) -end - -function (löhner::IndicatorLöhner)(u::AbstractArray{<:Any, 5}, - mesh, equations, dg::DGSEM, cache; - kwargs...) - @assert nnodes(dg)>=3 "IndicatorLöhner only works for nnodes >= 3 (polydeg > 1)" - @unpack alpha, indicator_threaded = löhner.cache - resize!(alpha, nelements(dg, cache)) - - @threaded for element in eachelement(dg, cache) - indicator = indicator_threaded[Threads.threadid()] - # Calculate indicator variables at Gauss-Lobatto nodes - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, k, element) - indicator[i, j, k] = löhner.variable(u_local, equations) + function apply_smoothing!( + mesh::Union{TreeMesh{3}, P4estMesh{3}, T8codeMesh{3}}, alpha, + alpha_tmp, dg, + cache + ) + + # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha + # Copy alpha values such that smoothing is indpedenent of the element access order + alpha_tmp .= alpha + + # Loop over interfaces + for interface in eachinterface(dg, cache) + # Get neighboring element ids + left = cache.interfaces.neighbor_ids[1, interface] + right = cache.interfaces.neighbor_ids[2, interface] + + # Apply smoothing + alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[right], alpha[left]) + alpha[right] = max(alpha_tmp[right], 0.5f0 * alpha_tmp[left], alpha[right]) end - estimate = zero(real(dg)) - for k in eachnode(dg), j in eachnode(dg), i in 2:(nnodes(dg) - 1) - # x direction - u0 = indicator[i, j, k] - up = indicator[i + 1, j, k] - um = indicator[i - 1, j, k] - estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) + # Loop over L2 mortars + for mortar in eachmortar(dg, cache) + # Get neighboring element ids + lower_left = cache.mortars.neighbor_ids[1, mortar] + lower_right = cache.mortars.neighbor_ids[2, mortar] + upper_left = cache.mortars.neighbor_ids[3, mortar] + upper_right = cache.mortars.neighbor_ids[4, mortar] + large = cache.mortars.neighbor_ids[5, mortar] + + # Apply smoothing + alpha[lower_left] = max( + alpha_tmp[lower_left], 0.5f0 * alpha_tmp[large], + alpha[lower_left] + ) + alpha[lower_right] = max( + alpha_tmp[lower_right], 0.5f0 * alpha_tmp[large], + alpha[lower_right] + ) + alpha[upper_left] = max( + alpha_tmp[upper_left], 0.5f0 * alpha_tmp[large], + alpha[upper_left] + ) + alpha[upper_right] = max( + alpha_tmp[upper_right], 0.5f0 * alpha_tmp[large], + alpha[upper_right] + ) + + alpha[large] = max( + alpha_tmp[large], 0.5f0 * alpha_tmp[lower_left], + alpha[large] + ) + alpha[large] = max( + alpha_tmp[large], 0.5f0 * alpha_tmp[lower_right], + alpha[large] + ) + alpha[large] = max( + alpha_tmp[large], 0.5f0 * alpha_tmp[upper_left], + alpha[large] + ) + alpha[large] = max( + alpha_tmp[large], 0.5f0 * alpha_tmp[upper_right], + alpha[large] + ) end + end - for k in eachnode(dg), j in 2:(nnodes(dg) - 1), i in eachnode(dg) - # y direction - u0 = indicator[i, j, k] - up = indicator[i, j + 1, k] - um = indicator[i, j - 1, k] - estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) - end + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function create_cache( + ::Type{IndicatorLöhner}, equations::AbstractEquations{3}, + basis::LobattoLegendreBasis + ) + alpha = Vector{real(basis)}() - for k in 2:(nnodes(dg) - 1), j in eachnode(dg), i in eachnode(dg) - # y direction - u0 = indicator[i, j, k] - up = indicator[i, j, k + 1] - um = indicator[i, j, k - 1] - estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) - end + A = Array{real(basis), ndims(equations)} + indicator_threaded = [ + A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] - # use the maximum as DG element indicator - alpha[element] = estimate + return (; alpha, indicator_threaded) end - return alpha -end + # this method is used when the indicator is constructed as for AMR + function create_cache( + typ::Type{IndicatorLöhner}, mesh, equations::AbstractEquations{3}, + dg::DGSEM, cache + ) + create_cache(typ, equations, dg.basis) + end -# this method is used when the indicator is constructed as for shock-capturing volume integrals -function create_cache(::Type{IndicatorMax}, equations::AbstractEquations{3}, - basis::LobattoLegendreBasis) - alpha = Vector{real(basis)}() + function (löhner::IndicatorLöhner)( + u::AbstractArray{<:Any, 5}, + mesh, equations, dg::DGSEM, cache; + kwargs... + ) + @assert nnodes(dg) >= 3 "IndicatorLöhner only works for nnodes >= 3 (polydeg > 1)" + @unpack alpha, indicator_threaded = löhner.cache + resize!(alpha, nelements(dg, cache)) + + @threaded for element in eachelement(dg, cache) + indicator = indicator_threaded[Threads.threadid()] + + # Calculate indicator variables at Gauss-Lobatto nodes + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, k, element) + indicator[i, j, k] = löhner.variable(u_local, equations) + end + + estimate = zero(real(dg)) + for k in eachnode(dg), j in eachnode(dg), i in 2:(nnodes(dg) - 1) + # x direction + u0 = indicator[i, j, k] + up = indicator[i + 1, j, k] + um = indicator[i - 1, j, k] + estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) + end + + for k in eachnode(dg), j in 2:(nnodes(dg) - 1), i in eachnode(dg) + # y direction + u0 = indicator[i, j, k] + up = indicator[i, j + 1, k] + um = indicator[i, j - 1, k] + estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) + end + + for k in 2:(nnodes(dg) - 1), j in eachnode(dg), i in eachnode(dg) + # y direction + u0 = indicator[i, j, k] + up = indicator[i, j, k + 1] + um = indicator[i, j, k - 1] + estimate = max(estimate, local_löhner_estimate(um, u0, up, löhner)) + end + + # use the maximum as DG element indicator + alpha[element] = estimate + end - A = Array{real(basis), ndims(equations)} - indicator_threaded = [A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) - for _ in 1:Threads.nthreads()] + return alpha + end - return (; alpha, indicator_threaded) -end + # this method is used when the indicator is constructed as for shock-capturing volume integrals + function create_cache( + ::Type{IndicatorMax}, equations::AbstractEquations{3}, + basis::LobattoLegendreBasis + ) + alpha = Vector{real(basis)}() -# this method is used when the indicator is constructed as for AMR -function create_cache(typ::Type{IndicatorMax}, mesh, equations::AbstractEquations{3}, - dg::DGSEM, cache) - cache = create_cache(typ, equations, dg.basis) -end + A = Array{real(basis), ndims(equations)} + indicator_threaded = [ + A(undef, nnodes(basis), nnodes(basis), nnodes(basis)) + for _ in 1:Threads.nthreads() + ] -function (indicator_max::IndicatorMax)(u::AbstractArray{<:Any, 5}, - mesh, equations, dg::DGSEM, cache; - kwargs...) - @unpack alpha, indicator_threaded = indicator_max.cache - resize!(alpha, nelements(dg, cache)) - indicator_variable = indicator_max.variable + return (; alpha, indicator_threaded) + end - @threaded for element in eachelement(dg, cache) - indicator = indicator_threaded[Threads.threadid()] + # this method is used when the indicator is constructed as for AMR + function create_cache( + typ::Type{IndicatorMax}, mesh, equations::AbstractEquations{3}, + dg::DGSEM, cache + ) + cache = create_cache(typ, equations, dg.basis) + end - # Calculate indicator variables at Gauss-Lobatto nodes - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - u_local = get_node_vars(u, equations, dg, i, j, k, element) - indicator[i, j, k] = indicator_variable(u_local, equations) + function (indicator_max::IndicatorMax)( + u::AbstractArray{<:Any, 5}, + mesh, equations, dg::DGSEM, cache; + kwargs... + ) + @unpack alpha, indicator_threaded = indicator_max.cache + resize!(alpha, nelements(dg, cache)) + indicator_variable = indicator_max.variable + + @threaded for element in eachelement(dg, cache) + indicator = indicator_threaded[Threads.threadid()] + + # Calculate indicator variables at Gauss-Lobatto nodes + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + u_local = get_node_vars(u, equations, dg, i, j, k, element) + indicator[i, j, k] = indicator_variable(u_local, equations) + end + + alpha[element] = maximum(indicator) end - alpha[element] = maximum(indicator) + return alpha end - - return alpha -end end # @muladd diff --git a/src/solvers/dgsem_tree/subcell_limiters.jl b/src/solvers/dgsem_tree/subcell_limiters.jl index f560018f46..ef76a58d3a 100644 --- a/src/solvers/dgsem_tree/subcell_limiters.jl +++ b/src/solvers/dgsem_tree/subcell_limiters.jl @@ -3,226 +3,252 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -abstract type AbstractSubcellLimiter end - -function create_cache(typ::Type{LimiterType}, - semi) where {LimiterType <: AbstractSubcellLimiter} - create_cache(typ, mesh_equations_solver_cache(semi)...) -end - -""" - SubcellLimiterIDP(equations::AbstractEquations, basis; - local_twosided_variables_cons = String[], - positivity_variables_cons = String[], - positivity_variables_nonlinear = [], - positivity_correction_factor = 0.1, - local_onesided_variables_nonlinear = [], - max_iterations_newton = 10, - newton_tolerances = (1.0e-12, 1.0e-14), - gamma_constant_newton = 2 * ndims(equations)) - -Subcell invariant domain preserving (IDP) limiting used with [`VolumeIntegralSubcellLimiting`](@ref) -including: -- Local two-sided Zalesak-type limiting for conservative variables (`local_twosided_variables_cons`) -- Positivity limiting for conservative variables (`positivity_variables_cons`) and nonlinear variables -(`positivity_variables_nonlinear`) -- Local one-sided limiting for nonlinear variables, e.g. `entropy_guermond_etal` and `entropy_math` -with `local_onesided_variables_nonlinear` - -To use these three limiting options use the following structure: - -***Conservative variables*** to be limited are passed as a vector of strings, e.g. -`local_twosided_variables_cons = ["rho"]` and `positivity_variables_cons = ["rho"]`. -For ***nonlinear variables***, the wanted variable functions are passed within a vector: To ensure -positivity use a plain vector including the desired variables, e.g. `positivity_variables_nonlinear = [pressure]`. -For local one-sided limiting pass the variable function combined with the requested bound -(`min` or `max`) as a tuple. For instance, to impose a lower local bound on the modified specific -entropy by Guermond et al. use `local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, min)]`. - -The bounds are calculated using the low-order FV solution. The positivity limiter uses -`positivity_correction_factor` such that `u^new >= positivity_correction_factor * u^FV`. -Local and global limiting of nonlinear variables uses a Newton-bisection method with a maximum of -`max_iterations_newton` iterations, relative and absolute tolerances of `newton_tolerances` -and a provisional update constant `gamma_constant_newton` (`gamma_constant_newton>=2*d`, -where `d = #dimensions`). See equation (20) of Pazner (2020) and equation (30) of Rueda-Ramírez et al. (2022). - -!!! note - This limiter and the correction callback [`SubcellLimiterIDPCorrection`](@ref) only work together. - Without the callback, no correction takes place, leading to a standard low-order FV scheme. - -## References - -- Rueda-Ramírez, Pazner, Gassner (2022) - Subcell Limiting Strategies for Discontinuous Galerkin Spectral Element Methods - [DOI: 10.1016/j.compfluid.2022.105627](https://doi.org/10.1016/j.compfluid.2022.105627) -- Pazner (2020) - Sparse invariant domain preserving discontinuous Galerkin methods with subcell convex limiting - [DOI: 10.1016/j.cma.2021.113876](https://doi.org/10.1016/j.cma.2021.113876) - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -struct SubcellLimiterIDP{RealT <: Real, LimitingVariablesNonlinear, - LimitingOnesidedVariablesNonlinear, Cache} <: - AbstractSubcellLimiter - local_twosided::Bool - local_twosided_variables_cons::Vector{Int} # Local two-sided limiting for conservative variables - positivity::Bool - positivity_variables_cons::Vector{Int} # Positivity for conservative variables - positivity_variables_nonlinear::LimitingVariablesNonlinear # Positivity for nonlinear variables - positivity_correction_factor::RealT - local_onesided::Bool - local_onesided_variables_nonlinear::LimitingOnesidedVariablesNonlinear # Local one-sided limiting for nonlinear variables - cache::Cache - max_iterations_newton::Int - newton_tolerances::Tuple{RealT, RealT} # Relative and absolute tolerances for Newton's method - gamma_constant_newton::RealT # Constant for the subcell limiting of convex (nonlinear) constraints -end - -# this method is used when the limiter is constructed as for shock-capturing volume integrals -function SubcellLimiterIDP(equations::AbstractEquations, basis; - local_twosided_variables_cons = String[], - positivity_variables_cons = String[], - positivity_variables_nonlinear = [], - positivity_correction_factor = 0.1, - local_onesided_variables_nonlinear = [], - max_iterations_newton = 10, - newton_tolerances = (1.0e-12, 1.0e-14), - gamma_constant_newton = 2 * ndims(equations)) - local_twosided = (length(local_twosided_variables_cons) > 0) - local_onesided = (length(local_onesided_variables_nonlinear) > 0) - positivity = (length(positivity_variables_cons) + - length(positivity_variables_nonlinear) > 0) - - # When passing `min` or `max` in the elixir, the specific function of Base is used. - # To speed up the simulation, we replace it with `Trixi.min` and `Trixi.max` respectively. - local_onesided_variables_nonlinear_ = Tuple{Function, Function}[] - for (variable, min_or_max) in local_onesided_variables_nonlinear - if min_or_max === Base.max - push!(local_onesided_variables_nonlinear_, (variable, max)) - elseif min_or_max === Base.min - push!(local_onesided_variables_nonlinear_, (variable, min)) - elseif min_or_max === Trixi.max || min_or_max === Trixi.min - push!(local_onesided_variables_nonlinear_, (variable, min_or_max)) - else - error("Parameter $min_or_max is not a valid input. Use `max` or `min` instead.") - end - end - local_onesided_variables_nonlinear_ = Tuple(local_onesided_variables_nonlinear_) - - local_twosided_variables_cons_ = get_variable_index.(local_twosided_variables_cons, - equations) - positivity_variables_cons_ = get_variable_index.(positivity_variables_cons, - equations) - - bound_keys = () - if local_twosided - for v in local_twosided_variables_cons_ - v_string = string(v) - bound_keys = (bound_keys..., Symbol(v_string, "_min"), - Symbol(v_string, "_max")) - end - end - if local_onesided - for (variable, min_or_max) in local_onesided_variables_nonlinear_ - bound_keys = (bound_keys..., - Symbol(string(variable), "_", string(min_or_max))) - end - end - for v in positivity_variables_cons_ - if !(v in local_twosided_variables_cons_) - bound_keys = (bound_keys..., Symbol(string(v), "_min")) - end + #! format: noindent + + abstract type AbstractSubcellLimiter end + + function create_cache( + typ::Type{LimiterType}, + semi + ) where {LimiterType <: AbstractSubcellLimiter} + create_cache(typ, mesh_equations_solver_cache(semi)...) end - for variable in positivity_variables_nonlinear - bound_keys = (bound_keys..., Symbol(string(variable), "_min")) + + """ + SubcellLimiterIDP(equations::AbstractEquations, basis; + local_twosided_variables_cons = String[], + positivity_variables_cons = String[], + positivity_variables_nonlinear = [], + positivity_correction_factor = 0.1, + local_onesided_variables_nonlinear = [], + max_iterations_newton = 10, + newton_tolerances = (1.0e-12, 1.0e-14), + gamma_constant_newton = 2 * ndims(equations)) + + Subcell invariant domain preserving (IDP) limiting used with [`VolumeIntegralSubcellLimiting`](@ref) + including: + - Local two-sided Zalesak-type limiting for conservative variables (`local_twosided_variables_cons`) + - Positivity limiting for conservative variables (`positivity_variables_cons`) and nonlinear variables + (`positivity_variables_nonlinear`) + - Local one-sided limiting for nonlinear variables, e.g. `entropy_guermond_etal` and `entropy_math` + with `local_onesided_variables_nonlinear` + + To use these three limiting options use the following structure: + + ***Conservative variables*** to be limited are passed as a vector of strings, e.g. + `local_twosided_variables_cons = ["rho"]` and `positivity_variables_cons = ["rho"]`. + For ***nonlinear variables***, the wanted variable functions are passed within a vector: To ensure + positivity use a plain vector including the desired variables, e.g. `positivity_variables_nonlinear = [pressure]`. + For local one-sided limiting pass the variable function combined with the requested bound + (`min` or `max`) as a tuple. For instance, to impose a lower local bound on the modified specific + entropy by Guermond et al. use `local_onesided_variables_nonlinear = [(Trixi.entropy_guermond_etal, min)]`. + + The bounds are calculated using the low-order FV solution. The positivity limiter uses + `positivity_correction_factor` such that `u^new >= positivity_correction_factor * u^FV`. + Local and global limiting of nonlinear variables uses a Newton-bisection method with a maximum of + `max_iterations_newton` iterations, relative and absolute tolerances of `newton_tolerances` + and a provisional update constant `gamma_constant_newton` (`gamma_constant_newton>=2*d`, + where `d = #dimensions`). See equation (20) of Pazner (2020) and equation (30) of Rueda-Ramírez et al. (2022). + + !!! note + This limiter and the correction callback [`SubcellLimiterIDPCorrection`](@ref) only work together. + Without the callback, no correction takes place, leading to a standard low-order FV scheme. + + ## References + + - Rueda-Ramírez, Pazner, Gassner (2022) + Subcell Limiting Strategies for Discontinuous Galerkin Spectral Element Methods + [DOI: 10.1016/j.compfluid.2022.105627](https://doi.org/10.1016/j.compfluid.2022.105627) + - Pazner (2020) + Sparse invariant domain preserving discontinuous Galerkin methods with subcell convex limiting + [DOI: 10.1016/j.cma.2021.113876](https://doi.org/10.1016/j.cma.2021.113876) + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + struct SubcellLimiterIDP{ + RealT <: Real, LimitingVariablesNonlinear, + LimitingOnesidedVariablesNonlinear, Cache, + } <: + AbstractSubcellLimiter + local_twosided::Bool + local_twosided_variables_cons::Vector{Int} # Local two-sided limiting for conservative variables + positivity::Bool + positivity_variables_cons::Vector{Int} # Positivity for conservative variables + positivity_variables_nonlinear::LimitingVariablesNonlinear # Positivity for nonlinear variables + positivity_correction_factor::RealT + local_onesided::Bool + local_onesided_variables_nonlinear::LimitingOnesidedVariablesNonlinear # Local one-sided limiting for nonlinear variables + cache::Cache + max_iterations_newton::Int + newton_tolerances::Tuple{RealT, RealT} # Relative and absolute tolerances for Newton's method + gamma_constant_newton::RealT # Constant for the subcell limiting of convex (nonlinear) constraints end - cache = create_cache(SubcellLimiterIDP, equations, basis, bound_keys) - - SubcellLimiterIDP{typeof(positivity_correction_factor), - typeof(positivity_variables_nonlinear), - typeof(local_onesided_variables_nonlinear_), - typeof(cache)}(local_twosided, local_twosided_variables_cons_, - positivity, positivity_variables_cons_, - positivity_variables_nonlinear, - positivity_correction_factor, - local_onesided, - local_onesided_variables_nonlinear_, - cache, - max_iterations_newton, newton_tolerances, - gamma_constant_newton) -end - -function Base.show(io::IO, limiter::SubcellLimiterIDP) - @nospecialize limiter # reduce precompilation time - (; local_twosided, positivity, local_onesided) = limiter - - print(io, "SubcellLimiterIDP(") - if !(local_twosided || positivity || local_onesided) - print(io, "No limiter selected => pure DG method") - else - features = String[] - if local_twosided - push!(features, "local min/max") + # this method is used when the limiter is constructed as for shock-capturing volume integrals + function SubcellLimiterIDP( + equations::AbstractEquations, basis; + local_twosided_variables_cons = String[], + positivity_variables_cons = String[], + positivity_variables_nonlinear = [], + positivity_correction_factor = 0.1, + local_onesided_variables_nonlinear = [], + max_iterations_newton = 10, + newton_tolerances = (1.0e-12, 1.0e-14), + gamma_constant_newton = 2 * ndims(equations) + ) + local_twosided = (length(local_twosided_variables_cons) > 0) + local_onesided = (length(local_onesided_variables_nonlinear) > 0) + positivity = ( + length(positivity_variables_cons) + + length(positivity_variables_nonlinear) > 0 + ) + + # When passing `min` or `max` in the elixir, the specific function of Base is used. + # To speed up the simulation, we replace it with `Trixi.min` and `Trixi.max` respectively. + local_onesided_variables_nonlinear_ = Tuple{Function, Function}[] + for (variable, min_or_max) in local_onesided_variables_nonlinear + if min_or_max === Base.max + push!(local_onesided_variables_nonlinear_, (variable, max)) + elseif min_or_max === Base.min + push!(local_onesided_variables_nonlinear_, (variable, min)) + elseif min_or_max === Trixi.max || min_or_max === Trixi.min + push!(local_onesided_variables_nonlinear_, (variable, min_or_max)) + else + error("Parameter $min_or_max is not a valid input. Use `max` or `min` instead.") + end end - if positivity - push!(features, "positivity") + local_onesided_variables_nonlinear_ = Tuple(local_onesided_variables_nonlinear_) + + local_twosided_variables_cons_ = get_variable_index.( + local_twosided_variables_cons, + equations + ) + positivity_variables_cons_ = get_variable_index.( + positivity_variables_cons, + equations + ) + + bound_keys = () + if local_twosided + for v in local_twosided_variables_cons_ + v_string = string(v) + bound_keys = ( + bound_keys..., Symbol(v_string, "_min"), + Symbol(v_string, "_max"), + ) + end end if local_onesided - push!(features, "local onesided") + for (variable, min_or_max) in local_onesided_variables_nonlinear_ + bound_keys = ( + bound_keys..., + Symbol(string(variable), "_", string(min_or_max)), + ) + end end - join(io, features, ", ") - print(io, "Limiter=($features), ") + for v in positivity_variables_cons_ + if !(v in local_twosided_variables_cons_) + bound_keys = (bound_keys..., Symbol(string(v), "_min")) + end + end + for variable in positivity_variables_nonlinear + bound_keys = (bound_keys..., Symbol(string(variable), "_min")) + end + + cache = create_cache(SubcellLimiterIDP, equations, basis, bound_keys) + + SubcellLimiterIDP{ + typeof(positivity_correction_factor), + typeof(positivity_variables_nonlinear), + typeof(local_onesided_variables_nonlinear_), + typeof(cache), + }( + local_twosided, local_twosided_variables_cons_, + positivity, positivity_variables_cons_, + positivity_variables_nonlinear, + positivity_correction_factor, + local_onesided, + local_onesided_variables_nonlinear_, + cache, + max_iterations_newton, newton_tolerances, + gamma_constant_newton + ) end - print(io, "Local bounds with FV solution") - print(io, ")") -end -function Base.show(io::IO, ::MIME"text/plain", limiter::SubcellLimiterIDP) - @nospecialize limiter # reduce precompilation time - (; local_twosided, positivity, local_onesided) = limiter + function Base.show(io::IO, limiter::SubcellLimiterIDP) + @nospecialize limiter # reduce precompilation time + (; local_twosided, positivity, local_onesided) = limiter - if get(io, :compact, false) - show(io, limiter) - else + print(io, "SubcellLimiterIDP(") if !(local_twosided || positivity || local_onesided) - setup = ["Limiter" => "No limiter selected => pure DG method"] + print(io, "No limiter selected => pure DG method") else - setup = ["Limiter" => ""] + features = String[] if local_twosided - push!(setup, - "" => "Local two-sided limiting for conservative variables $(limiter.local_twosided_variables_cons)") + push!(features, "local min/max") end if positivity - if !isempty(limiter.positivity_variables_cons) - string = "conservative variables $(limiter.positivity_variables_cons)" - push!(setup, "" => "Positivity limiting for " * string) - end - if !isempty(limiter.positivity_variables_nonlinear) - string = "$(limiter.positivity_variables_nonlinear)" - push!(setup, "" => "Positivity limiting for " * string) - end - push!(setup, - "" => "- with positivity correction factor = $(limiter.positivity_correction_factor)") + push!(features, "positivity") end if local_onesided - for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear - push!(setup, "" => "Local $min_or_max limiting for $variable") + push!(features, "local onesided") + end + join(io, features, ", ") + print(io, "Limiter=($features), ") + end + print(io, "Local bounds with FV solution") + print(io, ")") + end + + function Base.show(io::IO, ::MIME"text/plain", limiter::SubcellLimiterIDP) + @nospecialize limiter # reduce precompilation time + (; local_twosided, positivity, local_onesided) = limiter + + if get(io, :compact, false) + show(io, limiter) + else + if !(local_twosided || positivity || local_onesided) + setup = ["Limiter" => "No limiter selected => pure DG method"] + else + setup = ["Limiter" => ""] + if local_twosided + push!( + setup, + "" => "Local two-sided limiting for conservative variables $(limiter.local_twosided_variables_cons)" + ) end + if positivity + if !isempty(limiter.positivity_variables_cons) + string = "conservative variables $(limiter.positivity_variables_cons)" + push!(setup, "" => "Positivity limiting for " * string) + end + if !isempty(limiter.positivity_variables_nonlinear) + string = "$(limiter.positivity_variables_nonlinear)" + push!(setup, "" => "Positivity limiting for " * string) + end + push!( + setup, + "" => "- with positivity correction factor = $(limiter.positivity_correction_factor)" + ) + end + if local_onesided + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + push!(setup, "" => "Local $min_or_max limiting for $variable") + end + end + push!(setup, "Local bounds" => "FV solution") end - push!(setup, "Local bounds" => "FV solution") + summary_box(io, "SubcellLimiterIDP", setup) end - summary_box(io, "SubcellLimiterIDP", setup) end -end -function get_node_variables!(node_variables, limiter::SubcellLimiterIDP, - ::VolumeIntegralSubcellLimiting, equations) - node_variables[:limiting_coefficient] = limiter.cache.subcell_limiter_coefficients.alpha + function get_node_variables!( + node_variables, limiter::SubcellLimiterIDP, + ::VolumeIntegralSubcellLimiting, equations + ) + node_variables[:limiting_coefficient] = limiter.cache.subcell_limiter_coefficients.alpha - return nothing -end + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_tree/subcell_limiters_2d.jl b/src/solvers/dgsem_tree/subcell_limiters_2d.jl index 5dae5798a8..7a694c9214 100644 --- a/src/solvers/dgsem_tree/subcell_limiters_2d.jl +++ b/src/solvers/dgsem_tree/subcell_limiters_2d.jl @@ -3,678 +3,778 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -############################################################################### -# IDP Limiting -############################################################################### - -# this method is used when the limiter is constructed as for shock-capturing volume integrals -function create_cache(limiter::Type{SubcellLimiterIDP}, equations::AbstractEquations{2}, - basis::LobattoLegendreBasis, bound_keys) - subcell_limiter_coefficients = Trixi.ContainerSubcellLimiterIDP2D{real(basis)}(0, - nnodes(basis), - bound_keys) - - # Memory for bounds checking routine with `BoundsCheckCallback`. - # Local variable contains the maximum deviation since the last export. - idp_bounds_delta_local = Dict{Symbol, real(basis)}() - # Global variable contains the total maximum deviation. - idp_bounds_delta_global = Dict{Symbol, real(basis)}() - for key in bound_keys - idp_bounds_delta_local[key] = zero(real(basis)) - idp_bounds_delta_global[key] = zero(real(basis)) - end - - return (; subcell_limiter_coefficients, idp_bounds_delta_local, - idp_bounds_delta_global) -end - -function (limiter::SubcellLimiterIDP)(u::AbstractArray{<:Any, 4}, semi, dg::DGSEM, t, - dt; - kwargs...) - @unpack alpha = limiter.cache.subcell_limiter_coefficients - # TODO: Do not abuse `reset_du!` but maybe implement a generic `set_zero!` - @trixi_timeit timer() "reset alpha" reset_du!(alpha, dg, semi.cache) + #! format: noindent + + ############################################################################### + # IDP Limiting + ############################################################################### + + # this method is used when the limiter is constructed as for shock-capturing volume integrals + function create_cache( + limiter::Type{SubcellLimiterIDP}, equations::AbstractEquations{2}, + basis::LobattoLegendreBasis, bound_keys + ) + subcell_limiter_coefficients = Trixi.ContainerSubcellLimiterIDP2D{real(basis)}( + 0, + nnodes(basis), + bound_keys + ) + + # Memory for bounds checking routine with `BoundsCheckCallback`. + # Local variable contains the maximum deviation since the last export. + idp_bounds_delta_local = Dict{Symbol, real(basis)}() + # Global variable contains the total maximum deviation. + idp_bounds_delta_global = Dict{Symbol, real(basis)}() + for key in bound_keys + idp_bounds_delta_local[key] = zero(real(basis)) + idp_bounds_delta_global[key] = zero(real(basis)) + end - if limiter.local_twosided - @trixi_timeit timer() "local twosided" idp_local_twosided!(alpha, limiter, - u, t, dt, semi) - end - if limiter.positivity - @trixi_timeit timer() "positivity" idp_positivity!(alpha, limiter, u, dt, semi) - end - if limiter.local_onesided - @trixi_timeit timer() "local onesided" idp_local_onesided!(alpha, limiter, - u, t, dt, semi) + return (; + subcell_limiter_coefficients, idp_bounds_delta_local, + idp_bounds_delta_global, + ) end - # Calculate alpha1 and alpha2 - @unpack alpha1, alpha2 = limiter.cache.subcell_limiter_coefficients - @threaded for element in eachelement(dg, semi.cache) - for j in eachnode(dg), i in 2:nnodes(dg) - alpha1[i, j, element] = max(alpha[i - 1, j, element], alpha[i, j, element]) + function (limiter::SubcellLimiterIDP)( + u::AbstractArray{<:Any, 4}, semi, dg::DGSEM, t, + dt; + kwargs... + ) + @unpack alpha = limiter.cache.subcell_limiter_coefficients + # TODO: Do not abuse `reset_du!` but maybe implement a generic `set_zero!` + @trixi_timeit timer() "reset alpha" reset_du!(alpha, dg, semi.cache) + + if limiter.local_twosided + @trixi_timeit timer() "local twosided" idp_local_twosided!( + alpha, limiter, + u, t, dt, semi + ) end - for j in 2:nnodes(dg), i in eachnode(dg) - alpha2[i, j, element] = max(alpha[i, j - 1, element], alpha[i, j, element]) + if limiter.positivity + @trixi_timeit timer() "positivity" idp_positivity!(alpha, limiter, u, dt, semi) + end + if limiter.local_onesided + @trixi_timeit timer() "local onesided" idp_local_onesided!( + alpha, limiter, + u, t, dt, semi + ) end - alpha1[1, :, element] .= zero(eltype(alpha1)) - alpha1[nnodes(dg) + 1, :, element] .= zero(eltype(alpha1)) - alpha2[:, 1, element] .= zero(eltype(alpha2)) - alpha2[:, nnodes(dg) + 1, element] .= zero(eltype(alpha2)) - end - return nothing -end - -############################################################################### -# Calculation of local bounds using low-order FV solution - -@inline function calc_bounds_twosided!(var_min, var_max, variable, u, t, semi) - mesh, equations, dg, cache = mesh_equations_solver_cache(semi) - # Calc bounds inside elements - @threaded for element in eachelement(dg, cache) - var_min[:, :, element] .= typemax(eltype(var_min)) - var_max[:, :, element] .= typemin(eltype(var_max)) - # Calculate bounds at Gauss-Lobatto nodes using u - for j in eachnode(dg), i in eachnode(dg) - var = u[variable, i, j, element] - var_min[i, j, element] = min(var_min[i, j, element], var) - var_max[i, j, element] = max(var_max[i, j, element], var) - - if i > 1 - var_min[i - 1, j, element] = min(var_min[i - 1, j, element], var) - var_max[i - 1, j, element] = max(var_max[i - 1, j, element], var) - end - if i < nnodes(dg) - var_min[i + 1, j, element] = min(var_min[i + 1, j, element], var) - var_max[i + 1, j, element] = max(var_max[i + 1, j, element], var) + # Calculate alpha1 and alpha2 + @unpack alpha1, alpha2 = limiter.cache.subcell_limiter_coefficients + @threaded for element in eachelement(dg, semi.cache) + for j in eachnode(dg), i in 2:nnodes(dg) + alpha1[i, j, element] = max(alpha[i - 1, j, element], alpha[i, j, element]) end - if j > 1 - var_min[i, j - 1, element] = min(var_min[i, j - 1, element], var) - var_max[i, j - 1, element] = max(var_max[i, j - 1, element], var) - end - if j < nnodes(dg) - var_min[i, j + 1, element] = min(var_min[i, j + 1, element], var) - var_max[i, j + 1, element] = max(var_max[i, j + 1, element], var) + for j in 2:nnodes(dg), i in eachnode(dg) + alpha2[i, j, element] = max(alpha[i, j - 1, element], alpha[i, j, element]) end + alpha1[1, :, element] .= zero(eltype(alpha1)) + alpha1[nnodes(dg) + 1, :, element] .= zero(eltype(alpha1)) + alpha2[:, 1, element] .= zero(eltype(alpha2)) + alpha2[:, nnodes(dg) + 1, element] .= zero(eltype(alpha2)) end + + return nothing end - # Values at element boundary - calc_bounds_twosided_interface!(var_min, var_max, variable, u, t, semi, mesh) -end - -@inline function calc_bounds_twosided_interface!(var_min, var_max, variable, u, t, semi, - mesh::TreeMesh2D) - _, equations, dg, cache = mesh_equations_solver_cache(semi) - (; boundary_conditions) = semi - # Calc bounds at interfaces and periodic boundaries - for interface in eachinterface(dg, cache) - # Get neighboring element ids - left = cache.interfaces.neighbor_ids[1, interface] - right = cache.interfaces.neighbor_ids[2, interface] - - orientation = cache.interfaces.orientations[interface] - - for i in eachnode(dg) - index_left = (nnodes(dg), i) - index_right = (1, i) - if orientation == 2 - index_left = reverse(index_left) - index_right = reverse(index_right) + ############################################################################### + # Calculation of local bounds using low-order FV solution + + @inline function calc_bounds_twosided!(var_min, var_max, variable, u, t, semi) + mesh, equations, dg, cache = mesh_equations_solver_cache(semi) + # Calc bounds inside elements + @threaded for element in eachelement(dg, cache) + var_min[:, :, element] .= typemax(eltype(var_min)) + var_max[:, :, element] .= typemin(eltype(var_max)) + # Calculate bounds at Gauss-Lobatto nodes using u + for j in eachnode(dg), i in eachnode(dg) + var = u[variable, i, j, element] + var_min[i, j, element] = min(var_min[i, j, element], var) + var_max[i, j, element] = max(var_max[i, j, element], var) + + if i > 1 + var_min[i - 1, j, element] = min(var_min[i - 1, j, element], var) + var_max[i - 1, j, element] = max(var_max[i - 1, j, element], var) + end + if i < nnodes(dg) + var_min[i + 1, j, element] = min(var_min[i + 1, j, element], var) + var_max[i + 1, j, element] = max(var_max[i + 1, j, element], var) + end + if j > 1 + var_min[i, j - 1, element] = min(var_min[i, j - 1, element], var) + var_max[i, j - 1, element] = max(var_max[i, j - 1, element], var) + end + if j < nnodes(dg) + var_min[i, j + 1, element] = min(var_min[i, j + 1, element], var) + var_max[i, j + 1, element] = max(var_max[i, j + 1, element], var) + end end - var_left = u[variable, index_left..., left] - var_right = u[variable, index_right..., right] - - var_min[index_right..., right] = min(var_min[index_right..., right], - var_left) - var_max[index_right..., right] = max(var_max[index_right..., right], - var_left) - - var_min[index_left..., left] = min(var_min[index_left..., left], var_right) - var_max[index_left..., left] = max(var_max[index_left..., left], var_right) end + + # Values at element boundary + calc_bounds_twosided_interface!(var_min, var_max, variable, u, t, semi, mesh) end - # Calc bounds at physical boundaries - for boundary in eachboundary(dg, cache) - element = cache.boundaries.neighbor_ids[boundary] - orientation = cache.boundaries.orientations[boundary] - neighbor_side = cache.boundaries.neighbor_sides[boundary] - - for i in eachnode(dg) - if neighbor_side == 2 # Element is on the right, boundary on the left - index = (1, i) - boundary_index = 1 - else # Element is on the left, boundary on the right - index = (nnodes(dg), i) - boundary_index = 2 + @inline function calc_bounds_twosided_interface!( + var_min, var_max, variable, u, t, semi, + mesh::TreeMesh2D + ) + _, equations, dg, cache = mesh_equations_solver_cache(semi) + (; boundary_conditions) = semi + # Calc bounds at interfaces and periodic boundaries + for interface in eachinterface(dg, cache) + # Get neighboring element ids + left = cache.interfaces.neighbor_ids[1, interface] + right = cache.interfaces.neighbor_ids[2, interface] + + orientation = cache.interfaces.orientations[interface] + + for i in eachnode(dg) + index_left = (nnodes(dg), i) + index_right = (1, i) + if orientation == 2 + index_left = reverse(index_left) + index_right = reverse(index_right) + end + var_left = u[variable, index_left..., left] + var_right = u[variable, index_right..., right] + + var_min[index_right..., right] = min( + var_min[index_right..., right], + var_left + ) + var_max[index_right..., right] = max( + var_max[index_right..., right], + var_left + ) + + var_min[index_left..., left] = min(var_min[index_left..., left], var_right) + var_max[index_left..., left] = max(var_max[index_left..., left], var_right) end - if orientation == 2 - index = reverse(index) - boundary_index += 2 - end - u_inner = get_node_vars(u, equations, dg, index..., element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[boundary_index], - orientation, boundary_index, - equations, dg, cache, - index..., element) - var_outer = u_outer[variable] - - var_min[index..., element] = min(var_min[index..., element], var_outer) - var_max[index..., element] = max(var_max[index..., element], var_outer) end - end - return nothing -end - -@inline function calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi) - mesh, equations, dg, cache = mesh_equations_solver_cache(semi) - # Calc bounds inside elements - @threaded for element in eachelement(dg, cache) - # Reset bounds - for j in eachnode(dg), i in eachnode(dg) - if min_or_max === max - var_minmax[i, j, element] = typemin(eltype(var_minmax)) - else - var_minmax[i, j, element] = typemax(eltype(var_minmax)) + # Calc bounds at physical boundaries + for boundary in eachboundary(dg, cache) + element = cache.boundaries.neighbor_ids[boundary] + orientation = cache.boundaries.orientations[boundary] + neighbor_side = cache.boundaries.neighbor_sides[boundary] + + for i in eachnode(dg) + if neighbor_side == 2 # Element is on the right, boundary on the left + index = (1, i) + boundary_index = 1 + else # Element is on the left, boundary on the right + index = (nnodes(dg), i) + boundary_index = 2 + end + if orientation == 2 + index = reverse(index) + boundary_index += 2 + end + u_inner = get_node_vars(u, equations, dg, index..., element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[boundary_index], + orientation, boundary_index, + equations, dg, cache, + index..., element + ) + var_outer = u_outer[variable] + + var_min[index..., element] = min(var_min[index..., element], var_outer) + var_max[index..., element] = max(var_max[index..., element], var_outer) end end - # Calculate bounds at Gauss-Lobatto nodes using u - for j in eachnode(dg), i in eachnode(dg) - var = variable(get_node_vars(u, equations, dg, i, j, element), equations) - var_minmax[i, j, element] = min_or_max(var_minmax[i, j, element], var) + return nothing + end - if i > 1 - var_minmax[i - 1, j, element] = min_or_max(var_minmax[i - 1, j, - element], var) + @inline function calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi) + mesh, equations, dg, cache = mesh_equations_solver_cache(semi) + # Calc bounds inside elements + @threaded for element in eachelement(dg, cache) + # Reset bounds + for j in eachnode(dg), i in eachnode(dg) + if min_or_max === max + var_minmax[i, j, element] = typemin(eltype(var_minmax)) + else + var_minmax[i, j, element] = typemax(eltype(var_minmax)) + end end - if i < nnodes(dg) - var_minmax[i + 1, j, element] = min_or_max(var_minmax[i + 1, j, - element], var) - end - if j > 1 - var_minmax[i, j - 1, element] = min_or_max(var_minmax[i, j - 1, - element], var) - end - if j < nnodes(dg) - var_minmax[i, j + 1, element] = min_or_max(var_minmax[i, j + 1, - element], var) + + # Calculate bounds at Gauss-Lobatto nodes using u + for j in eachnode(dg), i in eachnode(dg) + var = variable(get_node_vars(u, equations, dg, i, j, element), equations) + var_minmax[i, j, element] = min_or_max(var_minmax[i, j, element], var) + + if i > 1 + var_minmax[i - 1, j, element] = min_or_max( + var_minmax[ + i - 1, j, + element, + ], var + ) + end + if i < nnodes(dg) + var_minmax[i + 1, j, element] = min_or_max( + var_minmax[ + i + 1, j, + element, + ], var + ) + end + if j > 1 + var_minmax[i, j - 1, element] = min_or_max( + var_minmax[ + i, j - 1, + element, + ], var + ) + end + if j < nnodes(dg) + var_minmax[i, j + 1, element] = min_or_max( + var_minmax[ + i, j + 1, + element, + ], var + ) + end end end + + # Values at element boundary + calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u, t, semi, mesh) end - # Values at element boundary - calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u, t, semi, mesh) -end - -@inline function calc_bounds_onesided_interface!(var_minmax, min_or_max, variable, u, t, - semi, mesh::TreeMesh2D) - _, equations, dg, cache = mesh_equations_solver_cache(semi) - (; boundary_conditions) = semi - # Calc bounds at interfaces and periodic boundaries - for interface in eachinterface(dg, cache) - # Get neighboring element ids - left = cache.interfaces.neighbor_ids[1, interface] - right = cache.interfaces.neighbor_ids[2, interface] - - orientation = cache.interfaces.orientations[interface] - - for i in eachnode(dg) - index_left = (nnodes(dg), i) - index_right = (1, i) - if orientation == 2 - index_left = reverse(index_left) - index_right = reverse(index_right) + @inline function calc_bounds_onesided_interface!( + var_minmax, min_or_max, variable, u, t, + semi, mesh::TreeMesh2D + ) + _, equations, dg, cache = mesh_equations_solver_cache(semi) + (; boundary_conditions) = semi + # Calc bounds at interfaces and periodic boundaries + for interface in eachinterface(dg, cache) + # Get neighboring element ids + left = cache.interfaces.neighbor_ids[1, interface] + right = cache.interfaces.neighbor_ids[2, interface] + + orientation = cache.interfaces.orientations[interface] + + for i in eachnode(dg) + index_left = (nnodes(dg), i) + index_right = (1, i) + if orientation == 2 + index_left = reverse(index_left) + index_right = reverse(index_right) + end + var_left = variable( + get_node_vars(u, equations, dg, index_left..., left), + equations + ) + var_right = variable( + get_node_vars(u, equations, dg, index_right..., right), + equations + ) + + var_minmax[index_right..., right] = min_or_max( + var_minmax[ + index_right..., + right, + ], var_left + ) + var_minmax[index_left..., left] = min_or_max( + var_minmax[ + index_left..., + left, + ], var_right + ) end - var_left = variable(get_node_vars(u, equations, dg, index_left..., left), - equations) - var_right = variable(get_node_vars(u, equations, dg, index_right..., right), - equations) - - var_minmax[index_right..., right] = min_or_max(var_minmax[index_right..., - right], var_left) - var_minmax[index_left..., left] = min_or_max(var_minmax[index_left..., - left], var_right) end - end - # Calc bounds at physical boundaries - for boundary in eachboundary(dg, cache) - element = cache.boundaries.neighbor_ids[boundary] - orientation = cache.boundaries.orientations[boundary] - neighbor_side = cache.boundaries.neighbor_sides[boundary] - - for i in eachnode(dg) - if neighbor_side == 2 # Element is on the right, boundary on the left - index = (1, i) - boundary_index = 1 - else # Element is on the left, boundary on the right - index = (nnodes(dg), i) - boundary_index = 2 + # Calc bounds at physical boundaries + for boundary in eachboundary(dg, cache) + element = cache.boundaries.neighbor_ids[boundary] + orientation = cache.boundaries.orientations[boundary] + neighbor_side = cache.boundaries.neighbor_sides[boundary] + + for i in eachnode(dg) + if neighbor_side == 2 # Element is on the right, boundary on the left + index = (1, i) + boundary_index = 1 + else # Element is on the left, boundary on the right + index = (nnodes(dg), i) + boundary_index = 2 + end + if orientation == 2 + index = reverse(index) + boundary_index += 2 + end + u_inner = get_node_vars(u, equations, dg, index..., element) + u_outer = get_boundary_outer_state( + u_inner, t, + boundary_conditions[boundary_index], + orientation, boundary_index, + equations, dg, cache, + index..., element + ) + var_outer = variable(u_outer, equations) + + var_minmax[index..., element] = min_or_max( + var_minmax[index..., element], + var_outer + ) end - if orientation == 2 - index = reverse(index) - boundary_index += 2 - end - u_inner = get_node_vars(u, equations, dg, index..., element) - u_outer = get_boundary_outer_state(u_inner, t, - boundary_conditions[boundary_index], - orientation, boundary_index, - equations, dg, cache, - index..., element) - var_outer = variable(u_outer, equations) - - var_minmax[index..., element] = min_or_max(var_minmax[index..., element], - var_outer) end + + return nothing end - return nothing -end + ############################################################################### + # Local two-sided limiting of conservative variables -############################################################################### -# Local two-sided limiting of conservative variables + @inline function idp_local_twosided!(alpha, limiter, u, t, dt, semi) + for variable in limiter.local_twosided_variables_cons + idp_local_twosided!(alpha, limiter, u, t, dt, semi, variable) + end -@inline function idp_local_twosided!(alpha, limiter, u, t, dt, semi) - for variable in limiter.local_twosided_variables_cons - idp_local_twosided!(alpha, limiter, u, t, dt, semi, variable) + return nothing end - return nothing -end - -@inline function idp_local_twosided!(alpha, limiter, u, t, dt, semi, variable) - mesh, _, dg, cache = mesh_equations_solver_cache(semi) - (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes - (; inverse_weights) = dg.basis - - (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - variable_string = string(variable) - var_min = variable_bounds[Symbol(variable_string, "_min")] - var_max = variable_bounds[Symbol(variable_string, "_max")] - calc_bounds_twosided!(var_min, var_max, variable, u, t, semi) - - @threaded for element in eachelement(dg, semi.cache) - for j in eachnode(dg), i in eachnode(dg) - inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, i, j, element) - var = u[variable, i, j, element] - # Real Zalesak type limiter - # * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids" - # * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics" - # Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is - # for each interface, not each node - - Qp = max(0, (var_max[i, j, element] - var) / dt) - Qm = min(0, (var_min[i, j, element] - var) / dt) - - # Calculate Pp and Pm - # Note: Boundaries of antidiffusive_flux1/2 are constant 0, so they make no difference here. - val_flux1_local = inverse_weights[i] * - antidiffusive_flux1_R[variable, i, j, element] - val_flux1_local_ip1 = -inverse_weights[i] * - antidiffusive_flux1_L[variable, i + 1, j, element] - val_flux2_local = inverse_weights[j] * - antidiffusive_flux2_R[variable, i, j, element] - val_flux2_local_jp1 = -inverse_weights[j] * - antidiffusive_flux2_L[variable, i, j + 1, element] - - Pp = max(0, val_flux1_local) + max(0, val_flux1_local_ip1) + - max(0, val_flux2_local) + max(0, val_flux2_local_jp1) - Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) + - min(0, val_flux2_local) + min(0, val_flux2_local_jp1) - - Qp = max(0, (var_max[i, j, element] - var) / dt) - Qm = min(0, (var_min[i, j, element] - var) / dt) - - Pp = inverse_jacobian * Pp - Pm = inverse_jacobian * Pm - - # Compute blending coefficient avoiding division by zero - # (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8)) - Qp = abs(Qp) / - (abs(Pp) + eps(typeof(Qp)) * 100 * abs(var_max[i, j, element])) - Qm = abs(Qm) / - (abs(Pm) + eps(typeof(Qm)) * 100 * abs(var_max[i, j, element])) - - # Calculate alpha at nodes - alpha[i, j, element] = max(alpha[i, j, element], 1 - min(1, Qp, Qm)) + @inline function idp_local_twosided!(alpha, limiter, u, t, dt, semi, variable) + mesh, _, dg, cache = mesh_equations_solver_cache(semi) + (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes + (; inverse_weights) = dg.basis + + (; variable_bounds) = limiter.cache.subcell_limiter_coefficients + variable_string = string(variable) + var_min = variable_bounds[Symbol(variable_string, "_min")] + var_max = variable_bounds[Symbol(variable_string, "_max")] + calc_bounds_twosided!(var_min, var_max, variable, u, t, semi) + + @threaded for element in eachelement(dg, semi.cache) + for j in eachnode(dg), i in eachnode(dg) + inverse_jacobian = get_inverse_jacobian( + cache.elements.inverse_jacobian, + mesh, i, j, element + ) + var = u[variable, i, j, element] + # Real Zalesak type limiter + # * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids" + # * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics" + # Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is + # for each interface, not each node + + Qp = max(0, (var_max[i, j, element] - var) / dt) + Qm = min(0, (var_min[i, j, element] - var) / dt) + + # Calculate Pp and Pm + # Note: Boundaries of antidiffusive_flux1/2 are constant 0, so they make no difference here. + val_flux1_local = inverse_weights[i] * + antidiffusive_flux1_R[variable, i, j, element] + val_flux1_local_ip1 = -inverse_weights[i] * + antidiffusive_flux1_L[variable, i + 1, j, element] + val_flux2_local = inverse_weights[j] * + antidiffusive_flux2_R[variable, i, j, element] + val_flux2_local_jp1 = -inverse_weights[j] * + antidiffusive_flux2_L[variable, i, j + 1, element] + + Pp = max(0, val_flux1_local) + max(0, val_flux1_local_ip1) + + max(0, val_flux2_local) + max(0, val_flux2_local_jp1) + Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) + + min(0, val_flux2_local) + min(0, val_flux2_local_jp1) + + Qp = max(0, (var_max[i, j, element] - var) / dt) + Qm = min(0, (var_min[i, j, element] - var) / dt) + + Pp = inverse_jacobian * Pp + Pm = inverse_jacobian * Pm + + # Compute blending coefficient avoiding division by zero + # (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8)) + Qp = abs(Qp) / + (abs(Pp) + eps(typeof(Qp)) * 100 * abs(var_max[i, j, element])) + Qm = abs(Qm) / + (abs(Pm) + eps(typeof(Qm)) * 100 * abs(var_max[i, j, element])) + + # Calculate alpha at nodes + alpha[i, j, element] = max(alpha[i, j, element], 1 - min(1, Qp, Qm)) + end end + + return nothing end - return nothing -end + ############################################################################## + # Local one-sided limiting of nonlinear variables -############################################################################## -# Local one-sided limiting of nonlinear variables + @inline function idp_local_onesided!(alpha, limiter, u, t, dt, semi) + for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear + idp_local_onesided!(alpha, limiter, u, t, dt, semi, variable, min_or_max) + end -@inline function idp_local_onesided!(alpha, limiter, u, t, dt, semi) - for (variable, min_or_max) in limiter.local_onesided_variables_nonlinear - idp_local_onesided!(alpha, limiter, u, t, dt, semi, variable, min_or_max) + return nothing end - return nothing -end - -@inline function idp_local_onesided!(alpha, limiter, u, t, dt, semi, - variable, min_or_max) - mesh, equations, dg, cache = mesh_equations_solver_cache(semi) - (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - var_minmax = variable_bounds[Symbol(string(variable), "_", string(min_or_max))] - calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi) - - # Perform Newton's bisection method to find new alpha - @threaded for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, i, j, element) - u_local = get_node_vars(u, equations, dg, i, j, element) - newton_loops_alpha!(alpha, var_minmax[i, j, element], u_local, - i, j, element, variable, min_or_max, - initial_check_local_onesided_newton_idp, - final_check_local_onesided_newton_idp, inverse_jacobian, - dt, equations, dg, cache, limiter) + @inline function idp_local_onesided!( + alpha, limiter, u, t, dt, semi, + variable, min_or_max + ) + mesh, equations, dg, cache = mesh_equations_solver_cache(semi) + (; variable_bounds) = limiter.cache.subcell_limiter_coefficients + var_minmax = variable_bounds[Symbol(string(variable), "_", string(min_or_max))] + calc_bounds_onesided!(var_minmax, min_or_max, variable, u, t, semi) + + # Perform Newton's bisection method to find new alpha + @threaded for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + inverse_jacobian = get_inverse_jacobian( + cache.elements.inverse_jacobian, + mesh, i, j, element + ) + u_local = get_node_vars(u, equations, dg, i, j, element) + newton_loops_alpha!( + alpha, var_minmax[i, j, element], u_local, + i, j, element, variable, min_or_max, + initial_check_local_onesided_newton_idp, + final_check_local_onesided_newton_idp, inverse_jacobian, + dt, equations, dg, cache, limiter + ) + end end - end - return nothing -end - -############################################################################### -# Global positivity limiting - -@inline function idp_positivity!(alpha, limiter, u, dt, semi) - # Conservative variables - for variable in limiter.positivity_variables_cons - @trixi_timeit timer() "conservative variables" idp_positivity_conservative!(alpha, - limiter, - u, - dt, - semi, - variable) + return nothing end - # Nonlinear variables - for variable in limiter.positivity_variables_nonlinear - @trixi_timeit timer() "nonlinear variables" idp_positivity_nonlinear!(alpha, - limiter, - u, dt, - semi, - variable) - end - - return nothing -end - -############################################################################### -# Global positivity limiting of conservative variables + ############################################################################### + # Global positivity limiting + + @inline function idp_positivity!(alpha, limiter, u, dt, semi) + # Conservative variables + for variable in limiter.positivity_variables_cons + @trixi_timeit timer() "conservative variables" idp_positivity_conservative!( + alpha, + limiter, + u, + dt, + semi, + variable + ) + end -@inline function idp_positivity_conservative!(alpha, limiter, u, dt, semi, variable) - mesh, _, dg, cache = mesh_equations_solver_cache(semi) - (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes - (; inverse_weights) = dg.basis - (; positivity_correction_factor) = limiter + # Nonlinear variables + for variable in limiter.positivity_variables_nonlinear + @trixi_timeit timer() "nonlinear variables" idp_positivity_nonlinear!( + alpha, + limiter, + u, dt, + semi, + variable + ) + end - (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - var_min = variable_bounds[Symbol(string(variable), "_min")] + return nothing + end - @threaded for element in eachelement(dg, semi.cache) - for j in eachnode(dg), i in eachnode(dg) - inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, i, j, element) - var = u[variable, i, j, element] - if var < 0 - error("Safe low-order method produces negative value for conservative variable $variable. Try a smaller time step.") + ############################################################################### + # Global positivity limiting of conservative variables + + @inline function idp_positivity_conservative!(alpha, limiter, u, dt, semi, variable) + mesh, _, dg, cache = mesh_equations_solver_cache(semi) + (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes + (; inverse_weights) = dg.basis + (; positivity_correction_factor) = limiter + + (; variable_bounds) = limiter.cache.subcell_limiter_coefficients + var_min = variable_bounds[Symbol(string(variable), "_min")] + + @threaded for element in eachelement(dg, semi.cache) + for j in eachnode(dg), i in eachnode(dg) + inverse_jacobian = get_inverse_jacobian( + cache.elements.inverse_jacobian, + mesh, i, j, element + ) + var = u[variable, i, j, element] + if var < 0 + error("Safe low-order method produces negative value for conservative variable $variable. Try a smaller time step.") + end + + # Compute bound + if limiter.local_twosided && + variable in limiter.local_twosided_variables_cons && + var_min[i, j, element] >= positivity_correction_factor * var + # Local limiting is more restrictive that positivity limiting + # => Skip positivity limiting for this node + continue + end + var_min[i, j, element] = positivity_correction_factor * var + + # Real one-sided Zalesak-type limiter + # * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids" + # * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics" + # Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is + # for each interface, not each node + Qm = min(0, (var_min[i, j, element] - var) / dt) + + # Calculate Pm + # Note: Boundaries of antidiffusive_flux1/2 are constant 0, so they make no difference here. + val_flux1_local = inverse_weights[i] * + antidiffusive_flux1_R[variable, i, j, element] + val_flux1_local_ip1 = -inverse_weights[i] * + antidiffusive_flux1_L[variable, i + 1, j, element] + val_flux2_local = inverse_weights[j] * + antidiffusive_flux2_R[variable, i, j, element] + val_flux2_local_jp1 = -inverse_weights[j] * + antidiffusive_flux2_L[variable, i, j + 1, element] + + Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) + + min(0, val_flux2_local) + min(0, val_flux2_local_jp1) + Pm = inverse_jacobian * Pm + + # Compute blending coefficient avoiding division by zero + # (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8)) + Qm = abs(Qm) / (abs(Pm) + eps(typeof(Qm)) * 100) + + # Calculate alpha + alpha[i, j, element] = max(alpha[i, j, element], 1 - Qm) end + end + + return nothing + end - # Compute bound - if limiter.local_twosided && - variable in limiter.local_twosided_variables_cons && - var_min[i, j, element] >= positivity_correction_factor * var - # Local limiting is more restrictive that positivity limiting - # => Skip positivity limiting for this node - continue + ############################################################################### + # Global positivity limiting of nonlinear variables + + @inline function idp_positivity_nonlinear!(alpha, limiter, u, dt, semi, variable) + mesh, equations, dg, cache = mesh_equations_solver_cache(semi) + (; positivity_correction_factor) = limiter + + (; variable_bounds) = limiter.cache.subcell_limiter_coefficients + var_min = variable_bounds[Symbol(string(variable), "_min")] + + @threaded for element in eachelement(dg, semi.cache) + for j in eachnode(dg), i in eachnode(dg) + inverse_jacobian = get_inverse_jacobian( + cache.elements.inverse_jacobian, + mesh, i, j, element + ) + + # Compute bound + u_local = get_node_vars(u, equations, dg, i, j, element) + var = variable(u_local, equations) + if var < 0 + error("Safe low-order method produces negative value for variable $variable. Try a smaller time step.") + end + var_min[i, j, element] = positivity_correction_factor * var + + # Perform Newton's bisection method to find new alpha + newton_loops_alpha!( + alpha, var_min[i, j, element], u_local, i, j, element, + variable, min, initial_check_nonnegative_newton_idp, + final_check_nonnegative_newton_idp, inverse_jacobian, + dt, equations, dg, cache, limiter + ) end - var_min[i, j, element] = positivity_correction_factor * var - - # Real one-sided Zalesak-type limiter - # * Zalesak (1979). "Fully multidimensional flux-corrected transport algorithms for fluids" - # * Kuzmin et al. (2010). "Failsafe flux limiting and constrained data projections for equations of gas dynamics" - # Note: The Zalesak limiter has to be computed, even if the state is valid, because the correction is - # for each interface, not each node - Qm = min(0, (var_min[i, j, element] - var) / dt) - - # Calculate Pm - # Note: Boundaries of antidiffusive_flux1/2 are constant 0, so they make no difference here. - val_flux1_local = inverse_weights[i] * - antidiffusive_flux1_R[variable, i, j, element] - val_flux1_local_ip1 = -inverse_weights[i] * - antidiffusive_flux1_L[variable, i + 1, j, element] - val_flux2_local = inverse_weights[j] * - antidiffusive_flux2_R[variable, i, j, element] - val_flux2_local_jp1 = -inverse_weights[j] * - antidiffusive_flux2_L[variable, i, j + 1, element] - - Pm = min(0, val_flux1_local) + min(0, val_flux1_local_ip1) + - min(0, val_flux2_local) + min(0, val_flux2_local_jp1) - Pm = inverse_jacobian * Pm - - # Compute blending coefficient avoiding division by zero - # (as in paper of [Guermond, Nazarov, Popov, Thomas] (4.8)) - Qm = abs(Qm) / (abs(Pm) + eps(typeof(Qm)) * 100) - - # Calculate alpha - alpha[i, j, element] = max(alpha[i, j, element], 1 - Qm) end + + return nothing end - return nothing -end + ############################################################################### + # Newton-bisection method + + @inline function newton_loops_alpha!( + alpha, bound, u, i, j, element, variable, + min_or_max, initial_check, final_check, + inverse_jacobian, dt, equations, dg, cache, + limiter + ) + (; inverse_weights) = dg.basis + (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes + + (; gamma_constant_newton) = limiter + + # negative xi direction + antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[i] * + get_node_vars( + antidiffusive_flux1_R, equations, dg, i, j, + element + ) + newton_loop!( + alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + final_check, equations, dt, limiter, antidiffusive_flux + ) + + # positive xi direction + antidiffusive_flux = -gamma_constant_newton * inverse_jacobian * + inverse_weights[i] * + get_node_vars( + antidiffusive_flux1_L, equations, dg, i + 1, j, + element + ) + newton_loop!( + alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + final_check, equations, dt, limiter, antidiffusive_flux + ) + + # negative eta direction + antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[j] * + get_node_vars( + antidiffusive_flux2_R, equations, dg, i, j, + element + ) + newton_loop!( + alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + final_check, equations, dt, limiter, antidiffusive_flux + ) + + # positive eta direction + antidiffusive_flux = -gamma_constant_newton * inverse_jacobian * + inverse_weights[j] * + get_node_vars( + antidiffusive_flux2_L, equations, dg, i, j + 1, + element + ) + newton_loop!( + alpha, bound, u, i, j, element, variable, min_or_max, initial_check, + final_check, equations, dt, limiter, antidiffusive_flux + ) + + return nothing + end -############################################################################### -# Global positivity limiting of nonlinear variables + @inline function newton_loop!( + alpha, bound, u, i, j, element, variable, min_or_max, + initial_check, final_check, equations, dt, limiter, + antidiffusive_flux + ) + newton_reltol, newton_abstol = limiter.newton_tolerances -@inline function idp_positivity_nonlinear!(alpha, limiter, u, dt, semi, variable) - mesh, equations, dg, cache = mesh_equations_solver_cache(semi) - (; positivity_correction_factor) = limiter + beta = 1 - alpha[i, j, element] - (; variable_bounds) = limiter.cache.subcell_limiter_coefficients - var_min = variable_bounds[Symbol(string(variable), "_min")] + beta_L = 0 # alpha = 1 + beta_R = beta # No higher beta (lower alpha) than the current one - @threaded for element in eachelement(dg, semi.cache) - for j in eachnode(dg), i in eachnode(dg) - inverse_jacobian = get_inverse_jacobian(cache.elements.inverse_jacobian, - mesh, i, j, element) + u_curr = u + beta * dt * antidiffusive_flux - # Compute bound - u_local = get_node_vars(u, equations, dg, i, j, element) - var = variable(u_local, equations) - if var < 0 - error("Safe low-order method produces negative value for variable $variable. Try a smaller time step.") - end - var_min[i, j, element] = positivity_correction_factor * var + # If state is valid, perform initial check and return if correction is not needed + if isvalid(u_curr, equations) + goal = goal_function_newton_idp(variable, bound, u_curr, equations) - # Perform Newton's bisection method to find new alpha - newton_loops_alpha!(alpha, var_min[i, j, element], u_local, i, j, element, - variable, min, initial_check_nonnegative_newton_idp, - final_check_nonnegative_newton_idp, inverse_jacobian, - dt, equations, dg, cache, limiter) + initial_check(min_or_max, bound, goal, newton_abstol) && return nothing end - end - return nothing -end - -############################################################################### -# Newton-bisection method - -@inline function newton_loops_alpha!(alpha, bound, u, i, j, element, variable, - min_or_max, initial_check, final_check, - inverse_jacobian, dt, equations, dg, cache, - limiter) - (; inverse_weights) = dg.basis - (; antidiffusive_flux1_L, antidiffusive_flux2_L, antidiffusive_flux1_R, antidiffusive_flux2_R) = cache.antidiffusive_fluxes - - (; gamma_constant_newton) = limiter - - # negative xi direction - antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[i] * - get_node_vars(antidiffusive_flux1_R, equations, dg, i, j, - element) - newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, - final_check, equations, dt, limiter, antidiffusive_flux) - - # positive xi direction - antidiffusive_flux = -gamma_constant_newton * inverse_jacobian * - inverse_weights[i] * - get_node_vars(antidiffusive_flux1_L, equations, dg, i + 1, j, - element) - newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, - final_check, equations, dt, limiter, antidiffusive_flux) - - # negative eta direction - antidiffusive_flux = gamma_constant_newton * inverse_jacobian * inverse_weights[j] * - get_node_vars(antidiffusive_flux2_R, equations, dg, i, j, - element) - newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, - final_check, equations, dt, limiter, antidiffusive_flux) - - # positive eta direction - antidiffusive_flux = -gamma_constant_newton * inverse_jacobian * - inverse_weights[j] * - get_node_vars(antidiffusive_flux2_L, equations, dg, i, j + 1, - element) - newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, initial_check, - final_check, equations, dt, limiter, antidiffusive_flux) - - return nothing -end - -@inline function newton_loop!(alpha, bound, u, i, j, element, variable, min_or_max, - initial_check, final_check, equations, dt, limiter, - antidiffusive_flux) - newton_reltol, newton_abstol = limiter.newton_tolerances - - beta = 1 - alpha[i, j, element] - - beta_L = 0 # alpha = 1 - beta_R = beta # No higher beta (lower alpha) than the current one - - u_curr = u + beta * dt * antidiffusive_flux - - # If state is valid, perform initial check and return if correction is not needed - if isvalid(u_curr, equations) - goal = goal_function_newton_idp(variable, bound, u_curr, equations) - - initial_check(min_or_max, bound, goal, newton_abstol) && return nothing - end + # Newton iterations + for iter in 1:(limiter.max_iterations_newton) + beta_old = beta + + # If the state is valid, evaluate d(goal)/d(beta) + if isvalid(u_curr, equations) + dgoal_dbeta = dgoal_function_newton_idp( + variable, u_curr, dt, + antidiffusive_flux, equations + ) + else # Otherwise, perform a bisection step + dgoal_dbeta = 0 + end - # Newton iterations - for iter in 1:(limiter.max_iterations_newton) - beta_old = beta + if dgoal_dbeta != 0 + # Update beta with Newton's method + beta = beta - goal / dgoal_dbeta + end - # If the state is valid, evaluate d(goal)/d(beta) - if isvalid(u_curr, equations) - dgoal_dbeta = dgoal_function_newton_idp(variable, u_curr, dt, - antidiffusive_flux, equations) - else # Otherwise, perform a bisection step - dgoal_dbeta = 0 - end + # Check bounds + if (beta < beta_L) || (beta > beta_R) || (dgoal_dbeta == 0) || isnan(beta) + # Out of bounds, do a bisection step + beta = 0.5f0 * (beta_L + beta_R) + # Get new u + u_curr = u + beta * dt * antidiffusive_flux + + # If the state is invalid, finish bisection step without checking tolerance and iterate further + if !isvalid(u_curr, equations) + beta_R = beta + continue + end + + # Check new beta for condition and update bounds + goal = goal_function_newton_idp(variable, bound, u_curr, equations) + if initial_check(min_or_max, bound, goal, newton_abstol) + # New beta fulfills condition + beta_L = beta + else + # New beta does not fulfill condition + beta_R = beta + end + else + # Get new u + u_curr = u + beta * dt * antidiffusive_flux - if dgoal_dbeta != 0 - # Update beta with Newton's method - beta = beta - goal / dgoal_dbeta - end + # If the state is invalid, redefine right bound without checking tolerance and iterate further + if !isvalid(u_curr, equations) + beta_R = beta + continue + end - # Check bounds - if (beta < beta_L) || (beta > beta_R) || (dgoal_dbeta == 0) || isnan(beta) - # Out of bounds, do a bisection step - beta = 0.5f0 * (beta_L + beta_R) - # Get new u - u_curr = u + beta * dt * antidiffusive_flux - - # If the state is invalid, finish bisection step without checking tolerance and iterate further - if !isvalid(u_curr, equations) - beta_R = beta - continue + # Evaluate goal function + goal = goal_function_newton_idp(variable, bound, u_curr, equations) end - # Check new beta for condition and update bounds - goal = goal_function_newton_idp(variable, bound, u_curr, equations) - if initial_check(min_or_max, bound, goal, newton_abstol) - # New beta fulfills condition - beta_L = beta - else - # New beta does not fulfill condition - beta_R = beta - end - else - # Get new u - u_curr = u + beta * dt * antidiffusive_flux - - # If the state is invalid, redefine right bound without checking tolerance and iterate further - if !isvalid(u_curr, equations) - beta_R = beta - continue + # Check relative tolerance + if abs(beta_old - beta) <= newton_reltol + break end - # Evaluate goal function - goal = goal_function_newton_idp(variable, bound, u_curr, equations) + # Check absolute tolerance + if final_check(bound, goal, newton_abstol) + break + end end - # Check relative tolerance - if abs(beta_old - beta) <= newton_reltol - break - end + new_alpha = 1 - beta + alpha[i, j, element] = new_alpha - # Check absolute tolerance - if final_check(bound, goal, newton_abstol) - break - end + return nothing end - new_alpha = 1 - beta - alpha[i, j, element] = new_alpha - - return nothing -end - -### Auxiliary routines for Newton's bisection method ### -# Initial checks -@inline function initial_check_local_onesided_newton_idp(::typeof(min), bound, - goal, newton_abstol) - goal <= max(newton_abstol, abs(bound) * newton_abstol) -end - -@inline function initial_check_local_onesided_newton_idp(::typeof(max), bound, - goal, newton_abstol) - goal >= -max(newton_abstol, abs(bound) * newton_abstol) -end - -@inline initial_check_nonnegative_newton_idp(min_or_max, bound, goal, newton_abstol) = goal <= - 0 - -# Goal and d(Goal)d(u) function -@inline goal_function_newton_idp(variable, bound, u, equations) = bound - - variable(u, equations) -@inline function dgoal_function_newton_idp(variable, u, dt, antidiffusive_flux, - equations) - -dot(gradient_conservative(variable, u, equations), dt * antidiffusive_flux) -end - -# Final checks -# final check for one-sided local limiting -@inline function final_check_local_onesided_newton_idp(bound, goal, newton_abstol) - abs(goal) < max(newton_abstol, abs(bound) * newton_abstol) -end - -# final check for nonnegativity limiting -@inline function final_check_nonnegative_newton_idp(bound, goal, newton_abstol) - (goal <= eps()) && (goal > -max(newton_abstol, abs(bound) * newton_abstol)) -end + ### Auxiliary routines for Newton's bisection method ### + # Initial checks + @inline function initial_check_local_onesided_newton_idp( + ::typeof(min), bound, + goal, newton_abstol + ) + goal <= max(newton_abstol, abs(bound) * newton_abstol) + end + + @inline function initial_check_local_onesided_newton_idp( + ::typeof(max), bound, + goal, newton_abstol + ) + goal >= -max(newton_abstol, abs(bound) * newton_abstol) + end + + @inline initial_check_nonnegative_newton_idp(min_or_max, bound, goal, newton_abstol) = goal <= + 0 + + # Goal and d(Goal)d(u) function + @inline goal_function_newton_idp(variable, bound, u, equations) = bound - + variable(u, equations) + @inline function dgoal_function_newton_idp( + variable, u, dt, antidiffusive_flux, + equations + ) + -dot(gradient_conservative(variable, u, equations), dt * antidiffusive_flux) + end + + # Final checks + # final check for one-sided local limiting + @inline function final_check_local_onesided_newton_idp(bound, goal, newton_abstol) + abs(goal) < max(newton_abstol, abs(bound) * newton_abstol) + end + + # final check for nonnegativity limiting + @inline function final_check_nonnegative_newton_idp(bound, goal, newton_abstol) + (goal <= eps()) && (goal > -max(newton_abstol, abs(bound) * newton_abstol)) + end end # @muladd diff --git a/src/solvers/dgsem_unstructured/containers_2d.jl b/src/solvers/dgsem_unstructured/containers_2d.jl index f51dd09801..0642828e4c 100644 --- a/src/solvers/dgsem_unstructured/containers_2d.jl +++ b/src/solvers/dgsem_unstructured/containers_2d.jl @@ -3,338 +3,396 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Container data structure (structure-of-arrays style) for DG elements on curved unstructured mesh -struct UnstructuredElementContainer2D{RealT <: Real, uEltype <: Real} - node_coordinates::Array{RealT, 4} # [ndims, nnodes, nnodes, nelement] - jacobian_matrix::Array{RealT, 5} # [ndims, ndims, nnodes, nnodes, nelement] - inverse_jacobian::Array{RealT, 3} # [nnodes, nnodes, nelement] - contravariant_vectors::Array{RealT, 5} # [ndims, ndims, nnodes, nnodes, nelement] - normal_directions::Array{RealT, 4} # [ndims, nnodes, local sides, nelement] - surface_flux_values::Array{uEltype, 4} # [variables, nnodes, local sides, elements] -end - -# construct an empty curved element container to be filled later with geometries in the -# unstructured mesh constructor -function UnstructuredElementContainer2D{RealT, uEltype}(capacity::Integer, n_variables, - n_nodes) where {RealT <: Real, - uEltype <: Real} - nan_RealT = convert(RealT, NaN) - nan_uEltype = convert(uEltype, NaN) - - node_coordinates = fill(nan_RealT, (2, n_nodes, n_nodes, capacity)) - jacobian_matrix = fill(nan_RealT, (2, 2, n_nodes, n_nodes, capacity)) - inverse_jacobian = fill(nan_RealT, (n_nodes, n_nodes, capacity)) - contravariant_vectors = fill(nan_RealT, (2, 2, n_nodes, n_nodes, capacity)) - normal_directions = fill(nan_RealT, (2, n_nodes, 4, capacity)) - surface_flux_values = fill(nan_uEltype, (n_variables, n_nodes, 4, capacity)) - - return UnstructuredElementContainer2D{RealT, uEltype}(node_coordinates, - jacobian_matrix, - inverse_jacobian, - contravariant_vectors, - normal_directions, - surface_flux_values) -end - -@inline function nelements(elements::UnstructuredElementContainer2D) - size(elements.surface_flux_values, 4) -end -""" - eachelement(elements::UnstructuredElementContainer2D) - -Return an iterator over the indices that specify the location in relevant data structures -for the elements in `elements`. -In particular, not the elements themselves are returned. -""" -@inline function eachelement(elements::UnstructuredElementContainer2D) - Base.OneTo(nelements(elements)) -end - -@inline function nvariables(elements::UnstructuredElementContainer2D) - size(elements.surface_flux_values, 1) -end -@inline function nnodes(elements::UnstructuredElementContainer2D) - size(elements.surface_flux_values, 2) -end - -Base.real(elements::UnstructuredElementContainer2D) = eltype(elements.node_coordinates) -function Base.eltype(elements::UnstructuredElementContainer2D) - eltype(elements.surface_flux_values) -end - -@inline function get_surface_normal(vec, indices...) - # way to extract the normal vector at the surfaces without allocating - surface_vector = SVector(ntuple(j -> vec[j, indices...], 2)) - return surface_vector -end - -function init_elements(mesh::UnstructuredMesh2D, equations, basis, RealT, uEltype) - elements = UnstructuredElementContainer2D{RealT, uEltype}(mesh.n_elements, - nvariables(equations), - nnodes(basis)) - init_elements!(elements, mesh, basis) - return elements -end - -function init_elements!(elements::UnstructuredElementContainer2D, mesh, basis) - four_corners = zeros(eltype(mesh.corners), 4, 2) - - # loop through elements and call the correct constructor based on whether the element is curved - for element in eachelement(elements) - if mesh.element_is_curved[element] - init_element!(elements, element, basis, - view(mesh.surface_curves, :, element)) - else # straight sided element - for i in 1:4, j in 1:2 - # pull the (x,y) values of these corners out of the global corners array - four_corners[i, j] = mesh.corners[j, mesh.element_node_ids[i, element]] + #! format: noindent + + # Container data structure (structure-of-arrays style) for DG elements on curved unstructured mesh + struct UnstructuredElementContainer2D{RealT <: Real, uEltype <: Real} + node_coordinates::Array{RealT, 4} # [ndims, nnodes, nnodes, nelement] + jacobian_matrix::Array{RealT, 5} # [ndims, ndims, nnodes, nnodes, nelement] + inverse_jacobian::Array{RealT, 3} # [nnodes, nnodes, nelement] + contravariant_vectors::Array{RealT, 5} # [ndims, ndims, nnodes, nnodes, nelement] + normal_directions::Array{RealT, 4} # [ndims, nnodes, local sides, nelement] + surface_flux_values::Array{uEltype, 4} # [variables, nnodes, local sides, elements] + end + + # construct an empty curved element container to be filled later with geometries in the + # unstructured mesh constructor + function UnstructuredElementContainer2D{RealT, uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where { + RealT <: Real, + uEltype <: Real, + } + nan_RealT = convert(RealT, NaN) + nan_uEltype = convert(uEltype, NaN) + + node_coordinates = fill(nan_RealT, (2, n_nodes, n_nodes, capacity)) + jacobian_matrix = fill(nan_RealT, (2, 2, n_nodes, n_nodes, capacity)) + inverse_jacobian = fill(nan_RealT, (n_nodes, n_nodes, capacity)) + contravariant_vectors = fill(nan_RealT, (2, 2, n_nodes, n_nodes, capacity)) + normal_directions = fill(nan_RealT, (2, n_nodes, 4, capacity)) + surface_flux_values = fill(nan_uEltype, (n_variables, n_nodes, 4, capacity)) + + return UnstructuredElementContainer2D{RealT, uEltype}( + node_coordinates, + jacobian_matrix, + inverse_jacobian, + contravariant_vectors, + normal_directions, + surface_flux_values + ) + end + + @inline function nelements(elements::UnstructuredElementContainer2D) + size(elements.surface_flux_values, 4) + end + """ + eachelement(elements::UnstructuredElementContainer2D) + + Return an iterator over the indices that specify the location in relevant data structures + for the elements in `elements`. + In particular, not the elements themselves are returned. + """ + @inline function eachelement(elements::UnstructuredElementContainer2D) + Base.OneTo(nelements(elements)) + end + + @inline function nvariables(elements::UnstructuredElementContainer2D) + size(elements.surface_flux_values, 1) + end + @inline function nnodes(elements::UnstructuredElementContainer2D) + size(elements.surface_flux_values, 2) + end + + Base.real(elements::UnstructuredElementContainer2D) = eltype(elements.node_coordinates) + function Base.eltype(elements::UnstructuredElementContainer2D) + eltype(elements.surface_flux_values) + end + + @inline function get_surface_normal(vec, indices...) + # way to extract the normal vector at the surfaces without allocating + surface_vector = SVector(ntuple(j -> vec[j, indices...], 2)) + return surface_vector + end + + function init_elements(mesh::UnstructuredMesh2D, equations, basis, RealT, uEltype) + elements = UnstructuredElementContainer2D{RealT, uEltype}( + mesh.n_elements, + nvariables(equations), + nnodes(basis) + ) + init_elements!(elements, mesh, basis) + return elements + end + + function init_elements!(elements::UnstructuredElementContainer2D, mesh, basis) + four_corners = zeros(eltype(mesh.corners), 4, 2) + + # loop through elements and call the correct constructor based on whether the element is curved + for element in eachelement(elements) + if mesh.element_is_curved[element] + init_element!( + elements, element, basis, + view(mesh.surface_curves, :, element) + ) + else # straight sided element + for i in 1:4, j in 1:2 + # pull the (x,y) values of these corners out of the global corners array + four_corners[i, j] = mesh.corners[j, mesh.element_node_ids[i, element]] + end + init_element!(elements, element, basis, four_corners) end - init_element!(elements, element, basis, four_corners) end end -end - -# initialize all the values in the container of a general element (either straight sided or curved) -function init_element!(elements, element, basis::LobattoLegendreBasis, - corners_or_surface_curves) - calc_node_coordinates!(elements.node_coordinates, element, get_nodes(basis), - corners_or_surface_curves) - - calc_metric_terms!(elements.jacobian_matrix, element, get_nodes(basis), - corners_or_surface_curves) - - calc_inverse_jacobian!(elements.inverse_jacobian, element, elements.jacobian_matrix) - - calc_contravariant_vectors!(elements.contravariant_vectors, element, - elements.jacobian_matrix) - - calc_normal_directions!(elements.normal_directions, element, get_nodes(basis), - corners_or_surface_curves) - - return elements -end - -# generic container for the interior interfaces of an unstructured mesh -struct UnstructuredInterfaceContainer2D{uEltype <: Real} - u::Array{uEltype, 4} # [primary/secondary, variables, i, interfaces] - start_index::Vector{Int} # [interfaces] - index_increment::Vector{Int} # [interfaces] - element_ids::Array{Int, 2} # [primary/secondary, interfaces] - element_side_ids::Array{Int, 2} # [primary/secondary, interfaces] -end - -# Construct an empty curved interface container to be filled later with neighbour -# information in the unstructured mesh constructor -function UnstructuredInterfaceContainer2D{uEltype}(capacity::Integer, n_variables, - n_nodes) where {uEltype <: Real} - nan_uEltype = convert(uEltype, NaN) - - u = fill(nan_uEltype, (2, n_variables, n_nodes, capacity)) - start_index = fill(typemin(Int), capacity) - index_increment = fill(typemin(Int), capacity) - element_ids = fill(typemin(Int), (2, capacity)) - element_side_ids = fill(typemin(Int), (2, capacity)) - - return UnstructuredInterfaceContainer2D{uEltype}(u, start_index, index_increment, - element_ids, element_side_ids) -end - -@inline function ninterfaces(interfaces::UnstructuredInterfaceContainer2D) - length(interfaces.start_index) -end -@inline nnodes(interfaces::UnstructuredInterfaceContainer2D) = size(interfaces.u, 3) - -function init_interfaces(mesh::UnstructuredMesh2D, - elements::UnstructuredElementContainer2D) - interfaces = UnstructuredInterfaceContainer2D{eltype(elements)}(mesh.n_interfaces, - nvariables(elements), - nnodes(elements)) - - # extract and save the appropriate neighbour information from the mesh skeleton - if isperiodic(mesh) - init_interfaces!(interfaces, mesh.neighbour_information, mesh.boundary_names, - mesh.n_elements, True()) - else - init_interfaces!(interfaces, mesh.neighbour_information, mesh.boundary_names, - mesh.n_elements, False()) + + # initialize all the values in the container of a general element (either straight sided or curved) + function init_element!( + elements, element, basis::LobattoLegendreBasis, + corners_or_surface_curves + ) + calc_node_coordinates!( + elements.node_coordinates, element, get_nodes(basis), + corners_or_surface_curves + ) + + calc_metric_terms!( + elements.jacobian_matrix, element, get_nodes(basis), + corners_or_surface_curves + ) + + calc_inverse_jacobian!(elements.inverse_jacobian, element, elements.jacobian_matrix) + + calc_contravariant_vectors!( + elements.contravariant_vectors, element, + elements.jacobian_matrix + ) + + calc_normal_directions!( + elements.normal_directions, element, get_nodes(basis), + corners_or_surface_curves + ) + + return elements end - return interfaces -end - -function init_interfaces!(interfaces, edge_information, boundary_names, n_elements, - periodic::False) - n_nodes = nnodes(interfaces) - n_surfaces = size(edge_information, 2) - intr_count = 1 - for j in 1:n_surfaces - if edge_information[4, j] > 0 - # get the primary/secondary element information and coupling for an interior interface - interfaces.element_ids[1, intr_count] = edge_information[3, j] # primary element id - interfaces.element_ids[2, intr_count] = edge_information[4, j] # secondary element id - interfaces.element_side_ids[1, intr_count] = edge_information[5, j] # primary side id - interfaces.element_side_ids[2, intr_count] = abs(edge_information[6, j]) # secondary side id - # default the start and increment indexing - interfaces.start_index[intr_count] = 1 - interfaces.index_increment[intr_count] = 1 - if edge_information[6, j] < 0 - # coordinate system in the secondary element is "flipped" compared to the primary element. - # Adjust the start and increment indexes such that the secondary element coordinate system - # can match the primary neighbour when surface coupling is computed - interfaces.start_index[intr_count] = n_nodes - interfaces.index_increment[intr_count] = -1 - end - intr_count += 1 + # generic container for the interior interfaces of an unstructured mesh + struct UnstructuredInterfaceContainer2D{uEltype <: Real} + u::Array{uEltype, 4} # [primary/secondary, variables, i, interfaces] + start_index::Vector{Int} # [interfaces] + index_increment::Vector{Int} # [interfaces] + element_ids::Array{Int, 2} # [primary/secondary, interfaces] + element_side_ids::Array{Int, 2} # [primary/secondary, interfaces] + end + + # Construct an empty curved interface container to be filled later with neighbour + # information in the unstructured mesh constructor + function UnstructuredInterfaceContainer2D{uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where {uEltype <: Real} + nan_uEltype = convert(uEltype, NaN) + + u = fill(nan_uEltype, (2, n_variables, n_nodes, capacity)) + start_index = fill(typemin(Int), capacity) + index_increment = fill(typemin(Int), capacity) + element_ids = fill(typemin(Int), (2, capacity)) + element_side_ids = fill(typemin(Int), (2, capacity)) + + return UnstructuredInterfaceContainer2D{uEltype}( + u, start_index, index_increment, + element_ids, element_side_ids + ) + end + + @inline function ninterfaces(interfaces::UnstructuredInterfaceContainer2D) + length(interfaces.start_index) + end + @inline nnodes(interfaces::UnstructuredInterfaceContainer2D) = size(interfaces.u, 3) + + function init_interfaces( + mesh::UnstructuredMesh2D, + elements::UnstructuredElementContainer2D + ) + interfaces = UnstructuredInterfaceContainer2D{eltype(elements)}( + mesh.n_interfaces, + nvariables(elements), + nnodes(elements) + ) + + # extract and save the appropriate neighbour information from the mesh skeleton + if isperiodic(mesh) + init_interfaces!( + interfaces, mesh.neighbour_information, mesh.boundary_names, + mesh.n_elements, True() + ) + else + init_interfaces!( + interfaces, mesh.neighbour_information, mesh.boundary_names, + mesh.n_elements, False() + ) end + + return interfaces end - return nothing -end - -function init_interfaces!(interfaces, edge_information, boundary_names, n_elements, - periodic::True) - n_nodes = nnodes(interfaces) - n_surfaces = size(edge_information, 2) - # for now this set a fully periodic domain - # TODO: possibly adjust to be able to set periodic in only the x or y direction - for j in 1:n_surfaces - if edge_information[4, j] > 0 - # get the primary/secondary element information and coupling for an interior interface - interfaces.element_ids[1, j] = edge_information[3, j] # primary element id - interfaces.element_ids[2, j] = edge_information[4, j] # secondary element id - interfaces.element_side_ids[1, j] = edge_information[5, j] # primary side id - interfaces.element_side_ids[2, j] = abs(edge_information[6, j]) # secondary side id - # default the start and increment indexing - interfaces.start_index[j] = 1 - interfaces.index_increment[j] = 1 - if edge_information[6, j] < 0 - # coordinate system in the secondary element is "flipped" compared to the primary element. - # Adjust the start and increment indexes such that the secondary element coordinate system - # can match the primary neighbour when surface coupling is computed - interfaces.start_index[j] = n_nodes - interfaces.index_increment[j] = -1 + function init_interfaces!( + interfaces, edge_information, boundary_names, n_elements, + periodic::False + ) + n_nodes = nnodes(interfaces) + n_surfaces = size(edge_information, 2) + intr_count = 1 + for j in 1:n_surfaces + if edge_information[4, j] > 0 + # get the primary/secondary element information and coupling for an interior interface + interfaces.element_ids[1, intr_count] = edge_information[3, j] # primary element id + interfaces.element_ids[2, intr_count] = edge_information[4, j] # secondary element id + interfaces.element_side_ids[1, intr_count] = edge_information[5, j] # primary side id + interfaces.element_side_ids[2, intr_count] = abs(edge_information[6, j]) # secondary side id + # default the start and increment indexing + interfaces.start_index[intr_count] = 1 + interfaces.index_increment[intr_count] = 1 + if edge_information[6, j] < 0 + # coordinate system in the secondary element is "flipped" compared to the primary element. + # Adjust the start and increment indexes such that the secondary element coordinate system + # can match the primary neighbour when surface coupling is computed + interfaces.start_index[intr_count] = n_nodes + interfaces.index_increment[intr_count] = -1 + end + intr_count += 1 end - else - # way to set periodic BCs where we are assuming to have a structured mesh with internal curves - primary_side = edge_information[5, j] - primary_element = edge_information[3, j] - # Note: This is a way to get the neighbour element number and local side from a square - # structured mesh where the element local surface numbering is right-handed - if boundary_names[primary_side, primary_element] === :Bottom - secondary_element = primary_element + - (n_elements - convert(Int, sqrt(n_elements))) - secondary_side = 3 - elseif boundary_names[primary_side, primary_element] === :Top - secondary_element = primary_element - - (n_elements - convert(Int, sqrt(n_elements))) - secondary_side = 1 - elseif boundary_names[primary_side, primary_element] === :Left - secondary_element = primary_element + - (convert(Int, sqrt(n_elements)) - 1) - secondary_side = 2 - elseif boundary_names[primary_side, primary_element] === :Right - secondary_element = primary_element - - (convert(Int, sqrt(n_elements)) - 1) - secondary_side = 4 + end + + return nothing + end + + function init_interfaces!( + interfaces, edge_information, boundary_names, n_elements, + periodic::True + ) + n_nodes = nnodes(interfaces) + n_surfaces = size(edge_information, 2) + # for now this set a fully periodic domain + # TODO: possibly adjust to be able to set periodic in only the x or y direction + for j in 1:n_surfaces + if edge_information[4, j] > 0 + # get the primary/secondary element information and coupling for an interior interface + interfaces.element_ids[1, j] = edge_information[3, j] # primary element id + interfaces.element_ids[2, j] = edge_information[4, j] # secondary element id + interfaces.element_side_ids[1, j] = edge_information[5, j] # primary side id + interfaces.element_side_ids[2, j] = abs(edge_information[6, j]) # secondary side id + # default the start and increment indexing + interfaces.start_index[j] = 1 + interfaces.index_increment[j] = 1 + if edge_information[6, j] < 0 + # coordinate system in the secondary element is "flipped" compared to the primary element. + # Adjust the start and increment indexes such that the secondary element coordinate system + # can match the primary neighbour when surface coupling is computed + interfaces.start_index[j] = n_nodes + interfaces.index_increment[j] = -1 + end + else + # way to set periodic BCs where we are assuming to have a structured mesh with internal curves + primary_side = edge_information[5, j] + primary_element = edge_information[3, j] + # Note: This is a way to get the neighbour element number and local side from a square + # structured mesh where the element local surface numbering is right-handed + if boundary_names[primary_side, primary_element] === :Bottom + secondary_element = primary_element + + (n_elements - convert(Int, sqrt(n_elements))) + secondary_side = 3 + elseif boundary_names[primary_side, primary_element] === :Top + secondary_element = primary_element - + (n_elements - convert(Int, sqrt(n_elements))) + secondary_side = 1 + elseif boundary_names[primary_side, primary_element] === :Left + secondary_element = primary_element + + (convert(Int, sqrt(n_elements)) - 1) + secondary_side = 2 + elseif boundary_names[primary_side, primary_element] === :Right + secondary_element = primary_element - + (convert(Int, sqrt(n_elements)) - 1) + secondary_side = 4 + end + interfaces.element_ids[1, j] = primary_element + interfaces.element_ids[2, j] = secondary_element + interfaces.element_side_ids[1, j] = primary_side + interfaces.element_side_ids[2, j] = secondary_side + # set the start and increment indexing + # Note! We assume that the periodic mesh has no flipped element coordinate systems + interfaces.start_index[j] = 1 + interfaces.index_increment[j] = 1 end - interfaces.element_ids[1, j] = primary_element - interfaces.element_ids[2, j] = secondary_element - interfaces.element_side_ids[1, j] = primary_side - interfaces.element_side_ids[2, j] = secondary_side - # set the start and increment indexing - # Note! We assume that the periodic mesh has no flipped element coordinate systems - interfaces.start_index[j] = 1 - interfaces.index_increment[j] = 1 end + + return nothing end - return nothing -end - -# TODO: Clean-up meshes. Find a better name since it's also used for other meshes -# generic container for the boundary interfaces of an unstructured mesh -struct UnstructuredBoundaryContainer2D{RealT <: Real, uEltype <: Real} - u::Array{uEltype, 3} # [variables, i, boundaries] - element_id::Vector{Int} # [boundaries] - element_side_id::Vector{Int} # [boundaries] - node_coordinates::Array{RealT, 3} # [ndims, nnodes, boundaries] - name::Vector{Symbol} # [boundaries] -end - -# construct an empty curved boundary container to be filled later with neighbour -# information in the unstructured mesh constructor -function UnstructuredBoundaryContainer2D{RealT, uEltype}(capacity::Integer, n_variables, - n_nodes) where {RealT <: Real, - uEltype <: - Real} - nan_RealT = convert(RealT, NaN) - nan_uEltype = convert(uEltype, NaN) - - u = fill(nan_uEltype, (n_variables, n_nodes, capacity)) - element_id = fill(typemin(Int), capacity) - element_side_id = fill(typemin(Int), capacity) - node_coordinates = fill(nan_RealT, (2, n_nodes, capacity)) - name = fill(:empty, capacity) - - return UnstructuredBoundaryContainer2D{RealT, uEltype}(u, element_id, - element_side_id, - node_coordinates, name) -end - -@inline function nboundaries(boundaries::UnstructuredBoundaryContainer2D) - length(boundaries.name) -end - -function init_boundaries(mesh::UnstructuredMesh2D, - elements::UnstructuredElementContainer2D) - boundaries = UnstructuredBoundaryContainer2D{real(elements), eltype(elements)}(mesh.n_boundaries, - nvariables(elements), - nnodes(elements)) - - # extract and save the appropriate boundary information provided any physical boundaries exist - if mesh.n_boundaries > 0 - init_boundaries!(boundaries, mesh.neighbour_information, mesh.boundary_names, - elements) + # TODO: Clean-up meshes. Find a better name since it's also used for other meshes + # generic container for the boundary interfaces of an unstructured mesh + struct UnstructuredBoundaryContainer2D{RealT <: Real, uEltype <: Real} + u::Array{uEltype, 3} # [variables, i, boundaries] + element_id::Vector{Int} # [boundaries] + element_side_id::Vector{Int} # [boundaries] + node_coordinates::Array{RealT, 3} # [ndims, nnodes, boundaries] + name::Vector{Symbol} # [boundaries] end - return boundaries -end - -function init_boundaries!(boundaries::UnstructuredBoundaryContainer2D, edge_information, - boundary_names, elements) - n_surfaces = size(edge_information, 2) - bndy_count = 1 - for j in 1:n_surfaces - if edge_information[4, j] == 0 - # get the primary element information at a boundary interface - primary_element = edge_information[3, j] - primary_side = edge_information[5, j] - boundaries.element_id[bndy_count] = primary_element - boundaries.element_side_id[bndy_count] = primary_side - - # extract the physical boundary's name from the global list - boundaries.name[bndy_count] = boundary_names[primary_side, primary_element] - - # Store copy of the (x,y) node coordinates on the physical boundary - enc = elements.node_coordinates - if primary_side == 1 - boundaries.node_coordinates[:, :, bndy_count] .= enc[:, :, 1, - primary_element] - elseif primary_side == 2 - boundaries.node_coordinates[:, :, bndy_count] .= enc[:, end, :, - primary_element] - elseif primary_side == 3 - boundaries.node_coordinates[:, :, bndy_count] .= enc[:, :, end, - primary_element] - else # primary_side == 4 - boundaries.node_coordinates[:, :, bndy_count] .= enc[:, 1, :, - primary_element] - end - bndy_count += 1 + + # construct an empty curved boundary container to be filled later with neighbour + # information in the unstructured mesh constructor + function UnstructuredBoundaryContainer2D{RealT, uEltype}( + capacity::Integer, n_variables, + n_nodes + ) where { + RealT <: Real, + uEltype <: + Real, + } + nan_RealT = convert(RealT, NaN) + nan_uEltype = convert(uEltype, NaN) + + u = fill(nan_uEltype, (n_variables, n_nodes, capacity)) + element_id = fill(typemin(Int), capacity) + element_side_id = fill(typemin(Int), capacity) + node_coordinates = fill(nan_RealT, (2, n_nodes, capacity)) + name = fill(:empty, capacity) + + return UnstructuredBoundaryContainer2D{RealT, uEltype}( + u, element_id, + element_side_id, + node_coordinates, name + ) + end + + @inline function nboundaries(boundaries::UnstructuredBoundaryContainer2D) + length(boundaries.name) + end + + function init_boundaries( + mesh::UnstructuredMesh2D, + elements::UnstructuredElementContainer2D + ) + boundaries = UnstructuredBoundaryContainer2D{real(elements), eltype(elements)}( + mesh.n_boundaries, + nvariables(elements), + nnodes(elements) + ) + + # extract and save the appropriate boundary information provided any physical boundaries exist + if mesh.n_boundaries > 0 + init_boundaries!( + boundaries, mesh.neighbour_information, mesh.boundary_names, + elements + ) end + return boundaries end - return nothing -end + function init_boundaries!( + boundaries::UnstructuredBoundaryContainer2D, edge_information, + boundary_names, elements + ) + n_surfaces = size(edge_information, 2) + bndy_count = 1 + for j in 1:n_surfaces + if edge_information[4, j] == 0 + # get the primary element information at a boundary interface + primary_element = edge_information[3, j] + primary_side = edge_information[5, j] + boundaries.element_id[bndy_count] = primary_element + boundaries.element_side_id[bndy_count] = primary_side + + # extract the physical boundary's name from the global list + boundaries.name[bndy_count] = boundary_names[primary_side, primary_element] + + # Store copy of the (x,y) node coordinates on the physical boundary + enc = elements.node_coordinates + if primary_side == 1 + boundaries.node_coordinates[:, :, bndy_count] .= enc[ + :, :, 1, + primary_element, + ] + elseif primary_side == 2 + boundaries.node_coordinates[:, :, bndy_count] .= enc[ + :, end, :, + primary_element, + ] + elseif primary_side == 3 + boundaries.node_coordinates[:, :, bndy_count] .= enc[ + :, :, end, + primary_element, + ] + else # primary_side == 4 + boundaries.node_coordinates[:, :, bndy_count] .= enc[ + :, 1, :, + primary_element, + ] + end + bndy_count += 1 + end + end + + return nothing + end end # @muladd diff --git a/src/solvers/dgsem_unstructured/dg.jl b/src/solvers/dgsem_unstructured/dg.jl index 3543f1a582..38c5203ab4 100644 --- a/src/solvers/dgsem_unstructured/dg.jl +++ b/src/solvers/dgsem_unstructured/dg.jl @@ -3,24 +3,26 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -@inline function get_one_sided_surface_node_vars(u, equations, solver::DG, j, - indices...) - # There is a cut-off at `n == 10` inside of the method - # `ntuple(f::F, n::Integer) where F` in Base at ntuple.jl:17 - # in Julia `v1.5`, leading to type instabilities if - # more than ten variables are used. That's why we use - # `Val(...)` below. - u_surface = SVector(ntuple(v -> u[j, v, indices...], Val(nvariables(equations)))) - return u_surface -end + @inline function get_one_sided_surface_node_vars( + u, equations, solver::DG, j, + indices... + ) + # There is a cut-off at `n == 10` inside of the method + # `ntuple(f::F, n::Integer) where F` in Base at ntuple.jl:17 + # in Julia `v1.5`, leading to type instabilities if + # more than ten variables are used. That's why we use + # `Val(...)` below. + u_surface = SVector(ntuple(v -> u[j, v, indices...], Val(nvariables(equations)))) + return u_surface + end -# 2D unstructured DG implementation -include("mappings_geometry_curved_2d.jl") -include("mappings_geometry_straight_2d.jl") -include("containers_2d.jl") -include("sort_boundary_conditions.jl") -include("dg_2d.jl") -include("indicators_2d.jl") + # 2D unstructured DG implementation + include("mappings_geometry_curved_2d.jl") + include("mappings_geometry_straight_2d.jl") + include("containers_2d.jl") + include("sort_boundary_conditions.jl") + include("dg_2d.jl") + include("indicators_2d.jl") end # @muladd diff --git a/src/solvers/dgsem_unstructured/dg_2d.jl b/src/solvers/dgsem_unstructured/dg_2d.jl index a531ded91c..d03dec23f7 100644 --- a/src/solvers/dgsem_unstructured/dg_2d.jl +++ b/src/solvers/dgsem_unstructured/dg_2d.jl @@ -3,538 +3,634 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This method is called when a SemidiscretizationHyperbolic is constructed. -# It constructs the basic `cache` used throughout the simulation to compute -# the RHS etc. -function create_cache(mesh::UnstructuredMesh2D, equations, - dg::DG, RealT, uEltype) - elements = init_elements(mesh, equations, dg.basis, RealT, uEltype) - - interfaces = init_interfaces(mesh, elements) - - boundaries = init_boundaries(mesh, elements) + #! format: noindent + + # This method is called when a SemidiscretizationHyperbolic is constructed. + # It constructs the basic `cache` used throughout the simulation to compute + # the RHS etc. + function create_cache( + mesh::UnstructuredMesh2D, equations, + dg::DG, RealT, uEltype + ) + elements = init_elements(mesh, equations, dg.basis, RealT, uEltype) + + interfaces = init_interfaces(mesh, elements) + + boundaries = init_boundaries(mesh, elements) + + cache = (; elements, interfaces, boundaries) + + # perform a check on the sufficient metric identities condition for free-stream preservation + # and halt computation if it fails + # For `Float64`, this gives 1.8189894035458565e-12 + # For `Float32`, this gives 1.1920929f-5 + atol = max(100 * eps(RealT), eps(RealT)^convert(RealT, 0.75f0)) + if !isapprox(max_discrete_metric_identities(dg, cache), 0, atol = atol) + error("metric terms fail free-stream preservation check with maximum error $(max_discrete_metric_identities(dg, cache))") + end - cache = (; elements, interfaces, boundaries) + # Add specialized parts of the cache required to compute the flux differencing volume integral + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) - # perform a check on the sufficient metric identities condition for free-stream preservation - # and halt computation if it fails - # For `Float64`, this gives 1.8189894035458565e-12 - # For `Float32`, this gives 1.1920929f-5 - atol = max(100 * eps(RealT), eps(RealT)^convert(RealT, 0.75f0)) - if !isapprox(max_discrete_metric_identities(dg, cache), 0, atol = atol) - error("metric terms fail free-stream preservation check with maximum error $(max_discrete_metric_identities(dg, cache))") + return cache end - # Add specialized parts of the cache required to compute the flux differencing volume integral - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - - return cache -end - -function rhs!(du, u, t, - mesh::UnstructuredMesh2D, equations, - initial_condition, boundary_conditions, source_terms::Source, - dg::DG, cache) where {Source} - # Reset du - @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) - - # Calculate volume integral - @trixi_timeit timer() "volume integral" begin - calc_volume_integral!(du, u, mesh, - have_nonconservative_terms(equations), equations, - dg.volume_integral, dg, cache) - end + function rhs!( + du, u, t, + mesh::UnstructuredMesh2D, equations, + initial_condition, boundary_conditions, source_terms::Source, + dg::DG, cache + ) where {Source} + # Reset du + @trixi_timeit timer() "reset ∂u/∂t" reset_du!(du, dg, cache) + + # Calculate volume integral + @trixi_timeit timer() "volume integral" begin + calc_volume_integral!( + du, u, mesh, + have_nonconservative_terms(equations), equations, + dg.volume_integral, dg, cache + ) + end - # Prolong solution to interfaces - @trixi_timeit timer() "prolong2interfaces" begin - prolong2interfaces!(cache, u, mesh, equations, - dg.surface_integral, dg) - end + # Prolong solution to interfaces + @trixi_timeit timer() "prolong2interfaces" begin + prolong2interfaces!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end - # Calculate interface fluxes - @trixi_timeit timer() "interface flux" begin - calc_interface_flux!(cache.elements.surface_flux_values, mesh, - have_nonconservative_terms(equations), equations, - dg.surface_integral, dg, cache) - end + # Calculate interface fluxes + @trixi_timeit timer() "interface flux" begin + calc_interface_flux!( + cache.elements.surface_flux_values, mesh, + have_nonconservative_terms(equations), equations, + dg.surface_integral, dg, cache + ) + end - # Prolong solution to boundaries - @trixi_timeit timer() "prolong2boundaries" begin - prolong2boundaries!(cache, u, mesh, equations, - dg.surface_integral, dg) - end + # Prolong solution to boundaries + @trixi_timeit timer() "prolong2boundaries" begin + prolong2boundaries!( + cache, u, mesh, equations, + dg.surface_integral, dg + ) + end - # Calculate boundary fluxes - @trixi_timeit timer() "boundary flux" begin - calc_boundary_flux!(cache, t, boundary_conditions, mesh, equations, - dg.surface_integral, dg) - end + # Calculate boundary fluxes + @trixi_timeit timer() "boundary flux" begin + calc_boundary_flux!( + cache, t, boundary_conditions, mesh, equations, + dg.surface_integral, dg + ) + end - # Calculate surface integrals - @trixi_timeit timer() "surface integral" begin - calc_surface_integral!(du, u, mesh, equations, - dg.surface_integral, dg, cache) - end + # Calculate surface integrals + @trixi_timeit timer() "surface integral" begin + calc_surface_integral!( + du, u, mesh, equations, + dg.surface_integral, dg, cache + ) + end - # Apply Jacobian from mapping to reference element - # Note! this routine is reused from dgsem_structured/dg_2d.jl - @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + # Apply Jacobian from mapping to reference element + # Note! this routine is reused from dgsem_structured/dg_2d.jl + @trixi_timeit timer() "Jacobian" apply_jacobian!(du, mesh, equations, dg, cache) + + # Calculate source terms + @trixi_timeit timer() "source terms" begin + calc_sources!(du, u, t, source_terms, equations, dg, cache) + end - # Calculate source terms - @trixi_timeit timer() "source terms" begin - calc_sources!(du, u, t, source_terms, equations, dg, cache) + return nothing end - return nothing -end - -# prolong the solution into the convenience array in the interior interface container -# We pass the `surface_integral` argument solely for dispatch -# Note! this routine is for quadrilateral elements with "right-handed" orientation -function prolong2interfaces!(cache, u, - mesh::UnstructuredMesh2D, - equations, surface_integral, dg::DG) - @unpack interfaces = cache - @unpack element_ids, element_side_ids = interfaces - interfaces_u = interfaces.u - - @threaded for interface in eachinterface(dg, cache) - primary_element = element_ids[1, interface] - secondary_element = element_ids[2, interface] - - primary_side = element_side_ids[1, interface] - secondary_side = element_side_ids[2, interface] - - if primary_side == 1 - for i in eachnode(dg), v in eachvariable(equations) - interfaces_u[1, v, i, interface] = u[v, i, 1, primary_element] + # prolong the solution into the convenience array in the interior interface container + # We pass the `surface_integral` argument solely for dispatch + # Note! this routine is for quadrilateral elements with "right-handed" orientation + function prolong2interfaces!( + cache, u, + mesh::UnstructuredMesh2D, + equations, surface_integral, dg::DG + ) + @unpack interfaces = cache + @unpack element_ids, element_side_ids = interfaces + interfaces_u = interfaces.u + + @threaded for interface in eachinterface(dg, cache) + primary_element = element_ids[1, interface] + secondary_element = element_ids[2, interface] + + primary_side = element_side_ids[1, interface] + secondary_side = element_side_ids[2, interface] + + if primary_side == 1 + for i in eachnode(dg), v in eachvariable(equations) + interfaces_u[1, v, i, interface] = u[v, i, 1, primary_element] + end + elseif primary_side == 2 + for i in eachnode(dg), v in eachvariable(equations) + interfaces_u[1, v, i, interface] = u[v, nnodes(dg), i, primary_element] + end + elseif primary_side == 3 + for i in eachnode(dg), v in eachvariable(equations) + interfaces_u[1, v, i, interface] = u[v, i, nnodes(dg), primary_element] + end + else # primary_side == 4 + for i in eachnode(dg), v in eachvariable(equations) + interfaces_u[1, v, i, interface] = u[v, 1, i, primary_element] + end end - elseif primary_side == 2 - for i in eachnode(dg), v in eachvariable(equations) - interfaces_u[1, v, i, interface] = u[v, nnodes(dg), i, primary_element] - end - elseif primary_side == 3 - for i in eachnode(dg), v in eachvariable(equations) - interfaces_u[1, v, i, interface] = u[v, i, nnodes(dg), primary_element] - end - else # primary_side == 4 - for i in eachnode(dg), v in eachvariable(equations) - interfaces_u[1, v, i, interface] = u[v, 1, i, primary_element] + + if secondary_side == 1 + for i in eachnode(dg), v in eachvariable(equations) + interfaces_u[2, v, i, interface] = u[v, i, 1, secondary_element] + end + elseif secondary_side == 2 + for i in eachnode(dg), v in eachvariable(equations) + interfaces_u[2, v, i, interface] = u[ + v, nnodes(dg), i, + secondary_element, + ] + end + elseif secondary_side == 3 + for i in eachnode(dg), v in eachvariable(equations) + interfaces_u[2, v, i, interface] = u[ + v, i, nnodes(dg), + secondary_element, + ] + end + else # secondary_side == 4 + for i in eachnode(dg), v in eachvariable(equations) + interfaces_u[2, v, i, interface] = u[v, 1, i, secondary_element] + end end end - if secondary_side == 1 - for i in eachnode(dg), v in eachvariable(equations) - interfaces_u[2, v, i, interface] = u[v, i, 1, secondary_element] - end - elseif secondary_side == 2 - for i in eachnode(dg), v in eachvariable(equations) - interfaces_u[2, v, i, interface] = u[v, nnodes(dg), i, - secondary_element] - end - elseif secondary_side == 3 - for i in eachnode(dg), v in eachvariable(equations) - interfaces_u[2, v, i, interface] = u[v, i, nnodes(dg), - secondary_element] - end - else # secondary_side == 4 - for i in eachnode(dg), v in eachvariable(equations) - interfaces_u[2, v, i, interface] = u[v, 1, i, secondary_element] + return nothing + end + + # compute the numerical flux interface coupling between two elements on an unstructured + # quadrilateral mesh + function calc_interface_flux!( + surface_flux_values, + mesh::UnstructuredMesh2D, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache + ) + @unpack surface_flux = surface_integral + @unpack u, start_index, index_increment, element_ids, element_side_ids = cache.interfaces + @unpack normal_directions = cache.elements + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + primary_element = element_ids[1, interface] + secondary_element = element_ids[2, interface] + + # Get the local side id on which to compute the flux + primary_side = element_side_ids[1, interface] + secondary_side = element_side_ids[2, interface] + + # initial index for the coordinate system on the secondary element + secondary_index = start_index[interface] + + # loop through the primary element coordinate system and compute the interface coupling + for primary_index in eachnode(dg) + # pull the primary and secondary states from the boundary u values + u_ll = get_one_sided_surface_node_vars( + u, equations, dg, 1, primary_index, + interface + ) + u_rr = get_one_sided_surface_node_vars( + u, equations, dg, 2, secondary_index, + interface + ) + + # pull the outward pointing (normal) directional vector + # Note! this assumes a conforming approximation, more must be done in terms of the normals + # for hanging nodes and other non-conforming approximation spaces + outward_direction = get_surface_normal( + normal_directions, primary_index, + primary_side, + primary_element + ) + + # Call pointwise numerical flux with rotation. Direction is normalized inside this function + flux = surface_flux(u_ll, u_rr, outward_direction, equations) + + # Copy flux back to primary/secondary element storage + # Note the sign change for the normal flux in the secondary element! + for v in eachvariable(equations) + surface_flux_values[v, primary_index, primary_side, primary_element] = flux[v] + surface_flux_values[v, secondary_index, secondary_side, secondary_element] = -flux[v] + end + + # increment the index of the coordinate system in the secondary element + secondary_index += index_increment[interface] end end + + return nothing end - return nothing -end - -# compute the numerical flux interface coupling between two elements on an unstructured -# quadrilateral mesh -function calc_interface_flux!(surface_flux_values, - mesh::UnstructuredMesh2D, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache) - @unpack surface_flux = surface_integral - @unpack u, start_index, index_increment, element_ids, element_side_ids = cache.interfaces - @unpack normal_directions = cache.elements - - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - primary_element = element_ids[1, interface] - secondary_element = element_ids[2, interface] - - # Get the local side id on which to compute the flux - primary_side = element_side_ids[1, interface] - secondary_side = element_side_ids[2, interface] - - # initial index for the coordinate system on the secondary element - secondary_index = start_index[interface] - - # loop through the primary element coordinate system and compute the interface coupling - for primary_index in eachnode(dg) - # pull the primary and secondary states from the boundary u values - u_ll = get_one_sided_surface_node_vars(u, equations, dg, 1, primary_index, - interface) - u_rr = get_one_sided_surface_node_vars(u, equations, dg, 2, secondary_index, - interface) - - # pull the outward pointing (normal) directional vector - # Note! this assumes a conforming approximation, more must be done in terms of the normals - # for hanging nodes and other non-conforming approximation spaces - outward_direction = get_surface_normal(normal_directions, primary_index, - primary_side, - primary_element) - - # Call pointwise numerical flux with rotation. Direction is normalized inside this function - flux = surface_flux(u_ll, u_rr, outward_direction, equations) - - # Copy flux back to primary/secondary element storage - # Note the sign change for the normal flux in the secondary element! - for v in eachvariable(equations) - surface_flux_values[v, primary_index, primary_side, primary_element] = flux[v] - surface_flux_values[v, secondary_index, secondary_side, secondary_element] = -flux[v] + # compute the numerical flux interface with nonconservative terms coupling between two elements + # on an unstructured quadrilateral mesh + function calc_interface_flux!( + surface_flux_values, + mesh::UnstructuredMesh2D, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache + ) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack u, start_index, index_increment, element_ids, element_side_ids = cache.interfaces + @unpack normal_directions = cache.elements + + @threaded for interface in eachinterface(dg, cache) + # Get the primary element index and local side index + primary_element = element_ids[1, interface] + primary_side = element_side_ids[1, interface] + + # Get neighboring element, local side index, and index increment on the + # secondary element + secondary_element = element_ids[2, interface] + secondary_side = element_side_ids[2, interface] + secondary_index_increment = index_increment[interface] + + secondary_index = start_index[interface] + for primary_index in eachnode(dg) + # pull the primary and secondary states from the boundary u values + u_ll = get_one_sided_surface_node_vars( + u, equations, dg, 1, primary_index, + interface + ) + u_rr = get_one_sided_surface_node_vars( + u, equations, dg, 2, secondary_index, + interface + ) + + # pull the outward pointing (normal) directional vector + # Note! This assumes a conforming approximation, more must be done in terms + # of the normals for hanging nodes and other non-conforming approximation spaces + outward_direction = get_surface_normal( + normal_directions, primary_index, + primary_side, + primary_element + ) + + # Calculate the conservative portion of the numerical flux + # Call pointwise numerical flux with rotation. Direction is normalized + # inside this function + flux = surface_flux(u_ll, u_rr, outward_direction, equations) + + # Compute both nonconservative fluxes + # In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. + # However, both are the same at watertight interfaces, so we pass the + # `outward_direction` twice. + noncons_primary = nonconservative_flux( + u_ll, u_rr, outward_direction, + outward_direction, equations + ) + noncons_secondary = nonconservative_flux( + u_rr, u_ll, outward_direction, + outward_direction, equations + ) + + # Copy flux to primary and secondary element storage + # Note the sign change for the components in the secondary element! + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[v, primary_index, primary_side, primary_element] = ( + flux[v] + + 0.5f0 * + noncons_primary[v] + ) + surface_flux_values[v, secondary_index, secondary_side, secondary_element] = -( + flux[v] + + 0.5f0 * + noncons_secondary[v] + ) + end + + # increment the index of the coordinate system in the secondary element + secondary_index += secondary_index_increment end - - # increment the index of the coordinate system in the secondary element - secondary_index += index_increment[interface] end + + return nothing end - return nothing -end - -# compute the numerical flux interface with nonconservative terms coupling between two elements -# on an unstructured quadrilateral mesh -function calc_interface_flux!(surface_flux_values, - mesh::UnstructuredMesh2D, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache) - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack u, start_index, index_increment, element_ids, element_side_ids = cache.interfaces - @unpack normal_directions = cache.elements - - @threaded for interface in eachinterface(dg, cache) - # Get the primary element index and local side index - primary_element = element_ids[1, interface] - primary_side = element_side_ids[1, interface] - - # Get neighboring element, local side index, and index increment on the - # secondary element - secondary_element = element_ids[2, interface] - secondary_side = element_side_ids[2, interface] - secondary_index_increment = index_increment[interface] - - secondary_index = start_index[interface] - for primary_index in eachnode(dg) - # pull the primary and secondary states from the boundary u values - u_ll = get_one_sided_surface_node_vars(u, equations, dg, 1, primary_index, - interface) - u_rr = get_one_sided_surface_node_vars(u, equations, dg, 2, secondary_index, - interface) - - # pull the outward pointing (normal) directional vector - # Note! This assumes a conforming approximation, more must be done in terms - # of the normals for hanging nodes and other non-conforming approximation spaces - outward_direction = get_surface_normal(normal_directions, primary_index, - primary_side, - primary_element) - - # Calculate the conservative portion of the numerical flux - # Call pointwise numerical flux with rotation. Direction is normalized - # inside this function - flux = surface_flux(u_ll, u_rr, outward_direction, equations) - - # Compute both nonconservative fluxes - # In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. - # However, both are the same at watertight interfaces, so we pass the - # `outward_direction` twice. - noncons_primary = nonconservative_flux(u_ll, u_rr, outward_direction, - outward_direction, equations) - noncons_secondary = nonconservative_flux(u_rr, u_ll, outward_direction, - outward_direction, equations) - - # Copy flux to primary and secondary element storage - # Note the sign change for the components in the secondary element! - for v in eachvariable(equations) - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - surface_flux_values[v, primary_index, primary_side, primary_element] = (flux[v] + - 0.5f0 * - noncons_primary[v]) - surface_flux_values[v, secondary_index, secondary_side, secondary_element] = -(flux[v] + - 0.5f0 * - noncons_secondary[v]) + # move the approximate solution onto physical boundaries within a "right-handed" element + function prolong2boundaries!( + cache, u, + mesh::UnstructuredMesh2D, + equations, surface_integral, dg::DG + ) + @unpack boundaries = cache + @unpack element_id, element_side_id = boundaries + boundaries_u = boundaries.u + + @threaded for boundary in eachboundary(dg, cache) + element = element_id[boundary] + side = element_side_id[boundary] + + if side == 1 + for l in eachnode(dg), v in eachvariable(equations) + boundaries_u[v, l, boundary] = u[v, l, 1, element] + end + elseif side == 2 + for l in eachnode(dg), v in eachvariable(equations) + boundaries_u[v, l, boundary] = u[v, nnodes(dg), l, element] + end + elseif side == 3 + for l in eachnode(dg), v in eachvariable(equations) + boundaries_u[v, l, boundary] = u[v, l, nnodes(dg), element] + end + else # side == 4 + for l in eachnode(dg), v in eachvariable(equations) + boundaries_u[v, l, boundary] = u[v, 1, l, element] + end end - - # increment the index of the coordinate system in the secondary element - secondary_index += secondary_index_increment end + + return nothing end - return nothing -end + # TODO: Taal dimension agnostic + function calc_boundary_flux!( + cache, t, boundary_condition::BoundaryConditionPeriodic, + mesh::Union{UnstructuredMesh2D, P4estMesh, T8codeMesh}, + equations, surface_integral, dg::DG + ) + @assert isempty(eachboundary(dg, cache)) + end -# move the approximate solution onto physical boundaries within a "right-handed" element -function prolong2boundaries!(cache, u, - mesh::UnstructuredMesh2D, - equations, surface_integral, dg::DG) - @unpack boundaries = cache - @unpack element_id, element_side_id = boundaries - boundaries_u = boundaries.u + # Function barrier for type stability + function calc_boundary_flux!( + cache, t, boundary_conditions, + mesh::Union{UnstructuredMesh2D, P4estMesh, T8codeMesh}, + equations, surface_integral, dg::DG + ) + @unpack boundary_condition_types, boundary_indices = boundary_conditions + + calc_boundary_flux_by_type!( + cache, t, boundary_condition_types, boundary_indices, + mesh, equations, surface_integral, dg + ) + return nothing + end - @threaded for boundary in eachboundary(dg, cache) - element = element_id[boundary] - side = element_side_id[boundary] + # Iterate over tuples of boundary condition types and associated indices + # in a type-stable way using "lispy tuple programming". + function calc_boundary_flux_by_type!( + cache, t, BCs::NTuple{N, Any}, + BC_indices::NTuple{N, Vector{Int}}, + mesh::Union{ + UnstructuredMesh2D, P4estMesh, + T8codeMesh, + }, + equations, surface_integral, dg::DG + ) where {N} + # Extract the boundary condition type and index vector + boundary_condition = first(BCs) + boundary_condition_indices = first(BC_indices) + # Extract the remaining types and indices to be processed later + remaining_boundary_conditions = Base.tail(BCs) + remaining_boundary_condition_indices = Base.tail(BC_indices) + + # process the first boundary condition type + calc_boundary_flux!( + cache, t, boundary_condition, boundary_condition_indices, + mesh, equations, surface_integral, dg + ) + + # recursively call this method with the unprocessed boundary types + calc_boundary_flux_by_type!( + cache, t, remaining_boundary_conditions, + remaining_boundary_condition_indices, + mesh, equations, surface_integral, dg + ) + + return nothing + end - if side == 1 - for l in eachnode(dg), v in eachvariable(equations) - boundaries_u[v, l, boundary] = u[v, l, 1, element] - end - elseif side == 2 - for l in eachnode(dg), v in eachvariable(equations) - boundaries_u[v, l, boundary] = u[v, nnodes(dg), l, element] - end - elseif side == 3 - for l in eachnode(dg), v in eachvariable(equations) - boundaries_u[v, l, boundary] = u[v, l, nnodes(dg), element] - end - else # side == 4 - for l in eachnode(dg), v in eachvariable(equations) - boundaries_u[v, l, boundary] = u[v, 1, l, element] + # terminate the type-stable iteration over tuples + function calc_boundary_flux_by_type!( + cache, t, BCs::Tuple{}, BC_indices::Tuple{}, + mesh::Union{ + UnstructuredMesh2D, P4estMesh, + T8codeMesh, + }, + equations, surface_integral, dg::DG + ) + nothing + end + + function calc_boundary_flux!( + cache, t, boundary_condition::BC, boundary_indexing, + mesh::UnstructuredMesh2D, equations, + surface_integral, dg::DG + ) where {BC} + @unpack surface_flux_values = cache.elements + @unpack element_id, element_side_id = cache.boundaries + + @threaded for local_index in eachindex(boundary_indexing) + # use the local index to get the global boundary index from the pre-sorted list + boundary = boundary_indexing[local_index] + + # get the element and side IDs on the boundary element + element = element_id[boundary] + side = element_side_id[boundary] + + # calc boundary flux on the current boundary interface + for node in eachnode(dg) + calc_boundary_flux!( + surface_flux_values, t, boundary_condition, + mesh, have_nonconservative_terms(equations), + equations, surface_integral, dg, cache, + node, side, element, boundary + ) end end end - return nothing -end - -# TODO: Taal dimension agnostic -function calc_boundary_flux!(cache, t, boundary_condition::BoundaryConditionPeriodic, - mesh::Union{UnstructuredMesh2D, P4estMesh, T8codeMesh}, - equations, surface_integral, dg::DG) - @assert isempty(eachboundary(dg, cache)) -end - -# Function barrier for type stability -function calc_boundary_flux!(cache, t, boundary_conditions, - mesh::Union{UnstructuredMesh2D, P4estMesh, T8codeMesh}, - equations, surface_integral, dg::DG) - @unpack boundary_condition_types, boundary_indices = boundary_conditions - - calc_boundary_flux_by_type!(cache, t, boundary_condition_types, boundary_indices, - mesh, equations, surface_integral, dg) - return nothing -end - -# Iterate over tuples of boundary condition types and associated indices -# in a type-stable way using "lispy tuple programming". -function calc_boundary_flux_by_type!(cache, t, BCs::NTuple{N, Any}, - BC_indices::NTuple{N, Vector{Int}}, - mesh::Union{UnstructuredMesh2D, P4estMesh, - T8codeMesh}, - equations, surface_integral, dg::DG) where {N} - # Extract the boundary condition type and index vector - boundary_condition = first(BCs) - boundary_condition_indices = first(BC_indices) - # Extract the remaining types and indices to be processed later - remaining_boundary_conditions = Base.tail(BCs) - remaining_boundary_condition_indices = Base.tail(BC_indices) - - # process the first boundary condition type - calc_boundary_flux!(cache, t, boundary_condition, boundary_condition_indices, - mesh, equations, surface_integral, dg) - - # recursively call this method with the unprocessed boundary types - calc_boundary_flux_by_type!(cache, t, remaining_boundary_conditions, - remaining_boundary_condition_indices, - mesh, equations, surface_integral, dg) - - return nothing -end - -# terminate the type-stable iteration over tuples -function calc_boundary_flux_by_type!(cache, t, BCs::Tuple{}, BC_indices::Tuple{}, - mesh::Union{UnstructuredMesh2D, P4estMesh, - T8codeMesh}, - equations, surface_integral, dg::DG) - nothing -end - -function calc_boundary_flux!(cache, t, boundary_condition::BC, boundary_indexing, - mesh::UnstructuredMesh2D, equations, - surface_integral, dg::DG) where {BC} - @unpack surface_flux_values = cache.elements - @unpack element_id, element_side_id = cache.boundaries - - @threaded for local_index in eachindex(boundary_indexing) - # use the local index to get the global boundary index from the pre-sorted list - boundary = boundary_indexing[local_index] - - # get the element and side IDs on the boundary element - element = element_id[boundary] - side = element_side_id[boundary] - - # calc boundary flux on the current boundary interface - for node in eachnode(dg) - calc_boundary_flux!(surface_flux_values, t, boundary_condition, - mesh, have_nonconservative_terms(equations), - equations, surface_integral, dg, cache, - node, side, element, boundary) + # inlined version of the boundary flux calculation along a physical interface where the + # boundary flux values are set according to a particular `boundary_condition` function + @inline function calc_boundary_flux!( + surface_flux_values, t, boundary_condition, + mesh::UnstructuredMesh2D, + nonconservative_terms::False, equations, + surface_integral, dg::DG, cache, + node_index, side_index, element_index, + boundary_index + ) + @unpack normal_directions = cache.elements + @unpack u, node_coordinates = cache.boundaries + @unpack surface_flux = surface_integral + + # pull the inner solution state from the boundary u values on the boundary element + u_inner = get_node_vars(u, equations, dg, node_index, boundary_index) + + # pull the outward pointing (normal) directional vector + outward_direction = get_surface_normal( + normal_directions, node_index, side_index, + element_index + ) + + # get the external solution values from the prescribed external state + x = get_node_coords(node_coordinates, equations, dg, node_index, boundary_index) + + # Call pointwise numerical flux function in the normal direction on the boundary + flux = boundary_condition(u_inner, outward_direction, x, t, surface_flux, equations) + + for v in eachvariable(equations) + surface_flux_values[v, node_index, side_index, element_index] = flux[v] end end -end - -# inlined version of the boundary flux calculation along a physical interface where the -# boundary flux values are set according to a particular `boundary_condition` function -@inline function calc_boundary_flux!(surface_flux_values, t, boundary_condition, - mesh::UnstructuredMesh2D, - nonconservative_terms::False, equations, - surface_integral, dg::DG, cache, - node_index, side_index, element_index, - boundary_index) - @unpack normal_directions = cache.elements - @unpack u, node_coordinates = cache.boundaries - @unpack surface_flux = surface_integral - - # pull the inner solution state from the boundary u values on the boundary element - u_inner = get_node_vars(u, equations, dg, node_index, boundary_index) - - # pull the outward pointing (normal) directional vector - outward_direction = get_surface_normal(normal_directions, node_index, side_index, - element_index) - - # get the external solution values from the prescribed external state - x = get_node_coords(node_coordinates, equations, dg, node_index, boundary_index) - - # Call pointwise numerical flux function in the normal direction on the boundary - flux = boundary_condition(u_inner, outward_direction, x, t, surface_flux, equations) - - for v in eachvariable(equations) - surface_flux_values[v, node_index, side_index, element_index] = flux[v] - end -end - -# inlined version of the boundary flux and nonconseravtive terms calculation along a -# physical interface. The conservative portion of the boundary flux values -# are set according to a particular `boundary_condition` function -# Note, it is necessary to set and add in the nonconservative values because -# the upper left/lower right diagonal terms have been peeled off due to the use of -# `derivative_split` from `dg.basis` in [`flux_differencing_kernel!`](@ref) -@inline function calc_boundary_flux!(surface_flux_values, t, boundary_condition, - mesh::UnstructuredMesh2D, - nonconservative_terms::True, equations, - surface_integral, dg::DG, cache, - node_index, side_index, element_index, - boundary_index) - surface_flux, nonconservative_flux = surface_integral.surface_flux - @unpack normal_directions = cache.elements - @unpack u, node_coordinates = cache.boundaries - - # pull the inner solution state from the boundary u values on the boundary element - u_inner = get_node_vars(u, equations, dg, node_index, boundary_index) - - # pull the outward pointing (normal) directional vector - outward_direction = get_surface_normal(normal_directions, node_index, side_index, - element_index) - - # get the external solution values from the prescribed external state - x = get_node_coords(node_coordinates, equations, dg, node_index, boundary_index) - - # Call pointwise numerical flux function for the conservative part - # in the normal direction on the boundary - flux = boundary_condition(u_inner, outward_direction, x, t, surface_flux, equations) - - # Compute pointwise nonconservative numerical flux at the boundary. - # In general, nonconservative fluxes can depend on both the contravariant - # vectors (normal direction) at the current node and the averaged ones. - # However, both are the same at watertight interfaces, so we pass the - # `outward_direction` twice. - # Note: This does not set any type of boundary condition for the nonconservative term - noncons_flux = nonconservative_flux(u_inner, u_inner, outward_direction, - outward_direction, equations) - - for v in eachvariable(equations) - # Note the factor 0.5 necessary for the nonconservative fluxes based on - # the interpretation of global SBP operators coupled discontinuously via - # central fluxes/SATs - surface_flux_values[v, node_index, side_index, element_index] = flux[v] + - 0.5f0 * - noncons_flux[v] - end -end - -# Note! The local side numbering for the unstructured quadrilateral element implementation differs -# from the structured TreeMesh or StructuredMesh local side numbering: -# -# TreeMesh/StructuredMesh sides versus UnstructuredMesh sides -# 4 3 -# ----------------- ----------------- -# | | | | -# | ^ eta | | ^ eta | -# 1 | | | 2 4 | | | 2 -# | | | | | | -# | ---> xi | | ---> xi | -# ----------------- ----------------- -# 3 1 -# Therefore, we require a different surface integral routine here despite their similar structure. -function calc_surface_integral!(du, u, mesh::UnstructuredMesh2D, - equations, surface_integral, dg::DGSEM, cache) - @unpack boundary_interpolation = dg.basis - @unpack surface_flux_values = cache.elements - - @threaded for element in eachelement(dg, cache) - for l in eachnode(dg), v in eachvariable(equations) - # surface contribution along local sides 2 and 4 (fixed x and y varies) - du[v, 1, l, element] += (surface_flux_values[v, l, 4, element] - * - boundary_interpolation[1, 1]) - du[v, nnodes(dg), l, element] += (surface_flux_values[v, l, 2, element] - * - boundary_interpolation[nnodes(dg), 2]) - # surface contribution along local sides 1 and 3 (fixed y and x varies) - du[v, l, 1, element] += (surface_flux_values[v, l, 1, element] - * - boundary_interpolation[1, 1]) - du[v, l, nnodes(dg), element] += (surface_flux_values[v, l, 3, element] - * - boundary_interpolation[nnodes(dg), 2]) + + # inlined version of the boundary flux and nonconseravtive terms calculation along a + # physical interface. The conservative portion of the boundary flux values + # are set according to a particular `boundary_condition` function + # Note, it is necessary to set and add in the nonconservative values because + # the upper left/lower right diagonal terms have been peeled off due to the use of + # `derivative_split` from `dg.basis` in [`flux_differencing_kernel!`](@ref) + @inline function calc_boundary_flux!( + surface_flux_values, t, boundary_condition, + mesh::UnstructuredMesh2D, + nonconservative_terms::True, equations, + surface_integral, dg::DG, cache, + node_index, side_index, element_index, + boundary_index + ) + surface_flux, nonconservative_flux = surface_integral.surface_flux + @unpack normal_directions = cache.elements + @unpack u, node_coordinates = cache.boundaries + + # pull the inner solution state from the boundary u values on the boundary element + u_inner = get_node_vars(u, equations, dg, node_index, boundary_index) + + # pull the outward pointing (normal) directional vector + outward_direction = get_surface_normal( + normal_directions, node_index, side_index, + element_index + ) + + # get the external solution values from the prescribed external state + x = get_node_coords(node_coordinates, equations, dg, node_index, boundary_index) + + # Call pointwise numerical flux function for the conservative part + # in the normal direction on the boundary + flux = boundary_condition(u_inner, outward_direction, x, t, surface_flux, equations) + + # Compute pointwise nonconservative numerical flux at the boundary. + # In general, nonconservative fluxes can depend on both the contravariant + # vectors (normal direction) at the current node and the averaged ones. + # However, both are the same at watertight interfaces, so we pass the + # `outward_direction` twice. + # Note: This does not set any type of boundary condition for the nonconservative term + noncons_flux = nonconservative_flux( + u_inner, u_inner, outward_direction, + outward_direction, equations + ) + + for v in eachvariable(equations) + # Note the factor 0.5 necessary for the nonconservative fluxes based on + # the interpretation of global SBP operators coupled discontinuously via + # central fluxes/SATs + surface_flux_values[v, node_index, side_index, element_index] = flux[v] + + 0.5f0 * + noncons_flux[v] end end - return nothing -end - -# This routine computes the maximum value of the discrete metric identities necessary to ensure -# that the approxmiation will be free-stream preserving (i.e. a constant solution remains constant) -# on a curvilinear mesh. -# Note! Independent of the equation system and is only a check on the discrete mapping terms. -# Can be used for a metric identities check on StructuredMesh{2} or UnstructuredMesh2D -function max_discrete_metric_identities(dg::DGSEM, cache) - @unpack derivative_matrix = dg.basis - @unpack contravariant_vectors = cache.elements - - ndims_ = size(contravariant_vectors, 1) - - metric_id_dx = zeros(eltype(contravariant_vectors), nnodes(dg), nnodes(dg)) - metric_id_dy = zeros(eltype(contravariant_vectors), nnodes(dg), nnodes(dg)) + # Note! The local side numbering for the unstructured quadrilateral element implementation differs + # from the structured TreeMesh or StructuredMesh local side numbering: + # + # TreeMesh/StructuredMesh sides versus UnstructuredMesh sides + # 4 3 + # ----------------- ----------------- + # | | | | + # | ^ eta | | ^ eta | + # 1 | | | 2 4 | | | 2 + # | | | | | | + # | ---> xi | | ---> xi | + # ----------------- ----------------- + # 3 1 + # Therefore, we require a different surface integral routine here despite their similar structure. + function calc_surface_integral!( + du, u, mesh::UnstructuredMesh2D, + equations, surface_integral, dg::DGSEM, cache + ) + @unpack boundary_interpolation = dg.basis + @unpack surface_flux_values = cache.elements + + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg), v in eachvariable(equations) + # surface contribution along local sides 2 and 4 (fixed x and y varies) + du[v, 1, l, element] += ( + surface_flux_values[v, l, 4, element] + * + boundary_interpolation[1, 1] + ) + du[v, nnodes(dg), l, element] += ( + surface_flux_values[v, l, 2, element] + * + boundary_interpolation[nnodes(dg), 2] + ) + # surface contribution along local sides 1 and 3 (fixed y and x varies) + du[v, l, 1, element] += ( + surface_flux_values[v, l, 1, element] + * + boundary_interpolation[1, 1] + ) + du[v, l, nnodes(dg), element] += ( + surface_flux_values[v, l, 3, element] + * + boundary_interpolation[nnodes(dg), 2] + ) + end + end - max_metric_ids = zero(eltype(contravariant_vectors)) + return nothing + end - for i in 1:ndims_, element in eachelement(dg, cache) - # compute D*Ja_1^i + Ja_2^i*D^T - @views mul!(metric_id_dx, derivative_matrix, - contravariant_vectors[i, 1, :, :, element]) - @views mul!(metric_id_dy, contravariant_vectors[i, 2, :, :, element], - derivative_matrix') - local_max_metric_ids = maximum(abs.(metric_id_dx + metric_id_dy)) + # This routine computes the maximum value of the discrete metric identities necessary to ensure + # that the approxmiation will be free-stream preserving (i.e. a constant solution remains constant) + # on a curvilinear mesh. + # Note! Independent of the equation system and is only a check on the discrete mapping terms. + # Can be used for a metric identities check on StructuredMesh{2} or UnstructuredMesh2D + function max_discrete_metric_identities(dg::DGSEM, cache) + @unpack derivative_matrix = dg.basis + @unpack contravariant_vectors = cache.elements + + ndims_ = size(contravariant_vectors, 1) + + metric_id_dx = zeros(eltype(contravariant_vectors), nnodes(dg), nnodes(dg)) + metric_id_dy = zeros(eltype(contravariant_vectors), nnodes(dg), nnodes(dg)) + + max_metric_ids = zero(eltype(contravariant_vectors)) + + for i in 1:ndims_, element in eachelement(dg, cache) + # compute D*Ja_1^i + Ja_2^i*D^T + @views mul!( + metric_id_dx, derivative_matrix, + contravariant_vectors[i, 1, :, :, element] + ) + @views mul!( + metric_id_dy, contravariant_vectors[i, 2, :, :, element], + derivative_matrix' + ) + local_max_metric_ids = maximum(abs.(metric_id_dx + metric_id_dy)) + + max_metric_ids = max(max_metric_ids, local_max_metric_ids) + end - max_metric_ids = max(max_metric_ids, local_max_metric_ids) + return max_metric_ids end - - return max_metric_ids -end end # @muladd diff --git a/src/solvers/dgsem_unstructured/indicators_2d.jl b/src/solvers/dgsem_unstructured/indicators_2d.jl index e331cb5ee7..3735a7b8cb 100644 --- a/src/solvers/dgsem_unstructured/indicators_2d.jl +++ b/src/solvers/dgsem_unstructured/indicators_2d.jl @@ -3,22 +3,22 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -function apply_smoothing!(mesh::UnstructuredMesh2D, alpha, alpha_tmp, dg, cache) - # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha - # Copy alpha values such that smoothing is indpedenent of the element access order - alpha_tmp .= alpha + function apply_smoothing!(mesh::UnstructuredMesh2D, alpha, alpha_tmp, dg, cache) + # Diffuse alpha values by setting each alpha to at least 50% of neighboring elements' alpha + # Copy alpha values such that smoothing is indpedenent of the element access order + alpha_tmp .= alpha - # Loop over interfaces - for interface in eachinterface(dg, cache) - # Get neighboring element ids - left = cache.interfaces.element_ids[1, interface] - right = cache.interfaces.element_ids[2, interface] + # Loop over interfaces + for interface in eachinterface(dg, cache) + # Get neighboring element ids + left = cache.interfaces.element_ids[1, interface] + right = cache.interfaces.element_ids[2, interface] - # Apply smoothing - alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[right], alpha[left]) - alpha[right] = max(alpha_tmp[right], 0.5f0 * alpha_tmp[left], alpha[right]) + # Apply smoothing + alpha[left] = max(alpha_tmp[left], 0.5f0 * alpha_tmp[right], alpha[left]) + alpha[right] = max(alpha_tmp[right], 0.5f0 * alpha_tmp[left], alpha[right]) + end end -end end # @muladd diff --git a/src/solvers/dgsem_unstructured/mappings_geometry_curved_2d.jl b/src/solvers/dgsem_unstructured/mappings_geometry_curved_2d.jl index 5949e1f97e..f61be650f2 100644 --- a/src/solvers/dgsem_unstructured/mappings_geometry_curved_2d.jl +++ b/src/solvers/dgsem_unstructured/mappings_geometry_curved_2d.jl @@ -3,159 +3,205 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# transfinite mapping formula from a point (xi, eta) in reference space [-1,1]^2 to a point -# (x,y) in physical coordinate space for a quadrilateral element with general curved sides -# Alg. 98 from the blue book of Kopriva -function transfinite_quad_map(xi, eta, surface_curves::AbstractVector{<:CurvedSurface}) - - # evaluate the gamma curves to get the four corner points of the element - x_corner1, y_corner1 = evaluate_at(-1, surface_curves[1]) - x_corner2, y_corner2 = evaluate_at(1, surface_curves[1]) - x_corner3, y_corner3 = evaluate_at(1, surface_curves[3]) - x_corner4, y_corner4 = evaluate_at(-1, surface_curves[3]) - - # evaluate along the gamma curves at a particular point (ξ, η) in computational space to get - # the value (x,y) in physical space - x1, y1 = evaluate_at(xi, surface_curves[1]) - x2, y2 = evaluate_at(eta, surface_curves[2]) - x3, y3 = evaluate_at(xi, surface_curves[3]) - x4, y4 = evaluate_at(eta, surface_curves[4]) - - x = (0.5f0 * ((1 - xi) * x4 + (1 + xi) * x2 + (1 - eta) * x1 + (1 + eta) * x3) - - - 0.25f0 * ((1 - xi) * ((1 - eta) * x_corner1 + (1 + eta) * x_corner4) + - (1 + xi) * ((1 - eta) * x_corner2 + (1 + eta) * x_corner3))) - - y = (0.5f0 * ((1 - xi) * y4 + (1 + xi) * y2 + (1 - eta) * y1 + (1 + eta) * y3) - - - 0.25f0 * ((1 - xi) * ((1 - eta) * y_corner1 + (1 + eta) * y_corner4) + - (1 + xi) * ((1 - eta) * y_corner2 + (1 + eta) * y_corner3))) - - return x, y -end - -# Compute the metric terms for the general curved sided quadrilateral transfitie mapping -# Alg. 99 from the blue book of Kopriva -function transfinite_quad_map_metrics(xi, eta, - surface_curves::AbstractVector{<:CurvedSurface}) - - # evaluate the gamma curves to get the four corner points of the element - x_corner1, y_corner1 = evaluate_at(-1, surface_curves[1]) - x_corner2, y_corner2 = evaluate_at(1, surface_curves[1]) - x_corner3, y_corner3 = evaluate_at(1, surface_curves[3]) - x_corner4, y_corner4 = evaluate_at(-1, surface_curves[3]) - - # evaluate along the gamma curves at a particular point (ξ, η) in computational space to get - # the value (x,y) in physical space - x1, y1 = evaluate_at(xi, surface_curves[1]) - x2, y2 = evaluate_at(eta, surface_curves[2]) - x3, y3 = evaluate_at(xi, surface_curves[3]) - x4, y4 = evaluate_at(eta, surface_curves[4]) - - # evaluate along the derivative of the gamma curves at a particular point (ξ, η) in - # computational space to get the value (x_prime,y_prime) in physical space - x1_prime, y1_prime = derivative_at(xi, surface_curves[1]) - x2_prime, y2_prime = derivative_at(eta, surface_curves[2]) - x3_prime, y3_prime = derivative_at(xi, surface_curves[3]) - x4_prime, y4_prime = derivative_at(eta, surface_curves[4]) - - X_xi = (0.5f0 * (x2 - x4 + (1 - eta) * x1_prime + (1 + eta) * x3_prime) - - - 0.25f0 * ((1 - eta) * (x_corner2 - x_corner1) + - (1 + eta) * (x_corner3 - x_corner4))) - - X_eta = (0.5f0 * ((1 - xi) * x4_prime + (1 + xi) * x2_prime + x3 - x1) - - - 0.25f0 * ((1 - xi) * (x_corner4 - x_corner1) + - (1 + xi) * (x_corner3 - x_corner2))) - - Y_xi = (0.5f0 * (y2 - y4 + (1 - eta) * y1_prime + (1 + eta) * y3_prime) - - - 0.25f0 * ((1 - eta) * (y_corner2 - y_corner1) + - (1 + eta) * (y_corner3 - y_corner4))) - - Y_eta = (0.5f0 * ((1 - xi) * y4_prime + (1 + xi) * y2_prime + y3 - y1) - - - 0.25f0 * ((1 - xi) * (y_corner4 - y_corner1) + - (1 + xi) * (y_corner3 - y_corner2))) - - return X_xi, X_eta, Y_xi, Y_eta -end - -# construct the (x,y) node coordinates in the volume of a curved sided element -function calc_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, element, - nodes, - surface_curves::AbstractVector{<:CurvedSurface}) - for j in eachindex(nodes), i in eachindex(nodes) - node_coordinates[:, i, j, element] .= transfinite_quad_map(nodes[i], nodes[j], - surface_curves) + #! format: noindent + + # transfinite mapping formula from a point (xi, eta) in reference space [-1,1]^2 to a point + # (x,y) in physical coordinate space for a quadrilateral element with general curved sides + # Alg. 98 from the blue book of Kopriva + function transfinite_quad_map(xi, eta, surface_curves::AbstractVector{<:CurvedSurface}) + + # evaluate the gamma curves to get the four corner points of the element + x_corner1, y_corner1 = evaluate_at(-1, surface_curves[1]) + x_corner2, y_corner2 = evaluate_at(1, surface_curves[1]) + x_corner3, y_corner3 = evaluate_at(1, surface_curves[3]) + x_corner4, y_corner4 = evaluate_at(-1, surface_curves[3]) + + # evaluate along the gamma curves at a particular point (ξ, η) in computational space to get + # the value (x,y) in physical space + x1, y1 = evaluate_at(xi, surface_curves[1]) + x2, y2 = evaluate_at(eta, surface_curves[2]) + x3, y3 = evaluate_at(xi, surface_curves[3]) + x4, y4 = evaluate_at(eta, surface_curves[4]) + + x = ( + 0.5f0 * ((1 - xi) * x4 + (1 + xi) * x2 + (1 - eta) * x1 + (1 + eta) * x3) + - + 0.25f0 * ( + (1 - xi) * ((1 - eta) * x_corner1 + (1 + eta) * x_corner4) + + (1 + xi) * ((1 - eta) * x_corner2 + (1 + eta) * x_corner3) + ) + ) + + y = ( + 0.5f0 * ((1 - xi) * y4 + (1 + xi) * y2 + (1 - eta) * y1 + (1 + eta) * y3) + - + 0.25f0 * ( + (1 - xi) * ((1 - eta) * y_corner1 + (1 + eta) * y_corner4) + + (1 + xi) * ((1 - eta) * y_corner2 + (1 + eta) * y_corner3) + ) + ) + + return x, y end - return node_coordinates -end - -# construct the metric terms for a curved sided element -function calc_metric_terms!(jacobian_matrix, element, nodes, - surface_curves::AbstractVector{<:CurvedSurface}) - - # storage format: - # jacobian_matrix[1,1,:,:,:] <- X_xi - # jacobian_matrix[1,2,:,:,:] <- X_eta - # jacobian_matrix[2,1,:,:,:] <- Y_xi - # jacobian_matrix[2,2,:,:,:] <- Y_eta - for j in eachindex(nodes), i in eachindex(nodes) - (jacobian_matrix[1, 1, i, j, element], - jacobian_matrix[1, 2, i, j, element], - jacobian_matrix[2, 1, i, j, element], - jacobian_matrix[2, 2, i, j, element]) = transfinite_quad_map_metrics(nodes[i], - nodes[j], - surface_curves) + # Compute the metric terms for the general curved sided quadrilateral transfitie mapping + # Alg. 99 from the blue book of Kopriva + function transfinite_quad_map_metrics( + xi, eta, + surface_curves::AbstractVector{<:CurvedSurface} + ) + + # evaluate the gamma curves to get the four corner points of the element + x_corner1, y_corner1 = evaluate_at(-1, surface_curves[1]) + x_corner2, y_corner2 = evaluate_at(1, surface_curves[1]) + x_corner3, y_corner3 = evaluate_at(1, surface_curves[3]) + x_corner4, y_corner4 = evaluate_at(-1, surface_curves[3]) + + # evaluate along the gamma curves at a particular point (ξ, η) in computational space to get + # the value (x,y) in physical space + x1, y1 = evaluate_at(xi, surface_curves[1]) + x2, y2 = evaluate_at(eta, surface_curves[2]) + x3, y3 = evaluate_at(xi, surface_curves[3]) + x4, y4 = evaluate_at(eta, surface_curves[4]) + + # evaluate along the derivative of the gamma curves at a particular point (ξ, η) in + # computational space to get the value (x_prime,y_prime) in physical space + x1_prime, y1_prime = derivative_at(xi, surface_curves[1]) + x2_prime, y2_prime = derivative_at(eta, surface_curves[2]) + x3_prime, y3_prime = derivative_at(xi, surface_curves[3]) + x4_prime, y4_prime = derivative_at(eta, surface_curves[4]) + + X_xi = ( + 0.5f0 * (x2 - x4 + (1 - eta) * x1_prime + (1 + eta) * x3_prime) + - + 0.25f0 * ( + (1 - eta) * (x_corner2 - x_corner1) + + (1 + eta) * (x_corner3 - x_corner4) + ) + ) + + X_eta = ( + 0.5f0 * ((1 - xi) * x4_prime + (1 + xi) * x2_prime + x3 - x1) + - + 0.25f0 * ( + (1 - xi) * (x_corner4 - x_corner1) + + (1 + xi) * (x_corner3 - x_corner2) + ) + ) + + Y_xi = ( + 0.5f0 * (y2 - y4 + (1 - eta) * y1_prime + (1 + eta) * y3_prime) + - + 0.25f0 * ( + (1 - eta) * (y_corner2 - y_corner1) + + (1 + eta) * (y_corner3 - y_corner4) + ) + ) + + Y_eta = ( + 0.5f0 * ((1 - xi) * y4_prime + (1 + xi) * y2_prime + y3 - y1) + - + 0.25f0 * ( + (1 - xi) * (y_corner4 - y_corner1) + + (1 + xi) * (y_corner3 - y_corner2) + ) + ) + + return X_xi, X_eta, Y_xi, Y_eta end - return jacobian_matrix -end - -# construct the normal direction vectors (but not actually normalized) for a curved sided element -# normalization occurs on the fly during the surface flux computation -function calc_normal_directions!(normal_directions, element, nodes, - surface_curves::AbstractVector{<:CurvedSurface}) - - # normal directions on the boundary for the left (local side 4) and right (local side 2) - for j in eachindex(nodes) - # side 2 - X_xi, X_eta, Y_xi, Y_eta = transfinite_quad_map_metrics(1, nodes[j], - surface_curves) - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, j, 2, element] = sign(Jtemp) * (Y_eta) - normal_directions[2, j, 2, element] = sign(Jtemp) * (-X_eta) - - # side 4 - X_xi, X_eta, Y_xi, Y_eta = transfinite_quad_map_metrics(-1, nodes[j], - surface_curves) - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, j, 4, element] = -sign(Jtemp) * (Y_eta) - normal_directions[2, j, 4, element] = -sign(Jtemp) * (-X_eta) + # construct the (x,y) node coordinates in the volume of a curved sided element + function calc_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 4}, element, + nodes, + surface_curves::AbstractVector{<:CurvedSurface} + ) + for j in eachindex(nodes), i in eachindex(nodes) + node_coordinates[:, i, j, element] .= transfinite_quad_map( + nodes[i], nodes[j], + surface_curves + ) + end + + return node_coordinates end - # normal directions on the boundary for the top (local side 3) and bottom (local side 1) - for i in eachindex(nodes) - # side 1 - X_xi, X_eta, Y_xi, Y_eta = transfinite_quad_map_metrics(nodes[i], -1, - surface_curves) - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, i, 1, element] = -sign(Jtemp) * (-Y_xi) - normal_directions[2, i, 1, element] = -sign(Jtemp) * (X_xi) - - # side 3 - X_xi, X_eta, Y_xi, Y_eta = transfinite_quad_map_metrics(nodes[i], 1, - surface_curves) - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, i, 3, element] = sign(Jtemp) * (-Y_xi) - normal_directions[2, i, 3, element] = sign(Jtemp) * (X_xi) + # construct the metric terms for a curved sided element + function calc_metric_terms!( + jacobian_matrix, element, nodes, + surface_curves::AbstractVector{<:CurvedSurface} + ) + + # storage format: + # jacobian_matrix[1,1,:,:,:] <- X_xi + # jacobian_matrix[1,2,:,:,:] <- X_eta + # jacobian_matrix[2,1,:,:,:] <- Y_xi + # jacobian_matrix[2,2,:,:,:] <- Y_eta + for j in eachindex(nodes), i in eachindex(nodes) + ( + jacobian_matrix[1, 1, i, j, element], + jacobian_matrix[1, 2, i, j, element], + jacobian_matrix[2, 1, i, j, element], + jacobian_matrix[2, 2, i, j, element], + ) = transfinite_quad_map_metrics( + nodes[i], + nodes[j], + surface_curves + ) + end + + return jacobian_matrix end - return normal_directions -end + # construct the normal direction vectors (but not actually normalized) for a curved sided element + # normalization occurs on the fly during the surface flux computation + function calc_normal_directions!( + normal_directions, element, nodes, + surface_curves::AbstractVector{<:CurvedSurface} + ) + + # normal directions on the boundary for the left (local side 4) and right (local side 2) + for j in eachindex(nodes) + # side 2 + X_xi, X_eta, Y_xi, Y_eta = transfinite_quad_map_metrics( + 1, nodes[j], + surface_curves + ) + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, j, 2, element] = sign(Jtemp) * (Y_eta) + normal_directions[2, j, 2, element] = sign(Jtemp) * (-X_eta) + + # side 4 + X_xi, X_eta, Y_xi, Y_eta = transfinite_quad_map_metrics( + -1, nodes[j], + surface_curves + ) + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, j, 4, element] = -sign(Jtemp) * (Y_eta) + normal_directions[2, j, 4, element] = -sign(Jtemp) * (-X_eta) + end + + # normal directions on the boundary for the top (local side 3) and bottom (local side 1) + for i in eachindex(nodes) + # side 1 + X_xi, X_eta, Y_xi, Y_eta = transfinite_quad_map_metrics( + nodes[i], -1, + surface_curves + ) + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, i, 1, element] = -sign(Jtemp) * (-Y_xi) + normal_directions[2, i, 1, element] = -sign(Jtemp) * (X_xi) + + # side 3 + X_xi, X_eta, Y_xi, Y_eta = transfinite_quad_map_metrics( + nodes[i], 1, + surface_curves + ) + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, i, 3, element] = sign(Jtemp) * (-Y_xi) + normal_directions[2, i, 3, element] = sign(Jtemp) * (X_xi) + end + + return normal_directions + end end # @muladd diff --git a/src/solvers/dgsem_unstructured/mappings_geometry_straight_2d.jl b/src/solvers/dgsem_unstructured/mappings_geometry_straight_2d.jl index 4f2f4e6f4b..c26060e6c3 100644 --- a/src/solvers/dgsem_unstructured/mappings_geometry_straight_2d.jl +++ b/src/solvers/dgsem_unstructured/mappings_geometry_straight_2d.jl @@ -3,112 +3,140 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# mapping formula from a point (xi, eta) in reference space [-1,1]^2 to a point (x,y) -# in physical coordinate space for a quadrilateral element with straight sides -# Alg. 95 from the blue book of Kopriva -function straight_side_quad_map(xi, eta, corner_points) - x = 0.25f0 * (corner_points[1, 1] * (1 - xi) * (1 - eta) - + corner_points[2, 1] * (1 + xi) * (1 - eta) - + corner_points[3, 1] * (1 + xi) * (1 + eta) - + corner_points[4, 1] * (1 - xi) * (1 + eta)) - - y = 0.25f0 * (corner_points[1, 2] * (1 - xi) * (1 - eta) - + corner_points[2, 2] * (1 + xi) * (1 - eta) - + corner_points[3, 2] * (1 + xi) * (1 + eta) - + corner_points[4, 2] * (1 - xi) * (1 + eta)) - - return x, y -end - -# Compute the metric terms for the straight sided quadrilateral mapping -# Alg. 100 from the blue book of Kopriva -function straight_side_quad_map_metrics(xi, eta, corner_points) - X_xi = 0.25f0 * ((1 - eta) * (corner_points[2, 1] - corner_points[1, 1]) + - (1 + eta) * (corner_points[3, 1] - corner_points[4, 1])) - - X_eta = 0.25f0 * ((1 - xi) * (corner_points[4, 1] - corner_points[1, 1]) + - (1 + xi) * (corner_points[3, 1] - corner_points[2, 1])) - - Y_xi = 0.25f0 * ((1 - eta) * (corner_points[2, 2] - corner_points[1, 2]) + - (1 + eta) * (corner_points[3, 2] - corner_points[4, 2])) - - Y_eta = 0.25f0 * ((1 - xi) * (corner_points[4, 2] - corner_points[1, 2]) + - (1 + xi) * (corner_points[3, 2] - corner_points[2, 2])) - - return X_xi, X_eta, Y_xi, Y_eta -end - -# construct the (x,y) node coordinates in the volume of a straight sided element -function calc_node_coordinates!(node_coordinates::AbstractArray{<:Any, 4}, element, - nodes, corners) - for j in eachindex(nodes), i in eachindex(nodes) - node_coordinates[:, i, j, element] .= straight_side_quad_map(nodes[i], nodes[j], - corners) + #! format: noindent + + # mapping formula from a point (xi, eta) in reference space [-1,1]^2 to a point (x,y) + # in physical coordinate space for a quadrilateral element with straight sides + # Alg. 95 from the blue book of Kopriva + function straight_side_quad_map(xi, eta, corner_points) + x = 0.25f0 * ( + corner_points[1, 1] * (1 - xi) * (1 - eta) + + corner_points[2, 1] * (1 + xi) * (1 - eta) + + corner_points[3, 1] * (1 + xi) * (1 + eta) + + corner_points[4, 1] * (1 - xi) * (1 + eta) + ) + + y = 0.25f0 * ( + corner_points[1, 2] * (1 - xi) * (1 - eta) + + corner_points[2, 2] * (1 + xi) * (1 - eta) + + corner_points[3, 2] * (1 + xi) * (1 + eta) + + corner_points[4, 2] * (1 - xi) * (1 + eta) + ) + + return x, y end - return node_coordinates -end - -# construct the metric terms for a straight sided element -function calc_metric_terms!(jacobian_matrix, element, nodes, corners) - - # storage format: - # jacobian_matrix[1,1,:,:,:] <- X_xi - # jacobian_matrix[1,2,:,:,:] <- X_eta - # jacobian_matrix[2,1,:,:,:] <- Y_xi - # jacobian_matrix[2,2,:,:,:] <- Y_eta - for j in eachindex(nodes), i in eachindex(nodes) - (jacobian_matrix[1, 1, i, j, element], - jacobian_matrix[1, 2, i, j, element], - jacobian_matrix[2, 1, i, j, element], - jacobian_matrix[2, 2, i, j, element]) = straight_side_quad_map_metrics(nodes[i], - nodes[j], - corners) + # Compute the metric terms for the straight sided quadrilateral mapping + # Alg. 100 from the blue book of Kopriva + function straight_side_quad_map_metrics(xi, eta, corner_points) + X_xi = 0.25f0 * ( + (1 - eta) * (corner_points[2, 1] - corner_points[1, 1]) + + (1 + eta) * (corner_points[3, 1] - corner_points[4, 1]) + ) + + X_eta = 0.25f0 * ( + (1 - xi) * (corner_points[4, 1] - corner_points[1, 1]) + + (1 + xi) * (corner_points[3, 1] - corner_points[2, 1]) + ) + + Y_xi = 0.25f0 * ( + (1 - eta) * (corner_points[2, 2] - corner_points[1, 2]) + + (1 + eta) * (corner_points[3, 2] - corner_points[4, 2]) + ) + + Y_eta = 0.25f0 * ( + (1 - xi) * (corner_points[4, 2] - corner_points[1, 2]) + + (1 + xi) * (corner_points[3, 2] - corner_points[2, 2]) + ) + + return X_xi, X_eta, Y_xi, Y_eta end - return jacobian_matrix -end - -# construct the normal direction vectors (but not actually normalized) for a straight sided element -# normalization occurs on the fly during the surface flux computation -function calc_normal_directions!(normal_directions, element, nodes, corners) - - # normal directions on the boundary for the left (local side 4) and right (local side 2) - for j in eachindex(nodes) - # side 2 - X_xi, X_eta, Y_xi, Y_eta = straight_side_quad_map_metrics(1, nodes[j], - corners) - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, j, 2, element] = sign(Jtemp) * (Y_eta) - normal_directions[2, j, 2, element] = sign(Jtemp) * (-X_eta) - - # side 4 - X_xi, X_eta, Y_xi, Y_eta = straight_side_quad_map_metrics(-1, nodes[j], - corners) - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, j, 4, element] = -sign(Jtemp) * (Y_eta) - normal_directions[2, j, 4, element] = -sign(Jtemp) * (-X_eta) + # construct the (x,y) node coordinates in the volume of a straight sided element + function calc_node_coordinates!( + node_coordinates::AbstractArray{<:Any, 4}, element, + nodes, corners + ) + for j in eachindex(nodes), i in eachindex(nodes) + node_coordinates[:, i, j, element] .= straight_side_quad_map( + nodes[i], nodes[j], + corners + ) + end + + return node_coordinates end - # normal directions on the boundary for the top (local side 3) and bottom (local side 1) - for i in eachindex(nodes) - # side 1 - X_xi, X_eta, Y_xi, Y_eta = straight_side_quad_map_metrics(nodes[i], -1, - corners) - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, i, 1, element] = -sign(Jtemp) * (-Y_xi) - normal_directions[2, i, 1, element] = -sign(Jtemp) * (X_xi) - - # side 3 - X_xi, X_eta, Y_xi, Y_eta = straight_side_quad_map_metrics(nodes[i], 1, - corners) - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, i, 3, element] = sign(Jtemp) * (-Y_xi) - normal_directions[2, i, 3, element] = sign(Jtemp) * (X_xi) + # construct the metric terms for a straight sided element + function calc_metric_terms!(jacobian_matrix, element, nodes, corners) + + # storage format: + # jacobian_matrix[1,1,:,:,:] <- X_xi + # jacobian_matrix[1,2,:,:,:] <- X_eta + # jacobian_matrix[2,1,:,:,:] <- Y_xi + # jacobian_matrix[2,2,:,:,:] <- Y_eta + for j in eachindex(nodes), i in eachindex(nodes) + ( + jacobian_matrix[1, 1, i, j, element], + jacobian_matrix[1, 2, i, j, element], + jacobian_matrix[2, 1, i, j, element], + jacobian_matrix[2, 2, i, j, element], + ) = straight_side_quad_map_metrics( + nodes[i], + nodes[j], + corners + ) + end + + return jacobian_matrix end - return normal_directions -end + # construct the normal direction vectors (but not actually normalized) for a straight sided element + # normalization occurs on the fly during the surface flux computation + function calc_normal_directions!(normal_directions, element, nodes, corners) + + # normal directions on the boundary for the left (local side 4) and right (local side 2) + for j in eachindex(nodes) + # side 2 + X_xi, X_eta, Y_xi, Y_eta = straight_side_quad_map_metrics( + 1, nodes[j], + corners + ) + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, j, 2, element] = sign(Jtemp) * (Y_eta) + normal_directions[2, j, 2, element] = sign(Jtemp) * (-X_eta) + + # side 4 + X_xi, X_eta, Y_xi, Y_eta = straight_side_quad_map_metrics( + -1, nodes[j], + corners + ) + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, j, 4, element] = -sign(Jtemp) * (Y_eta) + normal_directions[2, j, 4, element] = -sign(Jtemp) * (-X_eta) + end + + # normal directions on the boundary for the top (local side 3) and bottom (local side 1) + for i in eachindex(nodes) + # side 1 + X_xi, X_eta, Y_xi, Y_eta = straight_side_quad_map_metrics( + nodes[i], -1, + corners + ) + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, i, 1, element] = -sign(Jtemp) * (-Y_xi) + normal_directions[2, i, 1, element] = -sign(Jtemp) * (X_xi) + + # side 3 + X_xi, X_eta, Y_xi, Y_eta = straight_side_quad_map_metrics( + nodes[i], 1, + corners + ) + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, i, 3, element] = sign(Jtemp) * (-Y_xi) + normal_directions[2, i, 3, element] = sign(Jtemp) * (X_xi) + end + + return normal_directions + end end # @muladd diff --git a/src/solvers/dgsem_unstructured/sort_boundary_conditions.jl b/src/solvers/dgsem_unstructured/sort_boundary_conditions.jl index 2c2c6876d7..92a896bc22 100644 --- a/src/solvers/dgsem_unstructured/sort_boundary_conditions.jl +++ b/src/solvers/dgsem_unstructured/sort_boundary_conditions.jl @@ -3,112 +3,130 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - UnstructuredSortedBoundaryTypes + """ + UnstructuredSortedBoundaryTypes -General container to sort the boundary conditions by type and name for some unstructured meshes/solvers. -It stores a set of global indices for each boundary condition type and name to expedite computation -during the call to `calc_boundary_flux!`. The original dictionary form of the boundary conditions -set by the user in the elixir file is also stored for printing. -""" -mutable struct UnstructuredSortedBoundaryTypes{N, BCs <: NTuple{N, Any}} - boundary_condition_types::BCs # specific boundary condition type(s), e.g. BoundaryConditionDirichlet - boundary_indices::NTuple{N, Vector{Int}} # integer vectors containing global boundary indices - boundary_dictionary::Dict{Symbol, Any} # boundary conditions as set by the user in the elixir file - boundary_symbol_indices::Dict{Symbol, Vector{Int}} # integer vectors containing global boundary indices per boundary identifier -end + General container to sort the boundary conditions by type and name for some unstructured meshes/solvers. + It stores a set of global indices for each boundary condition type and name to expedite computation + during the call to `calc_boundary_flux!`. The original dictionary form of the boundary conditions + set by the user in the elixir file is also stored for printing. + """ + mutable struct UnstructuredSortedBoundaryTypes{N, BCs <: NTuple{N, Any}} + boundary_condition_types::BCs # specific boundary condition type(s), e.g. BoundaryConditionDirichlet + boundary_indices::NTuple{N, Vector{Int}} # integer vectors containing global boundary indices + boundary_dictionary::Dict{Symbol, Any} # boundary conditions as set by the user in the elixir file + boundary_symbol_indices::Dict{Symbol, Vector{Int}} # integer vectors containing global boundary indices per boundary identifier + end -# constructor that "eats" the original boundary condition dictionary and sorts the information -# from the `UnstructuredBoundaryContainer2D` in cache.boundaries according to the boundary types -# and stores the associated global boundary indexing in NTuple -function UnstructuredSortedBoundaryTypes(boundary_conditions::Dict, cache) - # extract the unique boundary function routines from the dictionary - boundary_condition_types = Tuple(unique(collect(values(boundary_conditions)))) - n_boundary_types = length(boundary_condition_types) - boundary_indices = ntuple(_ -> [], n_boundary_types) + # constructor that "eats" the original boundary condition dictionary and sorts the information + # from the `UnstructuredBoundaryContainer2D` in cache.boundaries according to the boundary types + # and stores the associated global boundary indexing in NTuple + function UnstructuredSortedBoundaryTypes(boundary_conditions::Dict, cache) + # extract the unique boundary function routines from the dictionary + boundary_condition_types = Tuple(unique(collect(values(boundary_conditions)))) + n_boundary_types = length(boundary_condition_types) + boundary_indices = ntuple(_ -> [], n_boundary_types) - # Initialize `boundary_symbol_indices` as an empty dictionary, filled later in `initialize!` - boundary_symbol_indices = Dict{Symbol, Vector{Int}}() + # Initialize `boundary_symbol_indices` as an empty dictionary, filled later in `initialize!` + boundary_symbol_indices = Dict{Symbol, Vector{Int}}() - container = UnstructuredSortedBoundaryTypes{n_boundary_types, - typeof(boundary_condition_types)}(boundary_condition_types, - boundary_indices, - boundary_conditions, - boundary_symbol_indices) + container = UnstructuredSortedBoundaryTypes{ + n_boundary_types, + typeof(boundary_condition_types), + }( + boundary_condition_types, + boundary_indices, + boundary_conditions, + boundary_symbol_indices + ) - initialize!(container, cache) -end + initialize!(container, cache) + end -function initialize!(boundary_types_container::UnstructuredSortedBoundaryTypes{N}, - cache) where {N} - @unpack boundary_dictionary, boundary_condition_types = boundary_types_container + function initialize!( + boundary_types_container::UnstructuredSortedBoundaryTypes{N}, + cache + ) where {N} + @unpack boundary_dictionary, boundary_condition_types = boundary_types_container - unique_names = unique(cache.boundaries.name) + unique_names = unique(cache.boundaries.name) - if mpi_isparallel() - # Exchange of boundaries names - send_buffer = Vector{UInt8}(join(unique_names, "\0")) - push!(send_buffer, 0) - if mpi_isroot() - recv_buffer_length = MPI.Gather(length(send_buffer), mpi_root(), mpi_comm()) - recv_buffer = Vector{UInt8}(undef, sum(recv_buffer_length)) - MPI.Gatherv!(send_buffer, MPI.VBuffer(recv_buffer, recv_buffer_length), - mpi_root(), mpi_comm()) - all_names = unique(Symbol.(split(String(recv_buffer), "\0"; - keepempty = false))) - for key in keys(boundary_dictionary) - if !(key in all_names) - println(stderr, + if mpi_isparallel() + # Exchange of boundaries names + send_buffer = Vector{UInt8}(join(unique_names, "\0")) + push!(send_buffer, 0) + if mpi_isroot() + recv_buffer_length = MPI.Gather(length(send_buffer), mpi_root(), mpi_comm()) + recv_buffer = Vector{UInt8}(undef, sum(recv_buffer_length)) + MPI.Gatherv!( + send_buffer, MPI.VBuffer(recv_buffer, recv_buffer_length), + mpi_root(), mpi_comm() + ) + all_names = unique( + Symbol.( + split( + String(recv_buffer), "\0"; + keepempty = false + ) + ) + ) + for key in keys(boundary_dictionary) + if !(key in all_names) + println( + stderr, "ERROR: Key $(repr(key)) is not a valid boundary name. " * - "Valid names are $all_names.") - MPI.Abort(mpi_comm(), 1) + "Valid names are $all_names." + ) + MPI.Abort(mpi_comm(), 1) + end end + else + MPI.Gather(length(send_buffer), mpi_root(), mpi_comm()) + MPI.Gatherv!(send_buffer, nothing, mpi_root(), mpi_comm()) end else - MPI.Gather(length(send_buffer), mpi_root(), mpi_comm()) - MPI.Gatherv!(send_buffer, nothing, mpi_root(), mpi_comm()) - end - else - for key in keys(boundary_dictionary) - if !(key in unique_names) - error("Key $(repr(key)) is not a valid boundary name. " * - "Valid names are $unique_names.") + for key in keys(boundary_dictionary) + if !(key in unique_names) + error( + "Key $(repr(key)) is not a valid boundary name. " * + "Valid names are $unique_names." + ) + end end end - end - # Verify that each boundary has a boundary condition - for name in unique_names - if name !== Symbol("---") && !haskey(boundary_dictionary, name) - error("No boundary condition specified for boundary $(repr(name))") + # Verify that each boundary has a boundary condition + for name in unique_names + if name !== Symbol("---") && !haskey(boundary_dictionary, name) + error("No boundary condition specified for boundary $(repr(name))") + end end - end - # pull and sort the indexing for each boundary type - _boundary_indices = Vector{Any}(nothing, N) - for j in 1:N - indices_for_current_type = Int[] - for (test_name, test_condition) in boundary_dictionary - temp_indices = findall(x -> x === test_name, cache.boundaries.name) - if test_condition === boundary_condition_types[j] - indices_for_current_type = vcat(indices_for_current_type, temp_indices) + # pull and sort the indexing for each boundary type + _boundary_indices = Vector{Any}(nothing, N) + for j in 1:N + indices_for_current_type = Int[] + for (test_name, test_condition) in boundary_dictionary + temp_indices = findall(x -> x === test_name, cache.boundaries.name) + if test_condition === boundary_condition_types[j] + indices_for_current_type = vcat(indices_for_current_type, temp_indices) + end end + _boundary_indices[j] = sort!(indices_for_current_type) end - _boundary_indices[j] = sort!(indices_for_current_type) - end - # convert the work array with the boundary indices into a tuple - boundary_types_container.boundary_indices = Tuple(_boundary_indices) + # convert the work array with the boundary indices into a tuple + boundary_types_container.boundary_indices = Tuple(_boundary_indices) - # Store boundary indices per symbol (required for force computations, for instance) - for (symbol, _) in boundary_dictionary - indices = findall(x -> x === symbol, cache.boundaries.name) - # Store the indices in `boundary_symbol_indices` dictionary - boundary_types_container.boundary_symbol_indices[symbol] = sort!(indices) - end + # Store boundary indices per symbol (required for force computations, for instance) + for (symbol, _) in boundary_dictionary + indices = findall(x -> x === symbol, cache.boundaries.name) + # Store the indices in `boundary_symbol_indices` dictionary + boundary_types_container.boundary_symbol_indices[symbol] = sort!(indices) + end - return boundary_types_container -end + return boundary_types_container + end end # @muladd diff --git a/src/solvers/fdsbp_tree/fdsbp.jl b/src/solvers/fdsbp_tree/fdsbp.jl index 11b09c6df9..d87269ff34 100644 --- a/src/solvers/fdsbp_tree/fdsbp.jl +++ b/src/solvers/fdsbp_tree/fdsbp.jl @@ -6,67 +6,71 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -""" - FDSBP(D_SBP; surface_integral, volume_integral) + """ + FDSBP(D_SBP; surface_integral, volume_integral) -Specialization of [`DG`](@ref) methods that uses general summation by parts (SBP) -operators from -[SummationByPartsOperators.jl](https://github.com/ranocha/SummationByPartsOperators.jl). -In particular, this includes classical finite difference (FD) SBP methods. -These methods have the same structure as classical DG methods - local operations -on elements with connectivity through interfaces without imposing any continuity -constraints. + Specialization of [`DG`](@ref) methods that uses general summation by parts (SBP) + operators from + [SummationByPartsOperators.jl](https://github.com/ranocha/SummationByPartsOperators.jl). + In particular, this includes classical finite difference (FD) SBP methods. + These methods have the same structure as classical DG methods - local operations + on elements with connectivity through interfaces without imposing any continuity + constraints. -`D_SBP` is an SBP derivative operator from SummationByPartsOperators.jl. -The other arguments have the same meaning as in [`DG`](@ref) or [`DGSEM`](@ref). + `D_SBP` is an SBP derivative operator from SummationByPartsOperators.jl. + The other arguments have the same meaning as in [`DG`](@ref) or [`DGSEM`](@ref). -!!! warning "Experimental implementation (upwind SBP)" - This is an experimental feature and may change in future releases. -""" -const FDSBP = DG{Basis} where {Basis <: AbstractDerivativeOperator} + !!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. + """ + const FDSBP = DG{Basis} where {Basis <: AbstractDerivativeOperator} -# Internal abbreviation for easier-to-read dispatch (not exported) -const PeriodicFDSBP = FDSBP{Basis} where {Basis <: AbstractPeriodicDerivativeOperator} + # Internal abbreviation for easier-to-read dispatch (not exported) + const PeriodicFDSBP = FDSBP{Basis} where {Basis <: AbstractPeriodicDerivativeOperator} -function FDSBP(D_SBP::AbstractDerivativeOperator; surface_integral, volume_integral) - # `nothing` is passed as `mortar` - return DG(D_SBP, nothing, surface_integral, volume_integral) -end + function FDSBP(D_SBP::AbstractDerivativeOperator; surface_integral, volume_integral) + # `nothing` is passed as `mortar` + return DG(D_SBP, nothing, surface_integral, volume_integral) + end -# General interface methods for SummationByPartsOperators.jl and Trixi.jl -nnodes(D::AbstractDerivativeOperator) = size(D, 1) -eachnode(D::AbstractDerivativeOperator) = Base.OneTo(nnodes(D)) -get_nodes(D::AbstractDerivativeOperator) = grid(D) + # General interface methods for SummationByPartsOperators.jl and Trixi.jl + nnodes(D::AbstractDerivativeOperator) = size(D, 1) + eachnode(D::AbstractDerivativeOperator) = Base.OneTo(nnodes(D)) + get_nodes(D::AbstractDerivativeOperator) = grid(D) -# TODO: This is hack to enable the FDSBP solver to use the -# `SaveSolutionCallback`. -polydeg(D::AbstractDerivativeOperator) = size(D, 1) - 1 -polydeg(fdsbp::FDSBP) = polydeg(fdsbp.basis) + # TODO: This is hack to enable the FDSBP solver to use the + # `SaveSolutionCallback`. + polydeg(D::AbstractDerivativeOperator) = size(D, 1) - 1 + polydeg(fdsbp::FDSBP) = polydeg(fdsbp.basis) -# TODO: FD. No mortars supported at the moment -init_mortars(cell_ids, mesh, elements, mortar::Nothing) = nothing -create_cache(mesh, equations, mortar::Nothing, uEltype) = NamedTuple() -nmortars(mortar::Nothing) = 0 + # TODO: FD. No mortars supported at the moment + init_mortars(cell_ids, mesh, elements, mortar::Nothing) = nothing + create_cache(mesh, equations, mortar::Nothing, uEltype) = NamedTuple() + nmortars(mortar::Nothing) = 0 -function prolong2mortars!(cache, u, mesh, equations, mortar::Nothing, - surface_integral, dg::DG) - @assert isempty(eachmortar(dg, cache)) -end + function prolong2mortars!( + cache, u, mesh, equations, mortar::Nothing, + surface_integral, dg::DG + ) + @assert isempty(eachmortar(dg, cache)) + end -function calc_mortar_flux!(surface_flux_values, mesh, - nonconservative_terms, equations, - mortar::Nothing, - surface_integral, dg::DG, cache) - @assert isempty(eachmortar(dg, cache)) -end + function calc_mortar_flux!( + surface_flux_values, mesh, + nonconservative_terms, equations, + mortar::Nothing, + surface_integral, dg::DG, cache + ) + @assert isempty(eachmortar(dg, cache)) + end -# We do not use a specialized setup to analyze solutions -SolutionAnalyzer(D::AbstractDerivativeOperator) = D + # We do not use a specialized setup to analyze solutions + SolutionAnalyzer(D::AbstractDerivativeOperator) = D -# dimension-specific implementations -include("fdsbp_1d.jl") -include("fdsbp_2d.jl") -include("fdsbp_3d.jl") + # dimension-specific implementations + include("fdsbp_1d.jl") + include("fdsbp_2d.jl") + include("fdsbp_3d.jl") end # @muladd diff --git a/src/solvers/fdsbp_tree/fdsbp_1d.jl b/src/solvers/fdsbp_tree/fdsbp_1d.jl index 0de0cff485..7402ba5850 100644 --- a/src/solvers/fdsbp_tree/fdsbp_1d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_1d.jl @@ -6,321 +6,389 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# 1D caches -function create_cache(mesh::TreeMesh{1}, equations, - volume_integral::VolumeIntegralStrongForm, dg, uEltype) - prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}(undef, - ntuple(_ -> nnodes(dg), - ndims(mesh))...) - f_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] - - return (; f_threaded) -end - -function create_cache(mesh::TreeMesh{1}, equations, - volume_integral::VolumeIntegralUpwind, dg, uEltype) - u_node = SVector{nvariables(equations), uEltype}(ntuple(_ -> zero(uEltype), - Val{nvariables(equations)}())) - f = StructArray([(u_node, u_node)]) - f_minus_plus_threaded = [similar(f, ntuple(_ -> nnodes(dg), ndims(mesh))...) - for _ in 1:Threads.nthreads()] - - f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[1]) - f_minus_threaded = [f_minus] - f_plus_threaded = [f_plus] - for i in 2:Threads.nthreads() - f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[i]) - push!(f_minus_threaded, f_minus) - push!(f_plus_threaded, f_plus) + #! format: noindent + + # 1D caches + function create_cache( + mesh::TreeMesh{1}, equations, + volume_integral::VolumeIntegralStrongForm, dg, uEltype + ) + prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( + undef, + ntuple( + _ -> nnodes(dg), + ndims(mesh) + )... + ) + f_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + + return (; f_threaded) end - return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded) -end - -# 2D volume integral contributions for `VolumeIntegralStrongForm` -function calc_volume_integral!(du, u, - mesh::TreeMesh{1}, - nonconservative_terms::False, equations, - volume_integral::VolumeIntegralStrongForm, - dg::FDSBP, cache) - D = dg.basis # SBP derivative operator - @unpack f_threaded = cache - - # SBP operators from SummationByPartsOperators.jl implement the basic interface - # of matrix-vector multiplication. Thus, we pass an "array of structures", - # packing all variables per node in an `SVector`. - if nvariables(equations) == 1 - # `reinterpret(reshape, ...)` removes the leading dimension only if more - # than one variable is used. - u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u), - nnodes(dg), nelements(dg, cache)) - du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)}, - du), - nnodes(dg), nelements(dg, cache)) - else - u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) - du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)}, - du) + function create_cache( + mesh::TreeMesh{1}, equations, + volume_integral::VolumeIntegralUpwind, dg, uEltype + ) + u_node = SVector{nvariables(equations), uEltype}( + ntuple( + _ -> zero(uEltype), + Val{nvariables(equations)}() + ) + ) + f = StructArray([(u_node, u_node)]) + f_minus_plus_threaded = [ + similar(f, ntuple(_ -> nnodes(dg), ndims(mesh))...) + for _ in 1:Threads.nthreads() + ] + + f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[1]) + f_minus_threaded = [f_minus] + f_plus_threaded = [f_plus] + for i in 2:Threads.nthreads() + f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[i]) + push!(f_minus_threaded, f_minus) + push!(f_plus_threaded, f_plus) + end + + return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded) end - # Use the tensor product structure to compute the discrete derivatives of - # the fluxes line-by-line and add them to `du` for each element. - @threaded for element in eachelement(dg, cache) - f_element = f_threaded[Threads.threadid()] - u_element = view(u_vectors, :, element) + # 2D volume integral contributions for `VolumeIntegralStrongForm` + function calc_volume_integral!( + du, u, + mesh::TreeMesh{1}, + nonconservative_terms::False, equations, + volume_integral::VolumeIntegralStrongForm, + dg::FDSBP, cache + ) + D = dg.basis # SBP derivative operator + @unpack f_threaded = cache + + # SBP operators from SummationByPartsOperators.jl implement the basic interface + # of matrix-vector multiplication. Thus, we pass an "array of structures", + # packing all variables per node in an `SVector`. + if nvariables(equations) == 1 + # `reinterpret(reshape, ...)` removes the leading dimension only if more + # than one variable is used. + u_vectors = reshape( + reinterpret(SVector{nvariables(equations), eltype(u)}, u), + nnodes(dg), nelements(dg, cache) + ) + du_vectors = reshape( + reinterpret( + SVector{nvariables(equations), eltype(du)}, + du + ), + nnodes(dg), nelements(dg, cache) + ) + else + u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) + du_vectors = reinterpret( + reshape, SVector{nvariables(equations), eltype(du)}, + du + ) + end - # x direction - @. f_element = flux(u_element, 1, equations) - mul!(view(du_vectors, :, element), D, view(f_element, :), - one(eltype(du)), one(eltype(du))) - end + # Use the tensor product structure to compute the discrete derivatives of + # the fluxes line-by-line and add them to `du` for each element. + @threaded for element in eachelement(dg, cache) + f_element = f_threaded[Threads.threadid()] + u_element = view(u_vectors, :, element) + + # x direction + @. f_element = flux(u_element, 1, equations) + mul!( + view(du_vectors, :, element), D, view(f_element, :), + one(eltype(du)), one(eltype(du)) + ) + end - return nothing -end - -# 1D volume integral contributions for `VolumeIntegralUpwind`. -# Note that the plus / minus notation of the operators does not refer to the -# upwind / downwind directions of the fluxes. -# Instead, the plus / minus refers to the direction of the biasing within -# the finite difference stencils. Thus, the D^- operator acts on the positive -# part of the flux splitting f^+ and the D^+ operator acts on the negative part -# of the flux splitting f^-. -function calc_volume_integral!(du, u, - mesh::TreeMesh{1}, - nonconservative_terms::False, equations, - volume_integral::VolumeIntegralUpwind, - dg::FDSBP, cache) - # Assume that - # dg.basis isa SummationByPartsOperators.UpwindOperators - D_minus = dg.basis.minus # Upwind SBP D^- derivative operator - D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator - @unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache - @unpack splitting = volume_integral - - # SBP operators from SummationByPartsOperators.jl implement the basic interface - # of matrix-vector multiplication. Thus, we pass an "array of structures", - # packing all variables per node in an `SVector`. - if nvariables(equations) == 1 - # `reinterpret(reshape, ...)` removes the leading dimension only if more - # than one variable is used. - u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u), - nnodes(dg), nelements(dg, cache)) - du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)}, - du), - nnodes(dg), nelements(dg, cache)) - else - u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) - du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)}, - du) + return nothing end - # Use the tensor product structure to compute the discrete derivatives of - # the fluxes line-by-line and add them to `du` for each element. - @threaded for element in eachelement(dg, cache) - # f_minus_plus_element wraps the storage provided by f_minus_element and - # f_plus_element such that we can use a single plain broadcasting below. - # f_minus_element and f_plus_element are updated in broadcasting calls - # of the form `@. f_minus_plus_element = ...`. - f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()] - f_minus_element = f_minus_threaded[Threads.threadid()] - f_plus_element = f_plus_threaded[Threads.threadid()] - u_element = view(u_vectors, :, element) - - # x direction - @. f_minus_plus_element = splitting(u_element, 1, equations) - mul!(view(du_vectors, :, element), D_plus, view(f_minus_element, :), - one(eltype(du)), one(eltype(du))) - mul!(view(du_vectors, :, element), D_minus, view(f_plus_element, :), - one(eltype(du)), one(eltype(du))) - end + # 1D volume integral contributions for `VolumeIntegralUpwind`. + # Note that the plus / minus notation of the operators does not refer to the + # upwind / downwind directions of the fluxes. + # Instead, the plus / minus refers to the direction of the biasing within + # the finite difference stencils. Thus, the D^- operator acts on the positive + # part of the flux splitting f^+ and the D^+ operator acts on the negative part + # of the flux splitting f^-. + function calc_volume_integral!( + du, u, + mesh::TreeMesh{1}, + nonconservative_terms::False, equations, + volume_integral::VolumeIntegralUpwind, + dg::FDSBP, cache + ) + # Assume that + # dg.basis isa SummationByPartsOperators.UpwindOperators + D_minus = dg.basis.minus # Upwind SBP D^- derivative operator + D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator + @unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache + @unpack splitting = volume_integral + + # SBP operators from SummationByPartsOperators.jl implement the basic interface + # of matrix-vector multiplication. Thus, we pass an "array of structures", + # packing all variables per node in an `SVector`. + if nvariables(equations) == 1 + # `reinterpret(reshape, ...)` removes the leading dimension only if more + # than one variable is used. + u_vectors = reshape( + reinterpret(SVector{nvariables(equations), eltype(u)}, u), + nnodes(dg), nelements(dg, cache) + ) + du_vectors = reshape( + reinterpret( + SVector{nvariables(equations), eltype(du)}, + du + ), + nnodes(dg), nelements(dg, cache) + ) + else + u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) + du_vectors = reinterpret( + reshape, SVector{nvariables(equations), eltype(du)}, + du + ) + end + + # Use the tensor product structure to compute the discrete derivatives of + # the fluxes line-by-line and add them to `du` for each element. + @threaded for element in eachelement(dg, cache) + # f_minus_plus_element wraps the storage provided by f_minus_element and + # f_plus_element such that we can use a single plain broadcasting below. + # f_minus_element and f_plus_element are updated in broadcasting calls + # of the form `@. f_minus_plus_element = ...`. + f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()] + f_minus_element = f_minus_threaded[Threads.threadid()] + f_plus_element = f_plus_threaded[Threads.threadid()] + u_element = view(u_vectors, :, element) + + # x direction + @. f_minus_plus_element = splitting(u_element, 1, equations) + mul!( + view(du_vectors, :, element), D_plus, view(f_minus_element, :), + one(eltype(du)), one(eltype(du)) + ) + mul!( + view(du_vectors, :, element), D_minus, view(f_plus_element, :), + one(eltype(du)), one(eltype(du)) + ) + end - return nothing -end - -function calc_surface_integral!(du, u, mesh::TreeMesh{1}, - equations, surface_integral::SurfaceIntegralStrongForm, - dg::DG, cache) - inv_weight_left = inv(left_boundary_weight(dg.basis)) - inv_weight_right = inv(right_boundary_weight(dg.basis)) - @unpack surface_flux_values = cache.elements - - @threaded for element in eachelement(dg, cache) - # surface at -x - u_node = get_node_vars(u, equations, dg, 1, element) - f_node = flux(u_node, 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, 1, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, 1, element) - - # surface at +x - u_node = get_node_vars(u, equations, dg, nnodes(dg), element) - f_node = flux(u_node, 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, 2, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, nnodes(dg), element) + return nothing end - return nothing -end - -# Periodic FDSBP operators need to use a single element without boundaries -function calc_surface_integral!(du, u, mesh::TreeMesh1D, - equations, surface_integral::SurfaceIntegralStrongForm, - dg::PeriodicFDSBP, cache) - @assert nelements(dg, cache) == 1 - return nothing -end - -# Specialized interface flux computation because the upwind solver does -# not require a standard numerical flux (Riemann solver). The flux splitting -# already separates the solution information into right-traveling and -# left-traveling information. So we only need to compute the appropriate -# flux information at each side of an interface. -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{1}, - nonconservative_terms::False, equations, - surface_integral::SurfaceIntegralUpwind, - dg::FDSBP, cache) - @unpack splitting = surface_integral - @unpack u, neighbor_ids, orientations = cache.interfaces - - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] - - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 - - # Pull the left and right solution data - u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface) - - # Compute the upwind coupling terms where right-traveling - # information comes from the left and left-traveling information - # comes from the right - flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface], - equations) - flux_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], equations) - - # Save the upwind coupling into the appropriate side of the elements - for v in eachvariable(equations) - surface_flux_values[v, left_direction, left_id] = flux_minus_rr[v] - surface_flux_values[v, right_direction, right_id] = flux_plus_ll[v] + function calc_surface_integral!( + du, u, mesh::TreeMesh{1}, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::DG, cache + ) + inv_weight_left = inv(left_boundary_weight(dg.basis)) + inv_weight_right = inv(right_boundary_weight(dg.basis)) + @unpack surface_flux_values = cache.elements + + @threaded for element in eachelement(dg, cache) + # surface at -x + u_node = get_node_vars(u, equations, dg, 1, element) + f_node = flux(u_node, 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, 1, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, 1, element + ) + + # surface at +x + u_node = get_node_vars(u, equations, dg, nnodes(dg), element) + f_node = flux(u_node, 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, 2, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, nnodes(dg), element + ) end - end - return nothing -end - -# Implementation of fully upwind SATs. The surface flux values are pre-computed -# in the specialized `calc_interface_flux` routine. These SATs are still of -# a strong form penalty type, except that the interior flux at a particular -# side of the element are computed in the upwind direction. -function calc_surface_integral!(du, u, mesh::TreeMesh{1}, - equations, surface_integral::SurfaceIntegralUpwind, - dg::FDSBP, cache) - inv_weight_left = inv(left_boundary_weight(dg.basis)) - inv_weight_right = inv(right_boundary_weight(dg.basis)) - @unpack surface_flux_values = cache.elements - @unpack splitting = surface_integral - - @threaded for element in eachelement(dg, cache) - # surface at -x - u_node = get_node_vars(u, equations, dg, 1, element) - f_node = splitting(u_node, Val{:plus}(), 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, 1, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, 1, element) - - # surface at +x - u_node = get_node_vars(u, equations, dg, nnodes(dg), element) - f_node = splitting(u_node, Val{:minus}(), 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, 2, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, nnodes(dg), element) + return nothing end - return nothing -end - -# Periodic FDSBP operators need to use a single element without boundaries -function calc_surface_integral!(du, u, mesh::TreeMesh1D, - equations, surface_integral::SurfaceIntegralUpwind, - dg::PeriodicFDSBP, cache) - @assert nelements(dg, cache) == 1 - return nothing -end - -# AnalysisCallback - -function integrate_via_indices(func::Func, u, - mesh::TreeMesh{1}, equations, - dg::FDSBP, cache, args...; normalize = true) where {Func} - # TODO: FD. This is rather inefficient right now and allocates... - M = SummationByPartsOperators.mass_matrix(dg.basis) - if M isa UniformScaling - M = M(nnodes(dg)) + # Periodic FDSBP operators need to use a single element without boundaries + function calc_surface_integral!( + du, u, mesh::TreeMesh1D, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::PeriodicFDSBP, cache + ) + @assert nelements(dg, cache) == 1 + return nothing end - weights = diag(M) - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, equations, dg, args...)) + # Specialized interface flux computation because the upwind solver does + # not require a standard numerical flux (Riemann solver). The flux splitting + # already separates the solution information into right-traveling and + # left-traveling information. So we only need to compute the appropriate + # flux information at each side of an interface. + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{1}, + nonconservative_terms::False, equations, + surface_integral::SurfaceIntegralUpwind, + dg::FDSBP, cache + ) + @unpack splitting = surface_integral + @unpack u, neighbor_ids, orientations = cache.interfaces + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 + + # Pull the left and right solution data + u_ll, u_rr = get_surface_node_vars(u, equations, dg, interface) + + # Compute the upwind coupling terms where right-traveling + # information comes from the left and left-traveling information + # comes from the right + flux_minus_rr = splitting( + u_rr, Val{:minus}(), orientations[interface], + equations + ) + flux_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], equations) + + # Save the upwind coupling into the appropriate side of the elements + for v in eachvariable(equations) + surface_flux_values[v, left_direction, left_id] = flux_minus_rr[v] + surface_flux_values[v, right_direction, right_id] = flux_plus_ll[v] + end + end + + return nothing + end - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - volume_jacobian_ = volume_jacobian(element, mesh, cache) - for i in eachnode(dg) - integral += volume_jacobian_ * weights[i] * - func(u, i, element, equations, dg, args...) + # Implementation of fully upwind SATs. The surface flux values are pre-computed + # in the specialized `calc_interface_flux` routine. These SATs are still of + # a strong form penalty type, except that the interior flux at a particular + # side of the element are computed in the upwind direction. + function calc_surface_integral!( + du, u, mesh::TreeMesh{1}, + equations, surface_integral::SurfaceIntegralUpwind, + dg::FDSBP, cache + ) + inv_weight_left = inv(left_boundary_weight(dg.basis)) + inv_weight_right = inv(right_boundary_weight(dg.basis)) + @unpack surface_flux_values = cache.elements + @unpack splitting = surface_integral + + @threaded for element in eachelement(dg, cache) + # surface at -x + u_node = get_node_vars(u, equations, dg, 1, element) + f_node = splitting(u_node, Val{:plus}(), 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, 1, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, 1, element + ) + + # surface at +x + u_node = get_node_vars(u, equations, dg, nnodes(dg), element) + f_node = splitting(u_node, Val{:minus}(), 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, 2, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, nnodes(dg), element + ) end + + return nothing end - # Normalize with total volume - if normalize - integral = integral / total_volume(mesh) + # Periodic FDSBP operators need to use a single element without boundaries + function calc_surface_integral!( + du, u, mesh::TreeMesh1D, + equations, surface_integral::SurfaceIntegralUpwind, + dg::PeriodicFDSBP, cache + ) + @assert nelements(dg, cache) == 1 + return nothing end - return integral -end + # AnalysisCallback + + function integrate_via_indices( + func::Func, u, + mesh::TreeMesh{1}, equations, + dg::FDSBP, cache, args...; normalize = true + ) where {Func} + # TODO: FD. This is rather inefficient right now and allocates... + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) + + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, equations, dg, args...)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + volume_jacobian_ = volume_jacobian(element, mesh, cache) + for i in eachnode(dg) + integral += volume_jacobian_ * weights[i] * + func(u, i, element, equations, dg, args...) + end + end -function calc_error_norms(func, u, t, analyzer, - mesh::TreeMesh{1}, equations, initial_condition, - dg::FDSBP, cache, cache_analysis) - # TODO: FD. This is rather inefficient right now and allocates... - M = SummationByPartsOperators.mass_matrix(dg.basis) - if M isa UniformScaling - M = M(nnodes(dg)) - end - weights = diag(M) - @unpack node_coordinates = cache.elements - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1), equations)) - linf_error = copy(l2_error) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Calculate errors at each node - volume_jacobian_ = volume_jacobian(element, mesh, cache) - - for i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(node_coordinates, equations, dg, - i, element), t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u, equations, dg, i, element), equations) - l2_error += diff .^ 2 * (weights[i] * volume_jacobian_) - linf_error = @. max(linf_error, abs(diff)) + # Normalize with total volume + if normalize + integral = integral / total_volume(mesh) end + + return integral end - # For L2 error, divide by total volume - total_volume_ = total_volume(mesh) - l2_error = @. sqrt(l2_error / total_volume_) + function calc_error_norms( + func, u, t, analyzer, + mesh::TreeMesh{1}, equations, initial_condition, + dg::FDSBP, cache, cache_analysis + ) + # TODO: FD. This is rather inefficient right now and allocates... + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) + @unpack node_coordinates = cache.elements + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1), equations)) + linf_error = copy(l2_error) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Calculate errors at each node + volume_jacobian_ = volume_jacobian(element, mesh, cache) + + for i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords( + node_coordinates, equations, dg, + i, element + ), t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u, equations, dg, i, element), equations) + l2_error += diff .^ 2 * (weights[i] * volume_jacobian_) + linf_error = @. max(linf_error, abs(diff)) + end + end - return l2_error, linf_error -end + # For L2 error, divide by total volume + total_volume_ = total_volume(mesh) + l2_error = @. sqrt(l2_error / total_volume_) + + return l2_error, linf_error + end end # @muladd diff --git a/src/solvers/fdsbp_tree/fdsbp_2d.jl b/src/solvers/fdsbp_tree/fdsbp_2d.jl index 36afbbc022..8980bf353d 100644 --- a/src/solvers/fdsbp_tree/fdsbp_2d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_2d.jl @@ -6,376 +6,460 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# 2D caches -function create_cache(mesh::Union{TreeMesh{2}, UnstructuredMesh2D}, equations, - volume_integral::VolumeIntegralStrongForm, dg, uEltype) - prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}(undef, - ntuple(_ -> nnodes(dg), - ndims(mesh))...) - f_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] - - return (; f_threaded) -end - -function create_cache(mesh::Union{TreeMesh{2}, UnstructuredMesh2D}, equations, - volume_integral::VolumeIntegralUpwind, dg, uEltype) - u_node = SVector{nvariables(equations), uEltype}(ntuple(_ -> zero(uEltype), - Val{nvariables(equations)}())) - f = StructArray([(u_node, u_node)]) - f_minus_plus_threaded = [similar(f, ntuple(_ -> nnodes(dg), ndims(mesh))...) - for _ in 1:Threads.nthreads()] - - f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[1]) - f_minus_threaded = [f_minus] - f_plus_threaded = [f_plus] - for i in 2:Threads.nthreads() - f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[i]) - push!(f_minus_threaded, f_minus) - push!(f_plus_threaded, f_plus) + #! format: noindent + + # 2D caches + function create_cache( + mesh::Union{TreeMesh{2}, UnstructuredMesh2D}, equations, + volume_integral::VolumeIntegralStrongForm, dg, uEltype + ) + prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( + undef, + ntuple( + _ -> nnodes(dg), + ndims(mesh) + )... + ) + f_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + + return (; f_threaded) end - return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded) -end - -# 2D volume integral contributions for `VolumeIntegralStrongForm` -function calc_volume_integral!(du, u, - mesh::TreeMesh{2}, - nonconservative_terms::False, equations, - volume_integral::VolumeIntegralStrongForm, - dg::FDSBP, cache) - D = dg.basis # SBP derivative operator - @unpack f_threaded = cache - - # SBP operators from SummationByPartsOperators.jl implement the basic interface - # of matrix-vector multiplication. Thus, we pass an "array of structures", - # packing all variables per node in an `SVector`. - if nvariables(equations) == 1 - # `reinterpret(reshape, ...)` removes the leading dimension only if more - # than one variable is used. - u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u), - nnodes(dg), nnodes(dg), nelements(dg, cache)) - du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)}, - du), - nnodes(dg), nnodes(dg), nelements(dg, cache)) - else - u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) - du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)}, - du) + function create_cache( + mesh::Union{TreeMesh{2}, UnstructuredMesh2D}, equations, + volume_integral::VolumeIntegralUpwind, dg, uEltype + ) + u_node = SVector{nvariables(equations), uEltype}( + ntuple( + _ -> zero(uEltype), + Val{nvariables(equations)}() + ) + ) + f = StructArray([(u_node, u_node)]) + f_minus_plus_threaded = [ + similar(f, ntuple(_ -> nnodes(dg), ndims(mesh))...) + for _ in 1:Threads.nthreads() + ] + + f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[1]) + f_minus_threaded = [f_minus] + f_plus_threaded = [f_plus] + for i in 2:Threads.nthreads() + f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[i]) + push!(f_minus_threaded, f_minus) + push!(f_plus_threaded, f_plus) + end + + return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded) end - # Use the tensor product structure to compute the discrete derivatives of - # the fluxes line-by-line and add them to `du` for each element. - @threaded for element in eachelement(dg, cache) - f_element = f_threaded[Threads.threadid()] - u_element = view(u_vectors, :, :, element) - - # x direction - @. f_element = flux(u_element, 1, equations) - for j in eachnode(dg) - mul!(view(du_vectors, :, j, element), D, view(f_element, :, j), - one(eltype(du)), one(eltype(du))) + # 2D volume integral contributions for `VolumeIntegralStrongForm` + function calc_volume_integral!( + du, u, + mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + volume_integral::VolumeIntegralStrongForm, + dg::FDSBP, cache + ) + D = dg.basis # SBP derivative operator + @unpack f_threaded = cache + + # SBP operators from SummationByPartsOperators.jl implement the basic interface + # of matrix-vector multiplication. Thus, we pass an "array of structures", + # packing all variables per node in an `SVector`. + if nvariables(equations) == 1 + # `reinterpret(reshape, ...)` removes the leading dimension only if more + # than one variable is used. + u_vectors = reshape( + reinterpret(SVector{nvariables(equations), eltype(u)}, u), + nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + du_vectors = reshape( + reinterpret( + SVector{nvariables(equations), eltype(du)}, + du + ), + nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + else + u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) + du_vectors = reinterpret( + reshape, SVector{nvariables(equations), eltype(du)}, + du + ) end - # y direction - @. f_element = flux(u_element, 2, equations) - for i in eachnode(dg) - mul!(view(du_vectors, i, :, element), D, view(f_element, i, :), - one(eltype(du)), one(eltype(du))) + # Use the tensor product structure to compute the discrete derivatives of + # the fluxes line-by-line and add them to `du` for each element. + @threaded for element in eachelement(dg, cache) + f_element = f_threaded[Threads.threadid()] + u_element = view(u_vectors, :, :, element) + + # x direction + @. f_element = flux(u_element, 1, equations) + for j in eachnode(dg) + mul!( + view(du_vectors, :, j, element), D, view(f_element, :, j), + one(eltype(du)), one(eltype(du)) + ) + end + + # y direction + @. f_element = flux(u_element, 2, equations) + for i in eachnode(dg) + mul!( + view(du_vectors, i, :, element), D, view(f_element, i, :), + one(eltype(du)), one(eltype(du)) + ) + end end - end - return nothing -end - -# 2D volume integral contributions for `VolumeIntegralUpwind`. -# Note that the plus / minus notation of the operators does not refer to the -# upwind / downwind directions of the fluxes. -# Instead, the plus / minus refers to the direction of the biasing within -# the finite difference stencils. Thus, the D^- operator acts on the positive -# part of the flux splitting f^+ and the D^+ operator acts on the negative part -# of the flux splitting f^-. -function calc_volume_integral!(du, u, - mesh::TreeMesh{2}, - nonconservative_terms::False, equations, - volume_integral::VolumeIntegralUpwind, - dg::FDSBP, cache) - # Assume that - # dg.basis isa SummationByPartsOperators.UpwindOperators - D_minus = dg.basis.minus # Upwind SBP D^- derivative operator - D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator - @unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache - @unpack splitting = volume_integral - - # SBP operators from SummationByPartsOperators.jl implement the basic interface - # of matrix-vector multiplication. Thus, we pass an "array of structures", - # packing all variables per node in an `SVector`. - if nvariables(equations) == 1 - # `reinterpret(reshape, ...)` removes the leading dimension only if more - # than one variable is used. - u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u), - nnodes(dg), nnodes(dg), nelements(dg, cache)) - du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)}, - du), - nnodes(dg), nnodes(dg), nelements(dg, cache)) - else - u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) - du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)}, - du) + return nothing end - # Use the tensor product structure to compute the discrete derivatives of - # the fluxes line-by-line and add them to `du` for each element. - @threaded for element in eachelement(dg, cache) - # f_minus_plus_element wraps the storage provided by f_minus_element and - # f_plus_element such that we can use a single plain broadcasting below. - # f_minus_element and f_plus_element are updated in broadcasting calls - # of the form `@. f_minus_plus_element = ...`. - f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()] - f_minus_element = f_minus_threaded[Threads.threadid()] - f_plus_element = f_plus_threaded[Threads.threadid()] - u_element = view(u_vectors, :, :, element) - - # x direction - @. f_minus_plus_element = splitting(u_element, 1, equations) - for j in eachnode(dg) - mul!(view(du_vectors, :, j, element), D_minus, view(f_plus_element, :, j), - one(eltype(du)), one(eltype(du))) - mul!(view(du_vectors, :, j, element), D_plus, view(f_minus_element, :, j), - one(eltype(du)), one(eltype(du))) + # 2D volume integral contributions for `VolumeIntegralUpwind`. + # Note that the plus / minus notation of the operators does not refer to the + # upwind / downwind directions of the fluxes. + # Instead, the plus / minus refers to the direction of the biasing within + # the finite difference stencils. Thus, the D^- operator acts on the positive + # part of the flux splitting f^+ and the D^+ operator acts on the negative part + # of the flux splitting f^-. + function calc_volume_integral!( + du, u, + mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + volume_integral::VolumeIntegralUpwind, + dg::FDSBP, cache + ) + # Assume that + # dg.basis isa SummationByPartsOperators.UpwindOperators + D_minus = dg.basis.minus # Upwind SBP D^- derivative operator + D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator + @unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache + @unpack splitting = volume_integral + + # SBP operators from SummationByPartsOperators.jl implement the basic interface + # of matrix-vector multiplication. Thus, we pass an "array of structures", + # packing all variables per node in an `SVector`. + if nvariables(equations) == 1 + # `reinterpret(reshape, ...)` removes the leading dimension only if more + # than one variable is used. + u_vectors = reshape( + reinterpret(SVector{nvariables(equations), eltype(u)}, u), + nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + du_vectors = reshape( + reinterpret( + SVector{nvariables(equations), eltype(du)}, + du + ), + nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + else + u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) + du_vectors = reinterpret( + reshape, SVector{nvariables(equations), eltype(du)}, + du + ) end - # y direction - @. f_minus_plus_element = splitting(u_element, 2, equations) - for i in eachnode(dg) - mul!(view(du_vectors, i, :, element), D_minus, view(f_plus_element, i, :), - one(eltype(du)), one(eltype(du))) - mul!(view(du_vectors, i, :, element), D_plus, view(f_minus_element, i, :), - one(eltype(du)), one(eltype(du))) + # Use the tensor product structure to compute the discrete derivatives of + # the fluxes line-by-line and add them to `du` for each element. + @threaded for element in eachelement(dg, cache) + # f_minus_plus_element wraps the storage provided by f_minus_element and + # f_plus_element such that we can use a single plain broadcasting below. + # f_minus_element and f_plus_element are updated in broadcasting calls + # of the form `@. f_minus_plus_element = ...`. + f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()] + f_minus_element = f_minus_threaded[Threads.threadid()] + f_plus_element = f_plus_threaded[Threads.threadid()] + u_element = view(u_vectors, :, :, element) + + # x direction + @. f_minus_plus_element = splitting(u_element, 1, equations) + for j in eachnode(dg) + mul!( + view(du_vectors, :, j, element), D_minus, view(f_plus_element, :, j), + one(eltype(du)), one(eltype(du)) + ) + mul!( + view(du_vectors, :, j, element), D_plus, view(f_minus_element, :, j), + one(eltype(du)), one(eltype(du)) + ) + end + + # y direction + @. f_minus_plus_element = splitting(u_element, 2, equations) + for i in eachnode(dg) + mul!( + view(du_vectors, i, :, element), D_minus, view(f_plus_element, i, :), + one(eltype(du)), one(eltype(du)) + ) + mul!( + view(du_vectors, i, :, element), D_plus, view(f_minus_element, i, :), + one(eltype(du)), one(eltype(du)) + ) + end end + + return nothing end - return nothing -end - -function calc_surface_integral!(du, u, mesh::TreeMesh{2}, - equations, surface_integral::SurfaceIntegralStrongForm, - dg::DG, cache) - inv_weight_left = inv(left_boundary_weight(dg.basis)) - inv_weight_right = inv(right_boundary_weight(dg.basis)) - @unpack surface_flux_values = cache.elements - - @threaded for element in eachelement(dg, cache) - for l in eachnode(dg) - # surface at -x - u_node = get_node_vars(u, equations, dg, 1, l, element) - f_node = flux(u_node, 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 1, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, 1, l, element) - - # surface at +x - u_node = get_node_vars(u, equations, dg, nnodes(dg), l, element) - f_node = flux(u_node, 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 2, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, nnodes(dg), l, element) - - # surface at -y - u_node = get_node_vars(u, equations, dg, l, 1, element) - f_node = flux(u_node, 2, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 3, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, l, 1, element) - - # surface at +y - u_node = get_node_vars(u, equations, dg, l, nnodes(dg), element) - f_node = flux(u_node, 2, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 4, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, l, nnodes(dg), element) + function calc_surface_integral!( + du, u, mesh::TreeMesh{2}, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::DG, cache + ) + inv_weight_left = inv(left_boundary_weight(dg.basis)) + inv_weight_right = inv(right_boundary_weight(dg.basis)) + @unpack surface_flux_values = cache.elements + + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg) + # surface at -x + u_node = get_node_vars(u, equations, dg, 1, l, element) + f_node = flux(u_node, 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 1, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, 1, l, element + ) + + # surface at +x + u_node = get_node_vars(u, equations, dg, nnodes(dg), l, element) + f_node = flux(u_node, 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 2, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, nnodes(dg), l, element + ) + + # surface at -y + u_node = get_node_vars(u, equations, dg, l, 1, element) + f_node = flux(u_node, 2, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 3, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, l, 1, element + ) + + # surface at +y + u_node = get_node_vars(u, equations, dg, l, nnodes(dg), element) + f_node = flux(u_node, 2, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 4, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, l, nnodes(dg), element + ) + end end + + return nothing end - return nothing -end - -# Periodic FDSBP operators need to use a single element without boundaries -function calc_surface_integral!(du, u, mesh::TreeMesh2D, - equations, surface_integral::SurfaceIntegralStrongForm, - dg::PeriodicFDSBP, cache) - @assert nelements(dg, cache) == 1 - return nothing -end - -# Specialized interface flux computation because the upwind solver does -# not require a standard numerical flux (Riemann solver). The flux splitting -# already separates the solution information into right-traveling and -# left-traveling information. So we only need to compute the appropriate -# flux information at each side of an interface. -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{2}, - nonconservative_terms::False, equations, - surface_integral::SurfaceIntegralUpwind, - dg::FDSBP, cache) - @unpack splitting = surface_integral - @unpack u, neighbor_ids, orientations = cache.interfaces - - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] - - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 - - for i in eachnode(dg) - # Pull the left and right solution data - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface) - - # Compute the upwind coupling terms where right-traveling - # information comes from the left and left-traveling information - # comes from the right - flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface], - equations) - flux_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], - equations) - - # Save the upwind coupling into the appropriate side of the elements - for v in eachvariable(equations) - surface_flux_values[v, i, left_direction, left_id] = flux_minus_rr[v] - surface_flux_values[v, i, right_direction, right_id] = flux_plus_ll[v] + # Periodic FDSBP operators need to use a single element without boundaries + function calc_surface_integral!( + du, u, mesh::TreeMesh2D, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::PeriodicFDSBP, cache + ) + @assert nelements(dg, cache) == 1 + return nothing + end + + # Specialized interface flux computation because the upwind solver does + # not require a standard numerical flux (Riemann solver). The flux splitting + # already separates the solution information into right-traveling and + # left-traveling information. So we only need to compute the appropriate + # flux information at each side of an interface. + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::False, equations, + surface_integral::SurfaceIntegralUpwind, + dg::FDSBP, cache + ) + @unpack splitting = surface_integral + @unpack u, neighbor_ids, orientations = cache.interfaces + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 + + for i in eachnode(dg) + # Pull the left and right solution data + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, interface) + + # Compute the upwind coupling terms where right-traveling + # information comes from the left and left-traveling information + # comes from the right + flux_minus_rr = splitting( + u_rr, Val{:minus}(), orientations[interface], + equations + ) + flux_plus_ll = splitting( + u_ll, Val{:plus}(), orientations[interface], + equations + ) + + # Save the upwind coupling into the appropriate side of the elements + for v in eachvariable(equations) + surface_flux_values[v, i, left_direction, left_id] = flux_minus_rr[v] + surface_flux_values[v, i, right_direction, right_id] = flux_plus_ll[v] + end end end + + return nothing end - return nothing -end - -# Implementation of fully upwind SATs. The surface flux values are pre-computed -# in the specialized `calc_interface_flux` routine. These SATs are still of -# a strong form penalty type, except that the interior flux at a particular -# side of the element are computed in the upwind direction. -function calc_surface_integral!(du, u, mesh::TreeMesh{2}, - equations, surface_integral::SurfaceIntegralUpwind, - dg::FDSBP, cache) - inv_weight_left = inv(left_boundary_weight(dg.basis)) - inv_weight_right = inv(right_boundary_weight(dg.basis)) - @unpack surface_flux_values = cache.elements - @unpack splitting = surface_integral - - @threaded for element in eachelement(dg, cache) - for l in eachnode(dg) - # surface at -x - u_node = get_node_vars(u, equations, dg, 1, l, element) - f_node = splitting(u_node, Val{:plus}(), 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 1, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, 1, l, element) - - # surface at +x - u_node = get_node_vars(u, equations, dg, nnodes(dg), l, element) - f_node = splitting(u_node, Val{:minus}(), 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 2, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, nnodes(dg), l, element) - - # surface at -y - u_node = get_node_vars(u, equations, dg, l, 1, element) - f_node = splitting(u_node, Val{:plus}(), 2, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 3, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, l, 1, element) - - # surface at +y - u_node = get_node_vars(u, equations, dg, l, nnodes(dg), element) - f_node = splitting(u_node, Val{:minus}(), 2, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 4, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, l, nnodes(dg), element) + # Implementation of fully upwind SATs. The surface flux values are pre-computed + # in the specialized `calc_interface_flux` routine. These SATs are still of + # a strong form penalty type, except that the interior flux at a particular + # side of the element are computed in the upwind direction. + function calc_surface_integral!( + du, u, mesh::TreeMesh{2}, + equations, surface_integral::SurfaceIntegralUpwind, + dg::FDSBP, cache + ) + inv_weight_left = inv(left_boundary_weight(dg.basis)) + inv_weight_right = inv(right_boundary_weight(dg.basis)) + @unpack surface_flux_values = cache.elements + @unpack splitting = surface_integral + + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg) + # surface at -x + u_node = get_node_vars(u, equations, dg, 1, l, element) + f_node = splitting(u_node, Val{:plus}(), 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 1, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, 1, l, element + ) + + # surface at +x + u_node = get_node_vars(u, equations, dg, nnodes(dg), l, element) + f_node = splitting(u_node, Val{:minus}(), 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 2, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, nnodes(dg), l, element + ) + + # surface at -y + u_node = get_node_vars(u, equations, dg, l, 1, element) + f_node = splitting(u_node, Val{:plus}(), 2, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 3, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, l, 1, element + ) + + # surface at +y + u_node = get_node_vars(u, equations, dg, l, nnodes(dg), element) + f_node = splitting(u_node, Val{:minus}(), 2, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 4, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, l, nnodes(dg), element + ) + end end - end - return nothing -end - -# Periodic FDSBP operators need to use a single element without boundaries -function calc_surface_integral!(du, u, mesh::TreeMesh2D, - equations, surface_integral::SurfaceIntegralUpwind, - dg::PeriodicFDSBP, cache) - @assert nelements(dg, cache) == 1 - return nothing -end - -# AnalysisCallback -function integrate_via_indices(func::Func, u, - mesh::TreeMesh{2}, equations, - dg::FDSBP, cache, args...; normalize = true) where {Func} - # TODO: FD. This is rather inefficient right now and allocates... - M = SummationByPartsOperators.mass_matrix(dg.basis) - if M isa UniformScaling - M = M(nnodes(dg)) + return nothing end - weights = diag(M) - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, 1, equations, dg, args...)) + # Periodic FDSBP operators need to use a single element without boundaries + function calc_surface_integral!( + du, u, mesh::TreeMesh2D, + equations, surface_integral::SurfaceIntegralUpwind, + dg::PeriodicFDSBP, cache + ) + @assert nelements(dg, cache) == 1 + return nothing + end - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - volume_jacobian_ = volume_jacobian(element, mesh, cache) - for j in eachnode(dg), i in eachnode(dg) - integral += volume_jacobian_ * weights[i] * weights[j] * - func(u, i, j, element, equations, dg, args...) + # AnalysisCallback + function integrate_via_indices( + func::Func, u, + mesh::TreeMesh{2}, equations, + dg::FDSBP, cache, args...; normalize = true + ) where {Func} + # TODO: FD. This is rather inefficient right now and allocates... + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) end - end + weights = diag(M) - # Normalize with total volume - if normalize - integral = integral / total_volume(mesh) - end + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, 1, equations, dg, args...)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + volume_jacobian_ = volume_jacobian(element, mesh, cache) + for j in eachnode(dg), i in eachnode(dg) + integral += volume_jacobian_ * weights[i] * weights[j] * + func(u, i, j, element, equations, dg, args...) + end + end - return integral -end + # Normalize with total volume + if normalize + integral = integral / total_volume(mesh) + end -function calc_error_norms(func, u, t, analyzer, - mesh::TreeMesh{2}, equations, initial_condition, - dg::FDSBP, cache, cache_analysis) - # TODO: FD. This is rather inefficient right now and allocates... - M = SummationByPartsOperators.mass_matrix(dg.basis) - if M isa UniformScaling - M = M(nnodes(dg)) + return integral end - weights = diag(M) - @unpack node_coordinates = cache.elements - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) - linf_error = copy(l2_error) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Calculate errors at each node - volume_jacobian_ = volume_jacobian(element, mesh, cache) - - for j in eachnode(analyzer), i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(node_coordinates, equations, dg, - i, j, element), t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u, equations, dg, i, j, element), equations) - l2_error += diff .^ 2 * (weights[i] * weights[j] * volume_jacobian_) - linf_error = @. max(linf_error, abs(diff)) + + function calc_error_norms( + func, u, t, analyzer, + mesh::TreeMesh{2}, equations, initial_condition, + dg::FDSBP, cache, cache_analysis + ) + # TODO: FD. This is rather inefficient right now and allocates... + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) + @unpack node_coordinates = cache.elements + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) + linf_error = copy(l2_error) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Calculate errors at each node + volume_jacobian_ = volume_jacobian(element, mesh, cache) + + for j in eachnode(analyzer), i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords( + node_coordinates, equations, dg, + i, j, element + ), t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u, equations, dg, i, j, element), equations) + l2_error += diff .^ 2 * (weights[i] * weights[j] * volume_jacobian_) + linf_error = @. max(linf_error, abs(diff)) + end end - end - # For L2 error, divide by total volume - total_volume_ = total_volume(mesh) - l2_error = @. sqrt(l2_error / total_volume_) + # For L2 error, divide by total volume + total_volume_ = total_volume(mesh) + l2_error = @. sqrt(l2_error / total_volume_) - return l2_error, linf_error -end + return l2_error, linf_error + end end # @muladd diff --git a/src/solvers/fdsbp_tree/fdsbp_3d.jl b/src/solvers/fdsbp_tree/fdsbp_3d.jl index 0c3f18b6d6..4b345340df 100644 --- a/src/solvers/fdsbp_tree/fdsbp_3d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_3d.jl @@ -6,429 +6,527 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# 3D caches -function create_cache(mesh::TreeMesh{3}, equations, - volume_integral::VolumeIntegralStrongForm, dg, uEltype) - prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}(undef, - ntuple(_ -> nnodes(dg), - ndims(mesh))...) - f_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] - - return (; f_threaded) -end - -function create_cache(mesh::TreeMesh{3}, equations, - volume_integral::VolumeIntegralUpwind, dg, uEltype) - u_node = SVector{nvariables(equations), uEltype}(ntuple(_ -> zero(uEltype), - Val{nvariables(equations)}())) - f = StructArray([(u_node, u_node)]) - f_minus_plus_threaded = [similar(f, ntuple(_ -> nnodes(dg), ndims(mesh))...) - for _ in 1:Threads.nthreads()] - - f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[1]) - f_minus_threaded = [f_minus] - f_plus_threaded = [f_plus] - for i in 2:Threads.nthreads() - f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[i]) - push!(f_minus_threaded, f_minus) - push!(f_plus_threaded, f_plus) + #! format: noindent + + # 3D caches + function create_cache( + mesh::TreeMesh{3}, equations, + volume_integral::VolumeIntegralStrongForm, dg, uEltype + ) + prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( + undef, + ntuple( + _ -> nnodes(dg), + ndims(mesh) + )... + ) + f_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + + return (; f_threaded) end - return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded) -end - -# 3D volume integral contributions for `VolumeIntegralStrongForm` -function calc_volume_integral!(du, u, - mesh::TreeMesh{3}, - nonconservative_terms::False, equations, - volume_integral::VolumeIntegralStrongForm, - dg::FDSBP, cache) - D = dg.basis # SBP derivative operator - @unpack f_threaded = cache - - # SBP operators from SummationByPartsOperators.jl implement the basic interface - # of matrix-vector multiplication. Thus, we pass an "array of structures", - # packing all variables per node in an `SVector`. - if nvariables(equations) == 1 - # `reinterpret(reshape, ...)` removes the leading dimension only if more - # than one variable is used. - u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u), - nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache)) - du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)}, - du), - nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache)) - else - u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) - du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)}, - du) + function create_cache( + mesh::TreeMesh{3}, equations, + volume_integral::VolumeIntegralUpwind, dg, uEltype + ) + u_node = SVector{nvariables(equations), uEltype}( + ntuple( + _ -> zero(uEltype), + Val{nvariables(equations)}() + ) + ) + f = StructArray([(u_node, u_node)]) + f_minus_plus_threaded = [ + similar(f, ntuple(_ -> nnodes(dg), ndims(mesh))...) + for _ in 1:Threads.nthreads() + ] + + f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[1]) + f_minus_threaded = [f_minus] + f_plus_threaded = [f_plus] + for i in 2:Threads.nthreads() + f_minus, f_plus = StructArrays.components(f_minus_plus_threaded[i]) + push!(f_minus_threaded, f_minus) + push!(f_plus_threaded, f_plus) + end + + return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded) end - # Use the tensor product structure to compute the discrete derivatives of - # the fluxes line-by-line and add them to `du` for each element. - @threaded for element in eachelement(dg, cache) - f_element = f_threaded[Threads.threadid()] - u_element = view(u_vectors, :, :, :, element) - - # x direction - @. f_element = flux(u_element, 1, equations) - for j in eachnode(dg), k in eachnode(dg) - mul!(view(du_vectors, :, j, k, element), D, view(f_element, :, j, k), - one(eltype(du)), one(eltype(du))) + # 3D volume integral contributions for `VolumeIntegralStrongForm` + function calc_volume_integral!( + du, u, + mesh::TreeMesh{3}, + nonconservative_terms::False, equations, + volume_integral::VolumeIntegralStrongForm, + dg::FDSBP, cache + ) + D = dg.basis # SBP derivative operator + @unpack f_threaded = cache + + # SBP operators from SummationByPartsOperators.jl implement the basic interface + # of matrix-vector multiplication. Thus, we pass an "array of structures", + # packing all variables per node in an `SVector`. + if nvariables(equations) == 1 + # `reinterpret(reshape, ...)` removes the leading dimension only if more + # than one variable is used. + u_vectors = reshape( + reinterpret(SVector{nvariables(equations), eltype(u)}, u), + nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + du_vectors = reshape( + reinterpret( + SVector{nvariables(equations), eltype(du)}, + du + ), + nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + else + u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) + du_vectors = reinterpret( + reshape, SVector{nvariables(equations), eltype(du)}, + du + ) end - # y direction - @. f_element = flux(u_element, 2, equations) - for i in eachnode(dg), k in eachnode(dg) - mul!(view(du_vectors, i, :, k, element), D, view(f_element, i, :, k), - one(eltype(du)), one(eltype(du))) - end + # Use the tensor product structure to compute the discrete derivatives of + # the fluxes line-by-line and add them to `du` for each element. + @threaded for element in eachelement(dg, cache) + f_element = f_threaded[Threads.threadid()] + u_element = view(u_vectors, :, :, :, element) + + # x direction + @. f_element = flux(u_element, 1, equations) + for j in eachnode(dg), k in eachnode(dg) + mul!( + view(du_vectors, :, j, k, element), D, view(f_element, :, j, k), + one(eltype(du)), one(eltype(du)) + ) + end - # z direction - @. f_element = flux(u_element, 3, equations) - for i in eachnode(dg), j in eachnode(dg) - mul!(view(du_vectors, i, j, :, element), D, view(f_element, i, j, :), - one(eltype(du)), one(eltype(du))) + # y direction + @. f_element = flux(u_element, 2, equations) + for i in eachnode(dg), k in eachnode(dg) + mul!( + view(du_vectors, i, :, k, element), D, view(f_element, i, :, k), + one(eltype(du)), one(eltype(du)) + ) + end + + # z direction + @. f_element = flux(u_element, 3, equations) + for i in eachnode(dg), j in eachnode(dg) + mul!( + view(du_vectors, i, j, :, element), D, view(f_element, i, j, :), + one(eltype(du)), one(eltype(du)) + ) + end end - end - return nothing -end - -# 3D volume integral contributions for `VolumeIntegralUpwind`. -# Note that the plus / minus notation of the operators does not refer to the -# upwind / downwind directions of the fluxes. -# Instead, the plus / minus refers to the direction of the biasing within -# the finite difference stencils. Thus, the D^- operator acts on the positive -# part of the flux splitting f^+ and the D^+ operator acts on the negative part -# of the flux splitting f^-. -function calc_volume_integral!(du, u, - mesh::TreeMesh{3}, - nonconservative_terms::False, equations, - volume_integral::VolumeIntegralUpwind, - dg::FDSBP, cache) - # Assume that - # dg.basis isa SummationByPartsOperators.UpwindOperators - D_minus = dg.basis.minus # Upwind SBP D^- derivative operator - D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator - @unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache - @unpack splitting = volume_integral - - # SBP operators from SummationByPartsOperators.jl implement the basic interface - # of matrix-vector multiplication. Thus, we pass an "array of structures", - # packing all variables per node in an `SVector`. - if nvariables(equations) == 1 - # `reinterpret(reshape, ...)` removes the leading dimension only if more - # than one variable is used. - u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u), - nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache)) - du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)}, - du), - nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache)) - else - u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) - du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)}, - du) + return nothing end - # Use the tensor product structure to compute the discrete derivatives of - # the fluxes line-by-line and add them to `du` for each element. - @threaded for element in eachelement(dg, cache) - # f_minus_plus_element wraps the storage provided by f_minus_element and - # f_plus_element such that we can use a single plain broadcasting below. - # f_minus_element and f_plus_element are updated in broadcasting calls - # of the form `@. f_minus_plus_element = ...`. - f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()] - f_minus_element = f_minus_threaded[Threads.threadid()] - f_plus_element = f_plus_threaded[Threads.threadid()] - u_element = view(u_vectors, :, :, :, element) - - # x direction - @. f_minus_plus_element = splitting(u_element, 1, equations) - for j in eachnode(dg), k in eachnode(dg) - mul!(view(du_vectors, :, j, k, element), D_minus, - view(f_plus_element, :, j, k), - one(eltype(du)), one(eltype(du))) - mul!(view(du_vectors, :, j, k, element), D_plus, - view(f_minus_element, :, j, k), - one(eltype(du)), one(eltype(du))) + # 3D volume integral contributions for `VolumeIntegralUpwind`. + # Note that the plus / minus notation of the operators does not refer to the + # upwind / downwind directions of the fluxes. + # Instead, the plus / minus refers to the direction of the biasing within + # the finite difference stencils. Thus, the D^- operator acts on the positive + # part of the flux splitting f^+ and the D^+ operator acts on the negative part + # of the flux splitting f^-. + function calc_volume_integral!( + du, u, + mesh::TreeMesh{3}, + nonconservative_terms::False, equations, + volume_integral::VolumeIntegralUpwind, + dg::FDSBP, cache + ) + # Assume that + # dg.basis isa SummationByPartsOperators.UpwindOperators + D_minus = dg.basis.minus # Upwind SBP D^- derivative operator + D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator + @unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache + @unpack splitting = volume_integral + + # SBP operators from SummationByPartsOperators.jl implement the basic interface + # of matrix-vector multiplication. Thus, we pass an "array of structures", + # packing all variables per node in an `SVector`. + if nvariables(equations) == 1 + # `reinterpret(reshape, ...)` removes the leading dimension only if more + # than one variable is used. + u_vectors = reshape( + reinterpret(SVector{nvariables(equations), eltype(u)}, u), + nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + du_vectors = reshape( + reinterpret( + SVector{nvariables(equations), eltype(du)}, + du + ), + nnodes(dg), nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + else + u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) + du_vectors = reinterpret( + reshape, SVector{nvariables(equations), eltype(du)}, + du + ) end - # y direction - @. f_minus_plus_element = splitting(u_element, 2, equations) - for i in eachnode(dg), k in eachnode(dg) - mul!(view(du_vectors, i, :, k, element), D_minus, - view(f_plus_element, i, :, k), - one(eltype(du)), one(eltype(du))) - mul!(view(du_vectors, i, :, k, element), D_plus, - view(f_minus_element, i, :, k), - one(eltype(du)), one(eltype(du))) - end + # Use the tensor product structure to compute the discrete derivatives of + # the fluxes line-by-line and add them to `du` for each element. + @threaded for element in eachelement(dg, cache) + # f_minus_plus_element wraps the storage provided by f_minus_element and + # f_plus_element such that we can use a single plain broadcasting below. + # f_minus_element and f_plus_element are updated in broadcasting calls + # of the form `@. f_minus_plus_element = ...`. + f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()] + f_minus_element = f_minus_threaded[Threads.threadid()] + f_plus_element = f_plus_threaded[Threads.threadid()] + u_element = view(u_vectors, :, :, :, element) + + # x direction + @. f_minus_plus_element = splitting(u_element, 1, equations) + for j in eachnode(dg), k in eachnode(dg) + mul!( + view(du_vectors, :, j, k, element), D_minus, + view(f_plus_element, :, j, k), + one(eltype(du)), one(eltype(du)) + ) + mul!( + view(du_vectors, :, j, k, element), D_plus, + view(f_minus_element, :, j, k), + one(eltype(du)), one(eltype(du)) + ) + end + + # y direction + @. f_minus_plus_element = splitting(u_element, 2, equations) + for i in eachnode(dg), k in eachnode(dg) + mul!( + view(du_vectors, i, :, k, element), D_minus, + view(f_plus_element, i, :, k), + one(eltype(du)), one(eltype(du)) + ) + mul!( + view(du_vectors, i, :, k, element), D_plus, + view(f_minus_element, i, :, k), + one(eltype(du)), one(eltype(du)) + ) + end - # z direction - @. f_minus_plus_element = splitting(u_element, 3, equations) - for i in eachnode(dg), j in eachnode(dg) - mul!(view(du_vectors, i, j, :, element), D_minus, - view(f_plus_element, i, j, :), - one(eltype(du)), one(eltype(du))) - mul!(view(du_vectors, i, j, :, element), D_plus, - view(f_minus_element, i, j, :), - one(eltype(du)), one(eltype(du))) + # z direction + @. f_minus_plus_element = splitting(u_element, 3, equations) + for i in eachnode(dg), j in eachnode(dg) + mul!( + view(du_vectors, i, j, :, element), D_minus, + view(f_plus_element, i, j, :), + one(eltype(du)), one(eltype(du)) + ) + mul!( + view(du_vectors, i, j, :, element), D_plus, + view(f_minus_element, i, j, :), + one(eltype(du)), one(eltype(du)) + ) + end end + + return nothing end - return nothing -end - -function calc_surface_integral!(du, u, mesh::TreeMesh{3}, - equations, surface_integral::SurfaceIntegralStrongForm, - dg::DG, cache) - inv_weight_left = inv(left_boundary_weight(dg.basis)) - inv_weight_right = inv(right_boundary_weight(dg.basis)) - @unpack surface_flux_values = cache.elements - - @threaded for element in eachelement(dg, cache) - for m in eachnode(dg), l in eachnode(dg) - # surface at -x - u_node = get_node_vars(u, equations, dg, 1, l, m, element) - f_node = flux(u_node, 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 1, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, 1, l, m, element) - - # surface at +x - u_node = get_node_vars(u, equations, dg, nnodes(dg), l, m, element) - f_node = flux(u_node, 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 2, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, nnodes(dg), l, m, element) - - # surface at -y - u_node = get_node_vars(u, equations, dg, l, 1, m, element) - f_node = flux(u_node, 2, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 3, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, l, 1, m, element) - - # surface at +y - u_node = get_node_vars(u, equations, dg, l, nnodes(dg), m, element) - f_node = flux(u_node, 2, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 4, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, l, nnodes(dg), m, element) - - # surface at -z - u_node = get_node_vars(u, equations, dg, l, m, 1, element) - f_node = flux(u_node, 3, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 5, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, l, m, 1, element) - - # surface at +z - u_node = get_node_vars(u, equations, dg, l, m, nnodes(dg), element) - f_node = flux(u_node, 3, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 6, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, l, m, nnodes(dg), element) + function calc_surface_integral!( + du, u, mesh::TreeMesh{3}, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::DG, cache + ) + inv_weight_left = inv(left_boundary_weight(dg.basis)) + inv_weight_right = inv(right_boundary_weight(dg.basis)) + @unpack surface_flux_values = cache.elements + + @threaded for element in eachelement(dg, cache) + for m in eachnode(dg), l in eachnode(dg) + # surface at -x + u_node = get_node_vars(u, equations, dg, 1, l, m, element) + f_node = flux(u_node, 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 1, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, 1, l, m, element + ) + + # surface at +x + u_node = get_node_vars(u, equations, dg, nnodes(dg), l, m, element) + f_node = flux(u_node, 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 2, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, nnodes(dg), l, m, element + ) + + # surface at -y + u_node = get_node_vars(u, equations, dg, l, 1, m, element) + f_node = flux(u_node, 2, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 3, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, l, 1, m, element + ) + + # surface at +y + u_node = get_node_vars(u, equations, dg, l, nnodes(dg), m, element) + f_node = flux(u_node, 2, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 4, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, l, nnodes(dg), m, element + ) + + # surface at -z + u_node = get_node_vars(u, equations, dg, l, m, 1, element) + f_node = flux(u_node, 3, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 5, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, l, m, 1, element + ) + + # surface at +z + u_node = get_node_vars(u, equations, dg, l, m, nnodes(dg), element) + f_node = flux(u_node, 3, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 6, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, l, m, nnodes(dg), element + ) + end end + + return nothing end - return nothing -end - -# Periodic FDSBP operators need to use a single element without boundaries -function calc_surface_integral!(du, u, mesh::TreeMesh3D, - equations, surface_integral::SurfaceIntegralStrongForm, - dg::PeriodicFDSBP, cache) - @assert nelements(dg, cache) == 1 - return nothing -end - -# Specialized interface flux computation because the upwind solver does -# not require a standard numerical flux (Riemann solver). The flux splitting -# already separates the solution information into right-traveling and -# left-traveling information. So we only need to compute the appropriate -# flux information at each side of an interface. -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{3}, - nonconservative_terms::False, equations, - surface_integral::SurfaceIntegralUpwind, - dg::FDSBP, cache) - @unpack splitting = surface_integral - @unpack u, neighbor_ids, orientations = cache.interfaces - - @threaded for interface in eachinterface(dg, cache) - # Get neighboring elements - left_id = neighbor_ids[1, interface] - right_id = neighbor_ids[2, interface] - - # Determine interface direction with respect to elements: - # orientation = 1: left -> 2, right -> 1 - # orientation = 2: left -> 4, right -> 3 - # orientation = 3: left -> 6, right -> 5 - left_direction = 2 * orientations[interface] - right_direction = 2 * orientations[interface] - 1 - - for j in eachnode(dg), i in eachnode(dg) - # Pull the left and right solution data - u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, interface) - - # Compute the upwind coupling terms where right-traveling - # information comes from the left and left-traveling information - # comes from the right - flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface], - equations) - flux_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], - equations) - - # Save the upwind coupling into the appropriate side of the elements - for v in eachvariable(equations) - surface_flux_values[v, i, j, left_direction, left_id] = flux_minus_rr[v] - surface_flux_values[v, i, j, right_direction, right_id] = flux_plus_ll[v] + # Periodic FDSBP operators need to use a single element without boundaries + function calc_surface_integral!( + du, u, mesh::TreeMesh3D, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::PeriodicFDSBP, cache + ) + @assert nelements(dg, cache) == 1 + return nothing + end + + # Specialized interface flux computation because the upwind solver does + # not require a standard numerical flux (Riemann solver). The flux splitting + # already separates the solution information into right-traveling and + # left-traveling information. So we only need to compute the appropriate + # flux information at each side of an interface. + function calc_interface_flux!( + surface_flux_values, + mesh::TreeMesh{3}, + nonconservative_terms::False, equations, + surface_integral::SurfaceIntegralUpwind, + dg::FDSBP, cache + ) + @unpack splitting = surface_integral + @unpack u, neighbor_ids, orientations = cache.interfaces + + @threaded for interface in eachinterface(dg, cache) + # Get neighboring elements + left_id = neighbor_ids[1, interface] + right_id = neighbor_ids[2, interface] + + # Determine interface direction with respect to elements: + # orientation = 1: left -> 2, right -> 1 + # orientation = 2: left -> 4, right -> 3 + # orientation = 3: left -> 6, right -> 5 + left_direction = 2 * orientations[interface] + right_direction = 2 * orientations[interface] - 1 + + for j in eachnode(dg), i in eachnode(dg) + # Pull the left and right solution data + u_ll, u_rr = get_surface_node_vars(u, equations, dg, i, j, interface) + + # Compute the upwind coupling terms where right-traveling + # information comes from the left and left-traveling information + # comes from the right + flux_minus_rr = splitting( + u_rr, Val{:minus}(), orientations[interface], + equations + ) + flux_plus_ll = splitting( + u_ll, Val{:plus}(), orientations[interface], + equations + ) + + # Save the upwind coupling into the appropriate side of the elements + for v in eachvariable(equations) + surface_flux_values[v, i, j, left_direction, left_id] = flux_minus_rr[v] + surface_flux_values[v, i, j, right_direction, right_id] = flux_plus_ll[v] + end end end + + return nothing end - return nothing -end - -# Implementation of fully upwind SATs. The surface flux values are pre-computed -# in the specialized `calc_interface_flux` routine. These SATs are still of -# a strong form penalty type, except that the interior flux at a particular -# side of the element are computed in the upwind direction. -function calc_surface_integral!(du, u, mesh::TreeMesh{3}, - equations, surface_integral::SurfaceIntegralUpwind, - dg::FDSBP, cache) - inv_weight_left = inv(left_boundary_weight(dg.basis)) - inv_weight_right = inv(right_boundary_weight(dg.basis)) - @unpack surface_flux_values = cache.elements - @unpack splitting = surface_integral - - @threaded for element in eachelement(dg, cache) - for m in eachnode(dg), l in eachnode(dg) - # surface at -x - u_node = get_node_vars(u, equations, dg, 1, l, m, element) - f_node = splitting(u_node, Val{:plus}(), 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 1, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, 1, l, m, element) - - # surface at +x - u_node = get_node_vars(u, equations, dg, nnodes(dg), l, m, element) - f_node = splitting(u_node, Val{:minus}(), 1, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 2, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, nnodes(dg), l, m, element) - - # surface at -y - u_node = get_node_vars(u, equations, dg, l, 1, m, element) - f_node = splitting(u_node, Val{:plus}(), 2, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 3, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, l, 1, m, element) - - # surface at +y - u_node = get_node_vars(u, equations, dg, l, nnodes(dg), m, element) - f_node = splitting(u_node, Val{:minus}(), 2, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 4, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, l, nnodes(dg), m, element) - - # surface at -z - u_node = get_node_vars(u, equations, dg, l, m, 1, element) - f_node = splitting(u_node, Val{:plus}(), 3, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 5, element) - multiply_add_to_node_vars!(du, inv_weight_left, -(f_num - f_node), - equations, dg, l, m, 1, element) - - # surface at +z - u_node = get_node_vars(u, equations, dg, l, m, nnodes(dg), element) - f_node = splitting(u_node, Val{:minus}(), 3, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 6, element) - multiply_add_to_node_vars!(du, inv_weight_right, +(f_num - f_node), - equations, dg, l, m, nnodes(dg), element) + # Implementation of fully upwind SATs. The surface flux values are pre-computed + # in the specialized `calc_interface_flux` routine. These SATs are still of + # a strong form penalty type, except that the interior flux at a particular + # side of the element are computed in the upwind direction. + function calc_surface_integral!( + du, u, mesh::TreeMesh{3}, + equations, surface_integral::SurfaceIntegralUpwind, + dg::FDSBP, cache + ) + inv_weight_left = inv(left_boundary_weight(dg.basis)) + inv_weight_right = inv(right_boundary_weight(dg.basis)) + @unpack surface_flux_values = cache.elements + @unpack splitting = surface_integral + + @threaded for element in eachelement(dg, cache) + for m in eachnode(dg), l in eachnode(dg) + # surface at -x + u_node = get_node_vars(u, equations, dg, 1, l, m, element) + f_node = splitting(u_node, Val{:plus}(), 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 1, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, 1, l, m, element + ) + + # surface at +x + u_node = get_node_vars(u, equations, dg, nnodes(dg), l, m, element) + f_node = splitting(u_node, Val{:minus}(), 1, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 2, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, nnodes(dg), l, m, element + ) + + # surface at -y + u_node = get_node_vars(u, equations, dg, l, 1, m, element) + f_node = splitting(u_node, Val{:plus}(), 2, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 3, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, l, 1, m, element + ) + + # surface at +y + u_node = get_node_vars(u, equations, dg, l, nnodes(dg), m, element) + f_node = splitting(u_node, Val{:minus}(), 2, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 4, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, l, nnodes(dg), m, element + ) + + # surface at -z + u_node = get_node_vars(u, equations, dg, l, m, 1, element) + f_node = splitting(u_node, Val{:plus}(), 3, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 5, element) + multiply_add_to_node_vars!( + du, inv_weight_left, -(f_num - f_node), + equations, dg, l, m, 1, element + ) + + # surface at +z + u_node = get_node_vars(u, equations, dg, l, m, nnodes(dg), element) + f_node = splitting(u_node, Val{:minus}(), 3, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, m, 6, element) + multiply_add_to_node_vars!( + du, inv_weight_right, +(f_num - f_node), + equations, dg, l, m, nnodes(dg), element + ) + end end - end - return nothing -end - -# Periodic FDSBP operators need to use a single element without boundaries -function calc_surface_integral!(du, u, mesh::TreeMesh3D, - equations, surface_integral::SurfaceIntegralUpwind, - dg::PeriodicFDSBP, cache) - @assert nelements(dg, cache) == 1 - return nothing -end - -# AnalysisCallback - -function integrate_via_indices(func::Func, u, - mesh::TreeMesh{3}, equations, - dg::FDSBP, cache, args...; normalize = true) where {Func} - # TODO: FD. This is rather inefficient right now and allocates... - M = SummationByPartsOperators.mass_matrix(dg.basis) - if M isa UniformScaling - M = M(nnodes(dg)) + return nothing end - weights = diag(M) - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...)) + # Periodic FDSBP operators need to use a single element without boundaries + function calc_surface_integral!( + du, u, mesh::TreeMesh3D, + equations, surface_integral::SurfaceIntegralUpwind, + dg::PeriodicFDSBP, cache + ) + @assert nelements(dg, cache) == 1 + return nothing + end - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - volume_jacobian_ = volume_jacobian(element, mesh, cache) - for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) - integral += volume_jacobian_ * weights[i] * weights[j] * weights[k] * - func(u, i, j, k, element, equations, dg, args...) + # AnalysisCallback + + function integrate_via_indices( + func::Func, u, + mesh::TreeMesh{3}, equations, + dg::FDSBP, cache, args...; normalize = true + ) where {Func} + # TODO: FD. This is rather inefficient right now and allocates... + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) end - end + weights = diag(M) - # Normalize with total volume - if normalize - integral = integral / total_volume(mesh) - end + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, 1, 1, equations, dg, args...)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + volume_jacobian_ = volume_jacobian(element, mesh, cache) + for k in eachnode(dg), j in eachnode(dg), i in eachnode(dg) + integral += volume_jacobian_ * weights[i] * weights[j] * weights[k] * + func(u, i, j, k, element, equations, dg, args...) + end + end - return integral -end + # Normalize with total volume + if normalize + integral = integral / total_volume(mesh) + end -function calc_error_norms(func, u, t, analyzer, - mesh::TreeMesh{3}, equations, initial_condition, - dg::FDSBP, cache, cache_analysis) - # TODO: FD. This is rather inefficient right now and allocates... - M = SummationByPartsOperators.mass_matrix(dg.basis) - if M isa UniformScaling - M = M(nnodes(dg)) + return integral end - weights = diag(M) - @unpack node_coordinates = cache.elements - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations)) - linf_error = copy(l2_error) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - # Calculate errors at each node - volume_jacobian_ = volume_jacobian(element, mesh, cache) - - for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer) - u_exact = initial_condition(get_node_coords(node_coordinates, equations, dg, - i, j, k, element), t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u, equations, dg, i, j, k, element), equations) - l2_error += diff .^ 2 * - (weights[i] * weights[j] * weights[k] * volume_jacobian_) - linf_error = @. max(linf_error, abs(diff)) + + function calc_error_norms( + func, u, t, analyzer, + mesh::TreeMesh{3}, equations, initial_condition, + dg::FDSBP, cache, cache_analysis + ) + # TODO: FD. This is rather inefficient right now and allocates... + M = SummationByPartsOperators.mass_matrix(dg.basis) + if M isa UniformScaling + M = M(nnodes(dg)) + end + weights = diag(M) + @unpack node_coordinates = cache.elements + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1, 1), equations)) + linf_error = copy(l2_error) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + # Calculate errors at each node + volume_jacobian_ = volume_jacobian(element, mesh, cache) + + for k in eachnode(analyzer), j in eachnode(analyzer), i in eachnode(analyzer) + u_exact = initial_condition( + get_node_coords( + node_coordinates, equations, dg, + i, j, k, element + ), t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u, equations, dg, i, j, k, element), equations) + l2_error += diff .^ 2 * + (weights[i] * weights[j] * weights[k] * volume_jacobian_) + linf_error = @. max(linf_error, abs(diff)) + end end - end - # For L2 error, divide by total volume - total_volume_ = total_volume(mesh) - l2_error = @. sqrt(l2_error / total_volume_) + # For L2 error, divide by total volume + total_volume_ = total_volume(mesh) + l2_error = @. sqrt(l2_error / total_volume_) - return l2_error, linf_error -end + return l2_error, linf_error + end end # @muladd diff --git a/src/solvers/fdsbp_unstructured/containers_2d.jl b/src/solvers/fdsbp_unstructured/containers_2d.jl index f68b1e00f5..03816aa96c 100644 --- a/src/solvers/fdsbp_unstructured/containers_2d.jl +++ b/src/solvers/fdsbp_unstructured/containers_2d.jl @@ -6,125 +6,147 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# initialize all the values in the container of a general FD block (either straight sided or curved) -# OBS! Requires the SBP derivative matrix in order to compute metric terms. -function init_element!(elements, element, basis::AbstractDerivativeOperator, - corners_or_surface_curves) - calc_node_coordinates!(elements.node_coordinates, element, get_nodes(basis), - corners_or_surface_curves) - - calc_metric_terms!(elements.jacobian_matrix, element, basis, - elements.node_coordinates) - - calc_inverse_jacobian!(elements.inverse_jacobian, element, elements.jacobian_matrix) - - calc_contravariant_vectors!(elements.contravariant_vectors, element, - elements.jacobian_matrix) - - calc_normal_directions!(elements.normal_directions, element, - elements.jacobian_matrix) - - return elements -end - -# Specialization to pass the central differencing matrix from an upwind SBP operator -function calc_metric_terms!(jacobian_matrix, element, - D_SBP::SummationByPartsOperators.UpwindOperators, - node_coordinates) - calc_metric_terms!(jacobian_matrix, element, D_SBP.central, node_coordinates) -end - -# construct the metric terms for a FDSBP element "block". Directly use the derivative matrix -# applied to the node coordinates. -function calc_metric_terms!(jacobian_matrix, element, D_SBP::AbstractDerivativeOperator, - node_coordinates) - - # storage format: - # jacobian_matrix[1,1,:,:,:] <- X_xi - # jacobian_matrix[1,2,:,:,:] <- X_eta - # jacobian_matrix[2,1,:,:,:] <- Y_xi - # jacobian_matrix[2,2,:,:,:] <- Y_eta - - # Compute the xi derivatives by applying D on the left - # This is basically the same as - # jacobian_matrix[1, 1, :, :, element] = Matrix(D_SBP) * node_coordinates[1, :, :, element] - # but uses only matrix-vector products instead of a matrix-matrix product. - for j in eachnode(D_SBP) - mul!(view(jacobian_matrix, 1, 1, :, j, element), D_SBP, - view(node_coordinates, 1, :, j, element)) - end - # jacobian_matrix[2, 1, :, :, element] = Matrix(D_SBP) * node_coordinates[2, :, :, element] - for j in eachnode(D_SBP) - mul!(view(jacobian_matrix, 2, 1, :, j, element), D_SBP, - view(node_coordinates, 2, :, j, element)) + #! format: noindent + + # initialize all the values in the container of a general FD block (either straight sided or curved) + # OBS! Requires the SBP derivative matrix in order to compute metric terms. + function init_element!( + elements, element, basis::AbstractDerivativeOperator, + corners_or_surface_curves + ) + calc_node_coordinates!( + elements.node_coordinates, element, get_nodes(basis), + corners_or_surface_curves + ) + + calc_metric_terms!( + elements.jacobian_matrix, element, basis, + elements.node_coordinates + ) + + calc_inverse_jacobian!(elements.inverse_jacobian, element, elements.jacobian_matrix) + + calc_contravariant_vectors!( + elements.contravariant_vectors, element, + elements.jacobian_matrix + ) + + calc_normal_directions!( + elements.normal_directions, element, + elements.jacobian_matrix + ) + + return elements end - # Compute the eta derivatives by applying transpose of D on the right - # jacobian_matrix[1, 2, :, :, element] = node_coordinates[1, :, :, element] * Matrix(D_SBP)' - for i in eachnode(D_SBP) - mul!(view(jacobian_matrix, 1, 2, i, :, element), D_SBP, - view(node_coordinates, 1, i, :, element)) - end - # jacobian_matrix[2, 2, :, :, element] = node_coordinates[2, :, :, element] * Matrix(D_SBP)' - for i in eachnode(D_SBP) - mul!(view(jacobian_matrix, 2, 2, i, :, element), D_SBP, - view(node_coordinates, 2, i, :, element)) + # Specialization to pass the central differencing matrix from an upwind SBP operator + function calc_metric_terms!( + jacobian_matrix, element, + D_SBP::SummationByPartsOperators.UpwindOperators, + node_coordinates + ) + calc_metric_terms!(jacobian_matrix, element, D_SBP.central, node_coordinates) end - return jacobian_matrix -end - -# construct the normal direction vectors (but not actually normalized) for a curved sided FDSBP element "block" -# normalization occurs on the fly during the surface flux computation -# OBS! This assumes that the boundary points are included. -function calc_normal_directions!(normal_directions, element, jacobian_matrix) - - # normal directions on the boundary for the left (local side 4) and right (local side 2) - N = size(jacobian_matrix, 4) - for j in 1:N - # +x side or side 2 in the local indexing - X_xi = jacobian_matrix[1, 1, N, j, element] - X_eta = jacobian_matrix[1, 2, N, j, element] - Y_xi = jacobian_matrix[2, 1, N, j, element] - Y_eta = jacobian_matrix[2, 2, N, j, element] - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, j, 2, element] = sign(Jtemp) * (Y_eta) - normal_directions[2, j, 2, element] = sign(Jtemp) * (-X_eta) - - # -x side or side 4 in the local indexing - X_xi = jacobian_matrix[1, 1, 1, j, element] - X_eta = jacobian_matrix[1, 2, 1, j, element] - Y_xi = jacobian_matrix[2, 1, 1, j, element] - Y_eta = jacobian_matrix[2, 2, 1, j, element] - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, j, 4, element] = -sign(Jtemp) * (Y_eta) - normal_directions[2, j, 4, element] = -sign(Jtemp) * (-X_eta) + # construct the metric terms for a FDSBP element "block". Directly use the derivative matrix + # applied to the node coordinates. + function calc_metric_terms!( + jacobian_matrix, element, D_SBP::AbstractDerivativeOperator, + node_coordinates + ) + + # storage format: + # jacobian_matrix[1,1,:,:,:] <- X_xi + # jacobian_matrix[1,2,:,:,:] <- X_eta + # jacobian_matrix[2,1,:,:,:] <- Y_xi + # jacobian_matrix[2,2,:,:,:] <- Y_eta + + # Compute the xi derivatives by applying D on the left + # This is basically the same as + # jacobian_matrix[1, 1, :, :, element] = Matrix(D_SBP) * node_coordinates[1, :, :, element] + # but uses only matrix-vector products instead of a matrix-matrix product. + for j in eachnode(D_SBP) + mul!( + view(jacobian_matrix, 1, 1, :, j, element), D_SBP, + view(node_coordinates, 1, :, j, element) + ) + end + # jacobian_matrix[2, 1, :, :, element] = Matrix(D_SBP) * node_coordinates[2, :, :, element] + for j in eachnode(D_SBP) + mul!( + view(jacobian_matrix, 2, 1, :, j, element), D_SBP, + view(node_coordinates, 2, :, j, element) + ) + end + + # Compute the eta derivatives by applying transpose of D on the right + # jacobian_matrix[1, 2, :, :, element] = node_coordinates[1, :, :, element] * Matrix(D_SBP)' + for i in eachnode(D_SBP) + mul!( + view(jacobian_matrix, 1, 2, i, :, element), D_SBP, + view(node_coordinates, 1, i, :, element) + ) + end + # jacobian_matrix[2, 2, :, :, element] = node_coordinates[2, :, :, element] * Matrix(D_SBP)' + for i in eachnode(D_SBP) + mul!( + view(jacobian_matrix, 2, 2, i, :, element), D_SBP, + view(node_coordinates, 2, i, :, element) + ) + end + + return jacobian_matrix end - # normal directions on the boundary for the top (local side 3) and bottom (local side 1) - N = size(jacobian_matrix, 3) - for i in 1:N - # -y side or side 1 in the local indexing - X_xi = jacobian_matrix[1, 1, i, 1, element] - X_eta = jacobian_matrix[1, 2, i, 1, element] - Y_xi = jacobian_matrix[2, 1, i, 1, element] - Y_eta = jacobian_matrix[2, 2, i, 1, element] - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, i, 1, element] = -sign(Jtemp) * (-Y_xi) - normal_directions[2, i, 1, element] = -sign(Jtemp) * (X_xi) - - # +y side or side 3 in the local indexing - X_xi = jacobian_matrix[1, 1, i, N, element] - X_eta = jacobian_matrix[1, 2, i, N, element] - Y_xi = jacobian_matrix[2, 1, i, N, element] - Y_eta = jacobian_matrix[2, 2, i, N, element] - Jtemp = X_xi * Y_eta - X_eta * Y_xi - normal_directions[1, i, 3, element] = sign(Jtemp) * (-Y_xi) - normal_directions[2, i, 3, element] = sign(Jtemp) * (X_xi) + # construct the normal direction vectors (but not actually normalized) for a curved sided FDSBP element "block" + # normalization occurs on the fly during the surface flux computation + # OBS! This assumes that the boundary points are included. + function calc_normal_directions!(normal_directions, element, jacobian_matrix) + + # normal directions on the boundary for the left (local side 4) and right (local side 2) + N = size(jacobian_matrix, 4) + for j in 1:N + # +x side or side 2 in the local indexing + X_xi = jacobian_matrix[1, 1, N, j, element] + X_eta = jacobian_matrix[1, 2, N, j, element] + Y_xi = jacobian_matrix[2, 1, N, j, element] + Y_eta = jacobian_matrix[2, 2, N, j, element] + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, j, 2, element] = sign(Jtemp) * (Y_eta) + normal_directions[2, j, 2, element] = sign(Jtemp) * (-X_eta) + + # -x side or side 4 in the local indexing + X_xi = jacobian_matrix[1, 1, 1, j, element] + X_eta = jacobian_matrix[1, 2, 1, j, element] + Y_xi = jacobian_matrix[2, 1, 1, j, element] + Y_eta = jacobian_matrix[2, 2, 1, j, element] + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, j, 4, element] = -sign(Jtemp) * (Y_eta) + normal_directions[2, j, 4, element] = -sign(Jtemp) * (-X_eta) + end + + # normal directions on the boundary for the top (local side 3) and bottom (local side 1) + N = size(jacobian_matrix, 3) + for i in 1:N + # -y side or side 1 in the local indexing + X_xi = jacobian_matrix[1, 1, i, 1, element] + X_eta = jacobian_matrix[1, 2, i, 1, element] + Y_xi = jacobian_matrix[2, 1, i, 1, element] + Y_eta = jacobian_matrix[2, 2, i, 1, element] + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, i, 1, element] = -sign(Jtemp) * (-Y_xi) + normal_directions[2, i, 1, element] = -sign(Jtemp) * (X_xi) + + # +y side or side 3 in the local indexing + X_xi = jacobian_matrix[1, 1, i, N, element] + X_eta = jacobian_matrix[1, 2, i, N, element] + Y_xi = jacobian_matrix[2, 1, i, N, element] + Y_eta = jacobian_matrix[2, 2, i, N, element] + Jtemp = X_xi * Y_eta - X_eta * Y_xi + normal_directions[1, i, 3, element] = sign(Jtemp) * (-Y_xi) + normal_directions[2, i, 3, element] = sign(Jtemp) * (X_xi) + end + + return normal_directions end - - return normal_directions -end end # @muladd diff --git a/src/solvers/fdsbp_unstructured/fdsbp.jl b/src/solvers/fdsbp_unstructured/fdsbp.jl index dee9776abb..ac5d936b9f 100644 --- a/src/solvers/fdsbp_unstructured/fdsbp.jl +++ b/src/solvers/fdsbp_unstructured/fdsbp.jl @@ -6,9 +6,9 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# dimension specific curvilinear implementations and data structures -include("containers_2d.jl") -include("fdsbp_2d.jl") + # dimension specific curvilinear implementations and data structures + include("containers_2d.jl") + include("fdsbp_2d.jl") end # @muladd diff --git a/src/solvers/fdsbp_unstructured/fdsbp_2d.jl b/src/solvers/fdsbp_unstructured/fdsbp_2d.jl index cbe11ac6ac..4e5aec7fc2 100644 --- a/src/solvers/fdsbp_unstructured/fdsbp_2d.jl +++ b/src/solvers/fdsbp_unstructured/fdsbp_2d.jl @@ -6,293 +6,345 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# 2D unstructured cache -function create_cache(mesh::UnstructuredMesh2D, equations, dg::FDSBP, RealT, uEltype) - elements = init_elements(mesh, equations, dg.basis, RealT, uEltype) - - interfaces = init_interfaces(mesh, elements) - - boundaries = init_boundaries(mesh, elements) - - cache = (; elements, interfaces, boundaries) - - # Add specialized parts of the cache required to for efficient flux computations - cache = (; cache..., - create_cache(mesh, equations, dg.volume_integral, dg, uEltype)...) - - return cache -end - -# 2D volume integral contributions for `VolumeIntegralStrongForm` -# OBS! This is the standard (not de-aliased) form of the volume integral. -# So it is not provably stable for variable coefficients due to the the metric terms. -function calc_volume_integral!(du, u, - mesh::UnstructuredMesh2D, - nonconservative_terms::False, equations, - volume_integral::VolumeIntegralStrongForm, - dg::FDSBP, cache) - D = dg.basis # SBP derivative operator - @unpack f_threaded = cache - @unpack contravariant_vectors = cache.elements - - # SBP operators from SummationByPartsOperators.jl implement the basic interface - # of matrix-vector multiplication. Thus, we pass an "array of structures", - # packing all variables per node in an `SVector`. - if nvariables(equations) == 1 - # `reinterpret(reshape, ...)` removes the leading dimension only if more - # than one variable is used. - u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u), - nnodes(dg), nnodes(dg), nelements(dg, cache)) - du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)}, - du), - nnodes(dg), nnodes(dg), nelements(dg, cache)) - else - u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) - du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)}, - du) - end + #! format: noindent - # Use the tensor product structure to compute the discrete derivatives of - # the contravariant fluxes line-by-line and add them to `du` for each element. - @threaded for element in eachelement(dg, cache) - f_element = f_threaded[Threads.threadid()] - u_element = view(u_vectors, :, :, element) + # 2D unstructured cache + function create_cache(mesh::UnstructuredMesh2D, equations, dg::FDSBP, RealT, uEltype) + elements = init_elements(mesh, equations, dg.basis, RealT, uEltype) - # x direction - for j in eachnode(dg) - for i in eachnode(dg) - Ja1 = get_contravariant_vector(1, contravariant_vectors, i, j, element) - f_element[i, j] = flux(u_element[i, j], Ja1, equations) - end - mul!(view(du_vectors, :, j, element), D, view(f_element, :, j), - one(eltype(du)), one(eltype(du))) + interfaces = init_interfaces(mesh, elements) + + boundaries = init_boundaries(mesh, elements) + + cache = (; elements, interfaces, boundaries) + + # Add specialized parts of the cache required to for efficient flux computations + cache = (; + cache..., + create_cache(mesh, equations, dg.volume_integral, dg, uEltype)..., + ) + + return cache + end + + # 2D volume integral contributions for `VolumeIntegralStrongForm` + # OBS! This is the standard (not de-aliased) form of the volume integral. + # So it is not provably stable for variable coefficients due to the the metric terms. + function calc_volume_integral!( + du, u, + mesh::UnstructuredMesh2D, + nonconservative_terms::False, equations, + volume_integral::VolumeIntegralStrongForm, + dg::FDSBP, cache + ) + D = dg.basis # SBP derivative operator + @unpack f_threaded = cache + @unpack contravariant_vectors = cache.elements + + # SBP operators from SummationByPartsOperators.jl implement the basic interface + # of matrix-vector multiplication. Thus, we pass an "array of structures", + # packing all variables per node in an `SVector`. + if nvariables(equations) == 1 + # `reinterpret(reshape, ...)` removes the leading dimension only if more + # than one variable is used. + u_vectors = reshape( + reinterpret(SVector{nvariables(equations), eltype(u)}, u), + nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + du_vectors = reshape( + reinterpret( + SVector{nvariables(equations), eltype(du)}, + du + ), + nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + else + u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) + du_vectors = reinterpret( + reshape, SVector{nvariables(equations), eltype(du)}, + du + ) end - # y direction - for i in eachnode(dg) + # Use the tensor product structure to compute the discrete derivatives of + # the contravariant fluxes line-by-line and add them to `du` for each element. + @threaded for element in eachelement(dg, cache) + f_element = f_threaded[Threads.threadid()] + u_element = view(u_vectors, :, :, element) + + # x direction for j in eachnode(dg) - Ja2 = get_contravariant_vector(2, contravariant_vectors, i, j, element) - f_element[i, j] = flux(u_element[i, j], Ja2, equations) + for i in eachnode(dg) + Ja1 = get_contravariant_vector(1, contravariant_vectors, i, j, element) + f_element[i, j] = flux(u_element[i, j], Ja1, equations) + end + mul!( + view(du_vectors, :, j, element), D, view(f_element, :, j), + one(eltype(du)), one(eltype(du)) + ) + end + + # y direction + for i in eachnode(dg) + for j in eachnode(dg) + Ja2 = get_contravariant_vector(2, contravariant_vectors, i, j, element) + f_element[i, j] = flux(u_element[i, j], Ja2, equations) + end + mul!( + view(du_vectors, i, :, element), D, view(f_element, i, :), + one(eltype(du)), one(eltype(du)) + ) end - mul!(view(du_vectors, i, :, element), D, view(f_element, i, :), - one(eltype(du)), one(eltype(du))) end - end - return nothing -end - -# 2D volume integral contributions for `VolumeIntegralUpwind`. -# Note that the plus / minus notation of the operators does not refer to the -# upwind / downwind directions of the fluxes. -# Instead, the plus / minus refers to the direction of the biasing within -# the finite difference stencils. Thus, the D^- operator acts on the positive -# part of the flux splitting f^+ and the D^+ operator acts on the negative part -# of the flux splitting f^-. -function calc_volume_integral!(du, u, - mesh::UnstructuredMesh2D, - nonconservative_terms::False, equations, - volume_integral::VolumeIntegralUpwind, - dg::FDSBP, cache) - # Assume that - # dg.basis isa SummationByPartsOperators.UpwindOperators - D_minus = dg.basis.minus # Upwind SBP D^- derivative operator - D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator - @unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache - @unpack splitting = volume_integral - @unpack contravariant_vectors = cache.elements - - # SBP operators from SummationByPartsOperators.jl implement the basic interface - # of matrix-vector multiplication. Thus, we pass an "array of structures", - # packing all variables per node in an `SVector`. - if nvariables(equations) == 1 - # `reinterpret(reshape, ...)` removes the leading dimension only if more - # than one variable is used. - u_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(u)}, u), - nnodes(dg), nnodes(dg), nelements(dg, cache)) - du_vectors = reshape(reinterpret(SVector{nvariables(equations), eltype(du)}, - du), - nnodes(dg), nnodes(dg), nelements(dg, cache)) - else - u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) - du_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(du)}, - du) + return nothing end - # Use the tensor product structure to compute the discrete derivatives of - # the fluxes line-by-line and add them to `du` for each element. - @threaded for element in eachelement(dg, cache) - # f_minus_plus_element wraps the storage provided by f_minus_element and - # f_plus_element such that we can use a single assignment below. - # f_minus_element and f_plus_element are updated whenever we update - # `f_minus_plus_element[i, j] = ...` below. - f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()] - f_minus_element = f_minus_threaded[Threads.threadid()] - f_plus_element = f_plus_threaded[Threads.threadid()] - u_element = view(u_vectors, :, :, element) - - # x direction - # We use flux vector splittings in the directions of the contravariant - # basis vectors. Thus, we do not use a broadcasting operation like - # @. f_minus_plus_element = splitting(u_element, 1, equations) - # in the Cartesian case but loop over all nodes. - for j in eachnode(dg), i in eachnode(dg) - # contravariant vectors computed with central D matrix - Ja1 = get_contravariant_vector(1, contravariant_vectors, i, j, element) - f_minus_plus_element[i, j] = splitting(u_element[i, j], Ja1, equations) + # 2D volume integral contributions for `VolumeIntegralUpwind`. + # Note that the plus / minus notation of the operators does not refer to the + # upwind / downwind directions of the fluxes. + # Instead, the plus / minus refers to the direction of the biasing within + # the finite difference stencils. Thus, the D^- operator acts on the positive + # part of the flux splitting f^+ and the D^+ operator acts on the negative part + # of the flux splitting f^-. + function calc_volume_integral!( + du, u, + mesh::UnstructuredMesh2D, + nonconservative_terms::False, equations, + volume_integral::VolumeIntegralUpwind, + dg::FDSBP, cache + ) + # Assume that + # dg.basis isa SummationByPartsOperators.UpwindOperators + D_minus = dg.basis.minus # Upwind SBP D^- derivative operator + D_plus = dg.basis.plus # Upwind SBP D^+ derivative operator + @unpack f_minus_plus_threaded, f_minus_threaded, f_plus_threaded = cache + @unpack splitting = volume_integral + @unpack contravariant_vectors = cache.elements + + # SBP operators from SummationByPartsOperators.jl implement the basic interface + # of matrix-vector multiplication. Thus, we pass an "array of structures", + # packing all variables per node in an `SVector`. + if nvariables(equations) == 1 + # `reinterpret(reshape, ...)` removes the leading dimension only if more + # than one variable is used. + u_vectors = reshape( + reinterpret(SVector{nvariables(equations), eltype(u)}, u), + nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + du_vectors = reshape( + reinterpret( + SVector{nvariables(equations), eltype(du)}, + du + ), + nnodes(dg), nnodes(dg), nelements(dg, cache) + ) + else + u_vectors = reinterpret(reshape, SVector{nvariables(equations), eltype(u)}, u) + du_vectors = reinterpret( + reshape, SVector{nvariables(equations), eltype(du)}, + du + ) end - for j in eachnode(dg) - mul!(view(du_vectors, :, j, element), D_minus, view(f_plus_element, :, j), - one(eltype(du)), one(eltype(du))) - mul!(view(du_vectors, :, j, element), D_plus, view(f_minus_element, :, j), - one(eltype(du)), one(eltype(du))) - end + # Use the tensor product structure to compute the discrete derivatives of + # the fluxes line-by-line and add them to `du` for each element. + @threaded for element in eachelement(dg, cache) + # f_minus_plus_element wraps the storage provided by f_minus_element and + # f_plus_element such that we can use a single assignment below. + # f_minus_element and f_plus_element are updated whenever we update + # `f_minus_plus_element[i, j] = ...` below. + f_minus_plus_element = f_minus_plus_threaded[Threads.threadid()] + f_minus_element = f_minus_threaded[Threads.threadid()] + f_plus_element = f_plus_threaded[Threads.threadid()] + u_element = view(u_vectors, :, :, element) + + # x direction + # We use flux vector splittings in the directions of the contravariant + # basis vectors. Thus, we do not use a broadcasting operation like + # @. f_minus_plus_element = splitting(u_element, 1, equations) + # in the Cartesian case but loop over all nodes. + for j in eachnode(dg), i in eachnode(dg) + # contravariant vectors computed with central D matrix + Ja1 = get_contravariant_vector(1, contravariant_vectors, i, j, element) + f_minus_plus_element[i, j] = splitting(u_element[i, j], Ja1, equations) + end - # y direction - for j in eachnode(dg), i in eachnode(dg) - # contravariant vectors computed with central D matrix - Ja2 = get_contravariant_vector(2, contravariant_vectors, i, j, element) - f_minus_plus_element[i, j] = splitting(u_element[i, j], Ja2, equations) - end + for j in eachnode(dg) + mul!( + view(du_vectors, :, j, element), D_minus, view(f_plus_element, :, j), + one(eltype(du)), one(eltype(du)) + ) + mul!( + view(du_vectors, :, j, element), D_plus, view(f_minus_element, :, j), + one(eltype(du)), one(eltype(du)) + ) + end - for i in eachnode(dg) - mul!(view(du_vectors, i, :, element), D_minus, view(f_plus_element, i, :), - one(eltype(du)), one(eltype(du))) - mul!(view(du_vectors, i, :, element), D_plus, view(f_minus_element, i, :), - one(eltype(du)), one(eltype(du))) + # y direction + for j in eachnode(dg), i in eachnode(dg) + # contravariant vectors computed with central D matrix + Ja2 = get_contravariant_vector(2, contravariant_vectors, i, j, element) + f_minus_plus_element[i, j] = splitting(u_element[i, j], Ja2, equations) + end + + for i in eachnode(dg) + mul!( + view(du_vectors, i, :, element), D_minus, view(f_plus_element, i, :), + one(eltype(du)), one(eltype(du)) + ) + mul!( + view(du_vectors, i, :, element), D_plus, view(f_minus_element, i, :), + one(eltype(du)), one(eltype(du)) + ) + end end + + return nothing end - return nothing -end - -# Note! The local side numbering for the unstructured quadrilateral element implementation differs -# from the structured TreeMesh or StructuredMesh local side numbering: -# -# TreeMesh/StructuredMesh sides versus UnstructuredMesh sides -# 4 3 -# ----------------- ----------------- -# | | | | -# | ^ eta | | ^ eta | -# 1 | | | 2 4 | | | 2 -# | | | | | | -# | ---> xi | | ---> xi | -# ----------------- ----------------- -# 3 1 -# Therefore, we require a different surface integral routine here despite their similar structure. -# Also, the normal directions are already outward pointing for `UnstructuredMesh2D` so all the -# surface contributions are added. -function calc_surface_integral!(du, u, mesh::UnstructuredMesh2D, - equations, surface_integral::SurfaceIntegralStrongForm, - dg::DG, cache) - inv_weight_left = inv(left_boundary_weight(dg.basis)) - inv_weight_right = inv(right_boundary_weight(dg.basis)) - @unpack normal_directions, surface_flux_values = cache.elements - - @threaded for element in eachelement(dg, cache) - for l in eachnode(dg) - # surface at -x - u_node = get_node_vars(u, equations, dg, 1, l, element) - # compute internal flux in normal direction on side 4 - outward_direction = get_surface_normal(normal_directions, l, 4, element) - f_node = flux(u_node, outward_direction, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 4, element) - multiply_add_to_node_vars!(du, inv_weight_left, (f_num - f_node), - equations, dg, 1, l, element) - - # surface at +x - u_node = get_node_vars(u, equations, dg, nnodes(dg), l, element) - # compute internal flux in normal direction on side 2 - outward_direction = get_surface_normal(normal_directions, l, 2, element) - f_node = flux(u_node, outward_direction, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 2, element) - multiply_add_to_node_vars!(du, inv_weight_right, (f_num - f_node), - equations, dg, nnodes(dg), l, element) - - # surface at -y - u_node = get_node_vars(u, equations, dg, l, 1, element) - # compute internal flux in normal direction on side 1 - outward_direction = get_surface_normal(normal_directions, l, 1, element) - f_node = flux(u_node, outward_direction, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 1, element) - multiply_add_to_node_vars!(du, inv_weight_left, (f_num - f_node), - equations, dg, l, 1, element) - - # surface at +y - u_node = get_node_vars(u, equations, dg, l, nnodes(dg), element) - # compute internal flux in normal direction on side 3 - outward_direction = get_surface_normal(normal_directions, l, 3, element) - f_node = flux(u_node, outward_direction, equations) - f_num = get_node_vars(surface_flux_values, equations, dg, l, 3, element) - multiply_add_to_node_vars!(du, inv_weight_right, (f_num - f_node), - equations, dg, l, nnodes(dg), element) + # Note! The local side numbering for the unstructured quadrilateral element implementation differs + # from the structured TreeMesh or StructuredMesh local side numbering: + # + # TreeMesh/StructuredMesh sides versus UnstructuredMesh sides + # 4 3 + # ----------------- ----------------- + # | | | | + # | ^ eta | | ^ eta | + # 1 | | | 2 4 | | | 2 + # | | | | | | + # | ---> xi | | ---> xi | + # ----------------- ----------------- + # 3 1 + # Therefore, we require a different surface integral routine here despite their similar structure. + # Also, the normal directions are already outward pointing for `UnstructuredMesh2D` so all the + # surface contributions are added. + function calc_surface_integral!( + du, u, mesh::UnstructuredMesh2D, + equations, surface_integral::SurfaceIntegralStrongForm, + dg::DG, cache + ) + inv_weight_left = inv(left_boundary_weight(dg.basis)) + inv_weight_right = inv(right_boundary_weight(dg.basis)) + @unpack normal_directions, surface_flux_values = cache.elements + + @threaded for element in eachelement(dg, cache) + for l in eachnode(dg) + # surface at -x + u_node = get_node_vars(u, equations, dg, 1, l, element) + # compute internal flux in normal direction on side 4 + outward_direction = get_surface_normal(normal_directions, l, 4, element) + f_node = flux(u_node, outward_direction, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 4, element) + multiply_add_to_node_vars!( + du, inv_weight_left, (f_num - f_node), + equations, dg, 1, l, element + ) + + # surface at +x + u_node = get_node_vars(u, equations, dg, nnodes(dg), l, element) + # compute internal flux in normal direction on side 2 + outward_direction = get_surface_normal(normal_directions, l, 2, element) + f_node = flux(u_node, outward_direction, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 2, element) + multiply_add_to_node_vars!( + du, inv_weight_right, (f_num - f_node), + equations, dg, nnodes(dg), l, element + ) + + # surface at -y + u_node = get_node_vars(u, equations, dg, l, 1, element) + # compute internal flux in normal direction on side 1 + outward_direction = get_surface_normal(normal_directions, l, 1, element) + f_node = flux(u_node, outward_direction, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 1, element) + multiply_add_to_node_vars!( + du, inv_weight_left, (f_num - f_node), + equations, dg, l, 1, element + ) + + # surface at +y + u_node = get_node_vars(u, equations, dg, l, nnodes(dg), element) + # compute internal flux in normal direction on side 3 + outward_direction = get_surface_normal(normal_directions, l, 3, element) + f_node = flux(u_node, outward_direction, equations) + f_num = get_node_vars(surface_flux_values, equations, dg, l, 3, element) + multiply_add_to_node_vars!( + du, inv_weight_right, (f_num - f_node), + equations, dg, l, nnodes(dg), element + ) + end end + + return nothing end - return nothing -end - -# AnalysisCallback -function integrate_via_indices(func::Func, u, - mesh::UnstructuredMesh2D, equations, - dg::FDSBP, cache, args...; normalize = true) where {Func} - # TODO: FD. This is rather inefficient right now and allocates... - weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) - - # Initialize integral with zeros of the right shape - integral = zero(func(u, 1, 1, 1, equations, dg, args...)) - total_volume = zero(real(mesh)) - - # Use quadrature to numerically integrate over entire domain - for element in eachelement(dg, cache) - for j in eachnode(dg), i in eachnode(dg) - volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element])) - integral += volume_jacobian * weights[i] * weights[j] * - func(u, i, j, element, equations, dg, args...) - total_volume += volume_jacobian * weights[i] * weights[j] + # AnalysisCallback + function integrate_via_indices( + func::Func, u, + mesh::UnstructuredMesh2D, equations, + dg::FDSBP, cache, args...; normalize = true + ) where {Func} + # TODO: FD. This is rather inefficient right now and allocates... + weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) + + # Initialize integral with zeros of the right shape + integral = zero(func(u, 1, 1, 1, equations, dg, args...)) + total_volume = zero(real(mesh)) + + # Use quadrature to numerically integrate over entire domain + for element in eachelement(dg, cache) + for j in eachnode(dg), i in eachnode(dg) + volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element])) + integral += volume_jacobian * weights[i] * weights[j] * + func(u, i, j, element, equations, dg, args...) + total_volume += volume_jacobian * weights[i] * weights[j] + end end - end - # Normalize with total volume - if normalize - integral = integral / total_volume + # Normalize with total volume + if normalize + integral = integral / total_volume + end + + return integral end - return integral -end - -function calc_error_norms(func, u, t, analyzer, - mesh::UnstructuredMesh2D, equations, initial_condition, - dg::FDSBP, cache, cache_analysis) - # TODO: FD. This is rather inefficient right now and allocates... - weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) - @unpack node_coordinates, inverse_jacobian = cache.elements - - # Set up data structures - l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) - linf_error = copy(l2_error) - total_volume = zero(real(mesh)) - - # Iterate over all elements for error calculations - for element in eachelement(dg, cache) - for j in eachnode(analyzer), i in eachnode(analyzer) - volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element])) - u_exact = initial_condition(get_node_coords(node_coordinates, equations, dg, - i, j, element), t, equations) - diff = func(u_exact, equations) - - func(get_node_vars(u, equations, dg, i, j, element), equations) - l2_error += diff .^ 2 * (weights[i] * weights[j] * volume_jacobian) - linf_error = @. max(linf_error, abs(diff)) - total_volume += weights[i] * weights[j] * volume_jacobian + function calc_error_norms( + func, u, t, analyzer, + mesh::UnstructuredMesh2D, equations, initial_condition, + dg::FDSBP, cache, cache_analysis + ) + # TODO: FD. This is rather inefficient right now and allocates... + weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) + @unpack node_coordinates, inverse_jacobian = cache.elements + + # Set up data structures + l2_error = zero(func(get_node_vars(u, equations, dg, 1, 1, 1), equations)) + linf_error = copy(l2_error) + total_volume = zero(real(mesh)) + + # Iterate over all elements for error calculations + for element in eachelement(dg, cache) + for j in eachnode(analyzer), i in eachnode(analyzer) + volume_jacobian = abs(inv(cache.elements.inverse_jacobian[i, j, element])) + u_exact = initial_condition( + get_node_coords( + node_coordinates, equations, dg, + i, j, element + ), t, equations + ) + diff = func(u_exact, equations) - + func(get_node_vars(u, equations, dg, i, j, element), equations) + l2_error += diff .^ 2 * (weights[i] * weights[j] * volume_jacobian) + linf_error = @. max(linf_error, abs(diff)) + total_volume += weights[i] * weights[j] * volume_jacobian + end end - end - # For L2 error, divide by total volume - l2_error = @. sqrt(l2_error / total_volume) + # For L2 error, divide by total volume + l2_error = @. sqrt(l2_error / total_volume) - return l2_error, linf_error -end + return l2_error, linf_error + end end # @muladd diff --git a/src/solvers/solvers.jl b/src/solvers/solvers.jl index a39f7cb175..1bd3e7da21 100644 --- a/src/solvers/solvers.jl +++ b/src/solvers/solvers.jl @@ -3,11 +3,11 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# define types for parabolic solvers -include("solvers_parabolic.jl") + # define types for parabolic solvers + include("solvers_parabolic.jl") -include("dg.jl") -include("dgmulti.jl") + include("dg.jl") + include("dgmulti.jl") end # @muladd diff --git a/src/time_integration/methods_2N.jl b/src/time_integration/methods_2N.jl index 1143e4ecb9..fdbd34f302 100644 --- a/src/time_integration/methods_2N.jl +++ b/src/time_integration/methods_2N.jl @@ -3,236 +3,256 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Abstract base type for time integration schemes of storage class `2N` -abstract type SimpleAlgorithm2N end - -""" - CarpenterKennedy2N54() - -The following structures and methods provide a minimal implementation of -the low-storage explicit Runge-Kutta method of - - Carpenter, Kennedy (1994) Fourth order 2N storage RK schemes, Solution 3 - -using the same interface as OrdinaryDiffEq.jl. -""" -struct CarpenterKennedy2N54 <: SimpleAlgorithm2N - a::SVector{5, Float64} - b::SVector{5, Float64} - c::SVector{5, Float64} - - function CarpenterKennedy2N54() - a = SVector(0.0, 567301805773.0 / 1357537059087.0, - 2404267990393.0 / 2016746695238.0, - 3550918686646.0 / 2091501179385.0, 1275806237668.0 / 842570457699.0) - b = SVector(1432997174477.0 / 9575080441755.0, - 5161836677717.0 / 13612068292357.0, - 1720146321549.0 / 2090206949498.0, - 3134564353537.0 / 4481467310338.0, - 2277821191437.0 / 14882151754819.0) - c = SVector(0.0, 1432997174477.0 / 9575080441755.0, - 2526269341429.0 / 6820363962896.0, - 2006345519317.0 / 3224310063776.0, - 2802321613138.0 / 2924317926251.0) - - new(a, b, c) + #! format: noindent + + # Abstract base type for time integration schemes of storage class `2N` + abstract type SimpleAlgorithm2N end + + """ + CarpenterKennedy2N54() + + The following structures and methods provide a minimal implementation of + the low-storage explicit Runge-Kutta method of + + Carpenter, Kennedy (1994) Fourth order 2N storage RK schemes, Solution 3 + + using the same interface as OrdinaryDiffEq.jl. + """ + struct CarpenterKennedy2N54 <: SimpleAlgorithm2N + a::SVector{5, Float64} + b::SVector{5, Float64} + c::SVector{5, Float64} + + function CarpenterKennedy2N54() + a = SVector( + 0.0, 567301805773.0 / 1357537059087.0, + 2404267990393.0 / 2016746695238.0, + 3550918686646.0 / 2091501179385.0, 1275806237668.0 / 842570457699.0 + ) + b = SVector( + 1432997174477.0 / 9575080441755.0, + 5161836677717.0 / 13612068292357.0, + 1720146321549.0 / 2090206949498.0, + 3134564353537.0 / 4481467310338.0, + 2277821191437.0 / 14882151754819.0 + ) + c = SVector( + 0.0, 1432997174477.0 / 9575080441755.0, + 2526269341429.0 / 6820363962896.0, + 2006345519317.0 / 3224310063776.0, + 2802321613138.0 / 2924317926251.0 + ) + + new(a, b, c) + end end -end -""" - CarpenterKennedy2N43() + """ + CarpenterKennedy2N43() -Carpenter, Kennedy (1994) Third order 2N storage RK schemes with error control -""" -struct CarpenterKennedy2N43 <: SimpleAlgorithm2N - a::SVector{4, Float64} - b::SVector{4, Float64} - c::SVector{4, Float64} + Carpenter, Kennedy (1994) Third order 2N storage RK schemes with error control + """ + struct CarpenterKennedy2N43 <: SimpleAlgorithm2N + a::SVector{4, Float64} + b::SVector{4, Float64} + c::SVector{4, Float64} - function CarpenterKennedy2N43() - a = SVector(0, 756391 / 934407, 36441873 / 15625000, 1953125 / 1085297) - b = SVector(8 / 141, 6627 / 2000, 609375 / 1085297, 198961 / 526383) - c = SVector(0, 8 / 141, 86 / 125, 1) + function CarpenterKennedy2N43() + a = SVector(0, 756391 / 934407, 36441873 / 15625000, 1953125 / 1085297) + b = SVector(8 / 141, 6627 / 2000, 609375 / 1085297, 198961 / 526383) + c = SVector(0, 8 / 141, 86 / 125, 1) - new(a, b, c) + new(a, b, c) + end end -end - -# This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L1 -mutable struct SimpleIntegrator2NOptions{Callback} - callback::Callback # callbacks; used in Trixi.jl - adaptive::Bool # whether the algorithm is adaptive; ignored - dtmax::Float64 # ignored - maxiters::Int # maximal number of time steps - tstops::Vector{Float64} # tstops from https://diffeq.sciml.ai/v6.8/basics/common_solver_opts/#Output-Control-1; ignored -end - -function SimpleIntegrator2NOptions(callback, tspan; maxiters = typemax(Int), kwargs...) - SimpleIntegrator2NOptions{typeof(callback)}(callback, false, Inf, maxiters, - [last(tspan)]) -end - -# This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L77 -# This implements the interface components described at -# https://diffeq.sciml.ai/v6.8/basics/integrator/#Handing-Integrators-1 -# which are used in Trixi.jl. -mutable struct SimpleIntegrator2N{RealT <: Real, uType, Params, Sol, F, Alg, - SimpleIntegrator2NOptions} - u::uType # - du::uType - u_tmp::uType - t::RealT - dt::RealT # current time step - dtcache::RealT # ignored - iter::Int # current number of time steps (iteration) - p::Params # will be the semidiscretization from Trixi.jl - sol::Sol # faked - f::F - alg::Alg - opts::SimpleIntegrator2NOptions - finalstep::Bool # added for convenience -end - -# Forward integrator.stats.naccept to integrator.iter (see GitHub PR#771) -function Base.getproperty(integrator::SimpleIntegrator2N, field::Symbol) - if field === :stats - return (naccept = getfield(integrator, :iter),) + + # This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L1 + mutable struct SimpleIntegrator2NOptions{Callback} + callback::Callback # callbacks; used in Trixi.jl + adaptive::Bool # whether the algorithm is adaptive; ignored + dtmax::Float64 # ignored + maxiters::Int # maximal number of time steps + tstops::Vector{Float64} # tstops from https://diffeq.sciml.ai/v6.8/basics/common_solver_opts/#Output-Control-1; ignored end - # general fallback - return getfield(integrator, field) -end - -function init(ode::ODEProblem, alg::SimpleAlgorithm2N; - dt, callback::Union{CallbackSet, Nothing} = nothing, kwargs...) - u = copy(ode.u0) - du = similar(u) - u_tmp = similar(u) - t = first(ode.tspan) - iter = 0 - integrator = SimpleIntegrator2N(u, du, u_tmp, t, dt, zero(dt), iter, ode.p, - (prob = ode,), ode.f, alg, - SimpleIntegrator2NOptions(callback, ode.tspan; - kwargs...), false) - - # initialize callbacks - if callback isa CallbackSet - foreach(callback.continuous_callbacks) do cb - throw(ArgumentError("Continuous callbacks are unsupported with the 2N storage time integration methods.")) - end - foreach(callback.discrete_callbacks) do cb - cb.initialize(cb, integrator.u, integrator.t, integrator) - end + + function SimpleIntegrator2NOptions(callback, tspan; maxiters = typemax(Int), kwargs...) + SimpleIntegrator2NOptions{typeof(callback)}( + callback, false, Inf, maxiters, + [last(tspan)] + ) + end + + # This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L77 + # This implements the interface components described at + # https://diffeq.sciml.ai/v6.8/basics/integrator/#Handing-Integrators-1 + # which are used in Trixi.jl. + mutable struct SimpleIntegrator2N{ + RealT <: Real, uType, Params, Sol, F, Alg, + SimpleIntegrator2NOptions, + } + u::uType # + du::uType + u_tmp::uType + t::RealT + dt::RealT # current time step + dtcache::RealT # ignored + iter::Int # current number of time steps (iteration) + p::Params # will be the semidiscretization from Trixi.jl + sol::Sol # faked + f::F + alg::Alg + opts::SimpleIntegrator2NOptions + finalstep::Bool # added for convenience end - return integrator -end + # Forward integrator.stats.naccept to integrator.iter (see GitHub PR#771) + function Base.getproperty(integrator::SimpleIntegrator2N, field::Symbol) + if field === :stats + return (naccept = getfield(integrator, :iter),) + end + # general fallback + return getfield(integrator, field) + end -# Fakes `solve`: https://diffeq.sciml.ai/v6.8/basics/overview/#Solving-the-Problems-1 -function solve(ode::ODEProblem, alg::SimpleAlgorithm2N; - dt, callback = nothing, kwargs...) - integrator = init(ode, alg, dt = dt, callback = callback; kwargs...) + function init( + ode::ODEProblem, alg::SimpleAlgorithm2N; + dt, callback::Union{CallbackSet, Nothing} = nothing, kwargs... + ) + u = copy(ode.u0) + du = similar(u) + u_tmp = similar(u) + t = first(ode.tspan) + iter = 0 + integrator = SimpleIntegrator2N( + u, du, u_tmp, t, dt, zero(dt), iter, ode.p, + (prob = ode,), ode.f, alg, + SimpleIntegrator2NOptions( + callback, ode.tspan; + kwargs... + ), false + ) + + # initialize callbacks + if callback isa CallbackSet + foreach(callback.continuous_callbacks) do cb + throw(ArgumentError("Continuous callbacks are unsupported with the 2N storage time integration methods.")) + end + foreach(callback.discrete_callbacks) do cb + cb.initialize(cb, integrator.u, integrator.t, integrator) + end + end - # Start actual solve - solve!(integrator) -end + return integrator + end -function solve!(integrator::SimpleIntegrator2N) - @unpack prob = integrator.sol + # Fakes `solve`: https://diffeq.sciml.ai/v6.8/basics/overview/#Solving-the-Problems-1 + function solve( + ode::ODEProblem, alg::SimpleAlgorithm2N; + dt, callback = nothing, kwargs... + ) + integrator = init(ode, alg, dt = dt, callback = callback; kwargs...) - integrator.finalstep = false + # Start actual solve + solve!(integrator) + end - @trixi_timeit timer() "main loop" while !integrator.finalstep - step!(integrator) - end # "main loop" timer + function solve!(integrator::SimpleIntegrator2N) + @unpack prob = integrator.sol - return TimeIntegratorSolution((first(prob.tspan), integrator.t), - (prob.u0, integrator.u), - integrator.sol.prob) -end + integrator.finalstep = false -function step!(integrator::SimpleIntegrator2N) - @unpack prob = integrator.sol - @unpack alg = integrator - t_end = last(prob.tspan) - callbacks = integrator.opts.callback + @trixi_timeit timer() "main loop" while !integrator.finalstep + step!(integrator) + end # "main loop" timer - @assert !integrator.finalstep - if isnan(integrator.dt) - error("time step size `dt` is NaN") + return TimeIntegratorSolution( + (first(prob.tspan), integrator.t), + (prob.u0, integrator.u), + integrator.sol.prob + ) end - # if the next iteration would push the simulation beyond the end time, set dt accordingly - if integrator.t + integrator.dt > t_end || - isapprox(integrator.t + integrator.dt, t_end) - integrator.dt = t_end - integrator.t - terminate!(integrator) - end + function step!(integrator::SimpleIntegrator2N) + @unpack prob = integrator.sol + @unpack alg = integrator + t_end = last(prob.tspan) + callbacks = integrator.opts.callback - # one time step - integrator.u_tmp .= 0 - for stage in eachindex(alg.c) - t_stage = integrator.t + integrator.dt * alg.c[stage] - integrator.f(integrator.du, integrator.u, prob.p, t_stage) - - a_stage = alg.a[stage] - b_stage_dt = alg.b[stage] * integrator.dt - @trixi_timeit timer() "Runge-Kutta step" begin - @threaded for i in eachindex(integrator.u) - integrator.u_tmp[i] = integrator.du[i] - - integrator.u_tmp[i] * a_stage - integrator.u[i] += integrator.u_tmp[i] * b_stage_dt + @assert !integrator.finalstep + if isnan(integrator.dt) + error("time step size `dt` is NaN") + end + + # if the next iteration would push the simulation beyond the end time, set dt accordingly + if integrator.t + integrator.dt > t_end || + isapprox(integrator.t + integrator.dt, t_end) + integrator.dt = t_end - integrator.t + terminate!(integrator) + end + + # one time step + integrator.u_tmp .= 0 + for stage in eachindex(alg.c) + t_stage = integrator.t + integrator.dt * alg.c[stage] + integrator.f(integrator.du, integrator.u, prob.p, t_stage) + + a_stage = alg.a[stage] + b_stage_dt = alg.b[stage] * integrator.dt + @trixi_timeit timer() "Runge-Kutta step" begin + @threaded for i in eachindex(integrator.u) + integrator.u_tmp[i] = integrator.du[i] - + integrator.u_tmp[i] * a_stage + integrator.u[i] += integrator.u_tmp[i] * b_stage_dt + end end end - end - integrator.iter += 1 - integrator.t += integrator.dt - - # handle callbacks - if callbacks isa CallbackSet - foreach(callbacks.discrete_callbacks) do cb - if cb.condition(integrator.u, integrator.t, integrator) - cb.affect!(integrator) + integrator.iter += 1 + integrator.t += integrator.dt + + # handle callbacks + if callbacks isa CallbackSet + foreach(callbacks.discrete_callbacks) do cb + if cb.condition(integrator.u, integrator.t, integrator) + cb.affect!(integrator) + end + return nothing end - return nothing end + + # respect maximum number of iterations + if integrator.iter >= integrator.opts.maxiters && !integrator.finalstep + @warn "Interrupted. Larger maxiters is needed." + terminate!(integrator) + end + end + + # get a cache where the RHS can be stored + get_du(integrator::SimpleIntegrator2N) = integrator.du + get_tmp_cache(integrator::SimpleIntegrator2N) = (integrator.u_tmp,) + + # some algorithms from DiffEq like FSAL-ones need to be informed when a callback has modified u + u_modified!(integrator::SimpleIntegrator2N, ::Bool) = false + + # used by adaptive timestepping algorithms in DiffEq + function set_proposed_dt!(integrator::SimpleIntegrator2N, dt) + integrator.dt = dt + end + + # Required e.g. for `glm_speed_callback` + function get_proposed_dt(integrator::SimpleIntegrator2N) + return integrator.dt + end + + # stop the time integration + function terminate!(integrator::SimpleIntegrator2N) + integrator.finalstep = true + empty!(integrator.opts.tstops) end - # respect maximum number of iterations - if integrator.iter >= integrator.opts.maxiters && !integrator.finalstep - @warn "Interrupted. Larger maxiters is needed." - terminate!(integrator) + # used for AMR + function Base.resize!(integrator::SimpleIntegrator2N, new_size) + resize!(integrator.u, new_size) + resize!(integrator.du, new_size) + resize!(integrator.u_tmp, new_size) end -end - -# get a cache where the RHS can be stored -get_du(integrator::SimpleIntegrator2N) = integrator.du -get_tmp_cache(integrator::SimpleIntegrator2N) = (integrator.u_tmp,) - -# some algorithms from DiffEq like FSAL-ones need to be informed when a callback has modified u -u_modified!(integrator::SimpleIntegrator2N, ::Bool) = false - -# used by adaptive timestepping algorithms in DiffEq -function set_proposed_dt!(integrator::SimpleIntegrator2N, dt) - integrator.dt = dt -end - -# Required e.g. for `glm_speed_callback` -function get_proposed_dt(integrator::SimpleIntegrator2N) - return integrator.dt -end - -# stop the time integration -function terminate!(integrator::SimpleIntegrator2N) - integrator.finalstep = true - empty!(integrator.opts.tstops) -end - -# used for AMR -function Base.resize!(integrator::SimpleIntegrator2N, new_size) - resize!(integrator.u, new_size) - resize!(integrator.du, new_size) - resize!(integrator.u_tmp, new_size) -end end # @muladd diff --git a/src/time_integration/methods_3Sstar.jl b/src/time_integration/methods_3Sstar.jl index a3ab023b64..4a32a95c0d 100644 --- a/src/time_integration/methods_3Sstar.jl +++ b/src/time_integration/methods_3Sstar.jl @@ -3,315 +3,369 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Abstract base type for time integration schemes of storage class `3S*` -abstract type SimpleAlgorithm3Sstar end - -""" - HypDiffN3Erk3Sstar52() - -Five stage, second-order accurate explicit Runge-Kutta scheme with stability region optimized for -the hyperbolic diffusion equation with LLF flux and polynomials of degree polydeg=3. -""" -struct HypDiffN3Erk3Sstar52 <: SimpleAlgorithm3Sstar - gamma1::SVector{5, Float64} - gamma2::SVector{5, Float64} - gamma3::SVector{5, Float64} - beta::SVector{5, Float64} - delta::SVector{5, Float64} - c::SVector{5, Float64} - - function HypDiffN3Erk3Sstar52() - gamma1 = SVector(0.0000000000000000E+00, 5.2656474556752575E-01, - 1.0385212774098265E+00, 3.6859755007388034E-01, - -6.3350615190506088E-01) - gamma2 = SVector(1.0000000000000000E+00, 4.1892580153419307E-01, - -2.7595818152587825E-02, 9.1271323651988631E-02, - 6.8495995159465062E-01) - gamma3 = SVector(0.0000000000000000E+00, 0.0000000000000000E+00, - 0.0000000000000000E+00, 4.1301005663300466E-01, - -5.4537881202277507E-03) - beta = SVector(4.5158640252832094E-01, 7.5974836561844006E-01, - 3.7561630338850771E-01, 2.9356700007428856E-02, - 2.5205285143494666E-01) - delta = SVector(1.0000000000000000E+00, 1.3011720142005145E-01, - 2.6579275844515687E-01, 9.9687218193685878E-01, - 0.0000000000000000E+00) - c = SVector(0.0000000000000000E+00, 4.5158640252832094E-01, - 1.0221535725056414E+00, 1.4280257701954349E+00, - 7.1581334196229851E-01) - - new(gamma1, gamma2, gamma3, beta, delta, c) + #! format: noindent + + # Abstract base type for time integration schemes of storage class `3S*` + abstract type SimpleAlgorithm3Sstar end + + """ + HypDiffN3Erk3Sstar52() + + Five stage, second-order accurate explicit Runge-Kutta scheme with stability region optimized for + the hyperbolic diffusion equation with LLF flux and polynomials of degree polydeg=3. + """ + struct HypDiffN3Erk3Sstar52 <: SimpleAlgorithm3Sstar + gamma1::SVector{5, Float64} + gamma2::SVector{5, Float64} + gamma3::SVector{5, Float64} + beta::SVector{5, Float64} + delta::SVector{5, Float64} + c::SVector{5, Float64} + + function HypDiffN3Erk3Sstar52() + gamma1 = SVector( + 0.0e+0, 5.2656474556752575e-1, + 1.0385212774098265e+0, 3.6859755007388034e-1, + -6.3350615190506088e-1 + ) + gamma2 = SVector( + 1.0e+0, 4.1892580153419307e-1, + -2.7595818152587825e-2, 9.1271323651988631e-2, + 6.8495995159465062e-1 + ) + gamma3 = SVector( + 0.0e+0, 0.0e+0, + 0.0e+0, 4.1301005663300466e-1, + -5.4537881202277507e-3 + ) + beta = SVector( + 4.5158640252832094e-1, 7.5974836561844006e-1, + 3.7561630338850771e-1, 2.9356700007428856e-2, + 2.5205285143494666e-1 + ) + delta = SVector( + 1.0e+0, 1.3011720142005145e-1, + 2.6579275844515687e-1, 9.9687218193685878e-1, + 0.0e+0 + ) + c = SVector( + 0.0e+0, 4.5158640252832094e-1, + 1.0221535725056414e+0, 1.4280257701954349e+0, + 7.1581334196229851e-1 + ) + + new(gamma1, gamma2, gamma3, beta, delta, c) + end end -end - -""" - ParsaniKetchesonDeconinck3Sstar94() - -Parsani, Ketcheson, Deconinck (2013) - Optimized explicit RK schemes for the spectral difference method applied to wave propagation problems -[DOI: 10.1137/120885899](https://doi.org/10.1137/120885899) -""" -struct ParsaniKetchesonDeconinck3Sstar94 <: SimpleAlgorithm3Sstar - gamma1::SVector{9, Float64} - gamma2::SVector{9, Float64} - gamma3::SVector{9, Float64} - beta::SVector{9, Float64} - delta::SVector{9, Float64} - c::SVector{9, Float64} - - function ParsaniKetchesonDeconinck3Sstar94() - gamma1 = SVector(0.0000000000000000E+00, -4.6556413837561301E+00, - -7.7202649689034453E-01, -4.0244202720632174E+00, - -2.1296873883702272E-02, -2.4350219407769953E+00, - 1.9856336960249132E-02, -2.8107894116913812E-01, - 1.6894354373677900E-01) - gamma2 = SVector(1.0000000000000000E+00, 2.4992627683300688E+00, - 5.8668202764174726E-01, 1.2051419816240785E+00, - 3.4747937498564541E-01, 1.3213458736302766E+00, - 3.1196363453264964E-01, 4.3514189245414447E-01, - 2.3596980658341213E-01) - gamma3 = SVector(0.0000000000000000E+00, 0.0000000000000000E+00, - 0.0000000000000000E+00, 7.6209857891449362E-01, - -1.9811817832965520E-01, -6.2289587091629484E-01, - -3.7522475499063573E-01, -3.3554373281046146E-01, - -4.5609629702116454E-02) - beta = SVector(2.8363432481011769E-01, 9.7364980747486463E-01, - 3.3823592364196498E-01, -3.5849518935750763E-01, - -4.1139587569859462E-03, 1.4279689871485013E+00, - 1.8084680519536503E-02, 1.6057708856060501E-01, - 2.9522267863254809E-01) - delta = SVector(1.0000000000000000E+00, 1.2629238731608268E+00, - 7.5749675232391733E-01, 5.1635907196195419E-01, - -2.7463346616574083E-02, -4.3826743572318672E-01, - 1.2735870231839268E+00, -6.2947382217730230E-01, - 0.0000000000000000E+00) - c = SVector(0.0000000000000000E+00, 2.8363432481011769E-01, - 5.4840742446661772E-01, 3.6872298094969475E-01, - -6.8061183026103156E-01, 3.5185265855105619E-01, - 1.6659419385562171E+00, 9.7152778807463247E-01, - 9.0515694340066954E-01) - - new(gamma1, gamma2, gamma3, beta, delta, c) + + """ + ParsaniKetchesonDeconinck3Sstar94() + + Parsani, Ketcheson, Deconinck (2013) + Optimized explicit RK schemes for the spectral difference method applied to wave propagation problems + [DOI: 10.1137/120885899](https://doi.org/10.1137/120885899) + """ + struct ParsaniKetchesonDeconinck3Sstar94 <: SimpleAlgorithm3Sstar + gamma1::SVector{9, Float64} + gamma2::SVector{9, Float64} + gamma3::SVector{9, Float64} + beta::SVector{9, Float64} + delta::SVector{9, Float64} + c::SVector{9, Float64} + + function ParsaniKetchesonDeconinck3Sstar94() + gamma1 = SVector( + 0.0e+0, -4.6556413837561301e+0, + -7.7202649689034453e-1, -4.0244202720632174e+0, + -2.1296873883702272e-2, -2.4350219407769953e+0, + 1.9856336960249132e-2, -2.8107894116913812e-1, + 1.68943543736779e-1 + ) + gamma2 = SVector( + 1.0e+0, 2.4992627683300688e+0, + 5.8668202764174726e-1, 1.2051419816240785e+0, + 3.4747937498564541e-1, 1.3213458736302766e+0, + 3.1196363453264964e-1, 4.3514189245414447e-1, + 2.3596980658341213e-1 + ) + gamma3 = SVector( + 0.0e+0, 0.0e+0, + 0.0e+0, 7.6209857891449362e-1, + -1.981181783296552e-1, -6.2289587091629484e-1, + -3.7522475499063573e-1, -3.3554373281046146e-1, + -4.5609629702116454e-2 + ) + beta = SVector( + 2.8363432481011769e-1, 9.7364980747486463e-1, + 3.3823592364196498e-1, -3.5849518935750763e-1, + -4.1139587569859462e-3, 1.4279689871485013e+0, + 1.8084680519536503e-2, 1.6057708856060501e-1, + 2.9522267863254809e-1 + ) + delta = SVector( + 1.0e+0, 1.2629238731608268e+0, + 7.5749675232391733e-1, 5.1635907196195419e-1, + -2.7463346616574083e-2, -4.3826743572318672e-1, + 1.2735870231839268e+0, -6.294738221773023e-1, + 0.0e+0 + ) + c = SVector( + 0.0e+0, 2.8363432481011769e-1, + 5.4840742446661772e-1, 3.6872298094969475e-1, + -6.8061183026103156e-1, 3.5185265855105619e-1, + 1.6659419385562171e+0, 9.7152778807463247e-1, + 9.0515694340066954e-1 + ) + + new(gamma1, gamma2, gamma3, beta, delta, c) + end end -end - -""" - ParsaniKetchesonDeconinck3Sstar32() - -Parsani, Ketcheson, Deconinck (2013) - Optimized explicit RK schemes for the spectral difference method applied to wave propagation problems -[DOI: 10.1137/120885899](https://doi.org/10.1137/120885899) -""" -struct ParsaniKetchesonDeconinck3Sstar32 <: SimpleAlgorithm3Sstar - gamma1::SVector{3, Float64} - gamma2::SVector{3, Float64} - gamma3::SVector{3, Float64} - beta::SVector{3, Float64} - delta::SVector{3, Float64} - c::SVector{3, Float64} - - function ParsaniKetchesonDeconinck3Sstar32() - gamma1 = SVector(0.0000000000000000E+00, -1.2664395576322218E-01, - 1.1426980685848858E+00) - gamma2 = SVector(1.0000000000000000E+00, 6.5427782599406470E-01, - -8.2869287683723744E-02) - gamma3 = SVector(0.0000000000000000E+00, 0.0000000000000000E+00, - 0.0000000000000000E+00) - beta = SVector(7.2366074728360086E-01, 3.4217876502651023E-01, - 3.6640216242653251E-01) - delta = SVector(1.0000000000000000E+00, 7.2196567116037724E-01, - 0.0000000000000000E+00) - c = SVector(0.0000000000000000E+00, 7.2366074728360086E-01, - 5.9236433182015646E-01) - - new(gamma1, gamma2, gamma3, beta, delta, c) + + """ + ParsaniKetchesonDeconinck3Sstar32() + + Parsani, Ketcheson, Deconinck (2013) + Optimized explicit RK schemes for the spectral difference method applied to wave propagation problems + [DOI: 10.1137/120885899](https://doi.org/10.1137/120885899) + """ + struct ParsaniKetchesonDeconinck3Sstar32 <: SimpleAlgorithm3Sstar + gamma1::SVector{3, Float64} + gamma2::SVector{3, Float64} + gamma3::SVector{3, Float64} + beta::SVector{3, Float64} + delta::SVector{3, Float64} + c::SVector{3, Float64} + + function ParsaniKetchesonDeconinck3Sstar32() + gamma1 = SVector( + 0.0e+0, -1.2664395576322218e-1, + 1.1426980685848858e+0 + ) + gamma2 = SVector( + 1.0e+0, 6.542778259940647e-1, + -8.2869287683723744e-2 + ) + gamma3 = SVector( + 0.0e+0, 0.0e+0, + 0.0e+0 + ) + beta = SVector( + 7.2366074728360086e-1, 3.4217876502651023e-1, + 3.6640216242653251e-1 + ) + delta = SVector( + 1.0e+0, 7.2196567116037724e-1, + 0.0e+0 + ) + c = SVector( + 0.0e+0, 7.2366074728360086e-1, + 5.9236433182015646e-1 + ) + + new(gamma1, gamma2, gamma3, beta, delta, c) + end end -end - -mutable struct SimpleIntegrator3SstarOptions{Callback} - callback::Callback # callbacks; used in Trixi.jl - adaptive::Bool # whether the algorithm is adaptive; ignored - dtmax::Float64 # ignored - maxiters::Int # maximal number of time steps - tstops::Vector{Float64} # tstops from https://diffeq.sciml.ai/v6.8/basics/common_solver_opts/#Output-Control-1; ignored -end - -function SimpleIntegrator3SstarOptions(callback, tspan; maxiters = typemax(Int), - kwargs...) - SimpleIntegrator3SstarOptions{typeof(callback)}(callback, false, Inf, maxiters, - [last(tspan)]) -end - -mutable struct SimpleIntegrator3Sstar{RealT <: Real, uType, Params, Sol, F, Alg, - SimpleIntegrator3SstarOptions} - u::uType # - du::uType - u_tmp1::uType - u_tmp2::uType - t::RealT - dt::RealT # current time step - dtcache::RealT # ignored - iter::Int # current number of time step (iteration) - p::Params # will be the semidiscretization from Trixi.jl - sol::Sol # faked - f::F - alg::Alg - opts::SimpleIntegrator3SstarOptions - finalstep::Bool # added for convenience -end - -# Forward integrator.stats.naccept to integrator.iter (see GitHub PR#771) -function Base.getproperty(integrator::SimpleIntegrator3Sstar, field::Symbol) - if field === :stats - return (naccept = getfield(integrator, :iter),) + + mutable struct SimpleIntegrator3SstarOptions{Callback} + callback::Callback # callbacks; used in Trixi.jl + adaptive::Bool # whether the algorithm is adaptive; ignored + dtmax::Float64 # ignored + maxiters::Int # maximal number of time steps + tstops::Vector{Float64} # tstops from https://diffeq.sciml.ai/v6.8/basics/common_solver_opts/#Output-Control-1; ignored end - # general fallback - return getfield(integrator, field) -end - -function init(ode::ODEProblem, alg::SimpleAlgorithm3Sstar; - dt, callback::Union{CallbackSet, Nothing} = nothing, kwargs...) - u = copy(ode.u0) - du = similar(u) - u_tmp1 = similar(u) - u_tmp2 = similar(u) - t = first(ode.tspan) - iter = 0 - integrator = SimpleIntegrator3Sstar(u, du, u_tmp1, u_tmp2, t, dt, zero(dt), iter, - ode.p, - (prob = ode,), ode.f, alg, - SimpleIntegrator3SstarOptions(callback, - ode.tspan; - kwargs...), false) - - # initialize callbacks - if callback isa CallbackSet - foreach(callback.continuous_callbacks) do cb - throw(ArgumentError("Continuous callbacks are unsupported with the 3 star time integration methods.")) - end - foreach(callback.discrete_callbacks) do cb - cb.initialize(cb, integrator.u, integrator.t, integrator) - end + + function SimpleIntegrator3SstarOptions( + callback, tspan; maxiters = typemax(Int), + kwargs... + ) + SimpleIntegrator3SstarOptions{typeof(callback)}( + callback, false, Inf, maxiters, + [last(tspan)] + ) end - return integrator -end + mutable struct SimpleIntegrator3Sstar{ + RealT <: Real, uType, Params, Sol, F, Alg, + SimpleIntegrator3SstarOptions, + } + u::uType # + du::uType + u_tmp1::uType + u_tmp2::uType + t::RealT + dt::RealT # current time step + dtcache::RealT # ignored + iter::Int # current number of time step (iteration) + p::Params # will be the semidiscretization from Trixi.jl + sol::Sol # faked + f::F + alg::Alg + opts::SimpleIntegrator3SstarOptions + finalstep::Bool # added for convenience + end -# Fakes `solve`: https://diffeq.sciml.ai/v6.8/basics/overview/#Solving-the-Problems-1 -function solve(ode::ODEProblem, alg::SimpleAlgorithm3Sstar; - dt, callback = nothing, kwargs...) - integrator = init(ode, alg, dt = dt, callback = callback; kwargs...) + # Forward integrator.stats.naccept to integrator.iter (see GitHub PR#771) + function Base.getproperty(integrator::SimpleIntegrator3Sstar, field::Symbol) + if field === :stats + return (naccept = getfield(integrator, :iter),) + end + # general fallback + return getfield(integrator, field) + end - # Start actual solve - solve!(integrator) -end + function init( + ode::ODEProblem, alg::SimpleAlgorithm3Sstar; + dt, callback::Union{CallbackSet, Nothing} = nothing, kwargs... + ) + u = copy(ode.u0) + du = similar(u) + u_tmp1 = similar(u) + u_tmp2 = similar(u) + t = first(ode.tspan) + iter = 0 + integrator = SimpleIntegrator3Sstar( + u, du, u_tmp1, u_tmp2, t, dt, zero(dt), iter, + ode.p, + (prob = ode,), ode.f, alg, + SimpleIntegrator3SstarOptions( + callback, + ode.tspan; + kwargs... + ), false + ) + + # initialize callbacks + if callback isa CallbackSet + foreach(callback.continuous_callbacks) do cb + throw(ArgumentError("Continuous callbacks are unsupported with the 3 star time integration methods.")) + end + foreach(callback.discrete_callbacks) do cb + cb.initialize(cb, integrator.u, integrator.t, integrator) + end + end -function solve!(integrator::SimpleIntegrator3Sstar) - @unpack prob = integrator.sol + return integrator + end - integrator.finalstep = false + # Fakes `solve`: https://diffeq.sciml.ai/v6.8/basics/overview/#Solving-the-Problems-1 + function solve( + ode::ODEProblem, alg::SimpleAlgorithm3Sstar; + dt, callback = nothing, kwargs... + ) + integrator = init(ode, alg, dt = dt, callback = callback; kwargs...) - @trixi_timeit timer() "main loop" while !integrator.finalstep - step!(integrator) - end # "main loop" timer + # Start actual solve + solve!(integrator) + end - return TimeIntegratorSolution((first(prob.tspan), integrator.t), - (prob.u0, integrator.u), - integrator.sol.prob) -end + function solve!(integrator::SimpleIntegrator3Sstar) + @unpack prob = integrator.sol -function step!(integrator::SimpleIntegrator3Sstar) - @unpack prob = integrator.sol - @unpack alg = integrator - t_end = last(prob.tspan) - callbacks = integrator.opts.callback + integrator.finalstep = false - @assert !integrator.finalstep - if isnan(integrator.dt) - error("time step size `dt` is NaN") - end + @trixi_timeit timer() "main loop" while !integrator.finalstep + step!(integrator) + end # "main loop" timer - # if the next iteration would push the simulation beyond the end time, set dt accordingly - if integrator.t + integrator.dt > t_end || - isapprox(integrator.t + integrator.dt, t_end) - integrator.dt = t_end - integrator.t - terminate!(integrator) + return TimeIntegratorSolution( + (first(prob.tspan), integrator.t), + (prob.u0, integrator.u), + integrator.sol.prob + ) end - # one time step - integrator.u_tmp1 .= zero(eltype(integrator.u_tmp1)) - integrator.u_tmp2 .= integrator.u - for stage in eachindex(alg.c) - t_stage = integrator.t + integrator.dt * alg.c[stage] - prob.f(integrator.du, integrator.u, prob.p, t_stage) - - delta_stage = alg.delta[stage] - gamma1_stage = alg.gamma1[stage] - gamma2_stage = alg.gamma2[stage] - gamma3_stage = alg.gamma3[stage] - beta_stage_dt = alg.beta[stage] * integrator.dt - @trixi_timeit timer() "Runge-Kutta step" begin - @threaded for i in eachindex(integrator.u) - integrator.u_tmp1[i] += delta_stage * integrator.u[i] - integrator.u[i] = (gamma1_stage * integrator.u[i] + - gamma2_stage * integrator.u_tmp1[i] + - gamma3_stage * integrator.u_tmp2[i] + - beta_stage_dt * integrator.du[i]) + function step!(integrator::SimpleIntegrator3Sstar) + @unpack prob = integrator.sol + @unpack alg = integrator + t_end = last(prob.tspan) + callbacks = integrator.opts.callback + + @assert !integrator.finalstep + if isnan(integrator.dt) + error("time step size `dt` is NaN") + end + + # if the next iteration would push the simulation beyond the end time, set dt accordingly + if integrator.t + integrator.dt > t_end || + isapprox(integrator.t + integrator.dt, t_end) + integrator.dt = t_end - integrator.t + terminate!(integrator) + end + + # one time step + integrator.u_tmp1 .= zero(eltype(integrator.u_tmp1)) + integrator.u_tmp2 .= integrator.u + for stage in eachindex(alg.c) + t_stage = integrator.t + integrator.dt * alg.c[stage] + prob.f(integrator.du, integrator.u, prob.p, t_stage) + + delta_stage = alg.delta[stage] + gamma1_stage = alg.gamma1[stage] + gamma2_stage = alg.gamma2[stage] + gamma3_stage = alg.gamma3[stage] + beta_stage_dt = alg.beta[stage] * integrator.dt + @trixi_timeit timer() "Runge-Kutta step" begin + @threaded for i in eachindex(integrator.u) + integrator.u_tmp1[i] += delta_stage * integrator.u[i] + integrator.u[i] = ( + gamma1_stage * integrator.u[i] + + gamma2_stage * integrator.u_tmp1[i] + + gamma3_stage * integrator.u_tmp2[i] + + beta_stage_dt * integrator.du[i] + ) + end end end - end - integrator.iter += 1 - integrator.t += integrator.dt - - # handle callbacks - if callbacks isa CallbackSet - foreach(callbacks.discrete_callbacks) do cb - if cb.condition(integrator.u, integrator.t, integrator) - cb.affect!(integrator) + integrator.iter += 1 + integrator.t += integrator.dt + + # handle callbacks + if callbacks isa CallbackSet + foreach(callbacks.discrete_callbacks) do cb + if cb.condition(integrator.u, integrator.t, integrator) + cb.affect!(integrator) + end + return nothing end - return nothing end + + # respect maximum number of iterations + if integrator.iter >= integrator.opts.maxiters && !integrator.finalstep + @warn "Interrupted. Larger maxiters is needed." + terminate!(integrator) + end + end + + # get a cache where the RHS can be stored + get_du(integrator::SimpleIntegrator3Sstar) = integrator.du + function get_tmp_cache(integrator::SimpleIntegrator3Sstar) + (integrator.u_tmp1, integrator.u_tmp2) + end + + # some algorithms from DiffEq like FSAL-ones need to be informed when a callback has modified u + u_modified!(integrator::SimpleIntegrator3Sstar, ::Bool) = false + + # used by adaptive timestepping algorithms in DiffEq + function set_proposed_dt!(integrator::SimpleIntegrator3Sstar, dt) + integrator.dt = dt + end + + # Required e.g. for `glm_speed_callback` + function get_proposed_dt(integrator::SimpleIntegrator3Sstar) + return integrator.dt + end + + # stop the time integration + function terminate!(integrator::SimpleIntegrator3Sstar) + integrator.finalstep = true + empty!(integrator.opts.tstops) end - # respect maximum number of iterations - if integrator.iter >= integrator.opts.maxiters && !integrator.finalstep - @warn "Interrupted. Larger maxiters is needed." - terminate!(integrator) + # used for AMR + function Base.resize!(integrator::SimpleIntegrator3Sstar, new_size) + resize!(integrator.u, new_size) + resize!(integrator.du, new_size) + resize!(integrator.u_tmp1, new_size) + resize!(integrator.u_tmp2, new_size) end -end - -# get a cache where the RHS can be stored -get_du(integrator::SimpleIntegrator3Sstar) = integrator.du -function get_tmp_cache(integrator::SimpleIntegrator3Sstar) - (integrator.u_tmp1, integrator.u_tmp2) -end - -# some algorithms from DiffEq like FSAL-ones need to be informed when a callback has modified u -u_modified!(integrator::SimpleIntegrator3Sstar, ::Bool) = false - -# used by adaptive timestepping algorithms in DiffEq -function set_proposed_dt!(integrator::SimpleIntegrator3Sstar, dt) - integrator.dt = dt -end - -# Required e.g. for `glm_speed_callback` -function get_proposed_dt(integrator::SimpleIntegrator3Sstar) - return integrator.dt -end - -# stop the time integration -function terminate!(integrator::SimpleIntegrator3Sstar) - integrator.finalstep = true - empty!(integrator.opts.tstops) -end - -# used for AMR -function Base.resize!(integrator::SimpleIntegrator3Sstar, new_size) - resize!(integrator.u, new_size) - resize!(integrator.du, new_size) - resize!(integrator.u_tmp1, new_size) - resize!(integrator.u_tmp2, new_size) -end end # @muladd diff --git a/src/time_integration/methods_SSP.jl b/src/time_integration/methods_SSP.jl index 285827850c..5fae6b5bec 100644 --- a/src/time_integration/methods_SSP.jl +++ b/src/time_integration/methods_SSP.jl @@ -3,315 +3,331 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Abstract base type for time integration schemes of explicit strong stability-preserving (SSP) -# Runge-Kutta (RK) methods. They are high-order time discretizations that guarantee the TVD property. -abstract type SimpleAlgorithmSSP end - -""" - SimpleSSPRK33(; stage_callbacks=()) - -The third-order SSP Runge-Kutta method of Shu and Osher. - -## References - -- Shu, Osher (1988) - "Efficient Implementation of Essentially Non-oscillatory Shock-Capturing Schemes" (Eq. 2.18) - [DOI: 10.1016/0021-9991(88)90177-5](https://doi.org/10.1016/0021-9991(88)90177-5) - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -struct SimpleSSPRK33{StageCallbacks} <: SimpleAlgorithmSSP - numerator_a::SVector{3, Float64} - numerator_b::SVector{3, Float64} - denominator::SVector{3, Float64} - c::SVector{3, Float64} - stage_callbacks::StageCallbacks - - function SimpleSSPRK33(; stage_callbacks = ()) - # Mathematically speaking, it is not necessary for the algorithm to split the factors - # into numerator and denominator. Otherwise, however, rounding errors of the order of - # the machine accuracy will occur, which will add up over time and thus endanger the - # conservation of the simulation. - # See also https://github.com/trixi-framework/Trixi.jl/pull/1640. - numerator_a = SVector(0.0, 3.0, 1.0) # a = numerator_a / denominator - numerator_b = SVector(1.0, 1.0, 2.0) # b = numerator_b / denominator - denominator = SVector(1.0, 4.0, 3.0) - c = SVector(0.0, 1.0, 1 / 2) - - # Butcher tableau - # c | a - # 0 | - # 1 | 1 - # 1/2 | 1/4 1/4 - # -------------------- - # b | 1/6 1/6 2/3 - - new{typeof(stage_callbacks)}(numerator_a, numerator_b, denominator, c, - stage_callbacks) + #! format: noindent + + # Abstract base type for time integration schemes of explicit strong stability-preserving (SSP) + # Runge-Kutta (RK) methods. They are high-order time discretizations that guarantee the TVD property. + abstract type SimpleAlgorithmSSP end + + """ + SimpleSSPRK33(; stage_callbacks=()) + + The third-order SSP Runge-Kutta method of Shu and Osher. + + ## References + + - Shu, Osher (1988) + "Efficient Implementation of Essentially Non-oscillatory Shock-Capturing Schemes" (Eq. 2.18) + [DOI: 10.1016/0021-9991(88)90177-5](https://doi.org/10.1016/0021-9991(88)90177-5) + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + struct SimpleSSPRK33{StageCallbacks} <: SimpleAlgorithmSSP + numerator_a::SVector{3, Float64} + numerator_b::SVector{3, Float64} + denominator::SVector{3, Float64} + c::SVector{3, Float64} + stage_callbacks::StageCallbacks + + function SimpleSSPRK33(; stage_callbacks = ()) + # Mathematically speaking, it is not necessary for the algorithm to split the factors + # into numerator and denominator. Otherwise, however, rounding errors of the order of + # the machine accuracy will occur, which will add up over time and thus endanger the + # conservation of the simulation. + # See also https://github.com/trixi-framework/Trixi.jl/pull/1640. + numerator_a = SVector(0.0, 3.0, 1.0) # a = numerator_a / denominator + numerator_b = SVector(1.0, 1.0, 2.0) # b = numerator_b / denominator + denominator = SVector(1.0, 4.0, 3.0) + c = SVector(0.0, 1.0, 1 / 2) + + # Butcher tableau + # c | a + # 0 | + # 1 | 1 + # 1/2 | 1/4 1/4 + # -------------------- + # b | 1/6 1/6 2/3 + + new{typeof(stage_callbacks)}( + numerator_a, numerator_b, denominator, c, + stage_callbacks + ) + end end -end - -# This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L1 -mutable struct SimpleIntegratorSSPOptions{Callback, TStops} - callback::Callback # callbacks; used in Trixi - adaptive::Bool # whether the algorithm is adaptive; ignored - dtmax::Float64 # ignored - maxiters::Int # maximal number of time steps - tstops::TStops # tstops from https://diffeq.sciml.ai/v6.8/basics/common_solver_opts/#Output-Control-1; ignored -end - -function SimpleIntegratorSSPOptions(callback, tspan; maxiters = typemax(Int), kwargs...) - tstops_internal = BinaryHeap{eltype(tspan)}(FasterForward()) - # We add last(tspan) to make sure that the time integration stops at the end time - push!(tstops_internal, last(tspan)) - # We add 2 * last(tspan) because add_tstop!(integrator, t) is only called by DiffEqCallbacks.jl if tstops contains a time that is larger than t - # (https://github.com/SciML/DiffEqCallbacks.jl/blob/025dfe99029bd0f30a2e027582744528eb92cd24/src/iterative_and_periodic.jl#L92) - push!(tstops_internal, 2 * last(tspan)) - SimpleIntegratorSSPOptions{typeof(callback), typeof(tstops_internal)}(callback, - false, Inf, - maxiters, - tstops_internal) -end - -# This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L77 -# This implements the interface components described at -# https://diffeq.sciml.ai/v6.8/basics/integrator/#Handing-Integrators-1 -# which are used in Trixi. -mutable struct SimpleIntegratorSSP{RealT <: Real, uType, Params, Sol, F, Alg, - SimpleIntegratorSSPOptions} - u::uType - du::uType - r0::uType - t::RealT - tdir::RealT - dt::RealT # current time step - dtcache::RealT # manually set time step - iter::Int # current number of time steps (iteration) - p::Params # will be the semidiscretization from Trixi - sol::Sol # faked - f::F - alg::Alg - opts::SimpleIntegratorSSPOptions - finalstep::Bool # added for convenience - dtchangeable::Bool - force_stepfail::Bool -end - -""" - add_tstop!(integrator::SimpleIntegratorSSP, t) -Add a time stop during the time integration process. -This function is called after the periodic SaveSolutionCallback to specify the next stop to save the solution. -""" -function add_tstop!(integrator::SimpleIntegratorSSP, t) - integrator.tdir * (t - integrator.t) < zero(integrator.t) && - error("Tried to add a tstop that is behind the current time. This is strictly forbidden") - # We need to remove the first entry of tstops when a new entry is added. - # Otherwise, the simulation gets stuck at the previous tstop and dt is adjusted to zero. - if length(integrator.opts.tstops) > 1 - pop!(integrator.opts.tstops) + + # This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L1 + mutable struct SimpleIntegratorSSPOptions{Callback, TStops} + callback::Callback # callbacks; used in Trixi + adaptive::Bool # whether the algorithm is adaptive; ignored + dtmax::Float64 # ignored + maxiters::Int # maximal number of time steps + tstops::TStops # tstops from https://diffeq.sciml.ai/v6.8/basics/common_solver_opts/#Output-Control-1; ignored end - push!(integrator.opts.tstops, integrator.tdir * t) -end -has_tstop(integrator::SimpleIntegratorSSP) = !isempty(integrator.opts.tstops) -first_tstop(integrator::SimpleIntegratorSSP) = first(integrator.opts.tstops) + function SimpleIntegratorSSPOptions(callback, tspan; maxiters = typemax(Int), kwargs...) + tstops_internal = BinaryHeap{eltype(tspan)}(FasterForward()) + # We add last(tspan) to make sure that the time integration stops at the end time + push!(tstops_internal, last(tspan)) + # We add 2 * last(tspan) because add_tstop!(integrator, t) is only called by DiffEqCallbacks.jl if tstops contains a time that is larger than t + # (https://github.com/SciML/DiffEqCallbacks.jl/blob/025dfe99029bd0f30a2e027582744528eb92cd24/src/iterative_and_periodic.jl#L92) + push!(tstops_internal, 2 * last(tspan)) + SimpleIntegratorSSPOptions{typeof(callback), typeof(tstops_internal)}( + callback, + false, Inf, + maxiters, + tstops_internal + ) + end -# Forward integrator.stats.naccept to integrator.iter (see GitHub PR#771) -function Base.getproperty(integrator::SimpleIntegratorSSP, field::Symbol) - if field === :stats - return (naccept = getfield(integrator, :iter),) + # This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L77 + # This implements the interface components described at + # https://diffeq.sciml.ai/v6.8/basics/integrator/#Handing-Integrators-1 + # which are used in Trixi. + mutable struct SimpleIntegratorSSP{ + RealT <: Real, uType, Params, Sol, F, Alg, + SimpleIntegratorSSPOptions, + } + u::uType + du::uType + r0::uType + t::RealT + tdir::RealT + dt::RealT # current time step + dtcache::RealT # manually set time step + iter::Int # current number of time steps (iteration) + p::Params # will be the semidiscretization from Trixi + sol::Sol # faked + f::F + alg::Alg + opts::SimpleIntegratorSSPOptions + finalstep::Bool # added for convenience + dtchangeable::Bool + force_stepfail::Bool end - # general fallback - return getfield(integrator, field) -end - -""" - solve(ode, alg; dt, callbacks, kwargs...) - -The following structures and methods provide the infrastructure for SSP Runge-Kutta methods -of type `SimpleAlgorithmSSP`. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -function solve(ode::ODEProblem, alg = SimpleSSPRK33()::SimpleAlgorithmSSP; - dt, callback::Union{CallbackSet, Nothing} = nothing, kwargs...) - u = copy(ode.u0) - du = similar(u) - r0 = similar(u) - t = first(ode.tspan) - tdir = sign(ode.tspan[end] - ode.tspan[1]) - iter = 0 - integrator = SimpleIntegratorSSP(u, du, r0, t, tdir, dt, dt, iter, ode.p, - (prob = ode,), ode.f, alg, - SimpleIntegratorSSPOptions(callback, ode.tspan; - kwargs...), - false, true, false) - - # resize container - resize!(integrator.p, nelements(integrator.p.solver, integrator.p.cache)) - - # initialize callbacks - if callback isa CallbackSet - foreach(callback.continuous_callbacks) do cb - throw(ArgumentError("Continuous callbacks are unsupported with the SSP time integration methods.")) - end - foreach(callback.discrete_callbacks) do cb - cb.initialize(cb, integrator.u, integrator.t, integrator) + + """ + add_tstop!(integrator::SimpleIntegratorSSP, t) + Add a time stop during the time integration process. + This function is called after the periodic SaveSolutionCallback to specify the next stop to save the solution. + """ + function add_tstop!(integrator::SimpleIntegratorSSP, t) + integrator.tdir * (t - integrator.t) < zero(integrator.t) && + error("Tried to add a tstop that is behind the current time. This is strictly forbidden") + # We need to remove the first entry of tstops when a new entry is added. + # Otherwise, the simulation gets stuck at the previous tstop and dt is adjusted to zero. + if length(integrator.opts.tstops) > 1 + pop!(integrator.opts.tstops) end + push!(integrator.opts.tstops, integrator.tdir * t) end - for stage_callback in alg.stage_callbacks - init_callback(stage_callback, integrator.p) - end + has_tstop(integrator::SimpleIntegratorSSP) = !isempty(integrator.opts.tstops) + first_tstop(integrator::SimpleIntegratorSSP) = first(integrator.opts.tstops) - solve!(integrator) -end + # Forward integrator.stats.naccept to integrator.iter (see GitHub PR#771) + function Base.getproperty(integrator::SimpleIntegratorSSP, field::Symbol) + if field === :stats + return (naccept = getfield(integrator, :iter),) + end + # general fallback + return getfield(integrator, field) + end -function solve!(integrator::SimpleIntegratorSSP) - @unpack prob = integrator.sol - @unpack alg = integrator - t_end = last(prob.tspan) - callbacks = integrator.opts.callback + """ + solve(ode, alg; dt, callbacks, kwargs...) + + The following structures and methods provide the infrastructure for SSP Runge-Kutta methods + of type `SimpleAlgorithmSSP`. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + function solve( + ode::ODEProblem, alg = SimpleSSPRK33()::SimpleAlgorithmSSP; + dt, callback::Union{CallbackSet, Nothing} = nothing, kwargs... + ) + u = copy(ode.u0) + du = similar(u) + r0 = similar(u) + t = first(ode.tspan) + tdir = sign(ode.tspan[end] - ode.tspan[1]) + iter = 0 + integrator = SimpleIntegratorSSP( + u, du, r0, t, tdir, dt, dt, iter, ode.p, + (prob = ode,), ode.f, alg, + SimpleIntegratorSSPOptions( + callback, ode.tspan; + kwargs... + ), + false, true, false + ) + + # resize container + resize!(integrator.p, nelements(integrator.p.solver, integrator.p.cache)) + + # initialize callbacks + if callback isa CallbackSet + foreach(callback.continuous_callbacks) do cb + throw(ArgumentError("Continuous callbacks are unsupported with the SSP time integration methods.")) + end + foreach(callback.discrete_callbacks) do cb + cb.initialize(cb, integrator.u, integrator.t, integrator) + end + end - integrator.finalstep = false - @trixi_timeit timer() "main loop" while !integrator.finalstep - if isnan(integrator.dt) - error("time step size `dt` is NaN") + for stage_callback in alg.stage_callbacks + init_callback(stage_callback, integrator.p) end - modify_dt_for_tstops!(integrator) + solve!(integrator) + end - # if the next iteration would push the simulation beyond the end time, set dt accordingly - if integrator.t + integrator.dt > t_end || - isapprox(integrator.t + integrator.dt, t_end) - integrator.dt = t_end - integrator.t - terminate!(integrator) - end + function solve!(integrator::SimpleIntegratorSSP) + @unpack prob = integrator.sol + @unpack alg = integrator + t_end = last(prob.tspan) + callbacks = integrator.opts.callback - @. integrator.r0 = integrator.u - for stage in eachindex(alg.c) - t_stage = integrator.t + integrator.dt * alg.c[stage] - # compute du - integrator.f(integrator.du, integrator.u, integrator.p, t_stage) + integrator.finalstep = false + @trixi_timeit timer() "main loop" while !integrator.finalstep + if isnan(integrator.dt) + error("time step size `dt` is NaN") + end - # perform forward Euler step - @. integrator.u = integrator.u + integrator.dt * integrator.du + modify_dt_for_tstops!(integrator) - for stage_callback in alg.stage_callbacks - stage_callback(integrator.u, integrator, stage) + # if the next iteration would push the simulation beyond the end time, set dt accordingly + if integrator.t + integrator.dt > t_end || + isapprox(integrator.t + integrator.dt, t_end) + integrator.dt = t_end - integrator.t + terminate!(integrator) end - # perform convex combination - @. integrator.u = (alg.numerator_a[stage] * integrator.r0 + - alg.numerator_b[stage] * integrator.u) / - alg.denominator[stage] - end + @. integrator.r0 = integrator.u + for stage in eachindex(alg.c) + t_stage = integrator.t + integrator.dt * alg.c[stage] + # compute du + integrator.f(integrator.du, integrator.u, integrator.p, t_stage) + + # perform forward Euler step + @. integrator.u = integrator.u + integrator.dt * integrator.du + + for stage_callback in alg.stage_callbacks + stage_callback(integrator.u, integrator, stage) + end + + # perform convex combination + @. integrator.u = ( + alg.numerator_a[stage] * integrator.r0 + + alg.numerator_b[stage] * integrator.u + ) / + alg.denominator[stage] + end - integrator.iter += 1 - integrator.t += integrator.dt + integrator.iter += 1 + integrator.t += integrator.dt - # handle callbacks - if callbacks isa CallbackSet - foreach(callbacks.discrete_callbacks) do cb - if cb.condition(integrator.u, integrator.t, integrator) - cb.affect!(integrator) + # handle callbacks + if callbacks isa CallbackSet + foreach(callbacks.discrete_callbacks) do cb + if cb.condition(integrator.u, integrator.t, integrator) + cb.affect!(integrator) + end end end + + # respect maximum number of iterations + if integrator.iter >= integrator.opts.maxiters && !integrator.finalstep + @warn "Interrupted. Larger maxiters is needed." + terminate!(integrator) + end end - # respect maximum number of iterations - if integrator.iter >= integrator.opts.maxiters && !integrator.finalstep - @warn "Interrupted. Larger maxiters is needed." - terminate!(integrator) + # Empty the tstops array. + # This cannot be done in terminate!(integrator::SimpleIntegratorSSP) because DiffEqCallbacks.PeriodicCallbackAffect would return at error. + extract_all!(integrator.opts.tstops) + + for stage_callback in alg.stage_callbacks + finalize_callback(stage_callback, integrator.p) end + + return TimeIntegratorSolution( + (first(prob.tspan), integrator.t), + (prob.u0, integrator.u), prob + ) + end + + # get a cache where the RHS can be stored + get_du(integrator::SimpleIntegratorSSP) = integrator.du + get_tmp_cache(integrator::SimpleIntegratorSSP) = (integrator.r0,) + + # some algorithms from DiffEq like FSAL-ones need to be informed when a callback has modified u + u_modified!(integrator::SimpleIntegratorSSP, ::Bool) = false + + # used by adaptive timestepping algorithms in DiffEq + function set_proposed_dt!(integrator::SimpleIntegratorSSP, dt) + (integrator.dt = dt; integrator.dtcache = dt) end - # Empty the tstops array. - # This cannot be done in terminate!(integrator::SimpleIntegratorSSP) because DiffEqCallbacks.PeriodicCallbackAffect would return at error. - extract_all!(integrator.opts.tstops) + # used by adaptive timestepping algorithms in DiffEq + function get_proposed_dt(integrator::SimpleIntegratorSSP) + return ifelse(integrator.opts.adaptive, integrator.dt, integrator.dtcache) + end - for stage_callback in alg.stage_callbacks - finalize_callback(stage_callback, integrator.p) + # stop the time integration + function terminate!(integrator::SimpleIntegratorSSP) + integrator.finalstep = true end - return TimeIntegratorSolution((first(prob.tspan), integrator.t), - (prob.u0, integrator.u), prob) -end - -# get a cache where the RHS can be stored -get_du(integrator::SimpleIntegratorSSP) = integrator.du -get_tmp_cache(integrator::SimpleIntegratorSSP) = (integrator.r0,) - -# some algorithms from DiffEq like FSAL-ones need to be informed when a callback has modified u -u_modified!(integrator::SimpleIntegratorSSP, ::Bool) = false - -# used by adaptive timestepping algorithms in DiffEq -function set_proposed_dt!(integrator::SimpleIntegratorSSP, dt) - (integrator.dt = dt; integrator.dtcache = dt) -end - -# used by adaptive timestepping algorithms in DiffEq -function get_proposed_dt(integrator::SimpleIntegratorSSP) - return ifelse(integrator.opts.adaptive, integrator.dt, integrator.dtcache) -end - -# stop the time integration -function terminate!(integrator::SimpleIntegratorSSP) - integrator.finalstep = true -end - -""" - modify_dt_for_tstops!(integrator::SimpleIntegratorSSP) -Modify the time-step size to match the time stops specified in integrator.opts.tstops. -To avoid adding OrdinaryDiffEq to Trixi's dependencies, this routine is a copy of -https://github.com/SciML/OrdinaryDiffEq.jl/blob/d76335281c540ee5a6d1bd8bb634713e004f62ee/src/integrators/integrator_utils.jl#L38-L54 -""" -function modify_dt_for_tstops!(integrator::SimpleIntegratorSSP) - if has_tstop(integrator) - tdir_t = integrator.tdir * integrator.t - tdir_tstop = first_tstop(integrator) - if integrator.opts.adaptive - integrator.dt = integrator.tdir * - min(abs(integrator.dt), abs(tdir_tstop - tdir_t)) # step! to the end - elseif iszero(integrator.dtcache) && integrator.dtchangeable - integrator.dt = integrator.tdir * abs(tdir_tstop - tdir_t) - elseif integrator.dtchangeable && !integrator.force_stepfail - # always try to step! with dtcache, but lower if a tstop - # however, if force_stepfail then don't set to dtcache, and no tstop worry - integrator.dt = integrator.tdir * - min(abs(integrator.dtcache), abs(tdir_tstop - tdir_t)) # step! to the end + """ + modify_dt_for_tstops!(integrator::SimpleIntegratorSSP) + Modify the time-step size to match the time stops specified in integrator.opts.tstops. + To avoid adding OrdinaryDiffEq to Trixi's dependencies, this routine is a copy of + https://github.com/SciML/OrdinaryDiffEq.jl/blob/d76335281c540ee5a6d1bd8bb634713e004f62ee/src/integrators/integrator_utils.jl#L38-L54 + """ + function modify_dt_for_tstops!(integrator::SimpleIntegratorSSP) + if has_tstop(integrator) + tdir_t = integrator.tdir * integrator.t + tdir_tstop = first_tstop(integrator) + if integrator.opts.adaptive + integrator.dt = integrator.tdir * + min(abs(integrator.dt), abs(tdir_tstop - tdir_t)) # step! to the end + elseif iszero(integrator.dtcache) && integrator.dtchangeable + integrator.dt = integrator.tdir * abs(tdir_tstop - tdir_t) + elseif integrator.dtchangeable && !integrator.force_stepfail + # always try to step! with dtcache, but lower if a tstop + # however, if force_stepfail then don't set to dtcache, and no tstop worry + integrator.dt = integrator.tdir * + min(abs(integrator.dtcache), abs(tdir_tstop - tdir_t)) # step! to the end + end end end -end - -# used for AMR -function Base.resize!(integrator::SimpleIntegratorSSP, new_size) - resize!(integrator.u, new_size) - resize!(integrator.du, new_size) - resize!(integrator.r0, new_size) - - # Resize container - # new_size = n_variables * n_nodes^n_dims * n_elements - n_elements = nelements(integrator.p.solver, integrator.p.cache) - resize!(integrator.p, n_elements) -end - -function Base.resize!(semi::AbstractSemidiscretization, new_size) - resize!(semi, semi.solver.volume_integral, new_size) -end - -Base.resize!(semi, volume_integral::AbstractVolumeIntegral, new_size) = nothing - -function Base.resize!(semi, volume_integral::VolumeIntegralSubcellLimiting, new_size) - # Resize container antidiffusive_fluxes - resize!(semi.cache.antidiffusive_fluxes, new_size) - - # Resize container subcell_limiter_coefficients - @unpack limiter = volume_integral - resize!(limiter.cache.subcell_limiter_coefficients, new_size) -end + + # used for AMR + function Base.resize!(integrator::SimpleIntegratorSSP, new_size) + resize!(integrator.u, new_size) + resize!(integrator.du, new_size) + resize!(integrator.r0, new_size) + + # Resize container + # new_size = n_variables * n_nodes^n_dims * n_elements + n_elements = nelements(integrator.p.solver, integrator.p.cache) + resize!(integrator.p, n_elements) + end + + function Base.resize!(semi::AbstractSemidiscretization, new_size) + resize!(semi, semi.solver.volume_integral, new_size) + end + + Base.resize!(semi, volume_integral::AbstractVolumeIntegral, new_size) = nothing + + function Base.resize!(semi, volume_integral::VolumeIntegralSubcellLimiting, new_size) + # Resize container antidiffusive_fluxes + resize!(semi.cache.antidiffusive_fluxes, new_size) + + # Resize container subcell_limiter_coefficients + @unpack limiter = volume_integral + resize!(limiter.cache.subcell_limiter_coefficients, new_size) + end end # @muladd diff --git a/src/time_integration/paired_explicit_runge_kutta/methods_PERK2.jl b/src/time_integration/paired_explicit_runge_kutta/methods_PERK2.jl index 23a3ceba76..d827cfab7d 100644 --- a/src/time_integration/paired_explicit_runge_kutta/methods_PERK2.jl +++ b/src/time_integration/paired_explicit_runge_kutta/methods_PERK2.jl @@ -6,457 +6,495 @@ using DelimitedFiles: readdlm using LinearAlgebra: eigvals @muladd begin -#! format: noindent - -# Abstract base type for both single/standalone and multi-level -# PERK (Paired-Explicit Runge-Kutta) time integration schemes -abstract type AbstractPairedExplicitRK end -# Abstract base type for single/standalone PERK time integration schemes -abstract type AbstractPairedExplicitRKSingle <: AbstractPairedExplicitRK end - -# Compute the coefficients of the A matrix in the Butcher tableau using -# stage scaling factors and monomial coefficients -function compute_a_coeffs(num_stage_evals, stage_scaling_factors, monomial_coeffs) - a_coeffs = copy(monomial_coeffs) - - for stage in 1:(num_stage_evals - 2) - a_coeffs[stage] /= stage_scaling_factors[stage] - for prev_stage in 1:(stage - 1) - a_coeffs[stage] /= a_coeffs[prev_stage] + #! format: noindent + + # Abstract base type for both single/standalone and multi-level + # PERK (Paired-Explicit Runge-Kutta) time integration schemes + abstract type AbstractPairedExplicitRK end + # Abstract base type for single/standalone PERK time integration schemes + abstract type AbstractPairedExplicitRKSingle <: AbstractPairedExplicitRK end + + # Compute the coefficients of the A matrix in the Butcher tableau using + # stage scaling factors and monomial coefficients + function compute_a_coeffs(num_stage_evals, stage_scaling_factors, monomial_coeffs) + a_coeffs = copy(monomial_coeffs) + + for stage in 1:(num_stage_evals - 2) + a_coeffs[stage] /= stage_scaling_factors[stage] + for prev_stage in 1:(stage - 1) + a_coeffs[stage] /= a_coeffs[prev_stage] + end end - end - return reverse(a_coeffs) -end - -# Compute the Butcher tableau for a paired explicit Runge-Kutta method order 2 -# using a list of eigenvalues -function compute_PairedExplicitRK2_butcher_tableau(num_stages, eig_vals, tspan, - bS, cS; verbose = false) - # c Vector from Butcher Tableau (defines timestep per stage) - c = zeros(num_stages) - for k in 2:num_stages - c[k] = cS * (k - 1) / (num_stages - 1) + return reverse(a_coeffs) end - stage_scaling_factors = bS * reverse(c[2:(end - 1)]) - # - 2 Since first entry of A is always zero (explicit method) and second is given by c_2 (consistency) - coeffs_max = num_stages - 2 + # Compute the Butcher tableau for a paired explicit Runge-Kutta method order 2 + # using a list of eigenvalues + function compute_PairedExplicitRK2_butcher_tableau( + num_stages, eig_vals, tspan, + bS, cS; verbose = false + ) + # c Vector from Butcher Tableau (defines timestep per stage) + c = zeros(num_stages) + for k in 2:num_stages + c[k] = cS * (k - 1) / (num_stages - 1) + end + stage_scaling_factors = bS * reverse(c[2:(end - 1)]) + + # - 2 Since first entry of A is always zero (explicit method) and second is given by c_2 (consistency) + coeffs_max = num_stages - 2 + + a_matrix = zeros(coeffs_max, 2) + a_matrix[:, 1] = c[3:end] + + consistency_order = 2 + + dtmax = tspan[2] - tspan[1] + dteps = 1.0e-9 # Hyperparameter of the optimization, might be too large for systems requiring very small timesteps - a_matrix = zeros(coeffs_max, 2) - a_matrix[:, 1] = c[3:end] + num_eig_vals, eig_vals = filter_eig_vals(eig_vals; verbose) - consistency_order = 2 + monomial_coeffs, dt_opt = bisect_stability_polynomial( + consistency_order, + num_eig_vals, num_stages, + dtmax, + dteps, + eig_vals; verbose + ) + monomial_coeffs = undo_normalization!( + monomial_coeffs, consistency_order, + num_stages + ) - dtmax = tspan[2] - tspan[1] - dteps = 1e-9 # Hyperparameter of the optimization, might be too large for systems requiring very small timesteps + num_monomial_coeffs = length(monomial_coeffs) + @assert num_monomial_coeffs == coeffs_max + A = compute_a_coeffs(num_stages, stage_scaling_factors, monomial_coeffs) - num_eig_vals, eig_vals = filter_eig_vals(eig_vals; verbose) + a_matrix[:, 1] -= A + a_matrix[:, 2] = A - monomial_coeffs, dt_opt = bisect_stability_polynomial(consistency_order, - num_eig_vals, num_stages, - dtmax, - dteps, - eig_vals; verbose) - monomial_coeffs = undo_normalization!(monomial_coeffs, consistency_order, - num_stages) + return a_matrix, c + end + + # Compute the Butcher tableau for a paired explicit Runge-Kutta method order 2 + # using provided monomial coefficients file + function compute_PairedExplicitRK2_butcher_tableau( + num_stages, + base_path_monomial_coeffs::AbstractString, + bS, cS + ) + + # c Vector form Butcher Tableau (defines timestep per stage) + c = zeros(num_stages) + for k in 2:num_stages + c[k] = cS * (k - 1) / (num_stages - 1) + end + stage_scaling_factors = bS * reverse(c[2:(end - 1)]) + + # - 2 Since first entry of A is always zero (explicit method) and second is given by c_2 (consistency) + coeffs_max = num_stages - 2 - num_monomial_coeffs = length(monomial_coeffs) - @assert num_monomial_coeffs == coeffs_max - A = compute_a_coeffs(num_stages, stage_scaling_factors, monomial_coeffs) + a_matrix = zeros(coeffs_max, 2) + a_matrix[:, 1] = c[3:end] - a_matrix[:, 1] -= A - a_matrix[:, 2] = A + path_monomial_coeffs = joinpath( + base_path_monomial_coeffs, + "gamma_" * string(num_stages) * ".txt" + ) - return a_matrix, c -end + @assert isfile(path_monomial_coeffs) "Couldn't find file" + monomial_coeffs = readdlm(path_monomial_coeffs, Float64) + num_monomial_coeffs = size(monomial_coeffs, 1) -# Compute the Butcher tableau for a paired explicit Runge-Kutta method order 2 -# using provided monomial coefficients file -function compute_PairedExplicitRK2_butcher_tableau(num_stages, - base_path_monomial_coeffs::AbstractString, - bS, cS) + @assert num_monomial_coeffs == coeffs_max + A = compute_a_coeffs(num_stages, stage_scaling_factors, monomial_coeffs) - # c Vector form Butcher Tableau (defines timestep per stage) - c = zeros(num_stages) - for k in 2:num_stages - c[k] = cS * (k - 1) / (num_stages - 1) + a_matrix[:, 1] -= A + a_matrix[:, 2] = A + + return a_matrix, c end - stage_scaling_factors = bS * reverse(c[2:(end - 1)]) - - # - 2 Since first entry of A is always zero (explicit method) and second is given by c_2 (consistency) - coeffs_max = num_stages - 2 - - a_matrix = zeros(coeffs_max, 2) - a_matrix[:, 1] = c[3:end] - - path_monomial_coeffs = joinpath(base_path_monomial_coeffs, - "gamma_" * string(num_stages) * ".txt") - - @assert isfile(path_monomial_coeffs) "Couldn't find file" - monomial_coeffs = readdlm(path_monomial_coeffs, Float64) - num_monomial_coeffs = size(monomial_coeffs, 1) - - @assert num_monomial_coeffs == coeffs_max - A = compute_a_coeffs(num_stages, stage_scaling_factors, monomial_coeffs) - - a_matrix[:, 1] -= A - a_matrix[:, 2] = A - - return a_matrix, c -end - -@doc raw""" - PairedExplicitRK2(num_stages, base_path_monomial_coeffs::AbstractString, - bS = 1.0, cS = 0.5) - PairedExplicitRK2(num_stages, tspan, semi::AbstractSemidiscretization; - verbose = false, bS = 1.0, cS = 0.5) - PairedExplicitRK2(num_stages, tspan, eig_vals::Vector{ComplexF64}; - verbose = false, bS = 1.0, cS = 0.5) - Parameters: - - `num_stages` (`Int`): Number of stages in the PERK method. - - `base_path_monomial_coeffs` (`AbstractString`): Path to a file containing - monomial coefficients of the stability polynomial of PERK method. - The coefficients should be stored in a text file at `joinpath(base_path_monomial_coeffs, "gamma_$(num_stages).txt")` and separated by line breaks. - - `tspan`: Time span of the simulation. - - `semi` (`AbstractSemidiscretization`): Semidiscretization setup. - - `eig_vals` (`Vector{ComplexF64}`): Eigenvalues of the Jacobian of the right-hand side (rhs) of the ODEProblem after the - equation has been semidiscretized. - - `verbose` (`Bool`, optional): Verbosity flag, default is false. - - `bS` (`Float64`, optional): Value of b in the Butcher tableau at b_s, when - s is the number of stages, default is 1.0. - - `cS` (`Float64`, optional): Value of c in the Butcher tableau at c_s, when - s is the number of stages, default is 0.5. - -The following structures and methods provide a minimal implementation of -the second-order paired explicit Runge-Kutta (PERK) method -optimized for a certain simulation setup (PDE, IC & BC, Riemann Solver, DG Solver). - -- Brian Vermeire (2019). - Paired explicit Runge-Kutta schemes for stiff systems of equations - [DOI: 10.1016/j.jcp.2019.05.014](https://doi.org/10.1016/j.jcp.2019.05.014) -""" -mutable struct PairedExplicitRK2 <: AbstractPairedExplicitRKSingle - const num_stages::Int - - a_matrix::Matrix{Float64} - c::Vector{Float64} - b1::Float64 - bS::Float64 - cS::Float64 -end # struct PairedExplicitRK2 - -# Constructor that reads the coefficients from a file -function PairedExplicitRK2(num_stages, base_path_monomial_coeffs::AbstractString, - bS = 1.0, cS = 0.5) - a_matrix, c = compute_PairedExplicitRK2_butcher_tableau(num_stages, - base_path_monomial_coeffs, - bS, cS) - - return PairedExplicitRK2(num_stages, a_matrix, c, 1 - bS, bS, cS) -end - -# Constructor that calculates the coefficients with polynomial optimizer from a -# semidiscretization -function PairedExplicitRK2(num_stages, tspan, semi::AbstractSemidiscretization; - verbose = false, - bS = 1.0, cS = 0.5) - eig_vals = eigvals(jacobian_ad_forward(semi)) - - return PairedExplicitRK2(num_stages, tspan, eig_vals; verbose, bS, cS) -end - -# Constructor that calculates the coefficients with polynomial optimizer from a -# list of eigenvalues -function PairedExplicitRK2(num_stages, tspan, eig_vals::Vector{ComplexF64}; - verbose = false, - bS = 1.0, cS = 0.5) - a_matrix, c = compute_PairedExplicitRK2_butcher_tableau(num_stages, - eig_vals, tspan, - bS, cS; - verbose) - - return PairedExplicitRK2(num_stages, a_matrix, c, 1 - bS, bS, cS) -end - -# This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L1 -mutable struct PairedExplicitRKOptions{Callback, TStops} - callback::Callback # callbacks; used in Trixi - adaptive::Bool # whether the algorithm is adaptive - dtmax::Float64 # ignored - maxiters::Int # maximal number of time steps - tstops::TStops # tstops from https://diffeq.sciml.ai/v6.8/basics/common_solver_opts/#Output-Control-1; ignored -end - -function PairedExplicitRKOptions(callback, tspan; maxiters = typemax(Int), kwargs...) - tstops_internal = BinaryHeap{eltype(tspan)}(FasterForward()) - # We add last(tspan) to make sure that the time integration stops at the end time - push!(tstops_internal, last(tspan)) - # We add 2 * last(tspan) because add_tstop!(integrator, t) is only called by DiffEqCallbacks.jl if tstops contains a time that is larger than t - # (https://github.com/SciML/DiffEqCallbacks.jl/blob/025dfe99029bd0f30a2e027582744528eb92cd24/src/iterative_and_periodic.jl#L92) - push!(tstops_internal, 2 * last(tspan)) - PairedExplicitRKOptions{typeof(callback), typeof(tstops_internal)}(callback, - false, Inf, - maxiters, - tstops_internal) -end - -abstract type PairedExplicitRK end -abstract type AbstractPairedExplicitRKSingleIntegrator <: PairedExplicitRK end - -# This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L77 -# This implements the interface components described at -# https://diffeq.sciml.ai/v6.8/basics/integrator/#Handing-Integrators-1 -# which are used in Trixi. -mutable struct PairedExplicitRK2Integrator{RealT <: Real, uType, Params, Sol, F, Alg, - PairedExplicitRKOptions} <: - AbstractPairedExplicitRKSingleIntegrator - u::uType - du::uType - u_tmp::uType - t::RealT - tdir::RealT - dt::RealT # current time step - dtcache::RealT # manually set time step - iter::Int # current number of time steps (iteration) - p::Params # will be the semidiscretization from Trixi - sol::Sol # faked - f::F - alg::Alg # This is our own class written above; Abbreviation for ALGorithm - opts::PairedExplicitRKOptions - finalstep::Bool # added for convenience - dtchangeable::Bool - force_stepfail::Bool - # PairedExplicitRK2 stages: - k1::uType - k_higher::uType -end - -""" - add_tstop!(integrator::PairedExplicitRK2Integrator, t) -Add a time stop during the time integration process. -This function is called after the periodic SaveSolutionCallback to specify the next stop to save the solution. -""" -function add_tstop!(integrator::PairedExplicitRK2Integrator, t) - integrator.tdir * (t - integrator.t) < zero(integrator.t) && - error("Tried to add a tstop that is behind the current time. This is strictly forbidden") - # We need to remove the first entry of tstops when a new entry is added. - # Otherwise, the simulation gets stuck at the previous tstop and dt is adjusted to zero. - if length(integrator.opts.tstops) > 1 - pop!(integrator.opts.tstops) + + @doc raw""" + PairedExplicitRK2(num_stages, base_path_monomial_coeffs::AbstractString, + bS = 1.0, cS = 0.5) + PairedExplicitRK2(num_stages, tspan, semi::AbstractSemidiscretization; + verbose = false, bS = 1.0, cS = 0.5) + PairedExplicitRK2(num_stages, tspan, eig_vals::Vector{ComplexF64}; + verbose = false, bS = 1.0, cS = 0.5) + Parameters: + - `num_stages` (`Int`): Number of stages in the PERK method. + - `base_path_monomial_coeffs` (`AbstractString`): Path to a file containing + monomial coefficients of the stability polynomial of PERK method. + The coefficients should be stored in a text file at `joinpath(base_path_monomial_coeffs, "gamma_$(num_stages).txt")` and separated by line breaks. + - `tspan`: Time span of the simulation. + - `semi` (`AbstractSemidiscretization`): Semidiscretization setup. + - `eig_vals` (`Vector{ComplexF64}`): Eigenvalues of the Jacobian of the right-hand side (rhs) of the ODEProblem after the + equation has been semidiscretized. + - `verbose` (`Bool`, optional): Verbosity flag, default is false. + - `bS` (`Float64`, optional): Value of b in the Butcher tableau at b_s, when + s is the number of stages, default is 1.0. + - `cS` (`Float64`, optional): Value of c in the Butcher tableau at c_s, when + s is the number of stages, default is 0.5. + + The following structures and methods provide a minimal implementation of + the second-order paired explicit Runge-Kutta (PERK) method + optimized for a certain simulation setup (PDE, IC & BC, Riemann Solver, DG Solver). + + - Brian Vermeire (2019). + Paired explicit Runge-Kutta schemes for stiff systems of equations + [DOI: 10.1016/j.jcp.2019.05.014](https://doi.org/10.1016/j.jcp.2019.05.014) + """ + mutable struct PairedExplicitRK2 <: AbstractPairedExplicitRKSingle + const num_stages::Int + + a_matrix::Matrix{Float64} + c::Vector{Float64} + b1::Float64 + bS::Float64 + cS::Float64 + end # struct PairedExplicitRK2 + + # Constructor that reads the coefficients from a file + function PairedExplicitRK2( + num_stages, base_path_monomial_coeffs::AbstractString, + bS = 1.0, cS = 0.5 + ) + a_matrix, c = compute_PairedExplicitRK2_butcher_tableau( + num_stages, + base_path_monomial_coeffs, + bS, cS + ) + + return PairedExplicitRK2(num_stages, a_matrix, c, 1 - bS, bS, cS) end - push!(integrator.opts.tstops, integrator.tdir * t) -end -has_tstop(integrator::PairedExplicitRK2Integrator) = !isempty(integrator.opts.tstops) -first_tstop(integrator::PairedExplicitRK2Integrator) = first(integrator.opts.tstops) + # Constructor that calculates the coefficients with polynomial optimizer from a + # semidiscretization + function PairedExplicitRK2( + num_stages, tspan, semi::AbstractSemidiscretization; + verbose = false, + bS = 1.0, cS = 0.5 + ) + eig_vals = eigvals(jacobian_ad_forward(semi)) -# Forward integrator.stats.naccept to integrator.iter (see GitHub PR#771) -function Base.getproperty(integrator::PairedExplicitRK, field::Symbol) - if field === :stats - return (naccept = getfield(integrator, :iter),) + return PairedExplicitRK2(num_stages, tspan, eig_vals; verbose, bS, cS) end - # general fallback - return getfield(integrator, field) -end - -function init(ode::ODEProblem, alg::PairedExplicitRK2; - dt, callback::Union{CallbackSet, Nothing} = nothing, kwargs...) - u0 = copy(ode.u0) - du = zero(u0) - u_tmp = zero(u0) - - # PairedExplicitRK2 stages - k1 = zero(u0) - k_higher = zero(u0) - - t0 = first(ode.tspan) - tdir = sign(ode.tspan[end] - ode.tspan[1]) - iter = 0 - - integrator = PairedExplicitRK2Integrator(u0, du, u_tmp, t0, tdir, dt, dt, iter, - ode.p, - (prob = ode,), ode.f, alg, - PairedExplicitRKOptions(callback, - ode.tspan; - kwargs...), - false, true, false, - k1, k_higher) - - # initialize callbacks - if callback isa CallbackSet - for cb in callback.continuous_callbacks - throw(ArgumentError("Continuous callbacks are unsupported with paired explicit Runge-Kutta methods.")) - end - for cb in callback.discrete_callbacks - cb.initialize(cb, integrator.u, integrator.t, integrator) - end + + # Constructor that calculates the coefficients with polynomial optimizer from a + # list of eigenvalues + function PairedExplicitRK2( + num_stages, tspan, eig_vals::Vector{ComplexF64}; + verbose = false, + bS = 1.0, cS = 0.5 + ) + a_matrix, c = compute_PairedExplicitRK2_butcher_tableau( + num_stages, + eig_vals, tspan, + bS, cS; + verbose + ) + + return PairedExplicitRK2(num_stages, a_matrix, c, 1 - bS, bS, cS) end - return integrator -end + # This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L1 + mutable struct PairedExplicitRKOptions{Callback, TStops} + callback::Callback # callbacks; used in Trixi + adaptive::Bool # whether the algorithm is adaptive + dtmax::Float64 # ignored + maxiters::Int # maximal number of time steps + tstops::TStops # tstops from https://diffeq.sciml.ai/v6.8/basics/common_solver_opts/#Output-Control-1; ignored + end -# Fakes `solve`: https://diffeq.sciml.ai/v6.8/basics/overview/#Solving-the-Problems-1 -function solve(ode::ODEProblem, alg::PairedExplicitRK2; - dt, callback = nothing, kwargs...) - integrator = init(ode, alg, dt = dt, callback = callback; kwargs...) + function PairedExplicitRKOptions(callback, tspan; maxiters = typemax(Int), kwargs...) + tstops_internal = BinaryHeap{eltype(tspan)}(FasterForward()) + # We add last(tspan) to make sure that the time integration stops at the end time + push!(tstops_internal, last(tspan)) + # We add 2 * last(tspan) because add_tstop!(integrator, t) is only called by DiffEqCallbacks.jl if tstops contains a time that is larger than t + # (https://github.com/SciML/DiffEqCallbacks.jl/blob/025dfe99029bd0f30a2e027582744528eb92cd24/src/iterative_and_periodic.jl#L92) + push!(tstops_internal, 2 * last(tspan)) + PairedExplicitRKOptions{typeof(callback), typeof(tstops_internal)}( + callback, + false, Inf, + maxiters, + tstops_internal + ) + end - # Start actual solve - solve!(integrator) -end + abstract type PairedExplicitRK end + abstract type AbstractPairedExplicitRKSingleIntegrator <: PairedExplicitRK end + + # This struct is needed to fake https://github.com/SciML/OrdinaryDiffEq.jl/blob/0c2048a502101647ac35faabd80da8a5645beac7/src/integrators/type.jl#L77 + # This implements the interface components described at + # https://diffeq.sciml.ai/v6.8/basics/integrator/#Handing-Integrators-1 + # which are used in Trixi. + mutable struct PairedExplicitRK2Integrator{ + RealT <: Real, uType, Params, Sol, F, Alg, + PairedExplicitRKOptions, + } <: + AbstractPairedExplicitRKSingleIntegrator + u::uType + du::uType + u_tmp::uType + t::RealT + tdir::RealT + dt::RealT # current time step + dtcache::RealT # manually set time step + iter::Int # current number of time steps (iteration) + p::Params # will be the semidiscretization from Trixi + sol::Sol # faked + f::F + alg::Alg # This is our own class written above; Abbreviation for ALGorithm + opts::PairedExplicitRKOptions + finalstep::Bool # added for convenience + dtchangeable::Bool + force_stepfail::Bool + # PairedExplicitRK2 stages: + k1::uType + k_higher::uType + end + + """ + add_tstop!(integrator::PairedExplicitRK2Integrator, t) + Add a time stop during the time integration process. + This function is called after the periodic SaveSolutionCallback to specify the next stop to save the solution. + """ + function add_tstop!(integrator::PairedExplicitRK2Integrator, t) + integrator.tdir * (t - integrator.t) < zero(integrator.t) && + error("Tried to add a tstop that is behind the current time. This is strictly forbidden") + # We need to remove the first entry of tstops when a new entry is added. + # Otherwise, the simulation gets stuck at the previous tstop and dt is adjusted to zero. + if length(integrator.opts.tstops) > 1 + pop!(integrator.opts.tstops) + end + push!(integrator.opts.tstops, integrator.tdir * t) + end -function solve!(integrator::PairedExplicitRK2Integrator) - @unpack prob = integrator.sol + has_tstop(integrator::PairedExplicitRK2Integrator) = !isempty(integrator.opts.tstops) + first_tstop(integrator::PairedExplicitRK2Integrator) = first(integrator.opts.tstops) - integrator.finalstep = false + # Forward integrator.stats.naccept to integrator.iter (see GitHub PR#771) + function Base.getproperty(integrator::PairedExplicitRK, field::Symbol) + if field === :stats + return (naccept = getfield(integrator, :iter),) + end + # general fallback + return getfield(integrator, field) + end - @trixi_timeit timer() "main loop" while !integrator.finalstep - step!(integrator) - end # "main loop" timer + function init( + ode::ODEProblem, alg::PairedExplicitRK2; + dt, callback::Union{CallbackSet, Nothing} = nothing, kwargs... + ) + u0 = copy(ode.u0) + du = zero(u0) + u_tmp = zero(u0) + + # PairedExplicitRK2 stages + k1 = zero(u0) + k_higher = zero(u0) + + t0 = first(ode.tspan) + tdir = sign(ode.tspan[end] - ode.tspan[1]) + iter = 0 + + integrator = PairedExplicitRK2Integrator( + u0, du, u_tmp, t0, tdir, dt, dt, iter, + ode.p, + (prob = ode,), ode.f, alg, + PairedExplicitRKOptions( + callback, + ode.tspan; + kwargs... + ), + false, true, false, + k1, k_higher + ) + + # initialize callbacks + if callback isa CallbackSet + for cb in callback.continuous_callbacks + throw(ArgumentError("Continuous callbacks are unsupported with paired explicit Runge-Kutta methods.")) + end + for cb in callback.discrete_callbacks + cb.initialize(cb, integrator.u, integrator.t, integrator) + end + end - return TimeIntegratorSolution((first(prob.tspan), integrator.t), - (prob.u0, integrator.u), - integrator.sol.prob) -end + return integrator + end -function step!(integrator::PairedExplicitRK2Integrator) - @unpack prob = integrator.sol - @unpack alg = integrator - t_end = last(prob.tspan) - callbacks = integrator.opts.callback + # Fakes `solve`: https://diffeq.sciml.ai/v6.8/basics/overview/#Solving-the-Problems-1 + function solve( + ode::ODEProblem, alg::PairedExplicitRK2; + dt, callback = nothing, kwargs... + ) + integrator = init(ode, alg, dt = dt, callback = callback; kwargs...) - @assert !integrator.finalstep - if isnan(integrator.dt) - error("time step size `dt` is NaN") + # Start actual solve + solve!(integrator) end - modify_dt_for_tstops!(integrator) + function solve!(integrator::PairedExplicitRK2Integrator) + @unpack prob = integrator.sol - # if the next iteration would push the simulation beyond the end time, set dt accordingly - if integrator.t + integrator.dt > t_end || - isapprox(integrator.t + integrator.dt, t_end) - integrator.dt = t_end - integrator.t - terminate!(integrator) + integrator.finalstep = false + + @trixi_timeit timer() "main loop" while !integrator.finalstep + step!(integrator) + end # "main loop" timer + + return TimeIntegratorSolution( + (first(prob.tspan), integrator.t), + (prob.u0, integrator.u), + integrator.sol.prob + ) end - @trixi_timeit timer() "Paired Explicit Runge-Kutta ODE integration step" begin - # k1 - integrator.f(integrator.du, integrator.u, prob.p, integrator.t) - @threaded for i in eachindex(integrator.du) - integrator.k1[i] = integrator.du[i] * integrator.dt - end + function step!(integrator::PairedExplicitRK2Integrator) + @unpack prob = integrator.sol + @unpack alg = integrator + t_end = last(prob.tspan) + callbacks = integrator.opts.callback - # Construct current state - @threaded for i in eachindex(integrator.u) - integrator.u_tmp[i] = integrator.u[i] + alg.c[2] * integrator.k1[i] + @assert !integrator.finalstep + if isnan(integrator.dt) + error("time step size `dt` is NaN") end - # k2 - integrator.f(integrator.du, integrator.u_tmp, prob.p, - integrator.t + alg.c[2] * integrator.dt) - @threaded for i in eachindex(integrator.du) - integrator.k_higher[i] = integrator.du[i] * integrator.dt + modify_dt_for_tstops!(integrator) + + # if the next iteration would push the simulation beyond the end time, set dt accordingly + if integrator.t + integrator.dt > t_end || + isapprox(integrator.t + integrator.dt, t_end) + integrator.dt = t_end - integrator.t + terminate!(integrator) end - # Higher stages - for stage in 3:(alg.num_stages) + @trixi_timeit timer() "Paired Explicit Runge-Kutta ODE integration step" begin + # k1 + integrator.f(integrator.du, integrator.u, prob.p, integrator.t) + @threaded for i in eachindex(integrator.du) + integrator.k1[i] = integrator.du[i] * integrator.dt + end + # Construct current state @threaded for i in eachindex(integrator.u) - integrator.u_tmp[i] = integrator.u[i] + - alg.a_matrix[stage - 2, 1] * - integrator.k1[i] + - alg.a_matrix[stage - 2, 2] * - integrator.k_higher[i] + integrator.u_tmp[i] = integrator.u[i] + alg.c[2] * integrator.k1[i] end - - integrator.f(integrator.du, integrator.u_tmp, prob.p, - integrator.t + alg.c[stage] * integrator.dt) + # k2 + integrator.f( + integrator.du, integrator.u_tmp, prob.p, + integrator.t + alg.c[2] * integrator.dt + ) @threaded for i in eachindex(integrator.du) integrator.k_higher[i] = integrator.du[i] * integrator.dt end - end - @threaded for i in eachindex(integrator.u) - integrator.u[i] += alg.b1 * integrator.k1[i] + - alg.bS * integrator.k_higher[i] - end - end # PairedExplicitRK2 step + # Higher stages + for stage in 3:(alg.num_stages) + # Construct current state + @threaded for i in eachindex(integrator.u) + integrator.u_tmp[i] = integrator.u[i] + + alg.a_matrix[stage - 2, 1] * + integrator.k1[i] + + alg.a_matrix[stage - 2, 2] * + integrator.k_higher[i] + end - integrator.iter += 1 - integrator.t += integrator.dt + integrator.f( + integrator.du, integrator.u_tmp, prob.p, + integrator.t + alg.c[stage] * integrator.dt + ) - @trixi_timeit timer() "Step-Callbacks" begin - # handle callbacks - if callbacks isa CallbackSet - foreach(callbacks.discrete_callbacks) do cb - if cb.condition(integrator.u, integrator.t, integrator) - cb.affect!(integrator) + @threaded for i in eachindex(integrator.du) + integrator.k_higher[i] = integrator.du[i] * integrator.dt end - return nothing end + + @threaded for i in eachindex(integrator.u) + integrator.u[i] += alg.b1 * integrator.k1[i] + + alg.bS * integrator.k_higher[i] + end + end # PairedExplicitRK2 step + + integrator.iter += 1 + integrator.t += integrator.dt + + @trixi_timeit timer() "Step-Callbacks" begin + # handle callbacks + if callbacks isa CallbackSet + foreach(callbacks.discrete_callbacks) do cb + if cb.condition(integrator.u, integrator.t, integrator) + cb.affect!(integrator) + end + return nothing + end + end + end + + # respect maximum number of iterations + if integrator.iter >= integrator.opts.maxiters && !integrator.finalstep + @warn "Interrupted. Larger maxiters is needed." + terminate!(integrator) end end - # respect maximum number of iterations - if integrator.iter >= integrator.opts.maxiters && !integrator.finalstep - @warn "Interrupted. Larger maxiters is needed." - terminate!(integrator) + # get a cache where the RHS can be stored + get_du(integrator::PairedExplicitRK) = integrator.du + get_tmp_cache(integrator::PairedExplicitRK) = (integrator.u_tmp,) + + # some algorithms from DiffEq like FSAL-ones need to be informed when a callback has modified u + u_modified!(integrator::PairedExplicitRK, ::Bool) = false + + # used by adaptive timestepping algorithms in DiffEq + function set_proposed_dt!(integrator::PairedExplicitRK, dt) + (integrator.dt = dt; integrator.dtcache = dt) end -end - -# get a cache where the RHS can be stored -get_du(integrator::PairedExplicitRK) = integrator.du -get_tmp_cache(integrator::PairedExplicitRK) = (integrator.u_tmp,) - -# some algorithms from DiffEq like FSAL-ones need to be informed when a callback has modified u -u_modified!(integrator::PairedExplicitRK, ::Bool) = false - -# used by adaptive timestepping algorithms in DiffEq -function set_proposed_dt!(integrator::PairedExplicitRK, dt) - (integrator.dt = dt; integrator.dtcache = dt) -end - -function get_proposed_dt(integrator::PairedExplicitRK) - return ifelse(integrator.opts.adaptive, integrator.dt, integrator.dtcache) -end - -# stop the time integration -function terminate!(integrator::PairedExplicitRK) - integrator.finalstep = true -end - -""" - modify_dt_for_tstops!(integrator::PairedExplicitRK) -Modify the time-step size to match the time stops specified in integrator.opts.tstops. -To avoid adding OrdinaryDiffEq to Trixi's dependencies, this routine is a copy of -https://github.com/SciML/OrdinaryDiffEq.jl/blob/d76335281c540ee5a6d1bd8bb634713e004f62ee/src/integrators/integrator_utils.jl#L38-L54 -""" -function modify_dt_for_tstops!(integrator::PairedExplicitRK) - if has_tstop(integrator) - tdir_t = integrator.tdir * integrator.t - tdir_tstop = first_tstop(integrator) - if integrator.opts.adaptive - integrator.dt = integrator.tdir * - min(abs(integrator.dt), abs(tdir_tstop - tdir_t)) # step! to the end - elseif iszero(integrator.dtcache) && integrator.dtchangeable - integrator.dt = integrator.tdir * abs(tdir_tstop - tdir_t) - elseif integrator.dtchangeable && !integrator.force_stepfail - # always try to step! with dtcache, but lower if a tstop - # however, if force_stepfail then don't set to dtcache, and no tstop worry - integrator.dt = integrator.tdir * - min(abs(integrator.dtcache), abs(tdir_tstop - tdir_t)) # step! to the end + + function get_proposed_dt(integrator::PairedExplicitRK) + return ifelse(integrator.opts.adaptive, integrator.dt, integrator.dtcache) + end + + # stop the time integration + function terminate!(integrator::PairedExplicitRK) + integrator.finalstep = true + end + + """ + modify_dt_for_tstops!(integrator::PairedExplicitRK) + Modify the time-step size to match the time stops specified in integrator.opts.tstops. + To avoid adding OrdinaryDiffEq to Trixi's dependencies, this routine is a copy of + https://github.com/SciML/OrdinaryDiffEq.jl/blob/d76335281c540ee5a6d1bd8bb634713e004f62ee/src/integrators/integrator_utils.jl#L38-L54 + """ + function modify_dt_for_tstops!(integrator::PairedExplicitRK) + if has_tstop(integrator) + tdir_t = integrator.tdir * integrator.t + tdir_tstop = first_tstop(integrator) + if integrator.opts.adaptive + integrator.dt = integrator.tdir * + min(abs(integrator.dt), abs(tdir_tstop - tdir_t)) # step! to the end + elseif iszero(integrator.dtcache) && integrator.dtchangeable + integrator.dt = integrator.tdir * abs(tdir_tstop - tdir_t) + elseif integrator.dtchangeable && !integrator.force_stepfail + # always try to step! with dtcache, but lower if a tstop + # however, if force_stepfail then don't set to dtcache, and no tstop worry + integrator.dt = integrator.tdir * + min(abs(integrator.dtcache), abs(tdir_tstop - tdir_t)) # step! to the end + end end end -end -# used for AMR (Adaptive Mesh Refinement) -function Base.resize!(integrator::PairedExplicitRK2Integrator, new_size) - resize!(integrator.u, new_size) - resize!(integrator.du, new_size) - resize!(integrator.u_tmp, new_size) + # used for AMR (Adaptive Mesh Refinement) + function Base.resize!(integrator::PairedExplicitRK2Integrator, new_size) + resize!(integrator.u, new_size) + resize!(integrator.du, new_size) + resize!(integrator.u_tmp, new_size) - resize!(integrator.k1, new_size) - resize!(integrator.k_higher, new_size) -end + resize!(integrator.k1, new_size) + resize!(integrator.k_higher, new_size) + end end # @muladd diff --git a/src/time_integration/paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl b/src/time_integration/paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl index b73ea75831..40729eff88 100644 --- a/src/time_integration/paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl +++ b/src/time_integration/paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl @@ -3,10 +3,10 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Basic implementation of the second-order paired explicit Runge-Kutta (PERK) method -include("methods_PERK2.jl") -# Define all of the functions necessary for polynomial optimizations -include("polynomial_optimizer.jl") + # Basic implementation of the second-order paired explicit Runge-Kutta (PERK) method + include("methods_PERK2.jl") + # Define all of the functions necessary for polynomial optimizations + include("polynomial_optimizer.jl") end # @muladd diff --git a/src/time_integration/paired_explicit_runge_kutta/polynomial_optimizer.jl b/src/time_integration/paired_explicit_runge_kutta/polynomial_optimizer.jl index bfd53ba2ea..063e53e7cb 100644 --- a/src/time_integration/paired_explicit_runge_kutta/polynomial_optimizer.jl +++ b/src/time_integration/paired_explicit_runge_kutta/polynomial_optimizer.jl @@ -1,7 +1,7 @@ # Filter out eigenvalues with positive real parts, those with negative imaginary # parts due to eigenvalues' symmetry around the real axis, or the eigenvalues # that are smaller than a specified threshold. -function filter_eig_vals(eig_vals, threshold = 1e-12; verbose = false) +function filter_eig_vals(eig_vals, threshold = 1.0e-12; verbose = false) filtered_eig_vals = Complex{Float64}[] for eig_val in eig_vals @@ -13,9 +13,11 @@ function filter_eig_vals(eig_vals, threshold = 1e-12; verbose = false) filtered_eig_vals_count = length(eig_vals) - length(filtered_eig_vals) if verbose - println("$filtered_eig_vals_count eigenvalue(s) are not passed on because " * + println( + "$filtered_eig_vals_count eigenvalue(s) are not passed on because " * "they either are in magnitude smaller than $threshold, have positive " * - "real parts, or have negative imaginary parts.\n") + "real parts, or have negative imaginary parts.\n" + ) end return length(filtered_eig_vals), filtered_eig_vals diff --git a/src/time_integration/time_integration.jl b/src/time_integration/time_integration.jl index d19a1fcc37..55c5e49fcf 100644 --- a/src/time_integration/time_integration.jl +++ b/src/time_integration/time_integration.jl @@ -3,18 +3,18 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -# Wrapper type for solutions from Trixi.jl's own time integrators, partially mimicking -# SciMLBase.ODESolution -struct TimeIntegratorSolution{tType, uType, P} - t::tType - u::uType - prob::P -end + # Wrapper type for solutions from Trixi.jl's own time integrators, partially mimicking + # SciMLBase.ODESolution + struct TimeIntegratorSolution{tType, uType, P} + t::tType + u::uType + prob::P + end -include("methods_2N.jl") -include("methods_3Sstar.jl") -include("methods_SSP.jl") -include("paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl") + include("methods_2N.jl") + include("methods_3Sstar.jl") + include("methods_SSP.jl") + include("paired_explicit_runge_kutta/paired_explicit_runge_kutta.jl") end # @muladd diff --git a/src/visualization/recipes_plots.jl b/src/visualization/recipes_plots.jl index 0e9b5a66a8..927b1f05c5 100644 --- a/src/visualization/recipes_plots.jl +++ b/src/visualization/recipes_plots.jl @@ -3,273 +3,297 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# Visualize a single variable in a 2D plot (default: heatmap) -# -# Note: This is an experimental feature and may be changed in future releases without notice. -RecipesBase.@recipe function f(pds::PlotDataSeries{<:AbstractPlotData{2}}) - @unpack plot_data, variable_id = pds - @unpack x, y, data, variable_names, orientation_x, orientation_y = plot_data - - # Set geometric properties - xlims --> (x[begin], x[end]) - ylims --> (y[begin], y[end]) - aspect_ratio --> :equal - - # Set annotation properties - legend --> :none - title --> variable_names[variable_id] - colorbar --> :true - xguide --> _get_guide(orientation_x) - yguide --> _get_guide(orientation_y) - - # Set series properties - seriestype --> :heatmap - - # Return data for plotting - x, y, data[variable_id] -end - -# Visualize the mesh in a 2D plot -# -# Note: This is an experimental feature and may be changed in future releases without notice. -RecipesBase.@recipe function f(pm::PlotMesh{<:AbstractPlotData{2}}) - @unpack plot_data = pm - @unpack x, y, mesh_vertices_x, mesh_vertices_y = plot_data - - # Set geometric and annotation properties - xlims --> (x[begin], x[end]) - ylims --> (y[begin], y[end]) - aspect_ratio --> :equal - legend --> :none - grid --> false - - # Set series properties - seriestype --> :path - linecolor --> :grey - linewidth --> 1 - - # Return data for plotting - mesh_vertices_x, mesh_vertices_y -end - -# Visualize the mesh in a 2D plot -# -# Note: This is an experimental feature and may be changed in future releases without notice. -RecipesBase.@recipe function f(pm::PlotMesh{<:PlotData2DCartesian{<:Any, - <:AbstractVector{<:AbstractVector}}}) - @unpack plot_data = pm - @unpack x, y, mesh_vertices_x, mesh_vertices_y = plot_data - - # Set geometric and annotation properties - xlims --> (minimum(x), maximum(x)) - ylims --> (minimum(y), maximum(y)) - aspect_ratio --> :equal - legend --> :none - grid --> false - - # Set series properties - seriestype --> :path - linecolor --> :grey - linewidth --> 1 - - # Return data for plotting - mesh_vertices_x, mesh_vertices_y -end - -# Plot all available variables at once for convenience -# -# Note: This is an experimental feature and may be changed in future releases without notice. -RecipesBase.@recipe function f(pd::AbstractPlotData) - # Create layout that is as square as possible, when there are more than 3 subplots. - # This is done with a preference for more columns than rows if not. - - if length(pd) <= 3 - cols = length(pd) - rows = 1 - else - cols = ceil(Int, sqrt(length(pd))) - rows = ceil(Int, length(pd) / cols) + #! format: noindent + + # Visualize a single variable in a 2D plot (default: heatmap) + # + # Note: This is an experimental feature and may be changed in future releases without notice. + RecipesBase.@recipe function f(pds::PlotDataSeries{<:AbstractPlotData{2}}) + @unpack plot_data, variable_id = pds + @unpack x, y, data, variable_names, orientation_x, orientation_y = plot_data + + # Set geometric properties + xlims --> (x[begin], x[end]) + ylims --> (y[begin], y[end]) + aspect_ratio --> :equal + + # Set annotation properties + legend --> :none + title --> variable_names[variable_id] + colorbar --> :true + xguide --> _get_guide(orientation_x) + yguide --> _get_guide(orientation_y) + + # Set series properties + seriestype --> :heatmap + + # Return data for plotting + x, y, data[variable_id] end - layout := (rows, cols) + # Visualize the mesh in a 2D plot + # + # Note: This is an experimental feature and may be changed in future releases without notice. + RecipesBase.@recipe function f(pm::PlotMesh{<:AbstractPlotData{2}}) + @unpack plot_data = pm + @unpack x, y, mesh_vertices_x, mesh_vertices_y = plot_data + + # Set geometric and annotation properties + xlims --> (x[begin], x[end]) + ylims --> (y[begin], y[end]) + aspect_ratio --> :equal + legend --> :none + grid --> false + + # Set series properties + seriestype --> :path + linecolor --> :grey + linewidth --> 1 + + # Return data for plotting + mesh_vertices_x, mesh_vertices_y + end + + # Visualize the mesh in a 2D plot + # + # Note: This is an experimental feature and may be changed in future releases without notice. + RecipesBase.@recipe function f( + pm::PlotMesh{ + <:PlotData2DCartesian{ + <:Any, + <:AbstractVector{<:AbstractVector}, + }, + } + ) + @unpack plot_data = pm + @unpack x, y, mesh_vertices_x, mesh_vertices_y = plot_data + + # Set geometric and annotation properties + xlims --> (minimum(x), maximum(x)) + ylims --> (minimum(y), maximum(y)) + aspect_ratio --> :equal + legend --> :none + grid --> false + + # Set series properties + seriestype --> :path + linecolor --> :grey + linewidth --> 1 + + # Return data for plotting + mesh_vertices_x, mesh_vertices_y + end + + # Plot all available variables at once for convenience + # + # Note: This is an experimental feature and may be changed in future releases without notice. + RecipesBase.@recipe function f(pd::AbstractPlotData) + # Create layout that is as square as possible, when there are more than 3 subplots. + # This is done with a preference for more columns than rows if not. + + if length(pd) <= 3 + cols = length(pd) + rows = 1 + else + cols = ceil(Int, sqrt(length(pd))) + rows = ceil(Int, length(pd) / cols) + end + + layout := (rows, cols) + + # Plot all existing variables + for (i, (variable_name, series)) in enumerate(pd) + RecipesBase.@series begin + subplot := i + series + end + end + + # Fill remaining subplots with empty plot + for i in (length(pd) + 1):(rows * cols) + RecipesBase.@series begin + subplot := i + axis := false + ticks := false + legend := false + [], [] + end + end + end + + # Plot a single variable. + RecipesBase.@recipe function f(pds::PlotDataSeries{<:AbstractPlotData{1}}) + @unpack plot_data, variable_id = pds + @unpack x, data, variable_names, orientation_x = plot_data + + # Set geometric properties + xlims --> (x[begin], x[end]) + + # Set annotation properties + legend --> :none + title --> variable_names[variable_id] + xguide --> _get_guide(orientation_x) + + # Return data for plotting + x, data[:, variable_id] + end - # Plot all existing variables - for (i, (variable_name, series)) in enumerate(pd) - RecipesBase.@series begin - subplot := i - series + # Plot the mesh as vertical lines from a PlotMesh object. + RecipesBase.@recipe function f(pm::PlotMesh{<:AbstractPlotData{1}}) + @unpack plot_data = pm + @unpack x, mesh_vertices_x = plot_data + + # Set geometric and annotation properties + xlims --> (x[begin], x[end]) + legend --> :none + + # Set series properties + seriestype --> :vline + linecolor --> :grey + linewidth --> 1 + + # Return data for plotting + mesh_vertices_x + end + + # Create a plot directly from a TrixiODESolution for convenience + # The plot is created by a PlotData1D or PlotData2D object. + # + # Note: This is an experimental feature and may be changed in future releases without notice. + RecipesBase.@recipe function f(sol::TrixiODESolution) + # Redirect everything to the recipes below + return sol.u[end], sol.prob.p + end + + # Recipe for general semidiscretizations + # Note: If you change the defaults values here, you need to also change them in the PlotData1D or PlotData2D + # constructor. + RecipesBase.@recipe function f( + u, semi::AbstractSemidiscretization; + solution_variables = nothing + ) + if ndims(semi) == 1 + return PlotData1D(u, semi; solution_variables = solution_variables) + else + return PlotData2D(u, semi; solution_variables = solution_variables) + end + end + + # Recipe specifically for TreeMesh-type solutions + # Note: If you change the defaults values here, you need to also change them in the PlotData1D or PlotData2D + # constructor. + RecipesBase.@recipe function f( + u, semi::SemidiscretizationHyperbolic{<:TreeMesh}; + solution_variables = nothing, + grid_lines = true, max_supported_level = 11, + nvisnodes = nothing, slice = :xy, + point = (0.0, 0.0, 0.0), curve = nothing + ) + # Create a PlotData1D or PlotData2D object depending on the dimension. + if ndims(semi) == 1 + return PlotData1D(u, semi; solution_variables, nvisnodes, slice, point, curve) + else + return PlotData2D( + u, semi; + solution_variables, grid_lines, max_supported_level, + nvisnodes, slice, point + ) end end - # Fill remaining subplots with empty plot - for i in (length(pd) + 1):(rows * cols) - RecipesBase.@series begin - subplot := i - axis := false - ticks := false - legend := false - [], [] + # Series recipe for PlotData2DTriangulated + RecipesBase.@recipe function f(pds::PlotDataSeries{<:PlotData2DTriangulated}) + pd = pds.plot_data + @unpack variable_id = pds + @unpack x, y, data, t, variable_names = pd + + # extract specific solution field to plot + data_field = zeros(eltype(first(data)), size(data)) + for (i, data_i) in enumerate(data) + data_field[i] = data_i[variable_id] end + + legend --> false + aspect_ratio --> 1 + title --> pd.variable_names[variable_id] + xlims --> extrema(x) + ylims --> extrema(y) + xguide --> _get_guide(1) + yguide --> _get_guide(2) + seriestype --> :heatmap + colorbar --> :true + + return DGTriPseudocolor( + global_plotting_triangulation_triplot( + (x, y), data_field, + t + )... + ) end -end - -# Plot a single variable. -RecipesBase.@recipe function f(pds::PlotDataSeries{<:AbstractPlotData{1}}) - @unpack plot_data, variable_id = pds - @unpack x, data, variable_names, orientation_x = plot_data - - # Set geometric properties - xlims --> (x[begin], x[end]) - - # Set annotation properties - legend --> :none - title --> variable_names[variable_id] - xguide --> _get_guide(orientation_x) - - # Return data for plotting - x, data[:, variable_id] -end - -# Plot the mesh as vertical lines from a PlotMesh object. -RecipesBase.@recipe function f(pm::PlotMesh{<:AbstractPlotData{1}}) - @unpack plot_data = pm - @unpack x, mesh_vertices_x = plot_data - - # Set geometric and annotation properties - xlims --> (x[begin], x[end]) - legend --> :none - - # Set series properties - seriestype --> :vline - linecolor --> :grey - linewidth --> 1 - - # Return data for plotting - mesh_vertices_x -end - -# Create a plot directly from a TrixiODESolution for convenience -# The plot is created by a PlotData1D or PlotData2D object. -# -# Note: This is an experimental feature and may be changed in future releases without notice. -RecipesBase.@recipe function f(sol::TrixiODESolution) - # Redirect everything to the recipes below - return sol.u[end], sol.prob.p -end - -# Recipe for general semidiscretizations -# Note: If you change the defaults values here, you need to also change them in the PlotData1D or PlotData2D -# constructor. -RecipesBase.@recipe function f(u, semi::AbstractSemidiscretization; - solution_variables = nothing) - if ndims(semi) == 1 - return PlotData1D(u, semi; solution_variables = solution_variables) - else - return PlotData2D(u, semi; solution_variables = solution_variables) + + # Visualize a 2D mesh given an `PlotData2DTriangulated` object + RecipesBase.@recipe function f(pm::PlotMesh{<:PlotData2DTriangulated}) + pd = pm.plot_data + @unpack x_face, y_face = pd + + # This line separates solution lines on each edge by NaNs to ensure that they are rendered + # separately. The coordinates `xf`, `yf` and the solution `sol_f`` are assumed to be a matrix + # whose columns correspond to different elements. We add NaN separators by appending a row of + # NaNs to this matrix. We also flatten (e.g., apply `vec` to) the result, as this speeds up + # plotting. + x_face, y_face = map(x -> vec(vcat(x, fill(NaN, 1, size(x, 2)))), (x_face, y_face)) + + xlims --> extrema(x_face) + ylims --> extrema(y_face) + aspect_ratio --> :equal + legend --> :none + + # Set series properties + seriestype --> :path + linecolor --> :grey + linewidth --> 1 + + return x_face, y_face end -end - -# Recipe specifically for TreeMesh-type solutions -# Note: If you change the defaults values here, you need to also change them in the PlotData1D or PlotData2D -# constructor. -RecipesBase.@recipe function f(u, semi::SemidiscretizationHyperbolic{<:TreeMesh}; - solution_variables = nothing, - grid_lines = true, max_supported_level = 11, - nvisnodes = nothing, slice = :xy, - point = (0.0, 0.0, 0.0), curve = nothing) - # Create a PlotData1D or PlotData2D object depending on the dimension. - if ndims(semi) == 1 - return PlotData1D(u, semi; solution_variables, nvisnodes, slice, point, curve) - else - return PlotData2D(u, semi; - solution_variables, grid_lines, max_supported_level, - nvisnodes, slice, point) + + # Visualizes a single scalar field. Intended for use with ScalarPlotData2D. + # Example usage: `plot(ScalarPlotData2D(u, semi))`. + RecipesBase.@recipe function f(pd::PlotData2DTriangulated{<:ScalarData}) + @unpack x, y, data, t, variable_names = pd + + title_string = isnothing(variable_names) ? "" : variable_names + + legend --> false + aspect_ratio --> 1 + title --> title_string + xlims --> extrema(x) + ylims --> extrema(y) + xguide --> _get_guide(1) + yguide --> _get_guide(2) + seriestype --> :heatmap + colorbar --> :true + + # Since `data` is simply a ScalarData wrapper around the actual plot data, we pass in + # `data.data` instead. + return DGTriPseudocolor( + global_plotting_triangulation_triplot( + (x, y), data.data, + t + )... + ) end -end - -# Series recipe for PlotData2DTriangulated -RecipesBase.@recipe function f(pds::PlotDataSeries{<:PlotData2DTriangulated}) - pd = pds.plot_data - @unpack variable_id = pds - @unpack x, y, data, t, variable_names = pd - - # extract specific solution field to plot - data_field = zeros(eltype(first(data)), size(data)) - for (i, data_i) in enumerate(data) - data_field[i] = data_i[variable_id] + + RecipesBase.@recipe function f( + cb::DiscreteCallback{<:Any, <:TimeSeriesCallback}, + point_id::Integer + ) + return cb.affect!, point_id end - legend --> false - aspect_ratio --> 1 - title --> pd.variable_names[variable_id] - xlims --> extrema(x) - ylims --> extrema(y) - xguide --> _get_guide(1) - yguide --> _get_guide(2) - seriestype --> :heatmap - colorbar --> :true - - return DGTriPseudocolor(global_plotting_triangulation_triplot((x, y), data_field, - t)...) -end - -# Visualize a 2D mesh given an `PlotData2DTriangulated` object -RecipesBase.@recipe function f(pm::PlotMesh{<:PlotData2DTriangulated}) - pd = pm.plot_data - @unpack x_face, y_face = pd - - # This line separates solution lines on each edge by NaNs to ensure that they are rendered - # separately. The coordinates `xf`, `yf` and the solution `sol_f`` are assumed to be a matrix - # whose columns correspond to different elements. We add NaN separators by appending a row of - # NaNs to this matrix. We also flatten (e.g., apply `vec` to) the result, as this speeds up - # plotting. - x_face, y_face = map(x -> vec(vcat(x, fill(NaN, 1, size(x, 2)))), (x_face, y_face)) - - xlims --> extrema(x_face) - ylims --> extrema(y_face) - aspect_ratio --> :equal - legend --> :none - - # Set series properties - seriestype --> :path - linecolor --> :grey - linewidth --> 1 - - return x_face, y_face -end - -# Visualizes a single scalar field. Intended for use with ScalarPlotData2D. -# Example usage: `plot(ScalarPlotData2D(u, semi))`. -RecipesBase.@recipe function f(pd::PlotData2DTriangulated{<:ScalarData}) - @unpack x, y, data, t, variable_names = pd - - title_string = isnothing(variable_names) ? "" : variable_names - - legend --> false - aspect_ratio --> 1 - title --> title_string - xlims --> extrema(x) - ylims --> extrema(y) - xguide --> _get_guide(1) - yguide --> _get_guide(2) - seriestype --> :heatmap - colorbar --> :true - - # Since `data` is simply a ScalarData wrapper around the actual plot data, we pass in - # `data.data` instead. - return DGTriPseudocolor(global_plotting_triangulation_triplot((x, y), data.data, - t)...) -end - -RecipesBase.@recipe function f(cb::DiscreteCallback{<:Any, <:TimeSeriesCallback}, - point_id::Integer) - return cb.affect!, point_id -end - -RecipesBase.@recipe function f(time_series_callback::TimeSeriesCallback, - point_id::Integer) - return PlotData1D(time_series_callback, point_id) -end + RecipesBase.@recipe function f( + time_series_callback::TimeSeriesCallback, + point_id::Integer + ) + return PlotData1D(time_series_callback, point_id) + end end # @muladd diff --git a/src/visualization/types.jl b/src/visualization/types.jl index b294ce2560..f2268ce6aa 100644 --- a/src/visualization/types.jl +++ b/src/visualization/types.jl @@ -15,708 +15,826 @@ const TrixiODESolution = Union{ODESolution{T, N, uType, uType2, DType, tType, ra # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -# This file holds plotting types which can be used for both Plots.jl and Makie.jl. - -# This abstract type is used to derive PlotData types of different dimensions; but still allows to share some functions for them. -abstract type AbstractPlotData{NDIMS} end - -# Define additional methods for convenience. -# These are defined for AbstractPlotData, so they can be used for all types of plot data. -Base.firstindex(pd::AbstractPlotData) = first(pd.variable_names) -Base.lastindex(pd::AbstractPlotData) = last(pd.variable_names) -Base.length(pd::AbstractPlotData) = length(pd.variable_names) -Base.size(pd::AbstractPlotData) = (length(pd),) -Base.keys(pd::AbstractPlotData) = tuple(pd.variable_names...) - -function Base.iterate(pd::AbstractPlotData, state = 1) - if state > length(pd) - return nothing - else - return (pd.variable_names[state] => pd[pd.variable_names[state]], state + 1) - end -end - -""" - Base.getindex(pd::AbstractPlotData, variable_name) - -Extract a single variable `variable_name` from `pd` for plotting with `Plots.plot`. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -function Base.getindex(pd::AbstractPlotData, variable_name) - variable_id = findfirst(isequal(variable_name), pd.variable_names) - - if isnothing(variable_id) - throw(KeyError(variable_name)) - end - - return PlotDataSeries(pd, variable_id) -end - -Base.eltype(pd::AbstractPlotData) = Pair{String, PlotDataSeries{typeof(pd)}} - -""" - PlotData2D - -Holds all relevant data for creating 2D plots of multiple solution variables and to visualize the -mesh. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -struct PlotData2DCartesian{Coordinates, Data, VariableNames, Vertices} <: - AbstractPlotData{2} - x::Coordinates - y::Coordinates - data::Data - variable_names::VariableNames - mesh_vertices_x::Vertices - mesh_vertices_y::Vertices - orientation_x::Int - orientation_y::Int -end - -# Show only a truncated output for convenience (the full data does not make sense) -function Base.show(io::IO, pd::PlotData2DCartesian) - @nospecialize pd # reduce precompilation time - - print(io, "PlotData2DCartesian{", - typeof(pd.x), ",", - typeof(pd.data), ",", - typeof(pd.variable_names), ",", - typeof(pd.mesh_vertices_x), - "}(, , , , , )") -end - -# holds plotting information for UnstructuredMesh2D and DGMulti-compatible meshes -struct PlotData2DTriangulated{DataType, NodeType, FaceNodeType, FaceDataType, - VariableNames, PlottingTriangulation} <: - AbstractPlotData{2} - x::NodeType # physical nodal coordinates, size (num_plotting_nodes x num_elements) - y::NodeType - data::DataType - t::PlottingTriangulation - x_face::FaceNodeType - y_face::FaceNodeType - face_data::FaceDataType - variable_names::VariableNames -end - -# Show only a truncated output for convenience (the full data does not make sense) -function Base.show(io::IO, pd::PlotData2DTriangulated) - @nospecialize pd # reduce precompilation time - - print(io, "PlotData2DTriangulated{", - typeof(pd.x), ", ", - typeof(pd.data), ", ", - typeof(pd.x_face), ", ", - typeof(pd.face_data), ", ", - typeof(pd.variable_names), - "}(, , , , , , , )") -end - -""" - PlotData1D - -Holds all relevant data for creating 1D plots of multiple solution variables and to visualize the -mesh. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -struct PlotData1D{Coordinates, Data, VariableNames, Vertices} <: AbstractPlotData{1} - x::Coordinates - data::Data - variable_names::VariableNames - mesh_vertices_x::Vertices - orientation_x::Integer -end - -# Show only a truncated output for convenience (the full data does not make sense) -function Base.show(io::IO, pd::PlotData1D) - print(io, "PlotData1D{", - typeof(pd.x), ",", - typeof(pd.data), ",", - typeof(pd.variable_names), ",", - typeof(pd.mesh_vertices_x), - "}(, , , )") -end - -# Auxiliary data structure for visualizing a single variable -# -# Note: This is an experimental feature and may be changed in future releases without notice. -struct PlotDataSeries{PD <: AbstractPlotData} - plot_data::PD - variable_id::Int -end - -# Show only a truncated output for convenience (the full data does not make sense) -function Base.show(io::IO, pds::PlotDataSeries) - @nospecialize pds # reduce precompilation time - - print(io, "PlotDataSeries{", typeof(pds.plot_data), "}(, ", - pds.variable_id, ")") -end - -# Generic PlotMesh wrapper type. -struct PlotMesh{PD <: AbstractPlotData} - plot_data::PD -end - -# Show only a truncated output for convenience (the full data does not make sense) -function Base.show(io::IO, pm::PlotMesh) - @nospecialize pm # reduce precompilation time - - print(io, "PlotMesh{", typeof(pm.plot_data), "}()") -end - -""" - getmesh(pd::AbstractPlotData) - -Extract grid lines from `pd` for plotting with `Plots.plot`. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -getmesh(pd::AbstractPlotData) = PlotMesh(pd) - -""" - PlotData2D(u, semi [or mesh, equations, solver, cache]; - solution_variables=nothing, - grid_lines=true, max_supported_level=11, nvisnodes=nothing, - slice=:xy, point=(0.0, 0.0, 0.0)) - -Create a new `PlotData2D` object that can be used for visualizing 2D/3D DGSEM solution data array -`u` with `Plots.jl`. All relevant geometrical information is extracted from the semidiscretization -`semi`. By default, the primitive variables (if existent) or the conservative variables (otherwise) -from the solution are used for plotting. This can be changed by passing an appropriate conversion -function to `solution_variables`. - -If `grid_lines` is `true`, also extract grid vertices for visualizing the mesh. The output -resolution is indirectly set via `max_supported_level`: all data is interpolated to -`2^max_supported_level` uniformly distributed points in each spatial direction, also setting the -maximum allowed refinement level in the solution. `nvisnodes` specifies the number of visualization -nodes to be used. If it is `nothing`, twice the number of solution DG nodes are used for -visualization, and if set to `0`, exactly the number of nodes in the DG elements are used. - -When visualizing data from a three-dimensional simulation, a 2D slice is extracted for plotting. -`slice` specifies the plane that is being sliced and may be `:xy`, `:xz`, or `:yz`. -The slice position is specified by a `point` that lies on it, which defaults to `(0.0, 0.0, 0.0)`. -Both of these values are ignored when visualizing 2D data. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. - -# Examples -```julia -julia> using Trixi, Plots - -julia> trixi_include(default_example()) -[...] - -julia> pd = PlotData2D(sol) -PlotData2D(...) - -julia> plot(pd) # To plot all available variables - -julia> plot(pd["scalar"]) # To plot only a single variable - -julia> plot!(getmesh(pd)) # To add grid lines to the plot -``` -""" -function PlotData2D(u_ode, semi; kwargs...) - PlotData2D(wrap_array_native(u_ode, semi), - mesh_equations_solver_cache(semi)...; - kwargs...) -end - -# Redirect `PlotDataTriangulated2D` constructor. -function PlotData2DTriangulated(u_ode, semi; kwargs...) - PlotData2DTriangulated(wrap_array_native(u_ode, semi), - mesh_equations_solver_cache(semi)...; - kwargs...) -end - -# Create a PlotData2DCartesian object for TreeMeshes on default. -function PlotData2D(u, mesh::TreeMesh, equations, solver, cache; kwargs...) - PlotData2DCartesian(u, mesh::TreeMesh, equations, solver, cache; kwargs...) -end - -# Create a PlotData2DTriangulated object for any type of mesh other than the TreeMesh. -function PlotData2D(u, mesh, equations, solver, cache; kwargs...) - PlotData2DTriangulated(u, mesh, equations, solver, cache; kwargs...) -end - -# Create a PlotData2DCartesian for a TreeMesh. -function PlotData2DCartesian(u, mesh::TreeMesh, equations, solver, cache; - solution_variables = nothing, - grid_lines = true, max_supported_level = 11, - nvisnodes = nothing, - slice = :xy, point = (0.0, 0.0, 0.0)) - @assert ndims(mesh) in (2, 3) "unsupported number of dimensions $ndims (must be 2 or 3)" - solution_variables_ = digest_solution_variables(equations, solution_variables) - - # Extract mesh info - center_level_0 = mesh.tree.center_level_0 - length_level_0 = mesh.tree.length_level_0 - leaf_cell_ids = leaf_cells(mesh.tree) - coordinates = mesh.tree.coordinates[:, leaf_cell_ids] - levels = mesh.tree.levels[leaf_cell_ids] - - unstructured_data = get_unstructured_data(u, solution_variables_, mesh, equations, - solver, cache) - x, y, data, mesh_vertices_x, mesh_vertices_y = get_data_2d(center_level_0, - length_level_0, - leaf_cell_ids, - coordinates, levels, - ndims(mesh), - unstructured_data, - nnodes(solver), - grid_lines, - max_supported_level, - nvisnodes, - slice, point) - variable_names = SVector(varnames(solution_variables_, equations)) - - orientation_x, orientation_y = _get_orientations(mesh, slice) - - return PlotData2DCartesian(x, y, data, variable_names, mesh_vertices_x, - mesh_vertices_y, - orientation_x, orientation_y) -end - -""" - PlotData2D(sol; kwargs...) - -Create a `PlotData2D` object from a solution object created by either `OrdinaryDiffEq.solve!` (which -returns a `SciMLBase.ODESolution`) or Trixi.jl's own `solve!` (which returns a -`TimeIntegratorSolution`). - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -function PlotData2D(sol::TrixiODESolution; kwargs...) - PlotData2D(sol.u[end], sol.prob.p; kwargs...) -end - -# Also redirect when using PlotData2DTriangulate. -function PlotData2DTriangulated(sol::TrixiODESolution; kwargs...) - PlotData2DTriangulated(sol.u[end], sol.prob.p; kwargs...) -end - -# If `u` is an `Array{<:SVectors}` and not a `StructArray`, convert it to a `StructArray` first. -function PlotData2D(u::Array{<:SVector, 2}, mesh, equations, dg::DGMulti, cache; - solution_variables = nothing, nvisnodes = 2 * nnodes(dg)) - nvars = length(first(u)) - u_structarray = StructArray{eltype(u)}(ntuple(_ -> zeros(eltype(first(u)), size(u)), - nvars)) - for (i, u_i) in enumerate(u) - u_structarray[i] = u_i - end - - # re-dispatch to PlotData2D with mesh, equations, dg, cache arguments - return PlotData2D(u_structarray, mesh, equations, dg, cache; - solution_variables = solution_variables, nvisnodes = nvisnodes) -end - -# constructor which returns an `PlotData2DTriangulated` object. -function PlotData2D(u::StructArray, mesh, equations, dg::DGMulti, cache; - solution_variables = nothing, nvisnodes = 2 * nnodes(dg)) - rd = dg.basis - md = mesh.md - - # Vp = the interpolation matrix from nodal points to plotting points - @unpack Vp = rd - interpolate_to_plotting_points!(out, x) = mul!(out, Vp, x) - - solution_variables_ = digest_solution_variables(equations, solution_variables) - variable_names = SVector(varnames(solution_variables_, equations)) - - if Vp isa UniformScaling - num_plotting_points = size(u, 1) - else - num_plotting_points = size(Vp, 1) - end - nvars = nvariables(equations) - uEltype = eltype(first(u)) - u_plot = StructArray{SVector{nvars, uEltype}}(ntuple(_ -> zeros(uEltype, - num_plotting_points, - md.num_elements), - nvars)) - - for e in eachelement(mesh, dg, cache) - # interpolate solution to plotting nodes element-by-element - StructArrays.foreachfield(interpolate_to_plotting_points!, view(u_plot, :, e), - view(u, :, e)) - - # transform nodal values of the solution according to `solution_variables` - transform_to_solution_variables!(view(u_plot, :, e), solution_variables_, - equations) - end - - # interpolate nodal coordinates to plotting points - x_plot, y_plot = map(x -> Vp * x, md.xyz) # md.xyz is a tuple of arrays containing nodal coordinates - - # construct a triangulation of the reference plotting nodes - t = reference_plotting_triangulation(rd.rstp) # rd.rstp = reference coordinates of plotting points - - x_face, y_face, face_data = mesh_plotting_wireframe(u, mesh, equations, dg, cache; - nvisnodes = nvisnodes) - - return PlotData2DTriangulated(x_plot, y_plot, u_plot, t, x_face, y_face, face_data, - variable_names) -end - -# specializes the PlotData2D constructor to return an PlotData2DTriangulated for any type of mesh. -function PlotData2DTriangulated(u, mesh, equations, dg::DGSEM, cache; - solution_variables = nothing, - nvisnodes = 2 * polydeg(dg)) - @assert ndims(mesh)==2 "Input must be two-dimensional." - - n_nodes_2d = nnodes(dg)^ndims(mesh) - n_elements = nelements(dg, cache) - - # build nodes on reference element (seems to be the right ordering) - r, s = reference_node_coordinates_2d(dg) - - # reference plotting nodes - if nvisnodes == 0 || nvisnodes === nothing - nvisnodes = polydeg(dg) + 1 - end - plotting_interp_matrix = plotting_interpolation_matrix(dg; nvisnodes = nvisnodes) - - # create triangulation for plotting nodes - r_plot, s_plot = (x -> plotting_interp_matrix * x).((r, s)) # interpolate dg nodes to plotting nodes - - # construct a triangulation of the plotting nodes - t = reference_plotting_triangulation((r_plot, s_plot)) - - # extract x,y coordinates and solutions on each element - uEltype = eltype(u) - nvars = nvariables(equations) - x = reshape(view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d, - n_elements) - y = reshape(view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d, - n_elements) - u_extracted = StructArray{SVector{nvars, uEltype}}(ntuple(_ -> similar(x, - (n_nodes_2d, - n_elements)), - nvars)) - for element in eachelement(dg, cache) - sk = 1 - for j in eachnode(dg), i in eachnode(dg) - u_node = get_node_vars(u, equations, dg, i, j, element) - u_extracted[sk, element] = u_node - sk += 1 + #! format: noindent + + # This file holds plotting types which can be used for both Plots.jl and Makie.jl. + + # This abstract type is used to derive PlotData types of different dimensions; but still allows to share some functions for them. + abstract type AbstractPlotData{NDIMS} end + + # Define additional methods for convenience. + # These are defined for AbstractPlotData, so they can be used for all types of plot data. + Base.firstindex(pd::AbstractPlotData) = first(pd.variable_names) + Base.lastindex(pd::AbstractPlotData) = last(pd.variable_names) + Base.length(pd::AbstractPlotData) = length(pd.variable_names) + Base.size(pd::AbstractPlotData) = (length(pd),) + Base.keys(pd::AbstractPlotData) = tuple(pd.variable_names...) + + function Base.iterate(pd::AbstractPlotData, state = 1) + if state > length(pd) + return nothing + else + return (pd.variable_names[state] => pd[pd.variable_names[state]], state + 1) end end - # interpolate to volume plotting points - xplot, yplot = plotting_interp_matrix * x, plotting_interp_matrix * y - uplot = StructArray{SVector{nvars, uEltype}}(map(x -> plotting_interp_matrix * x, - StructArrays.components(u_extracted))) + """ + Base.getindex(pd::AbstractPlotData, variable_name) + + Extract a single variable `variable_name` from `pd` for plotting with `Plots.plot`. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + function Base.getindex(pd::AbstractPlotData, variable_name) + variable_id = findfirst(isequal(variable_name), pd.variable_names) + + if isnothing(variable_id) + throw(KeyError(variable_name)) + end + + return PlotDataSeries(pd, variable_id) + end + + Base.eltype(pd::AbstractPlotData) = Pair{String, PlotDataSeries{typeof(pd)}} + + """ + PlotData2D + + Holds all relevant data for creating 2D plots of multiple solution variables and to visualize the + mesh. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + struct PlotData2DCartesian{Coordinates, Data, VariableNames, Vertices} <: + AbstractPlotData{2} + x::Coordinates + y::Coordinates + data::Data + variable_names::VariableNames + mesh_vertices_x::Vertices + mesh_vertices_y::Vertices + orientation_x::Int + orientation_y::Int + end + + # Show only a truncated output for convenience (the full data does not make sense) + function Base.show(io::IO, pd::PlotData2DCartesian) + @nospecialize pd # reduce precompilation time + + print( + io, "PlotData2DCartesian{", + typeof(pd.x), ",", + typeof(pd.data), ",", + typeof(pd.variable_names), ",", + typeof(pd.mesh_vertices_x), + "}(, , , , , )" + ) + end + + # holds plotting information for UnstructuredMesh2D and DGMulti-compatible meshes + struct PlotData2DTriangulated{ + DataType, NodeType, FaceNodeType, FaceDataType, + VariableNames, PlottingTriangulation, + } <: + AbstractPlotData{2} + x::NodeType # physical nodal coordinates, size (num_plotting_nodes x num_elements) + y::NodeType + data::DataType + t::PlottingTriangulation + x_face::FaceNodeType + y_face::FaceNodeType + face_data::FaceDataType + variable_names::VariableNames + end + + # Show only a truncated output for convenience (the full data does not make sense) + function Base.show(io::IO, pd::PlotData2DTriangulated) + @nospecialize pd # reduce precompilation time + + print( + io, "PlotData2DTriangulated{", + typeof(pd.x), ", ", + typeof(pd.data), ", ", + typeof(pd.x_face), ", ", + typeof(pd.face_data), ", ", + typeof(pd.variable_names), + "}(, , , , , , , )" + ) + end + + """ + PlotData1D + + Holds all relevant data for creating 1D plots of multiple solution variables and to visualize the + mesh. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + struct PlotData1D{Coordinates, Data, VariableNames, Vertices} <: AbstractPlotData{1} + x::Coordinates + data::Data + variable_names::VariableNames + mesh_vertices_x::Vertices + orientation_x::Integer + end + + # Show only a truncated output for convenience (the full data does not make sense) + function Base.show(io::IO, pd::PlotData1D) + print( + io, "PlotData1D{", + typeof(pd.x), ",", + typeof(pd.data), ",", + typeof(pd.variable_names), ",", + typeof(pd.mesh_vertices_x), + "}(, , , )" + ) + end + + # Auxiliary data structure for visualizing a single variable + # + # Note: This is an experimental feature and may be changed in future releases without notice. + struct PlotDataSeries{PD <: AbstractPlotData} + plot_data::PD + variable_id::Int + end + + # Show only a truncated output for convenience (the full data does not make sense) + function Base.show(io::IO, pds::PlotDataSeries) + @nospecialize pds # reduce precompilation time - xfp, yfp, ufp = mesh_plotting_wireframe(u_extracted, mesh, equations, dg, cache; - nvisnodes = nvisnodes) + print( + io, "PlotDataSeries{", typeof(pds.plot_data), "}(, ", + pds.variable_id, ")" + ) + end + + # Generic PlotMesh wrapper type. + struct PlotMesh{PD <: AbstractPlotData} + plot_data::PD + end + + # Show only a truncated output for convenience (the full data does not make sense) + function Base.show(io::IO, pm::PlotMesh) + @nospecialize pm # reduce precompilation time + + print(io, "PlotMesh{", typeof(pm.plot_data), "}()") + end + + """ + getmesh(pd::AbstractPlotData) + + Extract grid lines from `pd` for plotting with `Plots.plot`. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + getmesh(pd::AbstractPlotData) = PlotMesh(pd) + + """ + PlotData2D(u, semi [or mesh, equations, solver, cache]; + solution_variables=nothing, + grid_lines=true, max_supported_level=11, nvisnodes=nothing, + slice=:xy, point=(0.0, 0.0, 0.0)) + + Create a new `PlotData2D` object that can be used for visualizing 2D/3D DGSEM solution data array + `u` with `Plots.jl`. All relevant geometrical information is extracted from the semidiscretization + `semi`. By default, the primitive variables (if existent) or the conservative variables (otherwise) + from the solution are used for plotting. This can be changed by passing an appropriate conversion + function to `solution_variables`. + + If `grid_lines` is `true`, also extract grid vertices for visualizing the mesh. The output + resolution is indirectly set via `max_supported_level`: all data is interpolated to + `2^max_supported_level` uniformly distributed points in each spatial direction, also setting the + maximum allowed refinement level in the solution. `nvisnodes` specifies the number of visualization + nodes to be used. If it is `nothing`, twice the number of solution DG nodes are used for + visualization, and if set to `0`, exactly the number of nodes in the DG elements are used. + + When visualizing data from a three-dimensional simulation, a 2D slice is extracted for plotting. + `slice` specifies the plane that is being sliced and may be `:xy`, `:xz`, or `:yz`. + The slice position is specified by a `point` that lies on it, which defaults to `(0.0, 0.0, 0.0)`. + Both of these values are ignored when visualizing 2D data. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + + # Examples + ```julia + julia> using Trixi, Plots + + julia> trixi_include(default_example()) + [...] + + julia> pd = PlotData2D(sol) + PlotData2D(...) + + julia> plot(pd) # To plot all available variables + + julia> plot(pd["scalar"]) # To plot only a single variable + + julia> plot!(getmesh(pd)) # To add grid lines to the plot + ``` + """ + function PlotData2D(u_ode, semi; kwargs...) + PlotData2D( + wrap_array_native(u_ode, semi), + mesh_equations_solver_cache(semi)...; + kwargs... + ) + end + + # Redirect `PlotDataTriangulated2D` constructor. + function PlotData2DTriangulated(u_ode, semi; kwargs...) + PlotData2DTriangulated( + wrap_array_native(u_ode, semi), + mesh_equations_solver_cache(semi)...; + kwargs... + ) + end + + # Create a PlotData2DCartesian object for TreeMeshes on default. + function PlotData2D(u, mesh::TreeMesh, equations, solver, cache; kwargs...) + PlotData2DCartesian(u, mesh::TreeMesh, equations, solver, cache; kwargs...) + end + + # Create a PlotData2DTriangulated object for any type of mesh other than the TreeMesh. + function PlotData2D(u, mesh, equations, solver, cache; kwargs...) + PlotData2DTriangulated(u, mesh, equations, solver, cache; kwargs...) + end - # convert variables based on solution_variables mapping - solution_variables_ = digest_solution_variables(equations, solution_variables) - variable_names = SVector(varnames(solution_variables_, equations)) + # Create a PlotData2DCartesian for a TreeMesh. + function PlotData2DCartesian( + u, mesh::TreeMesh, equations, solver, cache; + solution_variables = nothing, + grid_lines = true, max_supported_level = 11, + nvisnodes = nothing, + slice = :xy, point = (0.0, 0.0, 0.0) + ) + @assert ndims(mesh) in (2, 3) "unsupported number of dimensions $ndims (must be 2 or 3)" + solution_variables_ = digest_solution_variables(equations, solution_variables) + + # Extract mesh info + center_level_0 = mesh.tree.center_level_0 + length_level_0 = mesh.tree.length_level_0 + leaf_cell_ids = leaf_cells(mesh.tree) + coordinates = mesh.tree.coordinates[:, leaf_cell_ids] + levels = mesh.tree.levels[leaf_cell_ids] + + unstructured_data = get_unstructured_data( + u, solution_variables_, mesh, equations, + solver, cache + ) + x, y, data, mesh_vertices_x, mesh_vertices_y = get_data_2d( + center_level_0, + length_level_0, + leaf_cell_ids, + coordinates, levels, + ndims(mesh), + unstructured_data, + nnodes(solver), + grid_lines, + max_supported_level, + nvisnodes, + slice, point + ) + variable_names = SVector(varnames(solution_variables_, equations)) + + orientation_x, orientation_y = _get_orientations(mesh, slice) + + return PlotData2DCartesian( + x, y, data, variable_names, mesh_vertices_x, + mesh_vertices_y, + orientation_x, orientation_y + ) + end + + """ + PlotData2D(sol; kwargs...) + + Create a `PlotData2D` object from a solution object created by either `OrdinaryDiffEq.solve!` (which + returns a `SciMLBase.ODESolution`) or Trixi.jl's own `solve!` (which returns a + `TimeIntegratorSolution`). + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + function PlotData2D(sol::TrixiODESolution; kwargs...) + PlotData2D(sol.u[end], sol.prob.p; kwargs...) + end + + # Also redirect when using PlotData2DTriangulate. + function PlotData2DTriangulated(sol::TrixiODESolution; kwargs...) + PlotData2DTriangulated(sol.u[end], sol.prob.p; kwargs...) + end - transform_to_solution_variables!(uplot, solution_variables_, equations) - transform_to_solution_variables!(ufp, solution_variables_, equations) + # If `u` is an `Array{<:SVectors}` and not a `StructArray`, convert it to a `StructArray` first. + function PlotData2D( + u::Array{<:SVector, 2}, mesh, equations, dg::DGMulti, cache; + solution_variables = nothing, nvisnodes = 2 * nnodes(dg) + ) + nvars = length(first(u)) + u_structarray = StructArray{eltype(u)}( + ntuple( + _ -> zeros(eltype(first(u)), size(u)), + nvars + ) + ) + for (i, u_i) in enumerate(u) + u_structarray[i] = u_i + end + + # re-dispatch to PlotData2D with mesh, equations, dg, cache arguments + return PlotData2D( + u_structarray, mesh, equations, dg, cache; + solution_variables = solution_variables, nvisnodes = nvisnodes + ) + end + + # constructor which returns an `PlotData2DTriangulated` object. + function PlotData2D( + u::StructArray, mesh, equations, dg::DGMulti, cache; + solution_variables = nothing, nvisnodes = 2 * nnodes(dg) + ) + rd = dg.basis + md = mesh.md - return PlotData2DTriangulated(xplot, yplot, uplot, t, xfp, yfp, ufp, variable_names) -end - -# Wrapper struct to indicate that an array represents a scalar data field. Used only for dispatch. -struct ScalarData{T} - data::T -end - -""" - ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...) - -Returns an `PlotData2DTriangulated` object which is used to visualize a single scalar field. -`u` should be an array whose entries correspond to values of the scalar field at nodal points. -""" -function ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...) - ScalarPlotData2D(u, mesh_equations_solver_cache(semi)...; kwargs...) -end - -# Returns an `PlotData2DTriangulated` which is used to visualize a single scalar field -function ScalarPlotData2D(u, mesh, equations, dg::DGMulti, cache; - variable_name = nothing, nvisnodes = 2 * nnodes(dg)) - rd = dg.basis - md = mesh.md - - # Vp = the interpolation matrix from nodal points to plotting points - @unpack Vp = rd - - # interpolate nodal coordinates and solution field to plotting points - x_plot, y_plot = map(x -> Vp * x, md.xyz) # md.xyz is a tuple of arrays containing nodal coordinates - u_plot = Vp * u - - # construct a triangulation of the reference plotting nodes - t = reference_plotting_triangulation(rd.rstp) # rd.rstp = reference coordinates of plotting points - - # Ignore face data when plotting `ScalarPlotData2D`, since mesh lines can be plotted using - # existing functionality based on `PlotData2D(sol)`. - x_face, y_face, face_data = mesh_plotting_wireframe(ScalarData(u), mesh, equations, - dg, cache; - nvisnodes = 2 * nnodes(dg)) - - # wrap solution in ScalarData struct for recipe dispatch - return PlotData2DTriangulated(x_plot, y_plot, ScalarData(u_plot), t, - x_face, y_face, face_data, variable_name) -end - -function ScalarPlotData2D(u, mesh, equations, dg::DGSEM, cache; variable_name = nothing, - nvisnodes = 2 * nnodes(dg)) - n_nodes_2d = nnodes(dg)^ndims(mesh) - n_elements = nelements(dg, cache) - - # build nodes on reference element (seems to be the right ordering) - r, s = reference_node_coordinates_2d(dg) - - # reference plotting nodes - if nvisnodes == 0 || nvisnodes === nothing - nvisnodes = polydeg(dg) + 1 - end - plotting_interp_matrix = plotting_interpolation_matrix(dg; nvisnodes = nvisnodes) - - # create triangulation for plotting nodes - r_plot, s_plot = (x -> plotting_interp_matrix * x).((r, s)) # interpolate dg nodes to plotting nodes - - # construct a triangulation of the plotting nodes - t = reference_plotting_triangulation((r_plot, s_plot)) - - # extract x,y coordinates and reshape them into matrices of size (n_nodes_2d, n_elements) - x = view(cache.elements.node_coordinates, 1, :, :, :) - y = view(cache.elements.node_coordinates, 2, :, :, :) - x, y = reshape.((x, y), n_nodes_2d, n_elements) - - # interpolate to volume plotting points by multiplying each column by `plotting_interp_matrix` - x_plot, y_plot = plotting_interp_matrix * x, plotting_interp_matrix * y - u_plot = plotting_interp_matrix * reshape(u, size(x)) - - # Ignore face data when plotting `ScalarPlotData2D`, since mesh lines can be plotted using - # existing functionality based on `PlotData2D(sol)`. - x_face, y_face, face_data = mesh_plotting_wireframe(ScalarData(u), mesh, equations, - dg, cache; - nvisnodes = 2 * nnodes(dg)) - - # wrap solution in ScalarData struct for recipe dispatch - return PlotData2DTriangulated(x_plot, y_plot, ScalarData(u_plot), t, - x_face, y_face, face_data, variable_name) -end - -""" - PlotData1D(u, semi [or mesh, equations, solver, cache]; - solution_variables=nothing, nvisnodes=nothing) - -Create a new `PlotData1D` object that can be used for visualizing 1D DGSEM solution data array -`u` with `Plots.jl`. All relevant geometrical information is extracted from the semidiscretization -`semi`. By default, the primitive variables (if existent) or the conservative variables (otherwise) -from the solution are used for plotting. This can be changed by passing an appropriate conversion -function to `solution_variables`. - -`nvisnodes` specifies the number of visualization nodes to be used. If it is `nothing`, -twice the number of solution DG nodes are used for visualization, and if set to `0`, -exactly the number of nodes in the DG elements are used. - -When visualizing data from a two-dimensional simulation, a 1D slice is extracted for plotting. -`slice` specifies the axis along which the slice is extracted and may be `:x`, or `:y`. -The slice position is specified by a `point` that lies on it, which defaults to `(0.0, 0.0)`. -Both of these values are ignored when visualizing 1D data. -This applies analogously to three-dimensional simulations, where `slice` may be `:xy`, `:xz`, or `:yz`. - -Another way to visualize 2D/3D data is by creating a plot along a given curve. -This is done with the keyword argument `curve`. It can be set to a list of 2D/3D points -which define the curve. When using `curve` any other input from `slice` or `point` will be ignored. - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -function PlotData1D(u_ode, semi; kwargs...) - PlotData1D(wrap_array_native(u_ode, semi), - mesh_equations_solver_cache(semi)...; - kwargs...) -end - -function PlotData1D(u, mesh::TreeMesh, equations, solver, cache; - solution_variables = nothing, nvisnodes = nothing, - slice = :x, point = (0.0, 0.0, 0.0), curve = nothing) - solution_variables_ = digest_solution_variables(equations, solution_variables) - variable_names = SVector(varnames(solution_variables_, equations)) - - original_nodes = cache.elements.node_coordinates - unstructured_data = get_unstructured_data(u, solution_variables_, mesh, equations, - solver, cache) - - orientation_x = 0 # Set 'orientation' to zero on default. - - if ndims(mesh) == 1 - x, data, mesh_vertices_x = get_data_1d(original_nodes, unstructured_data, - nvisnodes) - orientation_x = 1 - - # Special care is required for first-order FV approximations since the nodes are the - # cell centers and do not contain the boundaries - n_nodes = size(unstructured_data, 1) - if n_nodes == 1 - n_visnodes = length(x) ÷ nelements(solver, cache) - if n_visnodes != 2 - throw(ArgumentError("This number of visualization nodes is currently not supported for finite volume approximations.")) + # Vp = the interpolation matrix from nodal points to plotting points + @unpack Vp = rd + interpolate_to_plotting_points!(out, x) = mul!(out, Vp, x) + + solution_variables_ = digest_solution_variables(equations, solution_variables) + variable_names = SVector(varnames(solution_variables_, equations)) + + if Vp isa UniformScaling + num_plotting_points = size(u, 1) + else + num_plotting_points = size(Vp, 1) + end + nvars = nvariables(equations) + uEltype = eltype(first(u)) + u_plot = StructArray{SVector{nvars, uEltype}}( + ntuple( + _ -> zeros( + uEltype, + num_plotting_points, + md.num_elements + ), + nvars + ) + ) + + for e in eachelement(mesh, dg, cache) + # interpolate solution to plotting nodes element-by-element + StructArrays.foreachfield( + interpolate_to_plotting_points!, view(u_plot, :, e), + view(u, :, e) + ) + + # transform nodal values of the solution according to `solution_variables` + transform_to_solution_variables!( + view(u_plot, :, e), solution_variables_, + equations + ) + end + + # interpolate nodal coordinates to plotting points + x_plot, y_plot = map(x -> Vp * x, md.xyz) # md.xyz is a tuple of arrays containing nodal coordinates + + # construct a triangulation of the reference plotting nodes + t = reference_plotting_triangulation(rd.rstp) # rd.rstp = reference coordinates of plotting points + + x_face, y_face, face_data = mesh_plotting_wireframe( + u, mesh, equations, dg, cache; + nvisnodes = nvisnodes + ) + + return PlotData2DTriangulated( + x_plot, y_plot, u_plot, t, x_face, y_face, face_data, + variable_names + ) + end + + # specializes the PlotData2D constructor to return an PlotData2DTriangulated for any type of mesh. + function PlotData2DTriangulated( + u, mesh, equations, dg::DGSEM, cache; + solution_variables = nothing, + nvisnodes = 2 * polydeg(dg) + ) + @assert ndims(mesh) == 2 "Input must be two-dimensional." + + n_nodes_2d = nnodes(dg)^ndims(mesh) + n_elements = nelements(dg, cache) + + # build nodes on reference element (seems to be the right ordering) + r, s = reference_node_coordinates_2d(dg) + + # reference plotting nodes + if nvisnodes == 0 || nvisnodes === nothing + nvisnodes = polydeg(dg) + 1 + end + plotting_interp_matrix = plotting_interpolation_matrix(dg; nvisnodes = nvisnodes) + + # create triangulation for plotting nodes + r_plot, s_plot = (x -> plotting_interp_matrix * x).((r, s)) # interpolate dg nodes to plotting nodes + + # construct a triangulation of the plotting nodes + t = reference_plotting_triangulation((r_plot, s_plot)) + + # extract x,y coordinates and solutions on each element + uEltype = eltype(u) + nvars = nvariables(equations) + x = reshape( + view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d, + n_elements + ) + y = reshape( + view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d, + n_elements + ) + u_extracted = StructArray{SVector{nvars, uEltype}}( + ntuple( + _ -> similar( + x, + ( + n_nodes_2d, + n_elements, + ) + ), + nvars + ) + ) + for element in eachelement(dg, cache) + sk = 1 + for j in eachnode(dg), i in eachnode(dg) + u_node = get_node_vars(u, equations, dg, i, j, element) + u_extracted[sk, element] = u_node + sk += 1 end - left_boundary = mesh.tree.center_level_0[1] - mesh.tree.length_level_0 / 2 - dx_2 = zero(left_boundary) - for i in 1:div(length(x), 2) - # Adjust plot nodes so that they are at the boundaries of each element - dx_2 = x[2 * i - 1] - left_boundary - x[2 * i - 1] -= dx_2 - x[2 * i] += dx_2 - left_boundary = left_boundary + 2 * dx_2 - - # Adjust mesh plot nodes - mesh_vertices_x[i] -= dx_2 + end + + # interpolate to volume plotting points + xplot, yplot = plotting_interp_matrix * x, plotting_interp_matrix * y + uplot = StructArray{SVector{nvars, uEltype}}( + map( + x -> plotting_interp_matrix * x, + StructArrays.components(u_extracted) + ) + ) + + xfp, yfp, ufp = mesh_plotting_wireframe( + u_extracted, mesh, equations, dg, cache; + nvisnodes = nvisnodes + ) + + # convert variables based on solution_variables mapping + solution_variables_ = digest_solution_variables(equations, solution_variables) + variable_names = SVector(varnames(solution_variables_, equations)) + + transform_to_solution_variables!(uplot, solution_variables_, equations) + transform_to_solution_variables!(ufp, solution_variables_, equations) + + return PlotData2DTriangulated(xplot, yplot, uplot, t, xfp, yfp, ufp, variable_names) + end + + # Wrapper struct to indicate that an array represents a scalar data field. Used only for dispatch. + struct ScalarData{T} + data::T + end + + """ + ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...) + + Returns an `PlotData2DTriangulated` object which is used to visualize a single scalar field. + `u` should be an array whose entries correspond to values of the scalar field at nodal points. + """ + function ScalarPlotData2D(u, semi::AbstractSemidiscretization; kwargs...) + ScalarPlotData2D(u, mesh_equations_solver_cache(semi)...; kwargs...) + end + + # Returns an `PlotData2DTriangulated` which is used to visualize a single scalar field + function ScalarPlotData2D( + u, mesh, equations, dg::DGMulti, cache; + variable_name = nothing, nvisnodes = 2 * nnodes(dg) + ) + rd = dg.basis + md = mesh.md + + # Vp = the interpolation matrix from nodal points to plotting points + @unpack Vp = rd + + # interpolate nodal coordinates and solution field to plotting points + x_plot, y_plot = map(x -> Vp * x, md.xyz) # md.xyz is a tuple of arrays containing nodal coordinates + u_plot = Vp * u + + # construct a triangulation of the reference plotting nodes + t = reference_plotting_triangulation(rd.rstp) # rd.rstp = reference coordinates of plotting points + + # Ignore face data when plotting `ScalarPlotData2D`, since mesh lines can be plotted using + # existing functionality based on `PlotData2D(sol)`. + x_face, y_face, face_data = mesh_plotting_wireframe( + ScalarData(u), mesh, equations, + dg, cache; + nvisnodes = 2 * nnodes(dg) + ) + + # wrap solution in ScalarData struct for recipe dispatch + return PlotData2DTriangulated( + x_plot, y_plot, ScalarData(u_plot), t, + x_face, y_face, face_data, variable_name + ) + end + + function ScalarPlotData2D( + u, mesh, equations, dg::DGSEM, cache; variable_name = nothing, + nvisnodes = 2 * nnodes(dg) + ) + n_nodes_2d = nnodes(dg)^ndims(mesh) + n_elements = nelements(dg, cache) + + # build nodes on reference element (seems to be the right ordering) + r, s = reference_node_coordinates_2d(dg) + + # reference plotting nodes + if nvisnodes == 0 || nvisnodes === nothing + nvisnodes = polydeg(dg) + 1 + end + plotting_interp_matrix = plotting_interpolation_matrix(dg; nvisnodes = nvisnodes) + + # create triangulation for plotting nodes + r_plot, s_plot = (x -> plotting_interp_matrix * x).((r, s)) # interpolate dg nodes to plotting nodes + + # construct a triangulation of the plotting nodes + t = reference_plotting_triangulation((r_plot, s_plot)) + + # extract x,y coordinates and reshape them into matrices of size (n_nodes_2d, n_elements) + x = view(cache.elements.node_coordinates, 1, :, :, :) + y = view(cache.elements.node_coordinates, 2, :, :, :) + x, y = reshape.((x, y), n_nodes_2d, n_elements) + + # interpolate to volume plotting points by multiplying each column by `plotting_interp_matrix` + x_plot, y_plot = plotting_interp_matrix * x, plotting_interp_matrix * y + u_plot = plotting_interp_matrix * reshape(u, size(x)) + + # Ignore face data when plotting `ScalarPlotData2D`, since mesh lines can be plotted using + # existing functionality based on `PlotData2D(sol)`. + x_face, y_face, face_data = mesh_plotting_wireframe( + ScalarData(u), mesh, equations, + dg, cache; + nvisnodes = 2 * nnodes(dg) + ) + + # wrap solution in ScalarData struct for recipe dispatch + return PlotData2DTriangulated( + x_plot, y_plot, ScalarData(u_plot), t, + x_face, y_face, face_data, variable_name + ) + end + + """ + PlotData1D(u, semi [or mesh, equations, solver, cache]; + solution_variables=nothing, nvisnodes=nothing) + + Create a new `PlotData1D` object that can be used for visualizing 1D DGSEM solution data array + `u` with `Plots.jl`. All relevant geometrical information is extracted from the semidiscretization + `semi`. By default, the primitive variables (if existent) or the conservative variables (otherwise) + from the solution are used for plotting. This can be changed by passing an appropriate conversion + function to `solution_variables`. + + `nvisnodes` specifies the number of visualization nodes to be used. If it is `nothing`, + twice the number of solution DG nodes are used for visualization, and if set to `0`, + exactly the number of nodes in the DG elements are used. + + When visualizing data from a two-dimensional simulation, a 1D slice is extracted for plotting. + `slice` specifies the axis along which the slice is extracted and may be `:x`, or `:y`. + The slice position is specified by a `point` that lies on it, which defaults to `(0.0, 0.0)`. + Both of these values are ignored when visualizing 1D data. + This applies analogously to three-dimensional simulations, where `slice` may be `:xy`, `:xz`, or `:yz`. + + Another way to visualize 2D/3D data is by creating a plot along a given curve. + This is done with the keyword argument `curve`. It can be set to a list of 2D/3D points + which define the curve. When using `curve` any other input from `slice` or `point` will be ignored. + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + function PlotData1D(u_ode, semi; kwargs...) + PlotData1D( + wrap_array_native(u_ode, semi), + mesh_equations_solver_cache(semi)...; + kwargs... + ) + end + + function PlotData1D( + u, mesh::TreeMesh, equations, solver, cache; + solution_variables = nothing, nvisnodes = nothing, + slice = :x, point = (0.0, 0.0, 0.0), curve = nothing + ) + solution_variables_ = digest_solution_variables(equations, solution_variables) + variable_names = SVector(varnames(solution_variables_, equations)) + + original_nodes = cache.elements.node_coordinates + unstructured_data = get_unstructured_data( + u, solution_variables_, mesh, equations, + solver, cache + ) + + orientation_x = 0 # Set 'orientation' to zero on default. + + if ndims(mesh) == 1 + x, data, mesh_vertices_x = get_data_1d( + original_nodes, unstructured_data, + nvisnodes + ) + orientation_x = 1 + + # Special care is required for first-order FV approximations since the nodes are the + # cell centers and do not contain the boundaries + n_nodes = size(unstructured_data, 1) + if n_nodes == 1 + n_visnodes = length(x) ÷ nelements(solver, cache) + if n_visnodes != 2 + throw(ArgumentError("This number of visualization nodes is currently not supported for finite volume approximations.")) + end + left_boundary = mesh.tree.center_level_0[1] - mesh.tree.length_level_0 / 2 + dx_2 = zero(left_boundary) + for i in 1:div(length(x), 2) + # Adjust plot nodes so that they are at the boundaries of each element + dx_2 = x[2 * i - 1] - left_boundary + x[2 * i - 1] -= dx_2 + x[2 * i] += dx_2 + left_boundary = left_boundary + 2 * dx_2 + + # Adjust mesh plot nodes + mesh_vertices_x[i] -= dx_2 + end + mesh_vertices_x[end] += dx_2 + end + elseif ndims(mesh) == 2 + if curve !== nothing + x, data, mesh_vertices_x = unstructured_2d_to_1d_curve( + original_nodes, + unstructured_data, + nvisnodes, curve, + mesh, solver, cache + ) + else + x, data, mesh_vertices_x = unstructured_2d_to_1d( + original_nodes, + unstructured_data, + nvisnodes, slice, point + ) + end + else # ndims(mesh) == 3 + if curve !== nothing + x, data, mesh_vertices_x = unstructured_3d_to_1d_curve( + original_nodes, + unstructured_data, + nvisnodes, curve, + mesh, solver, cache + ) + else + x, data, mesh_vertices_x = unstructured_3d_to_1d( + original_nodes, + unstructured_data, + nvisnodes, slice, point + ) end - mesh_vertices_x[end] += dx_2 end - elseif ndims(mesh) == 2 - if curve !== nothing - x, data, mesh_vertices_x = unstructured_2d_to_1d_curve(original_nodes, - unstructured_data, - nvisnodes, curve, - mesh, solver, cache) - else - x, data, mesh_vertices_x = unstructured_2d_to_1d(original_nodes, - unstructured_data, - nvisnodes, slice, point) + + return PlotData1D( + x, data, variable_names, mesh_vertices_x, + orientation_x + ) + end + + function PlotData1D( + u, mesh, equations, solver, cache; + solution_variables = nothing, nvisnodes = nothing, + slice = :x, point = (0.0, 0.0, 0.0), curve = nothing + ) + solution_variables_ = digest_solution_variables(equations, solution_variables) + variable_names = SVector(varnames(solution_variables_, equations)) + + original_nodes = cache.elements.node_coordinates + unstructured_data = get_unstructured_data( + u, solution_variables_, mesh, equations, + solver, cache + ) + + orientation_x = 0 # Set 'orientation' to zero on default. + + if ndims(mesh) == 1 + x, data, mesh_vertices_x = get_data_1d( + original_nodes, unstructured_data, + nvisnodes + ) + orientation_x = 1 + elseif ndims(mesh) == 2 + # Create a 'PlotData2DTriangulated' object so a triangulation can be used when extracting relevant data. + pd = PlotData2DTriangulated( + u, mesh, equations, solver, cache; + solution_variables, nvisnodes + ) + x, data, mesh_vertices_x = unstructured_2d_to_1d_curve( + pd, curve, slice, point, + nvisnodes + ) + else # ndims(mesh) == 3 + # Extract the information required to create a PlotData1D object. + x, data, mesh_vertices_x = unstructured_3d_to_1d_curve( + original_nodes, u, curve, + slice, point, nvisnodes + ) end - else # ndims(mesh) == 3 - if curve !== nothing - x, data, mesh_vertices_x = unstructured_3d_to_1d_curve(original_nodes, - unstructured_data, - nvisnodes, curve, - mesh, solver, cache) + + return PlotData1D( + x, data, variable_names, mesh_vertices_x, + orientation_x + ) + end + + # Specializes the `PlotData1D` constructor for one-dimensional `DGMulti` solvers. + function PlotData1D( + u, mesh, equations, dg::DGMulti{1}, cache; + solution_variables = nothing + ) + solution_variables_ = digest_solution_variables(equations, solution_variables) + variable_names = SVector(varnames(solution_variables_, equations)) + + orientation_x = 0 # Set 'orientation' to zero on default. + + if u isa StructArray + # Convert conserved variables to the given `solution_variables` and set up + # plotting coordinates + # This uses a "structure of arrays" + data = map( + x -> vcat(dg.basis.Vp * x, fill(NaN, 1, size(u, 2))), + StructArrays.components(solution_variables_.(u, equations)) + ) + x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2))) + + # Here, we ensure that `DGMulti` visualization uses the same data layout and format + # as `TreeMesh`. This enables us to reuse existing plot recipes. In particular, + # `hcat(data...)` creates a matrix of size `num_plotting_points` by `nvariables(equations)`, + # with data on different elements separated by `NaNs`. + x_plot = vec(x) + data_plot = hcat(vec.(data)...) else - x, data, mesh_vertices_x = unstructured_3d_to_1d(original_nodes, - unstructured_data, - nvisnodes, slice, point) + # Convert conserved variables to the given `solution_variables` and set up + # plotting coordinates + # This uses an "array of structures" + data_tmp = dg.basis.Vp * solution_variables_.(u, equations) + data = vcat(data_tmp, fill(NaN * zero(eltype(data_tmp)), 1, size(u, 2))) + x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2))) + + # Same as above - we create `data_plot` as array of size `num_plotting_points` + # by "number of plotting variables". + x_plot = vec(x) + data_plot = permutedims( + reinterpret(reshape, eltype(eltype(data)), vec(data)), + (2, 1) + ) end + + return PlotData1D(x_plot, data_plot, variable_names, mesh.md.VX, orientation_x) end - return PlotData1D(x, data, variable_names, mesh_vertices_x, - orientation_x) -end - -function PlotData1D(u, mesh, equations, solver, cache; - solution_variables = nothing, nvisnodes = nothing, - slice = :x, point = (0.0, 0.0, 0.0), curve = nothing) - solution_variables_ = digest_solution_variables(equations, solution_variables) - variable_names = SVector(varnames(solution_variables_, equations)) - - original_nodes = cache.elements.node_coordinates - unstructured_data = get_unstructured_data(u, solution_variables_, mesh, equations, - solver, cache) - - orientation_x = 0 # Set 'orientation' to zero on default. - - if ndims(mesh) == 1 - x, data, mesh_vertices_x = get_data_1d(original_nodes, unstructured_data, - nvisnodes) - orientation_x = 1 - elseif ndims(mesh) == 2 - # Create a 'PlotData2DTriangulated' object so a triangulation can be used when extracting relevant data. - pd = PlotData2DTriangulated(u, mesh, equations, solver, cache; - solution_variables, nvisnodes) - x, data, mesh_vertices_x = unstructured_2d_to_1d_curve(pd, curve, slice, point, - nvisnodes) - else # ndims(mesh) == 3 - # Extract the information required to create a PlotData1D object. - x, data, mesh_vertices_x = unstructured_3d_to_1d_curve(original_nodes, u, curve, - slice, point, nvisnodes) - end - - return PlotData1D(x, data, variable_names, mesh_vertices_x, - orientation_x) -end - -# Specializes the `PlotData1D` constructor for one-dimensional `DGMulti` solvers. -function PlotData1D(u, mesh, equations, dg::DGMulti{1}, cache; - solution_variables = nothing) - solution_variables_ = digest_solution_variables(equations, solution_variables) - variable_names = SVector(varnames(solution_variables_, equations)) - - orientation_x = 0 # Set 'orientation' to zero on default. - - if u isa StructArray - # Convert conserved variables to the given `solution_variables` and set up - # plotting coordinates - # This uses a "structure of arrays" - data = map(x -> vcat(dg.basis.Vp * x, fill(NaN, 1, size(u, 2))), - StructArrays.components(solution_variables_.(u, equations))) - x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2))) - - # Here, we ensure that `DGMulti` visualization uses the same data layout and format - # as `TreeMesh`. This enables us to reuse existing plot recipes. In particular, - # `hcat(data...)` creates a matrix of size `num_plotting_points` by `nvariables(equations)`, - # with data on different elements separated by `NaNs`. - x_plot = vec(x) - data_plot = hcat(vec.(data)...) - else - # Convert conserved variables to the given `solution_variables` and set up - # plotting coordinates - # This uses an "array of structures" - data_tmp = dg.basis.Vp * solution_variables_.(u, equations) - data = vcat(data_tmp, fill(NaN * zero(eltype(data_tmp)), 1, size(u, 2))) - x = vcat(dg.basis.Vp * mesh.md.x, fill(NaN, 1, size(u, 2))) - - # Same as above - we create `data_plot` as array of size `num_plotting_points` - # by "number of plotting variables". - x_plot = vec(x) - data_plot = permutedims(reinterpret(reshape, eltype(eltype(data)), vec(data)), - (2, 1)) - end - - return PlotData1D(x_plot, data_plot, variable_names, mesh.md.VX, orientation_x) -end - -""" - PlotData1D(sol; kwargs...) - -Create a `PlotData1D` object from a solution object created by either `OrdinaryDiffEq.solve!` -(which returns a `SciMLBase.ODESolution`) or Trixi.jl's own `solve!` (which returns a -`TimeIntegratorSolution`). - -!!! warning "Experimental implementation" - This is an experimental feature and may change in future releases. -""" -function PlotData1D(sol::TrixiODESolution; kwargs...) - PlotData1D(sol.u[end], sol.prob.p; kwargs...) -end - -function PlotData1D(time_series_callback::TimeSeriesCallback, point_id::Integer) - @unpack time, variable_names, point_data = time_series_callback - - n_solution_variables = length(variable_names) - data = Matrix{Float64}(undef, length(time), n_solution_variables) - reshaped = reshape(point_data[point_id], n_solution_variables, length(time)) - for v in 1:n_solution_variables - @views data[:, v] = reshaped[v, :] - end - - mesh_vertices_x = Vector{Float64}(undef, 0) - - return PlotData1D(time, data, SVector(variable_names), mesh_vertices_x, 0) -end - -function PlotData1D(cb::DiscreteCallback{<:Any, <:TimeSeriesCallback}, - point_id::Integer) - return PlotData1D(cb.affect!, point_id) -end + """ + PlotData1D(sol; kwargs...) + + Create a `PlotData1D` object from a solution object created by either `OrdinaryDiffEq.solve!` + (which returns a `SciMLBase.ODESolution`) or Trixi.jl's own `solve!` (which returns a + `TimeIntegratorSolution`). + + !!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. + """ + function PlotData1D(sol::TrixiODESolution; kwargs...) + PlotData1D(sol.u[end], sol.prob.p; kwargs...) + end + + function PlotData1D(time_series_callback::TimeSeriesCallback, point_id::Integer) + @unpack time, variable_names, point_data = time_series_callback + + n_solution_variables = length(variable_names) + data = Matrix{Float64}(undef, length(time), n_solution_variables) + reshaped = reshape(point_data[point_id], n_solution_variables, length(time)) + for v in 1:n_solution_variables + @views data[:, v] = reshaped[v, :] + end + + mesh_vertices_x = Vector{Float64}(undef, 0) + + return PlotData1D(time, data, SVector(variable_names), mesh_vertices_x, 0) + end + + function PlotData1D( + cb::DiscreteCallback{<:Any, <:TimeSeriesCallback}, + point_id::Integer + ) + return PlotData1D(cb.affect!, point_id) + end end # @muladd diff --git a/src/visualization/utilities.jl b/src/visualization/utilities.jl index 1f843c6a9d..070e64fc1b 100644 --- a/src/visualization/utilities.jl +++ b/src/visualization/utilities.jl @@ -3,1576 +3,1834 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent - -@inline num_faces(elem::Tri) = 3 -@inline num_faces(elem::Quad) = 4 - -# compute_triangle_area(tri) -# -# Computes the area of a triangle given `tri`, which is a tuple of three points (vectors), -# using the [Shoelace_formula](https://en.wikipedia.org/wiki/Shoelace_formula). -function compute_triangle_area(tri) - A, B, C = tri - return 0.5f0 * (A[1] * (B[2] - C[2]) + B[1] * (C[2] - A[2]) + C[1] * (A[2] - B[2])) -end - -# reference_plotting_triangulation(reference_plotting_coordinates) -# -# Computes a triangulation of the points in `reference_plotting_coordinates`, which is a tuple containing -# vectors of plotting points on the reference element (e.g., reference_plotting_coordinates = (r,s)). -# The reference element is assumed to be [-1,1]^d. -# -# This function returns `t` which is a `3 x N_tri` Matrix{Int} containing indices of triangles in the -# triangulation of the plotting points, with zero-volume triangles removed. -# -# For example, r[t[1, i]] returns the first reference coordinate of the 1st point on the ith triangle. -function reference_plotting_triangulation(reference_plotting_coordinates, - tol = 50 * eps()) - # on-the-fly triangulation of plotting nodes on the reference element - tri_in = Triangulate.TriangulateIO() - tri_in.pointlist = permutedims(hcat(reference_plotting_coordinates...)) - tri_out, _ = Triangulate.triangulate("Q", tri_in) - triangles = tri_out.trianglelist - - # filter out sliver triangles - has_volume = fill(true, size(triangles, 2)) - for i in axes(triangles, 2) - ids = @view triangles[:, i] - x_points = @view tri_out.pointlist[1, ids] - y_points = @view tri_out.pointlist[2, ids] - area = compute_triangle_area(zip(x_points, y_points)) - if abs(area) < tol - has_volume[i] = false + #! format: noindent + + @inline num_faces(elem::Tri) = 3 + @inline num_faces(elem::Quad) = 4 + + # compute_triangle_area(tri) + # + # Computes the area of a triangle given `tri`, which is a tuple of three points (vectors), + # using the [Shoelace_formula](https://en.wikipedia.org/wiki/Shoelace_formula). + function compute_triangle_area(tri) + A, B, C = tri + return 0.5f0 * (A[1] * (B[2] - C[2]) + B[1] * (C[2] - A[2]) + C[1] * (A[2] - B[2])) + end + + # reference_plotting_triangulation(reference_plotting_coordinates) + # + # Computes a triangulation of the points in `reference_plotting_coordinates`, which is a tuple containing + # vectors of plotting points on the reference element (e.g., reference_plotting_coordinates = (r,s)). + # The reference element is assumed to be [-1,1]^d. + # + # This function returns `t` which is a `3 x N_tri` Matrix{Int} containing indices of triangles in the + # triangulation of the plotting points, with zero-volume triangles removed. + # + # For example, r[t[1, i]] returns the first reference coordinate of the 1st point on the ith triangle. + function reference_plotting_triangulation( + reference_plotting_coordinates, + tol = 50 * eps() + ) + # on-the-fly triangulation of plotting nodes on the reference element + tri_in = Triangulate.TriangulateIO() + tri_in.pointlist = permutedims(hcat(reference_plotting_coordinates...)) + tri_out, _ = Triangulate.triangulate("Q", tri_in) + triangles = tri_out.trianglelist + + # filter out sliver triangles + has_volume = fill(true, size(triangles, 2)) + for i in axes(triangles, 2) + ids = @view triangles[:, i] + x_points = @view tri_out.pointlist[1, ids] + y_points = @view tri_out.pointlist[2, ids] + area = compute_triangle_area(zip(x_points, y_points)) + if abs(area) < tol + has_volume[i] = false + end end + return permutedims(triangles[:, findall(has_volume)]) end - return permutedims(triangles[:, findall(has_volume)]) -end -# This function is used to avoid type instabilities when calling `digest_solution_variables`. -function transform_to_solution_variables!(u, solution_variables, equations) - for (i, u_i) in enumerate(u) - u[i] = solution_variables(u_i, equations) - end -end - -# global_plotting_triangulation_triplot(u_plot, rst_plot, xyz_plot) -# -# Returns (plotting_coordinates_x, plotting_coordinates_y, ..., plotting_values, plotting_triangulation). -# Output can be used with TriplotRecipes.DGTriPseudocolor(...). -# -# Inputs: -# - xyz_plot = plotting points (tuple of matrices of size (Nplot, K)) -# - u_plot = matrix of size (Nplot, K) representing solution to plot. -# - t = triangulation of reference plotting points -function global_plotting_triangulation_triplot(xyz_plot, u_plot, t) - @assert size(first(xyz_plot), 1)==size(u_plot, 1) "Row dimension of u_plot does not match row dimension of xyz_plot" - - # build discontinuous data on plotting triangular mesh - num_plotting_points, num_elements = size(u_plot) - num_reference_plotting_triangles = size(t, 1) - num_plotting_elements_total = num_reference_plotting_triangles * num_elements - - # each column of `tp` corresponds to a vertex of a plotting triangle - tp = zeros(Int32, 3, num_plotting_elements_total) - zp = similar(tp, eltype(u_plot)) - for e in 1:num_elements - for i in 1:num_reference_plotting_triangles - tp[:, i + (e - 1) * num_reference_plotting_triangles] .= @views t[i, :] .+ - (e - 1) * - num_plotting_points - zp[:, i + (e - 1) * num_reference_plotting_triangles] .= @views u_plot[t[i, - :], - e] + # This function is used to avoid type instabilities when calling `digest_solution_variables`. + function transform_to_solution_variables!(u, solution_variables, equations) + for (i, u_i) in enumerate(u) + u[i] = solution_variables(u_i, equations) end end - return vec.(xyz_plot)..., zp, tp -end - -function get_face_node_indices(r, s, dg::DGSEM, tol = 100 * eps()) - face_1 = findall(@. abs(s + 1) < tol) - face_2 = findall(@. abs(r - 1) < tol) - face_3 = findall(@. abs(s - 1) < tol) - face_4 = findall(@. abs(r + 1) < tol) - Fmask = hcat(face_1, face_2, face_3, face_4) - return Fmask -end - -# dispatch on semi -function mesh_plotting_wireframe(u, semi) - mesh_plotting_wireframe(u, mesh_equations_solver_cache(semi)...) -end - -# mesh_plotting_wireframe(u, mesh, equations, dg::DGMulti, cache; num_plotting_pts=25) -# -# Generates data for plotting a mesh wireframe given StartUpDG data types. -# Returns (plotting_coordinates_x, plotting_coordinates_y, nothing) for a 2D mesh wireframe. -function mesh_plotting_wireframe(u::StructArray, mesh, equations, dg::DGMulti, cache; - nvisnodes = 2 * nnodes(dg)) - @unpack md = mesh - rd = dg.basis - - # Construct 1D plotting interpolation matrix `Vp1D` for a single face - @unpack N, Fmask = rd - num_face_points = length(Fmask) ÷ num_faces(rd.element_type) - vandermonde_matrix_1D = StartUpDG.vandermonde(Line(), N, - StartUpDG.nodes(Line(), - num_face_points - 1)) - rplot = LinRange(-1, 1, nvisnodes) - Vp1D = StartUpDG.vandermonde(Line(), N, rplot) / vandermonde_matrix_1D - - num_faces_total = num_faces(rd.element_type) * md.num_elements - xf, yf = map(x -> reshape(view(x, Fmask, :), num_face_points, num_faces_total), - md.xyz) - uf = similar(u, size(xf)) - apply_to_each_field((out, x) -> out .= reshape(view(x, Fmask, :), num_face_points, - num_faces_total), uf, u) - - num_face_plotting_points = size(Vp1D, 1) - x_mesh, y_mesh = ntuple(_ -> zeros(num_face_plotting_points, num_faces_total), 2) - u_mesh = similar(u, (num_face_plotting_points, num_faces_total)) - for f in 1:num_faces_total - mul!(view(x_mesh, :, f), Vp1D, view(xf, :, f)) - mul!(view(y_mesh, :, f), Vp1D, view(yf, :, f)) - apply_to_each_field(mul_by!(Vp1D), view(u_mesh, :, f), view(uf, :, f)) - end - - return x_mesh, y_mesh, u_mesh -end - -function mesh_plotting_wireframe(u::StructArray, mesh, equations, dg::DGSEM, cache; - nvisnodes = 2 * nnodes(dg)) - - # build nodes on reference element (seems to be the right ordering) - r, s = reference_node_coordinates_2d(dg) - - # extract node coordinates - uEltype = eltype(first(u)) - nvars = nvariables(equations) - n_nodes_2d = nnodes(dg)^ndims(mesh) - n_elements = nelements(dg, cache) - x = reshape(view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d, - n_elements) - y = reshape(view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d, - n_elements) - - # extract indices of local face nodes for wireframe plotting - Fmask = get_face_node_indices(r, s, dg) - plotting_interp_matrix1D = face_plotting_interpolation_matrix(dg; - nvisnodes = nvisnodes) - - # These 5 lines extract the face values on each element from the arrays x,y,sol_to_plot. - # The resulting arrays are then reshaped so that xf, yf, sol_f are Matrix types of size - # (Number of face plotting nodes) x (Number of faces). - function face_first_reshape(x, num_nodes_1D, num_nodes, num_elements) - num_reference_faces = 2 * ndims(mesh) - xf = view(reshape(x, num_nodes, num_elements), vec(Fmask), :) - return reshape(xf, num_nodes_1D, num_elements * num_reference_faces) - end - function reshape_and_interpolate(x) - plotting_interp_matrix1D * - face_first_reshape(x, nnodes(dg), n_nodes_2d, n_elements) - end - xfp, yfp = map(reshape_and_interpolate, (x, y)) - ufp = StructArray{SVector{nvars, uEltype}}(map(reshape_and_interpolate, - StructArrays.components(u))) - - return xfp, yfp, ufp -end - -function mesh_plotting_wireframe(u::ScalarData, mesh, equations, dg::DGSEM, cache; - nvisnodes = 2 * nnodes(dg)) - - # build nodes on reference element (seems to be the right ordering) - r, s = reference_node_coordinates_2d(dg) - - # extract node coordinates - n_nodes_2d = nnodes(dg)^ndims(mesh) - n_elements = nelements(dg, cache) - x = reshape(view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d, - n_elements) - y = reshape(view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d, - n_elements) - - # extract indices of local face nodes for wireframe plotting - Fmask = get_face_node_indices(r, s, dg) - plotting_interp_matrix1D = face_plotting_interpolation_matrix(dg; - nvisnodes = nvisnodes) - - # These 5 lines extract the face values on each element from the arrays x,y,sol_to_plot. - # The resulting arrays are then reshaped so that xf, yf, sol_f are Matrix types of size - # (Number of face plotting nodes) x (Number of faces). - function face_first_reshape(x, num_nodes_1D, num_nodes, num_elements) - num_reference_faces = 2 * ndims(mesh) - xf = view(reshape(x, num_nodes, num_elements), vec(Fmask), :) - return reshape(xf, num_nodes_1D, num_elements * num_reference_faces) - end - function reshape_and_interpolate(x) - plotting_interp_matrix1D * - face_first_reshape(x, nnodes(dg), n_nodes_2d, n_elements) - end - xfp, yfp, ufp = map(reshape_and_interpolate, (x, y, u.data)) - - return xfp, yfp, ufp -end - -function mesh_plotting_wireframe(u::ScalarData, mesh, equations, dg::DGMulti, cache; - nvisnodes = 2 * nnodes(dg)) - @unpack md = mesh - rd = dg.basis - - # Construct 1D plotting interpolation matrix `Vp1D` for a single face - @unpack N, Fmask = rd - vandermonde_matrix_1D = StartUpDG.vandermonde(Line(), N, StartUpDG.nodes(Line(), N)) - rplot = LinRange(-1, 1, nvisnodes) - Vp1D = StartUpDG.vandermonde(Line(), N, rplot) / vandermonde_matrix_1D - - num_face_points = N + 1 - num_faces_total = num_faces(rd.element_type) * md.num_elements - xf, yf, uf = map(x -> reshape(view(x, Fmask, :), num_face_points, num_faces_total), - (md.xyz..., u.data)) - - num_face_plotting_points = size(Vp1D, 1) - x_mesh, y_mesh = ntuple(_ -> zeros(num_face_plotting_points, num_faces_total), 2) - u_mesh = similar(u.data, (num_face_plotting_points, num_faces_total)) - for f in 1:num_faces_total - mul!(view(x_mesh, :, f), Vp1D, view(xf, :, f)) - mul!(view(y_mesh, :, f), Vp1D, view(yf, :, f)) - mul!(view(u_mesh, :, f), Vp1D, view(uf, :, f)) - end - return x_mesh, y_mesh, u_mesh -end - -# These methods are used internally to set the default value of the solution variables: -# - If a `cons2prim` for the given `equations` exists, use it -# - Otherwise, use `cons2cons`, which is defined for all systems of equations -digest_solution_variables(equations, solution_variables) = solution_variables -function digest_solution_variables(equations, solution_variables::Nothing) - if hasmethod(cons2prim, Tuple{AbstractVector, typeof(equations)}) - return cons2prim - else - return cons2cons - end -end - -""" - adapt_to_mesh_level!(u_ode, semi, level) - adapt_to_mesh_level!(sol::Trixi.TrixiODESolution, level) - -Like [`adapt_to_mesh_level`](@ref), but modifies the solution and parts of the -semidiscretization (mesh and caches) in place. -""" -function adapt_to_mesh_level!(u_ode, semi, level) - # Create AMR callback with controller that refines everything towards a single level - amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = level) - amr_callback = AMRCallback(semi, amr_controller, interval = 0) - - # Adapt mesh until it does not change anymore - has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0) - while has_changed - has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0) - end + # global_plotting_triangulation_triplot(u_plot, rst_plot, xyz_plot) + # + # Returns (plotting_coordinates_x, plotting_coordinates_y, ..., plotting_values, plotting_triangulation). + # Output can be used with TriplotRecipes.DGTriPseudocolor(...). + # + # Inputs: + # - xyz_plot = plotting points (tuple of matrices of size (Nplot, K)) + # - u_plot = matrix of size (Nplot, K) representing solution to plot. + # - t = triangulation of reference plotting points + function global_plotting_triangulation_triplot(xyz_plot, u_plot, t) + @assert size(first(xyz_plot), 1) == size(u_plot, 1) "Row dimension of u_plot does not match row dimension of xyz_plot" + + # build discontinuous data on plotting triangular mesh + num_plotting_points, num_elements = size(u_plot) + num_reference_plotting_triangles = size(t, 1) + num_plotting_elements_total = num_reference_plotting_triangles * num_elements + + # each column of `tp` corresponds to a vertex of a plotting triangle + tp = zeros(Int32, 3, num_plotting_elements_total) + zp = similar(tp, eltype(u_plot)) + for e in 1:num_elements + for i in 1:num_reference_plotting_triangles + tp[:, i + (e - 1) * num_reference_plotting_triangles] .= @views t[i, :] .+ + (e - 1) * + num_plotting_points + zp[:, i + (e - 1) * num_reference_plotting_triangles] .= @views u_plot[ + t[ + i, + :, + ], + e, + ] + end + end + return vec.(xyz_plot)..., zp, tp + end + + function get_face_node_indices(r, s, dg::DGSEM, tol = 100 * eps()) + face_1 = findall(@. abs(s + 1) < tol) + face_2 = findall(@. abs(r - 1) < tol) + face_3 = findall(@. abs(s - 1) < tol) + face_4 = findall(@. abs(r + 1) < tol) + Fmask = hcat(face_1, face_2, face_3, face_4) + return Fmask + end + + # dispatch on semi + function mesh_plotting_wireframe(u, semi) + mesh_plotting_wireframe(u, mesh_equations_solver_cache(semi)...) + end + + # mesh_plotting_wireframe(u, mesh, equations, dg::DGMulti, cache; num_plotting_pts=25) + # + # Generates data for plotting a mesh wireframe given StartUpDG data types. + # Returns (plotting_coordinates_x, plotting_coordinates_y, nothing) for a 2D mesh wireframe. + function mesh_plotting_wireframe( + u::StructArray, mesh, equations, dg::DGMulti, cache; + nvisnodes = 2 * nnodes(dg) + ) + @unpack md = mesh + rd = dg.basis + + # Construct 1D plotting interpolation matrix `Vp1D` for a single face + @unpack N, Fmask = rd + num_face_points = length(Fmask) ÷ num_faces(rd.element_type) + vandermonde_matrix_1D = StartUpDG.vandermonde( + Line(), N, + StartUpDG.nodes( + Line(), + num_face_points - 1 + ) + ) + rplot = LinRange(-1, 1, nvisnodes) + Vp1D = StartUpDG.vandermonde(Line(), N, rplot) / vandermonde_matrix_1D + + num_faces_total = num_faces(rd.element_type) * md.num_elements + xf, yf = map( + x -> reshape(view(x, Fmask, :), num_face_points, num_faces_total), + md.xyz + ) + uf = similar(u, size(xf)) + apply_to_each_field( + (out, x) -> out .= reshape( + view(x, Fmask, :), num_face_points, + num_faces_total + ), uf, u + ) + + num_face_plotting_points = size(Vp1D, 1) + x_mesh, y_mesh = ntuple(_ -> zeros(num_face_plotting_points, num_faces_total), 2) + u_mesh = similar(u, (num_face_plotting_points, num_faces_total)) + for f in 1:num_faces_total + mul!(view(x_mesh, :, f), Vp1D, view(xf, :, f)) + mul!(view(y_mesh, :, f), Vp1D, view(yf, :, f)) + apply_to_each_field(mul_by!(Vp1D), view(u_mesh, :, f), view(uf, :, f)) + end - return u_ode, semi -end - -function adapt_to_mesh_level!(sol::TrixiODESolution, level) - adapt_to_mesh_level!(sol.u[end], sol.prob.p, level) -end - -""" - adapt_to_mesh_level(u_ode, semi, level) - adapt_to_mesh_level(sol::Trixi.TrixiODESolution, level) - -Use the regular adaptive mesh refinement routines to adaptively refine/coarsen the solution `u_ode` -with semidiscretization `semi` towards a uniformly refined grid with refinement level `level`. The -solution and semidiscretization are copied such that the original objects remain *unaltered*. - -A convenience method accepts an ODE solution object, from which solution and semidiscretization are -extracted as needed. - -See also: [`adapt_to_mesh_level!`](@ref) -""" -function adapt_to_mesh_level(u_ode, semi, level) - # Create new semidiscretization with copy of the current mesh - mesh, _, _, _ = mesh_equations_solver_cache(semi) - new_semi = remake(semi, mesh = deepcopy(mesh)) - - return adapt_to_mesh_level!(deepcopy(u_ode), new_semi, level) -end - -function adapt_to_mesh_level(sol::TrixiODESolution, level) - adapt_to_mesh_level(sol.u[end], sol.prob.p, level) -end - -# Extract data from a 2D/3D DG solution and prepare it for visualization as a heatmap/contour plot. -# -# Returns a tuple with -# - x coordinates -# - y coordinates -# - nodal 2D data -# - x vertices for mesh visualization -# - y vertices for mesh visualization -# -# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may -# thus be changed in future releases. -function get_data_2d(center_level_0, length_level_0, leaf_cells, coordinates, levels, - ndims, unstructured_data, n_nodes, - grid_lines = false, max_supported_level = 11, nvisnodes = nothing, - slice = :xy, point = (0.0, 0.0, 0.0)) - # Determine resolution for data interpolation - max_level = maximum(levels) - if max_level > max_supported_level - error("Maximum refinement level $max_level is higher than " * - "maximum supported level $max_supported_level") - end - max_available_nodes_per_finest_element = 2^(max_supported_level - max_level) - if nvisnodes === nothing - max_nvisnodes = 2 * n_nodes - elseif nvisnodes == 0 - max_nvisnodes = n_nodes - else - max_nvisnodes = nvisnodes - end - nvisnodes_at_max_level = min(max_available_nodes_per_finest_element, max_nvisnodes) - resolution = nvisnodes_at_max_level * 2^max_level - nvisnodes_per_level = [2^(max_level - level) * nvisnodes_at_max_level - for level in 0:max_level] - # nvisnodes_per_level is an array (accessed by "level + 1" to accommodate - # level-0-cell) that contains the number of visualization nodes for any - # refinement level to visualize on an equidistant grid - - if ndims == 3 - (unstructured_data, coordinates, levels, - center_level_0) = unstructured_3d_to_2d(unstructured_data, - coordinates, levels, length_level_0, - center_level_0, slice, - point) - end + return x_mesh, y_mesh, u_mesh + end + + function mesh_plotting_wireframe( + u::StructArray, mesh, equations, dg::DGSEM, cache; + nvisnodes = 2 * nnodes(dg) + ) + + # build nodes on reference element (seems to be the right ordering) + r, s = reference_node_coordinates_2d(dg) + + # extract node coordinates + uEltype = eltype(first(u)) + nvars = nvariables(equations) + n_nodes_2d = nnodes(dg)^ndims(mesh) + n_elements = nelements(dg, cache) + x = reshape( + view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d, + n_elements + ) + y = reshape( + view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d, + n_elements + ) + + # extract indices of local face nodes for wireframe plotting + Fmask = get_face_node_indices(r, s, dg) + plotting_interp_matrix1D = face_plotting_interpolation_matrix( + dg; + nvisnodes = nvisnodes + ) + + # These 5 lines extract the face values on each element from the arrays x,y,sol_to_plot. + # The resulting arrays are then reshaped so that xf, yf, sol_f are Matrix types of size + # (Number of face plotting nodes) x (Number of faces). + function face_first_reshape(x, num_nodes_1D, num_nodes, num_elements) + num_reference_faces = 2 * ndims(mesh) + xf = view(reshape(x, num_nodes, num_elements), vec(Fmask), :) + return reshape(xf, num_nodes_1D, num_elements * num_reference_faces) + end + function reshape_and_interpolate(x) + plotting_interp_matrix1D * + face_first_reshape(x, nnodes(dg), n_nodes_2d, n_elements) + end + xfp, yfp = map(reshape_and_interpolate, (x, y)) + ufp = StructArray{SVector{nvars, uEltype}}( + map( + reshape_and_interpolate, + StructArrays.components(u) + ) + ) + + return xfp, yfp, ufp + end + + function mesh_plotting_wireframe( + u::ScalarData, mesh, equations, dg::DGSEM, cache; + nvisnodes = 2 * nnodes(dg) + ) + + # build nodes on reference element (seems to be the right ordering) + r, s = reference_node_coordinates_2d(dg) + + # extract node coordinates + n_nodes_2d = nnodes(dg)^ndims(mesh) + n_elements = nelements(dg, cache) + x = reshape( + view(cache.elements.node_coordinates, 1, :, :, :), n_nodes_2d, + n_elements + ) + y = reshape( + view(cache.elements.node_coordinates, 2, :, :, :), n_nodes_2d, + n_elements + ) + + # extract indices of local face nodes for wireframe plotting + Fmask = get_face_node_indices(r, s, dg) + plotting_interp_matrix1D = face_plotting_interpolation_matrix( + dg; + nvisnodes = nvisnodes + ) + + # These 5 lines extract the face values on each element from the arrays x,y,sol_to_plot. + # The resulting arrays are then reshaped so that xf, yf, sol_f are Matrix types of size + # (Number of face plotting nodes) x (Number of faces). + function face_first_reshape(x, num_nodes_1D, num_nodes, num_elements) + num_reference_faces = 2 * ndims(mesh) + xf = view(reshape(x, num_nodes, num_elements), vec(Fmask), :) + return reshape(xf, num_nodes_1D, num_elements * num_reference_faces) + end + function reshape_and_interpolate(x) + plotting_interp_matrix1D * + face_first_reshape(x, nnodes(dg), n_nodes_2d, n_elements) + end + xfp, yfp, ufp = map(reshape_and_interpolate, (x, y, u.data)) + + return xfp, yfp, ufp + end + + function mesh_plotting_wireframe( + u::ScalarData, mesh, equations, dg::DGMulti, cache; + nvisnodes = 2 * nnodes(dg) + ) + @unpack md = mesh + rd = dg.basis + + # Construct 1D plotting interpolation matrix `Vp1D` for a single face + @unpack N, Fmask = rd + vandermonde_matrix_1D = StartUpDG.vandermonde(Line(), N, StartUpDG.nodes(Line(), N)) + rplot = LinRange(-1, 1, nvisnodes) + Vp1D = StartUpDG.vandermonde(Line(), N, rplot) / vandermonde_matrix_1D + + num_face_points = N + 1 + num_faces_total = num_faces(rd.element_type) * md.num_elements + xf, yf, uf = map( + x -> reshape(view(x, Fmask, :), num_face_points, num_faces_total), + (md.xyz..., u.data) + ) + + num_face_plotting_points = size(Vp1D, 1) + x_mesh, y_mesh = ntuple(_ -> zeros(num_face_plotting_points, num_faces_total), 2) + u_mesh = similar(u.data, (num_face_plotting_points, num_faces_total)) + for f in 1:num_faces_total + mul!(view(x_mesh, :, f), Vp1D, view(xf, :, f)) + mul!(view(y_mesh, :, f), Vp1D, view(yf, :, f)) + mul!(view(u_mesh, :, f), Vp1D, view(uf, :, f)) + end - # Normalize element coordinates: move center to (0, 0) and domain size to [-1, 1]² - n_elements = length(levels) - normalized_coordinates = similar(coordinates) - for element_id in 1:n_elements - @views normalized_coordinates[:, element_id] .= ((coordinates[:, element_id] .- - center_level_0) ./ - (length_level_0 / 2)) + return x_mesh, y_mesh, u_mesh end - # Interpolate unstructured DG data to structured data - (structured_data = unstructured2structured(unstructured_data, - normalized_coordinates, - levels, resolution, nvisnodes_per_level)) - - # Interpolate cell-centered values to node-centered values - node_centered_data = cell2node(structured_data) - - # Determine axis coordinates for contour plot - xs = collect(range(-1, 1, length = resolution + 1)) .* length_level_0 / 2 .+ - center_level_0[1] - ys = collect(range(-1, 1, length = resolution + 1)) .* length_level_0 / 2 .+ - center_level_0[2] - - # Determine element vertices to plot grid lines - if grid_lines - mesh_vertices_x, mesh_vertices_y = calc_vertices(coordinates, levels, - length_level_0) - else - mesh_vertices_x = Vector{Float64}(undef, 0) - mesh_vertices_y = Vector{Float64}(undef, 0) + # These methods are used internally to set the default value of the solution variables: + # - If a `cons2prim` for the given `equations` exists, use it + # - Otherwise, use `cons2cons`, which is defined for all systems of equations + digest_solution_variables(equations, solution_variables) = solution_variables + function digest_solution_variables(equations, solution_variables::Nothing) + if hasmethod(cons2prim, Tuple{AbstractVector, typeof(equations)}) + return cons2prim + else + return cons2cons + end end - return xs, ys, node_centered_data, mesh_vertices_x, mesh_vertices_y -end - -# Extract data from a 1D DG solution and prepare it for visualization as a line plot. -# This returns a tuple with -# - x coordinates -# - nodal 1D data -# -# Note: This is a low-level function that is not considered as part of Trixi's interface and may -# thus be changed in future releases. -function get_data_1d(original_nodes, unstructured_data, nvisnodes) - # Get the dimensions of u; where n_vars is the number of variables, n_nodes the number of nodal values per element and n_elements the total number of elements. - n_nodes, n_elements, n_vars = size(unstructured_data) - - # Set the amount of nodes visualized according to nvisnodes. - if nvisnodes === nothing - max_nvisnodes = 2 * n_nodes - elseif nvisnodes == 0 - max_nvisnodes = n_nodes - else - @assert nvisnodes>=2 "nvisnodes must be zero or >= 2" - max_nvisnodes = nvisnodes - end + """ + adapt_to_mesh_level!(u_ode, semi, level) + adapt_to_mesh_level!(sol::Trixi.TrixiODESolution, level) - interpolated_nodes = Array{eltype(original_nodes), 2}(undef, max_nvisnodes, - n_elements) - interpolated_data = Array{eltype(unstructured_data), 3}(undef, max_nvisnodes, - n_elements, n_vars) + Like [`adapt_to_mesh_level`](@ref), but modifies the solution and parts of the + semidiscretization (mesh and caches) in place. + """ + function adapt_to_mesh_level!(u_ode, semi, level) + # Create AMR callback with controller that refines everything towards a single level + amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = level + ) + amr_callback = AMRCallback(semi, amr_controller, interval = 0) - for j in 1:n_elements - # Interpolate on an equidistant grid. - interpolated_nodes[:, j] .= range(original_nodes[1, 1, j], - original_nodes[1, end, j], - length = max_nvisnodes) - end + # Adapt mesh until it does not change anymore + has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0) + while has_changed + has_changed = amr_callback.affect!(u_ode, semi, 0.0, 0) + end - nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes) - nodes_out = collect(range(-1, 1, length = max_nvisnodes)) + return u_ode, semi + end + + function adapt_to_mesh_level!(sol::TrixiODESolution, level) + adapt_to_mesh_level!(sol.u[end], sol.prob.p, level) + end + + """ + adapt_to_mesh_level(u_ode, semi, level) + adapt_to_mesh_level(sol::Trixi.TrixiODESolution, level) + + Use the regular adaptive mesh refinement routines to adaptively refine/coarsen the solution `u_ode` + with semidiscretization `semi` towards a uniformly refined grid with refinement level `level`. The + solution and semidiscretization are copied such that the original objects remain *unaltered*. + + A convenience method accepts an ODE solution object, from which solution and semidiscretization are + extracted as needed. + + See also: [`adapt_to_mesh_level!`](@ref) + """ + function adapt_to_mesh_level(u_ode, semi, level) + # Create new semidiscretization with copy of the current mesh + mesh, _, _, _ = mesh_equations_solver_cache(semi) + new_semi = remake(semi, mesh = deepcopy(mesh)) + + return adapt_to_mesh_level!(deepcopy(u_ode), new_semi, level) + end + + function adapt_to_mesh_level(sol::TrixiODESolution, level) + adapt_to_mesh_level(sol.u[end], sol.prob.p, level) + end + + # Extract data from a 2D/3D DG solution and prepare it for visualization as a heatmap/contour plot. + # + # Returns a tuple with + # - x coordinates + # - y coordinates + # - nodal 2D data + # - x vertices for mesh visualization + # - y vertices for mesh visualization + # + # Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may + # thus be changed in future releases. + function get_data_2d( + center_level_0, length_level_0, leaf_cells, coordinates, levels, + ndims, unstructured_data, n_nodes, + grid_lines = false, max_supported_level = 11, nvisnodes = nothing, + slice = :xy, point = (0.0, 0.0, 0.0) + ) + # Determine resolution for data interpolation + max_level = maximum(levels) + if max_level > max_supported_level + error( + "Maximum refinement level $max_level is higher than " * + "maximum supported level $max_supported_level" + ) + end + max_available_nodes_per_finest_element = 2^(max_supported_level - max_level) + if nvisnodes === nothing + max_nvisnodes = 2 * n_nodes + elseif nvisnodes == 0 + max_nvisnodes = n_nodes + else + max_nvisnodes = nvisnodes + end + nvisnodes_at_max_level = min(max_available_nodes_per_finest_element, max_nvisnodes) + resolution = nvisnodes_at_max_level * 2^max_level + nvisnodes_per_level = [ + 2^(max_level - level) * nvisnodes_at_max_level + for level in 0:max_level + ] + # nvisnodes_per_level is an array (accessed by "level + 1" to accommodate + # level-0-cell) that contains the number of visualization nodes for any + # refinement level to visualize on an equidistant grid + + if ndims == 3 + ( + unstructured_data, coordinates, levels, + center_level_0, + ) = unstructured_3d_to_2d( + unstructured_data, + coordinates, levels, length_level_0, + center_level_0, slice, + point + ) + end - # Calculate vandermonde matrix for interpolation. - vandermonde = polynomial_interpolation_matrix(nodes_in, nodes_out) + # Normalize element coordinates: move center to (0, 0) and domain size to [-1, 1]² + n_elements = length(levels) + normalized_coordinates = similar(coordinates) + for element_id in 1:n_elements + @views normalized_coordinates[:, element_id] .= ( + ( + coordinates[:, element_id] .- + center_level_0 + ) ./ + (length_level_0 / 2) + ) + end - # Iterate over all variables. - for v in 1:n_vars - # Interpolate data for each element. - for element in 1:n_elements - multiply_scalar_dimensionwise!(@view(interpolated_data[:, element, v]), - vandermonde, - @view(unstructured_data[:, element, v])) + # Interpolate unstructured DG data to structured data + ( + structured_data = unstructured2structured( + unstructured_data, + normalized_coordinates, + levels, resolution, nvisnodes_per_level + ) + ) + + # Interpolate cell-centered values to node-centered values + node_centered_data = cell2node(structured_data) + + # Determine axis coordinates for contour plot + xs = collect(range(-1, 1, length = resolution + 1)) .* length_level_0 / 2 .+ + center_level_0[1] + ys = collect(range(-1, 1, length = resolution + 1)) .* length_level_0 / 2 .+ + center_level_0[2] + + # Determine element vertices to plot grid lines + if grid_lines + mesh_vertices_x, mesh_vertices_y = calc_vertices( + coordinates, levels, + length_level_0 + ) + else + mesh_vertices_x = Vector{Float64}(undef, 0) + mesh_vertices_y = Vector{Float64}(undef, 0) end - end - # Return results after data is reshaped - return vec(interpolated_nodes), reshape(interpolated_data, :, n_vars), - vcat(original_nodes[1, 1, :], original_nodes[1, end, end]) -end - -# Change order of dimensions (variables are now last) and convert data to `solution_variables` -# -# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may -# thus be changed in future releases. -function get_unstructured_data(u, solution_variables, mesh, equations, solver, cache) - if solution_variables === cons2cons - raw_data = u - n_vars = size(raw_data, 1) - else - # FIXME: Remove this comment once the implementation following it has been verified - # Reinterpret the solution array as an array of conservative variables, - # compute the solution variables via broadcasting, and reinterpret the - # result as a plain array of floating point numbers - # raw_data = Array(reinterpret(eltype(u), - # solution_variables.(reinterpret(SVector{nvariables(equations),eltype(u)}, u), - # Ref(equations)))) - # n_vars = size(raw_data, 1) - n_vars_in = nvariables(equations) - n_vars = length(solution_variables(get_node_vars(u, equations, solver), - equations)) - raw_data = Array{eltype(u)}(undef, n_vars, Base.tail(size(u))...) - reshaped_u = reshape(u, n_vars_in, :) - reshaped_r = reshape(raw_data, n_vars, :) - for idx in axes(reshaped_u, 2) - reshaped_r[:, idx] = solution_variables(get_node_vars(reshaped_u, equations, - solver, idx), - equations) + + return xs, ys, node_centered_data, mesh_vertices_x, mesh_vertices_y + end + + # Extract data from a 1D DG solution and prepare it for visualization as a line plot. + # This returns a tuple with + # - x coordinates + # - nodal 1D data + # + # Note: This is a low-level function that is not considered as part of Trixi's interface and may + # thus be changed in future releases. + function get_data_1d(original_nodes, unstructured_data, nvisnodes) + # Get the dimensions of u; where n_vars is the number of variables, n_nodes the number of nodal values per element and n_elements the total number of elements. + n_nodes, n_elements, n_vars = size(unstructured_data) + + # Set the amount of nodes visualized according to nvisnodes. + if nvisnodes === nothing + max_nvisnodes = 2 * n_nodes + elseif nvisnodes == 0 + max_nvisnodes = n_nodes + else + @assert nvisnodes >= 2 "nvisnodes must be zero or >= 2" + max_nvisnodes = nvisnodes end - end - unstructured_data = Array{eltype(raw_data)}(undef, - ntuple((d) -> nnodes(solver), - ndims(equations))..., - nelements(solver, cache), n_vars) - for variable in 1:n_vars - @views unstructured_data[.., :, variable] .= raw_data[variable, .., :] - end + interpolated_nodes = Array{eltype(original_nodes), 2}( + undef, max_nvisnodes, + n_elements + ) + interpolated_data = Array{eltype(unstructured_data), 3}( + undef, max_nvisnodes, + n_elements, n_vars + ) + + for j in 1:n_elements + # Interpolate on an equidistant grid. + interpolated_nodes[:, j] .= range( + original_nodes[1, 1, j], + original_nodes[1, end, j], + length = max_nvisnodes + ) + end - return unstructured_data -end - -# Convert cell-centered values to node-centered values by averaging over all -# four neighbors and making use of the periodicity of the solution -# -# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may -# thus be changed in future releases. -function cell2node(cell_centered_data) - # Create temporary data structure to make the averaging algorithm as simple - # as possible (by using a ghost layer) - tmp = similar(first(cell_centered_data), size(first(cell_centered_data)) .+ (2, 2)) - - # Create output data structure - resolution_in, _ = size(first(cell_centered_data)) - resolution_out = resolution_in + 1 - node_centered_data = [Matrix{Float64}(undef, resolution_out, resolution_out) - for _ in eachindex(cell_centered_data)] - - for (cell_data, node_data) in zip(cell_centered_data, node_centered_data) - # Fill center with original data - tmp[2:(end - 1), 2:(end - 1)] .= cell_data - - # Fill sides with opposite data (periodic domain) - # x-direction - tmp[1, 2:(end - 1)] .= cell_data[end, :] - tmp[end, 2:(end - 1)] .= cell_data[1, :] - # y-direction - tmp[2:(end - 1), 1] .= cell_data[:, end] - tmp[2:(end - 1), end] .= cell_data[:, 1] - # Corners - tmp[1, 1] = cell_data[end, end] - tmp[end, 1] = cell_data[1, end] - tmp[1, end] = cell_data[end, 1] - tmp[end, end] = cell_data[1, 1] - - # Obtain node-centered value by averaging over neighboring cell-centered values - for j in 1:resolution_out - for i in 1:resolution_out - node_data[i, j] = (tmp[i, j] + - tmp[i + 1, j] + - tmp[i, j + 1] + - tmp[i + 1, j + 1]) / 4 + nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes) + nodes_out = collect(range(-1, 1, length = max_nvisnodes)) + + # Calculate vandermonde matrix for interpolation. + vandermonde = polynomial_interpolation_matrix(nodes_in, nodes_out) + + # Iterate over all variables. + for v in 1:n_vars + # Interpolate data for each element. + for element in 1:n_elements + multiply_scalar_dimensionwise!( + @view(interpolated_data[:, element, v]), + vandermonde, + @view(unstructured_data[:, element, v]) + ) + end + end + # Return results after data is reshaped + return vec(interpolated_nodes), reshape(interpolated_data, :, n_vars), + vcat(original_nodes[1, 1, :], original_nodes[1, end, end]) + end + + # Change order of dimensions (variables are now last) and convert data to `solution_variables` + # + # Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may + # thus be changed in future releases. + function get_unstructured_data(u, solution_variables, mesh, equations, solver, cache) + if solution_variables === cons2cons + raw_data = u + n_vars = size(raw_data, 1) + else + # FIXME: Remove this comment once the implementation following it has been verified + # Reinterpret the solution array as an array of conservative variables, + # compute the solution variables via broadcasting, and reinterpret the + # result as a plain array of floating point numbers + # raw_data = Array(reinterpret(eltype(u), + # solution_variables.(reinterpret(SVector{nvariables(equations),eltype(u)}, u), + # Ref(equations)))) + # n_vars = size(raw_data, 1) + n_vars_in = nvariables(equations) + n_vars = length( + solution_variables( + get_node_vars(u, equations, solver), + equations + ) + ) + raw_data = Array{eltype(u)}(undef, n_vars, Base.tail(size(u))...) + reshaped_u = reshape(u, n_vars_in, :) + reshaped_r = reshape(raw_data, n_vars, :) + for idx in axes(reshaped_u, 2) + reshaped_r[:, idx] = solution_variables( + get_node_vars( + reshaped_u, equations, + solver, idx + ), + equations + ) end end - end - # Transpose - for (index, data) in enumerate(node_centered_data) - node_centered_data[index] = permutedims(data) - end + unstructured_data = Array{eltype(raw_data)}( + undef, + ntuple( + (d) -> nnodes(solver), + ndims(equations) + )..., + nelements(solver, cache), n_vars + ) + for variable in 1:n_vars + @views unstructured_data[.., :, variable] .= raw_data[variable, .., :] + end - return node_centered_data -end - -# Convert 3d unstructured data to 2d data. -# Additional to the new unstructured data updated coordinates, levels and -# center coordinates are returned. -# -# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may -# thus be changed in future releases. -function unstructured_3d_to_2d(unstructured_data, coordinates, levels, - length_level_0, center_level_0, slice, - point) - if slice === :yz - slice_dimension = 1 - other_dimensions = [2, 3] - elseif slice === :xz - slice_dimension = 2 - other_dimensions = [1, 3] - elseif slice === :xy - slice_dimension = 3 - other_dimensions = [1, 2] - else - error("illegal dimension '$slice', supported dimensions are :yz, :xz, and :xy") - end + return unstructured_data + end + + # Convert cell-centered values to node-centered values by averaging over all + # four neighbors and making use of the periodicity of the solution + # + # Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may + # thus be changed in future releases. + function cell2node(cell_centered_data) + # Create temporary data structure to make the averaging algorithm as simple + # as possible (by using a ghost layer) + tmp = similar(first(cell_centered_data), size(first(cell_centered_data)) .+ (2, 2)) + + # Create output data structure + resolution_in, _ = size(first(cell_centered_data)) + resolution_out = resolution_in + 1 + node_centered_data = [ + Matrix{Float64}(undef, resolution_out, resolution_out) + for _ in eachindex(cell_centered_data) + ] + + for (cell_data, node_data) in zip(cell_centered_data, node_centered_data) + # Fill center with original data + tmp[2:(end - 1), 2:(end - 1)] .= cell_data + + # Fill sides with opposite data (periodic domain) + # x-direction + tmp[1, 2:(end - 1)] .= cell_data[end, :] + tmp[end, 2:(end - 1)] .= cell_data[1, :] + # y-direction + tmp[2:(end - 1), 1] .= cell_data[:, end] + tmp[2:(end - 1), end] .= cell_data[:, 1] + # Corners + tmp[1, 1] = cell_data[end, end] + tmp[end, 1] = cell_data[1, end] + tmp[1, end] = cell_data[end, 1] + tmp[end, end] = cell_data[1, 1] + + # Obtain node-centered value by averaging over neighboring cell-centered values + for j in 1:resolution_out + for i in 1:resolution_out + node_data[i, j] = ( + tmp[i, j] + + tmp[i + 1, j] + + tmp[i, j + 1] + + tmp[i + 1, j + 1] + ) / 4 + end + end + end - # Limits of domain in slice dimension - lower_limit = center_level_0[slice_dimension] - length_level_0 / 2 - upper_limit = center_level_0[slice_dimension] + length_level_0 / 2 + # Transpose + for (index, data) in enumerate(node_centered_data) + node_centered_data[index] = permutedims(data) + end - @assert length(point)>=3 "Point must be three-dimensional." - if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit - error(string("Slice plane is outside of domain.", - " point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit")) - end + return node_centered_data + end + + # Convert 3d unstructured data to 2d data. + # Additional to the new unstructured data updated coordinates, levels and + # center coordinates are returned. + # + # Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may + # thus be changed in future releases. + function unstructured_3d_to_2d( + unstructured_data, coordinates, levels, + length_level_0, center_level_0, slice, + point + ) + if slice === :yz + slice_dimension = 1 + other_dimensions = [2, 3] + elseif slice === :xz + slice_dimension = 2 + other_dimensions = [1, 3] + elseif slice === :xy + slice_dimension = 3 + other_dimensions = [1, 2] + else + error("illegal dimension '$slice', supported dimensions are :yz, :xz, and :xy") + end - # Extract data shape information - n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data) + # Limits of domain in slice dimension + lower_limit = center_level_0[slice_dimension] - length_level_0 / 2 + upper_limit = center_level_0[slice_dimension] + length_level_0 / 2 + + @assert length(point) >= 3 "Point must be three-dimensional." + if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit + error( + string( + "Slice plane is outside of domain.", + " point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit" + ) + ) + end - # Get node coordinates for DG locations on reference element - nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in) + # Extract data shape information + n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data) - # New unstructured data has one dimension less. - # The redundant element ids are removed later. - @views new_unstructured_data = similar(unstructured_data[1, ..]) + # Get node coordinates for DG locations on reference element + nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in) - # Declare new empty arrays to fill in new coordinates and levels - new_coordinates = Array{Float64}(undef, 2, n_elements) - new_levels = Array{eltype(levels)}(undef, n_elements) + # New unstructured data has one dimension less. + # The redundant element ids are removed later. + @views new_unstructured_data = similar(unstructured_data[1, ..]) - # Counter for new element ids - new_id = 0 + # Declare new empty arrays to fill in new coordinates and levels + new_coordinates = Array{Float64}(undef, 2, n_elements) + new_levels = Array{eltype(levels)}(undef, n_elements) - # Save vandermonde matrices in a Dict to prevent redundant generation - vandermonde_to_2d = Dict() + # Counter for new element ids + new_id = 0 - # Permute dimensions such that the slice dimension is always the - # third dimension of the array. Below we can always interpolate in the - # third dimension. - if slice === :yz - unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5]) - elseif slice === :xz - unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5]) - end + # Save vandermonde matrices in a Dict to prevent redundant generation + vandermonde_to_2d = Dict() - for element_id in 1:n_elements - # Distance from center to border of this element (half the length) - element_length = length_level_0 / 2^levels[element_id] - min_coordinate = coordinates[:, element_id] .- element_length / 2 - max_coordinate = coordinates[:, element_id] .+ element_length / 2 - - # Check if slice plane and current element intersect. - # The first check uses a "greater but not equal" to only match one cell if the - # slice plane lies between two cells. - # The second check is needed if the slice plane is at the upper border of - # the domain due to this. - if !((min_coordinate[slice_dimension] <= point[slice_dimension] && - max_coordinate[slice_dimension] > point[slice_dimension]) || - (point[slice_dimension] == upper_limit && - max_coordinate[slice_dimension] == upper_limit)) - # Continue for loop if they don't intersect - continue - end - - # This element is of interest - new_id += 1 - - # Add element to new coordinates and levels - new_coordinates[:, new_id] = coordinates[other_dimensions, element_id] - new_levels[new_id] = levels[element_id] - - # Construct vandermonde matrix (or load from Dict if possible) - normalized_intercept = (point[slice_dimension] - - min_coordinate[slice_dimension]) / - element_length * 2 - 1 - - if haskey(vandermonde_to_2d, normalized_intercept) - vandermonde = vandermonde_to_2d[normalized_intercept] - else - # Generate vandermonde matrix to interpolate values at nodes_in to one value - vandermonde = polynomial_interpolation_matrix(nodes_in, - [normalized_intercept]) - vandermonde_to_2d[normalized_intercept] = vandermonde - end - - # 1D interpolation to specified slice plane - # We permuted the dimensions above such that now the dimension in which - # we will interpolate is always the third one. - for i in 1:n_nodes_in - for ii in 1:n_nodes_in - # Interpolate in the third dimension - data = unstructured_data[i, ii, :, element_id, :] - - value = multiply_dimensionwise(vandermonde, permutedims(data)) - new_unstructured_data[i, ii, new_id, :] = value[:, 1] - end + # Permute dimensions such that the slice dimension is always the + # third dimension of the array. Below we can always interpolate in the + # third dimension. + if slice === :yz + unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5]) + elseif slice === :xz + unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5]) end - end - # Remove redundant element ids - unstructured_data = new_unstructured_data[:, :, 1:new_id, :] - new_coordinates = new_coordinates[:, 1:new_id] - new_levels = new_levels[1:new_id] - - center_level_0 = center_level_0[other_dimensions] - - return unstructured_data, new_coordinates, new_levels, center_level_0 -end - -# Convert 2d unstructured data to 1d slice and interpolate them. -function unstructured_2d_to_1d(original_nodes, unstructured_data, nvisnodes, slice, - point) - if slice === :x - slice_dimension = 2 - other_dimension = 1 - elseif slice === :y - slice_dimension = 1 - other_dimension = 2 - else - error("illegal dimension '$slice', supported dimensions are :x and :y") - end + for element_id in 1:n_elements + # Distance from center to border of this element (half the length) + element_length = length_level_0 / 2^levels[element_id] + min_coordinate = coordinates[:, element_id] .- element_length / 2 + max_coordinate = coordinates[:, element_id] .+ element_length / 2 + + # Check if slice plane and current element intersect. + # The first check uses a "greater but not equal" to only match one cell if the + # slice plane lies between two cells. + # The second check is needed if the slice plane is at the upper border of + # the domain due to this. + if !( + ( + min_coordinate[slice_dimension] <= point[slice_dimension] && + max_coordinate[slice_dimension] > point[slice_dimension] + ) || + ( + point[slice_dimension] == upper_limit && + max_coordinate[slice_dimension] == upper_limit + ) + ) + # Continue for loop if they don't intersect + continue + end - # Set up data structures to store new 1D data. - @views new_unstructured_data = similar(unstructured_data[1, ..]) - @views new_nodes = similar(original_nodes[1, 1, ..]) + # This element is of interest + new_id += 1 + + # Add element to new coordinates and levels + new_coordinates[:, new_id] = coordinates[other_dimensions, element_id] + new_levels[new_id] = levels[element_id] + + # Construct vandermonde matrix (or load from Dict if possible) + normalized_intercept = ( + point[slice_dimension] - + min_coordinate[slice_dimension] + ) / + element_length * 2 - 1 + + if haskey(vandermonde_to_2d, normalized_intercept) + vandermonde = vandermonde_to_2d[normalized_intercept] + else + # Generate vandermonde matrix to interpolate values at nodes_in to one value + vandermonde = polynomial_interpolation_matrix( + nodes_in, + [normalized_intercept] + ) + vandermonde_to_2d[normalized_intercept] = vandermonde + end - n_nodes_in, _, n_elements, n_variables = size(unstructured_data) - nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in) + # 1D interpolation to specified slice plane + # We permuted the dimensions above such that now the dimension in which + # we will interpolate is always the third one. + for i in 1:n_nodes_in + for ii in 1:n_nodes_in + # Interpolate in the third dimension + data = unstructured_data[i, ii, :, element_id, :] - # Test if point lies in the domain. - lower_limit = original_nodes[1, 1, 1, 1] - upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_elements] + value = multiply_dimensionwise(vandermonde, permutedims(data)) + new_unstructured_data[i, ii, new_id, :] = value[:, 1] + end + end + end - @assert length(point)>=2 "Point must be two-dimensional." - if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit - error(string("Slice axis is outside of domain. ", - " point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit")) - end + # Remove redundant element ids + unstructured_data = new_unstructured_data[:, :, 1:new_id, :] + new_coordinates = new_coordinates[:, 1:new_id] + new_levels = new_levels[1:new_id] - # Count the amount of new elements. - new_id = 0 + center_level_0 = center_level_0[other_dimensions] - # Permute dimensions so that the slice dimension is always in the correct place for later use. - if slice === :y - original_nodes = permutedims(original_nodes, [1, 3, 2, 4]) - unstructured_data = permutedims(unstructured_data, [2, 1, 3, 4]) + return unstructured_data, new_coordinates, new_levels, center_level_0 end - # Iterate over all elements to find the ones that lie on the slice axis. - for element_id in 1:n_elements - min_coordinate = original_nodes[:, 1, 1, element_id] - max_coordinate = original_nodes[:, n_nodes_in, n_nodes_in, element_id] - element_length = max_coordinate - min_coordinate - - # Test if the element is on the slice axis. If not just continue with the next element. - if !((min_coordinate[slice_dimension] <= point[slice_dimension] && - max_coordinate[slice_dimension] > point[slice_dimension]) || - (point[slice_dimension] == upper_limit && - max_coordinate[slice_dimension] == upper_limit)) - continue + # Convert 2d unstructured data to 1d slice and interpolate them. + function unstructured_2d_to_1d( + original_nodes, unstructured_data, nvisnodes, slice, + point + ) + if slice === :x + slice_dimension = 2 + other_dimension = 1 + elseif slice === :y + slice_dimension = 1 + other_dimension = 2 + else + error("illegal dimension '$slice', supported dimensions are :x and :y") end - new_id += 1 - - # Construct vandermonde matrix for interpolation of each 2D element to a 1D element. - normalized_intercept = (point[slice_dimension] - - min_coordinate[slice_dimension]) / - element_length[1] * 2 - 1 - vandermonde = polynomial_interpolation_matrix(nodes_in, normalized_intercept) - - # Interpolate to each node of new 1D element. - for v in 1:n_variables - for node in 1:n_nodes_in - new_unstructured_data[node, new_id, v] = (vandermonde * unstructured_data[node, - :, - element_id, - v])[1] - end + # Set up data structures to store new 1D data. + @views new_unstructured_data = similar(unstructured_data[1, ..]) + @views new_nodes = similar(original_nodes[1, 1, ..]) + + n_nodes_in, _, n_elements, n_variables = size(unstructured_data) + nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in) + + # Test if point lies in the domain. + lower_limit = original_nodes[1, 1, 1, 1] + upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_elements] + + @assert length(point) >= 2 "Point must be two-dimensional." + if point[slice_dimension] < lower_limit || point[slice_dimension] > upper_limit + error( + string( + "Slice axis is outside of domain. ", + " point[$slice_dimension]=$(point[slice_dimension]) must be between $lower_limit and $upper_limit" + ) + ) end - new_nodes[:, new_id] = original_nodes[other_dimension, :, 1, element_id] - end + # Count the amount of new elements. + new_id = 0 - return get_data_1d(reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id), - new_unstructured_data[:, 1:new_id, :], nvisnodes) -end - -# Calculate the arc length of a curve given by ndims x npoints point coordinates (piece-wise linear approximation) -function calc_arc_length(coordinates) - n_points = size(coordinates)[2] - arc_length = zeros(n_points) - for i in 1:(n_points - 1) - arc_length[i + 1] = arc_length[i] + - sqrt(sum((coordinates[:, i] - coordinates[:, i + 1]) .^ 2)) - end - return arc_length -end - -# Convert 2d unstructured data to 1d data at given curve. -function unstructured_2d_to_1d_curve(original_nodes, unstructured_data, nvisnodes, - curve, mesh, solver, cache) - n_points_curve = size(curve)[2] - n_nodes, _, n_elements, n_variables = size(unstructured_data) - nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes) - - # Check if input is correct. - min = original_nodes[:, 1, 1, 1] - max = max_coordinate = original_nodes[:, n_nodes, n_nodes, n_elements] - @assert size(curve)==(2, size(curve)[2]) "Coordinates along curve must be 2xn dimensional." - for element in 1:n_points_curve - @assert (prod(vcat(curve[:, n_points_curve] .>= min, - curve[:, n_points_curve] - .<= - max))) "Some coordinates from `curve` are outside of the domain.." - end + # Permute dimensions so that the slice dimension is always in the correct place for later use. + if slice === :y + original_nodes = permutedims(original_nodes, [1, 3, 2, 4]) + unstructured_data = permutedims(unstructured_data, [2, 1, 3, 4]) + end - # Set nodes according to the length of the curve. - arc_length = calc_arc_length(curve) + # Iterate over all elements to find the ones that lie on the slice axis. + for element_id in 1:n_elements + min_coordinate = original_nodes[:, 1, 1, element_id] + max_coordinate = original_nodes[:, n_nodes_in, n_nodes_in, element_id] + element_length = max_coordinate - min_coordinate + + # Test if the element is on the slice axis. If not just continue with the next element. + if !( + ( + min_coordinate[slice_dimension] <= point[slice_dimension] && + max_coordinate[slice_dimension] > point[slice_dimension] + ) || + ( + point[slice_dimension] == upper_limit && + max_coordinate[slice_dimension] == upper_limit + ) + ) + continue + end - # Setup data structures. - data_on_curve = Array{Float64}(undef, n_points_curve, n_variables) - temp_data = Array{Float64}(undef, n_nodes, n_points_curve, n_variables) + new_id += 1 + + # Construct vandermonde matrix for interpolation of each 2D element to a 1D element. + normalized_intercept = ( + point[slice_dimension] - + min_coordinate[slice_dimension] + ) / + element_length[1] * 2 - 1 + vandermonde = polynomial_interpolation_matrix(nodes_in, normalized_intercept) + + # Interpolate to each node of new 1D element. + for v in 1:n_variables + for node in 1:n_nodes_in + new_unstructured_data[node, new_id, v] = ( + vandermonde * unstructured_data[ + node, + :, + element_id, + v, + ] + )[1] + end + end - # For each coordinate find the corresponding element with its id. - element_ids = get_elements_by_coordinates(curve, mesh, solver, cache) + new_nodes[:, new_id] = original_nodes[other_dimension, :, 1, element_id] + end - # Iterate over all found elements. - for element in 1:n_points_curve - min_coordinate = original_nodes[:, 1, 1, element_ids[element]] - max_coordinate = original_nodes[:, n_nodes, n_nodes, element_ids[element]] - element_length = max_coordinate - min_coordinate + return get_data_1d( + reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id), + new_unstructured_data[:, 1:new_id, :], nvisnodes + ) + end - normalized_coordinates = (curve[:, element] - min_coordinate) / - element_length[1] * 2 .- 1 + # Calculate the arc length of a curve given by ndims x npoints point coordinates (piece-wise linear approximation) + function calc_arc_length(coordinates) + n_points = size(coordinates)[2] + arc_length = zeros(n_points) + for i in 1:(n_points - 1) + arc_length[i + 1] = arc_length[i] + + sqrt(sum((coordinates[:, i] - coordinates[:, i + 1]) .^ 2)) + end + return arc_length + end + + # Convert 2d unstructured data to 1d data at given curve. + function unstructured_2d_to_1d_curve( + original_nodes, unstructured_data, nvisnodes, + curve, mesh, solver, cache + ) + n_points_curve = size(curve)[2] + n_nodes, _, n_elements, n_variables = size(unstructured_data) + nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes) + + # Check if input is correct. + min = original_nodes[:, 1, 1, 1] + max = max_coordinate = original_nodes[:, n_nodes, n_nodes, n_elements] + @assert size(curve) == (2, size(curve)[2]) "Coordinates along curve must be 2xn dimensional." + for element in 1:n_points_curve + @assert ( + prod( + vcat( + curve[:, n_points_curve] .>= min, + curve[:, n_points_curve] + .<= + max + ) + ) + ) "Some coordinates from `curve` are outside of the domain.." + end - # Interpolate to a single point in each element. - vandermonde_x = polynomial_interpolation_matrix(nodes_in, - normalized_coordinates[1]) - vandermonde_y = polynomial_interpolation_matrix(nodes_in, - normalized_coordinates[2]) - for v in 1:n_variables - for i in 1:n_nodes - temp_data[i, element, v] = (vandermonde_y * unstructured_data[i, :, - element_ids[element], - v])[1] + # Set nodes according to the length of the curve. + arc_length = calc_arc_length(curve) + + # Setup data structures. + data_on_curve = Array{Float64}(undef, n_points_curve, n_variables) + temp_data = Array{Float64}(undef, n_nodes, n_points_curve, n_variables) + + # For each coordinate find the corresponding element with its id. + element_ids = get_elements_by_coordinates(curve, mesh, solver, cache) + + # Iterate over all found elements. + for element in 1:n_points_curve + min_coordinate = original_nodes[:, 1, 1, element_ids[element]] + max_coordinate = original_nodes[:, n_nodes, n_nodes, element_ids[element]] + element_length = max_coordinate - min_coordinate + + normalized_coordinates = (curve[:, element] - min_coordinate) / + element_length[1] * 2 .- 1 + + # Interpolate to a single point in each element. + vandermonde_x = polynomial_interpolation_matrix( + nodes_in, + normalized_coordinates[1] + ) + vandermonde_y = polynomial_interpolation_matrix( + nodes_in, + normalized_coordinates[2] + ) + for v in 1:n_variables + for i in 1:n_nodes + temp_data[i, element, v] = ( + vandermonde_y * unstructured_data[ + i, :, + element_ids[element], + v, + ] + )[1] + end + data_on_curve[element, v] = (vandermonde_x * temp_data[:, element, v])[] end - data_on_curve[element, v] = (vandermonde_x * temp_data[:, element, v])[] end + + return arc_length, data_on_curve, nothing end - return arc_length, data_on_curve, nothing -end + # Convert a PlotData2DTriangulate object to a 1d data along given curve. + function unstructured_2d_to_1d_curve(pd, input_curve, slice, point, nvisnodes) -# Convert a PlotData2DTriangulate object to a 1d data along given curve. -function unstructured_2d_to_1d_curve(pd, input_curve, slice, point, nvisnodes) + # If no curve is defined, create a axis curve. + if input_curve === nothing + input_curve = axis_curve(pd.x, pd.y, nothing, slice, point, nvisnodes) + end - # If no curve is defined, create a axis curve. - if input_curve === nothing - input_curve = axis_curve(pd.x, pd.y, nothing, slice, point, nvisnodes) - end + @assert size(input_curve, 1) == 2 "Input 'curve' must be 2xn dimensional." - @assert size(input_curve, 1)==2 "Input 'curve' must be 2xn dimensional." + # For each coordinate find the corresponding triangle with its ids. + ids_by_coordinates = get_ids_by_coordinates(input_curve, pd) + found_coordinates = ids_by_coordinates[:, 1] .!= nothing - # For each coordinate find the corresponding triangle with its ids. - ids_by_coordinates = get_ids_by_coordinates(input_curve, pd) - found_coordinates = ids_by_coordinates[:, 1] .!= nothing + @assert found_coordinates != zeros(size(input_curve, 2)) "No points of 'curve' are inside of the solutions domain." - @assert found_coordinates!=zeros(size(input_curve, 2)) "No points of 'curve' are inside of the solutions domain." + # These hold the ids of the elements and triangles the points of the curve sit in. + element_ids = @view ids_by_coordinates[found_coordinates, 1] + triangle_ids = @view ids_by_coordinates[found_coordinates, 2] - # These hold the ids of the elements and triangles the points of the curve sit in. - element_ids = @view ids_by_coordinates[found_coordinates, 1] - triangle_ids = @view ids_by_coordinates[found_coordinates, 2] + # Shorten the curve, so that it contains only point that were found. + curve = @view input_curve[:, found_coordinates] - # Shorten the curve, so that it contains only point that were found. - curve = @view input_curve[:, found_coordinates] + n_variables = length(pd.data[1, 1]) + n_points_curve = size(curve, 2) - n_variables = length(pd.data[1, 1]) - n_points_curve = size(curve, 2) + # Set nodes according to the length of the curve. + arc_length = calc_arc_length(curve) - # Set nodes according to the length of the curve. - arc_length = calc_arc_length(curve) + # Setup data structures. + data_on_curve = Array{Float64}(undef, n_points_curve, n_variables) - # Setup data structures. - data_on_curve = Array{Float64}(undef, n_points_curve, n_variables) + # Iterate over all points on the curve. + for point in 1:n_points_curve + element = @view element_ids[point] + triangle = @view pd.t[triangle_ids[point], :] + for v in 1:n_variables + # Get the x and y coordinates of the corners of given triangle. + x_coordinates_triangle = SVector{3}(pd.x[triangle, element]) + y_coordinates_triangle = SVector{3}(pd.y[triangle, element]) - # Iterate over all points on the curve. - for point in 1:n_points_curve - element = @view element_ids[point] - triangle = @view pd.t[triangle_ids[point], :] - for v in 1:n_variables - # Get the x and y coordinates of the corners of given triangle. - x_coordinates_triangle = SVector{3}(pd.x[triangle, element]) - y_coordinates_triangle = SVector{3}(pd.y[triangle, element]) + # Extract solutions values in corners of the triangle. + values_triangle = SVector{3}(getindex.(view(pd.data, triangle, element), v)) - # Extract solutions values in corners of the triangle. - values_triangle = SVector{3}(getindex.(view(pd.data, triangle, element), v)) - - # Linear interpolation in each triangle to the points on the curve. - data_on_curve[point, v] = triangle_interpolation(x_coordinates_triangle, - y_coordinates_triangle, - values_triangle, - curve[:, point]) + # Linear interpolation in each triangle to the points on the curve. + data_on_curve[point, v] = triangle_interpolation( + x_coordinates_triangle, + y_coordinates_triangle, + values_triangle, + curve[:, point] + ) + end end - end - return arc_length, data_on_curve, nothing -end - -# Convert 3d unstructured data to 1d data at given curve. -function unstructured_3d_to_1d_curve(original_nodes, unstructured_data, nvisnodes, - curve, mesh, solver, cache) - n_points_curve = size(curve)[2] - n_nodes, _, _, n_elements, n_variables = size(unstructured_data) - nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes) - - # Check if input is correct. - min = original_nodes[:, 1, 1, 1, 1] - max = max_coordinate = original_nodes[:, n_nodes, n_nodes, n_nodes, n_elements] - @assert size(curve)==(3, n_points_curve) "Coordinates along curve must be 3xn dimensional." - for element in 1:n_points_curve - @assert (prod(vcat(curve[:, n_points_curve] .>= min, - curve[:, n_points_curve] - .<= - max))) "Some coordinates from `curve` are outside of the domain.." - end + return arc_length, data_on_curve, nothing + end + + # Convert 3d unstructured data to 1d data at given curve. + function unstructured_3d_to_1d_curve( + original_nodes, unstructured_data, nvisnodes, + curve, mesh, solver, cache + ) + n_points_curve = size(curve)[2] + n_nodes, _, _, n_elements, n_variables = size(unstructured_data) + nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes) + + # Check if input is correct. + min = original_nodes[:, 1, 1, 1, 1] + max = max_coordinate = original_nodes[:, n_nodes, n_nodes, n_nodes, n_elements] + @assert size(curve) == (3, n_points_curve) "Coordinates along curve must be 3xn dimensional." + for element in 1:n_points_curve + @assert ( + prod( + vcat( + curve[:, n_points_curve] .>= min, + curve[:, n_points_curve] + .<= + max + ) + ) + ) "Some coordinates from `curve` are outside of the domain.." + end - # Set nodes according to the length of the curve. - arc_length = calc_arc_length(curve) - - # Setup data structures. - data_on_curve = Array{Float64}(undef, n_points_curve, n_variables) - temp_data = Array{Float64}(undef, n_nodes, n_nodes + 1, n_points_curve, n_variables) - - # For each coordinate find the corresponding element with its id. - element_ids = get_elements_by_coordinates(curve, mesh, solver, cache) - - # Iterate over all found elements. - for element in 1:n_points_curve - min_coordinate = original_nodes[:, 1, 1, 1, element_ids[element]] - max_coordinate = original_nodes[:, n_nodes, n_nodes, n_nodes, - element_ids[element]] - element_length = max_coordinate - min_coordinate - - normalized_coordinates = (curve[:, element] - min_coordinate) / - element_length[1] * 2 .- 1 - - # Interpolate to a single point in each element. - vandermonde_x = polynomial_interpolation_matrix(nodes_in, - normalized_coordinates[1]) - vandermonde_y = polynomial_interpolation_matrix(nodes_in, - normalized_coordinates[2]) - vandermonde_z = polynomial_interpolation_matrix(nodes_in, - normalized_coordinates[3]) - for v in 1:n_variables - for i in 1:n_nodes - for ii in 1:n_nodes - temp_data[i, ii, element, v] = (vandermonde_z * unstructured_data[i, - ii, - :, - element_ids[element], - v])[1] + # Set nodes according to the length of the curve. + arc_length = calc_arc_length(curve) + + # Setup data structures. + data_on_curve = Array{Float64}(undef, n_points_curve, n_variables) + temp_data = Array{Float64}(undef, n_nodes, n_nodes + 1, n_points_curve, n_variables) + + # For each coordinate find the corresponding element with its id. + element_ids = get_elements_by_coordinates(curve, mesh, solver, cache) + + # Iterate over all found elements. + for element in 1:n_points_curve + min_coordinate = original_nodes[:, 1, 1, 1, element_ids[element]] + max_coordinate = original_nodes[ + :, n_nodes, n_nodes, n_nodes, + element_ids[element], + ] + element_length = max_coordinate - min_coordinate + + normalized_coordinates = (curve[:, element] - min_coordinate) / + element_length[1] * 2 .- 1 + + # Interpolate to a single point in each element. + vandermonde_x = polynomial_interpolation_matrix( + nodes_in, + normalized_coordinates[1] + ) + vandermonde_y = polynomial_interpolation_matrix( + nodes_in, + normalized_coordinates[2] + ) + vandermonde_z = polynomial_interpolation_matrix( + nodes_in, + normalized_coordinates[3] + ) + for v in 1:n_variables + for i in 1:n_nodes + for ii in 1:n_nodes + temp_data[i, ii, element, v] = ( + vandermonde_z * unstructured_data[ + i, + ii, + :, + element_ids[element], + v, + ] + )[1] + end + temp_data[i, n_nodes + 1, element, v] = ( + vandermonde_y * temp_data[ + i, + 1:n_nodes, + element, + v, + ] + )[1] end - temp_data[i, n_nodes + 1, element, v] = (vandermonde_y * temp_data[i, - 1:n_nodes, - element, - v])[1] + data_on_curve[element, v] = ( + vandermonde_x * temp_data[ + :, n_nodes + 1, + element, v, + ] + )[1] end - data_on_curve[element, v] = (vandermonde_x * temp_data[:, n_nodes + 1, - element, v])[1] end + + return arc_length, data_on_curve, nothing end - return arc_length, data_on_curve, nothing -end + # Convert 3d unstructured data from a general mesh to 1d data at given curve. + function unstructured_3d_to_1d_curve(nodes, data, curve, slice, point, nvisnodes) + # If no curve is defined, create a axis curve. + if curve === nothing + curve = axis_curve( + nodes[1, :, :, :, :], nodes[2, :, :, :, :], + nodes[3, :, :, :, :], slice, point, nvisnodes + ) + end -# Convert 3d unstructured data from a general mesh to 1d data at given curve. -function unstructured_3d_to_1d_curve(nodes, data, curve, slice, point, nvisnodes) - # If no curve is defined, create a axis curve. - if curve === nothing - curve = axis_curve(nodes[1, :, :, :, :], nodes[2, :, :, :, :], - nodes[3, :, :, :, :], slice, point, nvisnodes) - end + # Set up data structure. + n_points_curve = size(curve, 2) + n_variables = size(data, 1) + data_on_curve = Array{Float64}(undef, n_points_curve, n_variables) - # Set up data structure. - n_points_curve = size(curve, 2) - n_variables = size(data, 1) - data_on_curve = Array{Float64}(undef, n_points_curve, n_variables) + # Iterate over every point on the curve and determine the solutions value at given point. + for i in 1:n_points_curve + @views data_on_curve[i, :] .= get_value_at_point(curve[:, i], nodes, data) + end - # Iterate over every point on the curve and determine the solutions value at given point. - for i in 1:n_points_curve - @views data_on_curve[i, :] .= get_value_at_point(curve[:, i], nodes, data) + mesh_vertices_x = nothing + + return calc_arc_length(curve), data_on_curve, mesh_vertices_x + end + + # Check if the first 'amount'-many points can still form a valid tetrahedron. + function is_valid_tetrahedron(amount, coordinates; tol = 10^-4) + a = coordinates[:, 1] + b = coordinates[:, 2] + c = coordinates[:, 3] + d = coordinates[:, 4] + if amount == 2 # If two points are the same, then no tetrahedron can be formed. + return !(isapprox(a, b; atol = tol)) + elseif amount == 3 # Check if three points are on the same line. + return !on_the_same_line(a, b, c; tol = tol) + elseif amount == 4 # Check if four points form a tetrahedron. + A = hcat( + coordinates[1, :], coordinates[2, :], coordinates[3, :], + SVector(1, 1, 1, 1) + ) + return !isapprox(det(A), 0; atol = tol) + else # With one point a tetrahedron can always be formed. + return true + end end - mesh_vertices_x = nothing - - return calc_arc_length(curve), data_on_curve, mesh_vertices_x -end - -# Check if the first 'amount'-many points can still form a valid tetrahedron. -function is_valid_tetrahedron(amount, coordinates; tol = 10^-4) - a = coordinates[:, 1] - b = coordinates[:, 2] - c = coordinates[:, 3] - d = coordinates[:, 4] - if amount == 2 # If two points are the same, then no tetrahedron can be formed. - return !(isapprox(a, b; atol = tol)) - elseif amount == 3 # Check if three points are on the same line. - return !on_the_same_line(a, b, c; tol = tol) - elseif amount == 4 # Check if four points form a tetrahedron. - A = hcat(coordinates[1, :], coordinates[2, :], coordinates[3, :], - SVector(1, 1, 1, 1)) - return !isapprox(det(A), 0; atol = tol) - else # With one point a tetrahedron can always be formed. - return true - end -end - -# Check if three given 3D-points are on the same line. -function on_the_same_line(a, b, c; tol = 10^-4) - # Calculate the intersection of the a-b-axis at x=0. - if b[1] == 0 - intersect_a_b = b - else - intersect_a_b = a - b .* (a[1] / b[1]) + # Check if three given 3D-points are on the same line. + function on_the_same_line(a, b, c; tol = 10^-4) + # Calculate the intersection of the a-b-axis at x=0. + if b[1] == 0 + intersect_a_b = b + else + intersect_a_b = a - b .* (a[1] / b[1]) + end + # Calculate the intersection of the a-c-axis at x=0. + if c[1] == 0 + intersect_a_c = c + else + intersect_a_c = a - c .* (a[1] / c[1]) + end + return isapprox(intersect_a_b, intersect_a_c; atol = tol) end - # Calculate the intersection of the a-c-axis at x=0. - if c[1] == 0 - intersect_a_c = c - else - intersect_a_c = a - c .* (a[1] / c[1]) + + # Interpolate from four corners of a tetrahedron to a single point. + function tetrahedron_interpolation( + x_coordinates_in, y_coordinates_in, z_coordinates_in, + values_in, coordinate_out + ) + A = hcat(x_coordinates_in, y_coordinates_in, z_coordinates_in, SVector(1, 1, 1, 1)) + c = A \ values_in + return c[1] * coordinate_out[1] + c[2] * coordinate_out[2] + + c[3] * coordinate_out[3] + c[4] end - return isapprox(intersect_a_b, intersect_a_c; atol = tol) -end - -# Interpolate from four corners of a tetrahedron to a single point. -function tetrahedron_interpolation(x_coordinates_in, y_coordinates_in, z_coordinates_in, - values_in, coordinate_out) - A = hcat(x_coordinates_in, y_coordinates_in, z_coordinates_in, SVector(1, 1, 1, 1)) - c = A \ values_in - return c[1] * coordinate_out[1] + c[2] * coordinate_out[2] + - c[3] * coordinate_out[3] + c[4] -end - -# Calculate the distances from every entry in node to given point. -function distances_from_single_point(nodes, point) - _, n_nodes, _, _, n_elements = size(nodes) - shifted_data = nodes .- point - distances = zeros(n_nodes, n_nodes, n_nodes, n_elements) - - # Iterate over every entry. - for element in 1:n_elements - for x in 1:n_nodes - for y in 1:n_nodes - for z in 1:n_nodes - distances[x, y, z, element] = norm(shifted_data[:, x, y, z, - element]) + + # Calculate the distances from every entry in node to given point. + function distances_from_single_point(nodes, point) + _, n_nodes, _, _, n_elements = size(nodes) + shifted_data = nodes .- point + distances = zeros(n_nodes, n_nodes, n_nodes, n_elements) + + # Iterate over every entry. + for element in 1:n_elements + for x in 1:n_nodes + for y in 1:n_nodes + for z in 1:n_nodes + distances[x, y, z, element] = norm( + shifted_data[ + :, x, y, z, + element, + ] + ) + end end end end + return distances end - return distances -end -# Interpolate the data on given nodes to a single value at given point. -function get_value_at_point(point, nodes, data) - # Set up ata structures. - n_variables, n_x_nodes, n_y_nodes, n_z_nodes, _ = size(data) - distances = distances_from_single_point(nodes, point) - maximum_distance = maximum(distances) + # Interpolate the data on given nodes to a single value at given point. + function get_value_at_point(point, nodes, data) + # Set up ata structures. + n_variables, n_x_nodes, n_y_nodes, n_z_nodes, _ = size(data) + distances = distances_from_single_point(nodes, point) + maximum_distance = maximum(distances) - coordinates_tetrahedron = Array{Float64, 2}(undef, 3, 4) - value_tetrahedron = Array{Float64}(undef, n_variables, 4) + coordinates_tetrahedron = Array{Float64, 2}(undef, 3, 4) + value_tetrahedron = Array{Float64}(undef, n_variables, 4) - index = argmin(distances) + index = argmin(distances) - # If the point sits exactly on a node, no interpolation is needed. - if nodes[:, index[1], index[2], index[3], index[4]] == point - return data[1, index[1], index[2], index[3], index[4]] - end + # If the point sits exactly on a node, no interpolation is needed. + if nodes[:, index[1], index[2], index[3], index[4]] == point + return data[1, index[1], index[2], index[3], index[4]] + end - @views coordinates_tetrahedron[:, 1] = nodes[:, index[1], index[2], index[3], - index[4]] - @views value_tetrahedron[:, 1] = data[:, index[1], index[2], index[3], index[4]] - - # Restrict the interpolation to the closest element only. - closest_element = index[4] - @views element_distances = distances[:, :, :, closest_element] - - # Find a tetrahedron, which is given by four corners, to interpolate from. - for i in 1:4 - # Iterate until a valid tetrahedron is found. - while true - index = argmin(element_distances) - element_distances[index[1], index[2], index[3]] = maximum_distance - - @views coordinates_tetrahedron[:, i] = nodes[:, index[1], index[2], - index[3], closest_element] - @views value_tetrahedron[:, i] = data[:, index[1], index[2], index[3], - closest_element] - - # Look for another point if current tetrahedron is not valid. - if is_valid_tetrahedron(i, coordinates_tetrahedron) - break + @views coordinates_tetrahedron[:, 1] = nodes[ + :, index[1], index[2], index[3], + index[4], + ] + @views value_tetrahedron[:, 1] = data[:, index[1], index[2], index[3], index[4]] + + # Restrict the interpolation to the closest element only. + closest_element = index[4] + @views element_distances = distances[:, :, :, closest_element] + + # Find a tetrahedron, which is given by four corners, to interpolate from. + for i in 1:4 + # Iterate until a valid tetrahedron is found. + while true + index = argmin(element_distances) + element_distances[index[1], index[2], index[3]] = maximum_distance + + @views coordinates_tetrahedron[:, i] = nodes[ + :, index[1], index[2], + index[3], closest_element, + ] + @views value_tetrahedron[:, i] = data[ + :, index[1], index[2], index[3], + closest_element, + ] + + # Look for another point if current tetrahedron is not valid. + if is_valid_tetrahedron(i, coordinates_tetrahedron) + break + end end end - end - # Interpolate from tetrahedron to given point. - value_at_point = Array{Float64}(undef, n_variables) - for v in 1:n_variables - value_at_point[v] = tetrahedron_interpolation(coordinates_tetrahedron[1, :], - coordinates_tetrahedron[2, :], - coordinates_tetrahedron[3, :], - value_tetrahedron[v, :], point) - end + # Interpolate from tetrahedron to given point. + value_at_point = Array{Float64}(undef, n_variables) + for v in 1:n_variables + value_at_point[v] = tetrahedron_interpolation( + coordinates_tetrahedron[1, :], + coordinates_tetrahedron[2, :], + coordinates_tetrahedron[3, :], + value_tetrahedron[v, :], point + ) + end - return value_at_point -end - -# Convert 3d unstructured data to 1d slice and interpolate them. -function unstructured_3d_to_1d(original_nodes, unstructured_data, nvisnodes, slice, - point) - if slice === :x - slice_dimension = 1 - other_dimensions = [2, 3] - elseif slice === :y - slice_dimension = 2 - other_dimensions = [1, 3] - elseif slice === :z - slice_dimension = 3 - other_dimensions = [1, 2] - else - error("illegal dimension '$slice', supported dimensions are :x, :y and :z") - end + return value_at_point + end + + # Convert 3d unstructured data to 1d slice and interpolate them. + function unstructured_3d_to_1d( + original_nodes, unstructured_data, nvisnodes, slice, + point + ) + if slice === :x + slice_dimension = 1 + other_dimensions = [2, 3] + elseif slice === :y + slice_dimension = 2 + other_dimensions = [1, 3] + elseif slice === :z + slice_dimension = 3 + other_dimensions = [1, 2] + else + error("illegal dimension '$slice', supported dimensions are :x, :y and :z") + end - # Set up data structures to store new 1D data. - @views new_unstructured_data = similar(unstructured_data[1, 1, ..]) - @views temp_unstructured_data = similar(unstructured_data[1, ..]) - @views new_nodes = similar(original_nodes[1, 1, 1, ..]) + # Set up data structures to store new 1D data. + @views new_unstructured_data = similar(unstructured_data[1, 1, ..]) + @views temp_unstructured_data = similar(unstructured_data[1, ..]) + @views new_nodes = similar(original_nodes[1, 1, 1, ..]) + + n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data) + nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in) + + # Test if point lies in the domain. + lower_limit = original_nodes[1, 1, 1, 1, 1] + upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_nodes_in, n_elements] + + @assert length(point) >= 3 "Point must be three-dimensional." + if prod(point[other_dimensions] .< lower_limit) || + prod(point[other_dimensions] .> upper_limit) + error( + string( + "Slice axis is outside of domain. ", + " point[$other_dimensions]=$(point[other_dimensions]) must be between $lower_limit and $upper_limit" + ) + ) + end - n_nodes_in, _, _, n_elements, n_variables = size(unstructured_data) - nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in) + # Count the amount of new elements. + new_id = 0 - # Test if point lies in the domain. - lower_limit = original_nodes[1, 1, 1, 1, 1] - upper_limit = original_nodes[1, n_nodes_in, n_nodes_in, n_nodes_in, n_elements] + # Permute dimensions so that the slice dimensions are always the in correct places for later use. + if slice === :x + original_nodes = permutedims(original_nodes, [1, 3, 4, 2, 5]) + unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5]) + elseif slice === :y + original_nodes = permutedims(original_nodes, [1, 2, 4, 3, 5]) + unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5]) + end - @assert length(point)>=3 "Point must be three-dimensional." - if prod(point[other_dimensions] .< lower_limit) || - prod(point[other_dimensions] .> upper_limit) - error(string("Slice axis is outside of domain. ", - " point[$other_dimensions]=$(point[other_dimensions]) must be between $lower_limit and $upper_limit")) - end + # Iterate over all elements to find the ones that lie on the slice axis. + for element_id in 1:n_elements + min_coordinate = original_nodes[:, 1, 1, 1, element_id] + max_coordinate = original_nodes[ + :, n_nodes_in, n_nodes_in, n_nodes_in, + element_id, + ] + element_length = max_coordinate - min_coordinate + + # Test if the element is on the slice axis. If not just continue with the next element. + if !( + ( + prod(min_coordinate[other_dimensions] .<= point[other_dimensions]) && + prod(max_coordinate[other_dimensions] .> point[other_dimensions]) + ) || + ( + point[other_dimensions] == upper_limit && + prod(max_coordinate[other_dimensions] .== upper_limit) + ) + ) + continue + end + + new_id += 1 + + # Construct vandermonde matrix for interpolation of each 2D element to a 1D element. + normalized_intercept = ( + point[other_dimensions] .- + min_coordinate[other_dimensions] + ) / + element_length[1] * 2 .- 1 + vandermonde_i = polynomial_interpolation_matrix( + nodes_in, + normalized_intercept[1] + ) + vandermonde_ii = polynomial_interpolation_matrix( + nodes_in, + normalized_intercept[2] + ) + + # Interpolate to each node of new 1D element. + for v in 1:n_variables + for i in 1:n_nodes_in + for ii in 1:n_nodes_in + temp_unstructured_data[i, ii, new_id, v] = ( + vandermonde_ii * unstructured_data[ + ii, + :, + i, + element_id, + v, + ] + )[1] + end + new_unstructured_data[i, new_id, v] = ( + vandermonde_i * temp_unstructured_data[ + i, + :, + new_id, + v, + ] + )[1] + end + end + + new_nodes[:, new_id] = original_nodes[slice_dimension, 1, 1, :, element_id] + end - # Count the amount of new elements. - new_id = 0 + return get_data_1d( + reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id), + new_unstructured_data[:, 1:new_id, :], nvisnodes + ) + end + + # Interpolate unstructured DG data to structured data (cell-centered) + # + # This function takes DG data in an unstructured, Cartesian layout and converts it to a uniformly + # distributed 2D layout. + # + # Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may + # thus be changed in future releases. + function unstructured2structured( + unstructured_data, normalized_coordinates, + levels, resolution, nvisnodes_per_level + ) + # Extract data shape information + n_nodes_in, _, n_elements, n_variables = size(unstructured_data) + + # Get node coordinates for DG locations on reference element + nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in) + + # Calculate interpolation vandermonde matrices for each level + max_level = length(nvisnodes_per_level) - 1 + vandermonde_per_level = [] + for l in 0:max_level + n_nodes_out = nvisnodes_per_level[l + 1] + dx = 2 / n_nodes_out + nodes_out = collect(range(-1 + dx / 2, 1 - dx / 2, length = n_nodes_out)) + push!( + vandermonde_per_level, + polynomial_interpolation_matrix(nodes_in, nodes_out) + ) + end - # Permute dimensions so that the slice dimensions are always the in correct places for later use. - if slice === :x - original_nodes = permutedims(original_nodes, [1, 3, 4, 2, 5]) - unstructured_data = permutedims(unstructured_data, [2, 3, 1, 4, 5]) - elseif slice === :y - original_nodes = permutedims(original_nodes, [1, 2, 4, 3, 5]) - unstructured_data = permutedims(unstructured_data, [1, 3, 2, 4, 5]) - end + # For each element, calculate index position at which to insert data in global data structure + lower_left_index = element2index( + normalized_coordinates, levels, resolution, + nvisnodes_per_level + ) - # Iterate over all elements to find the ones that lie on the slice axis. - for element_id in 1:n_elements - min_coordinate = original_nodes[:, 1, 1, 1, element_id] - max_coordinate = original_nodes[:, n_nodes_in, n_nodes_in, n_nodes_in, - element_id] - element_length = max_coordinate - min_coordinate - - # Test if the element is on the slice axis. If not just continue with the next element. - if !((prod(min_coordinate[other_dimensions] .<= point[other_dimensions]) && - prod(max_coordinate[other_dimensions] .> point[other_dimensions])) || - (point[other_dimensions] == upper_limit && - prod(max_coordinate[other_dimensions] .== upper_limit))) - continue - end - - new_id += 1 - - # Construct vandermonde matrix for interpolation of each 2D element to a 1D element. - normalized_intercept = (point[other_dimensions] .- - min_coordinate[other_dimensions]) / - element_length[1] * 2 .- 1 - vandermonde_i = polynomial_interpolation_matrix(nodes_in, - normalized_intercept[1]) - vandermonde_ii = polynomial_interpolation_matrix(nodes_in, - normalized_intercept[2]) - - # Interpolate to each node of new 1D element. + # Create output data structure + structured = [Matrix{Float64}(undef, resolution, resolution) for _ in 1:n_variables] + + # For each variable, interpolate element data and store to global data structure for v in 1:n_variables - for i in 1:n_nodes_in - for ii in 1:n_nodes_in - temp_unstructured_data[i, ii, new_id, v] = (vandermonde_ii * unstructured_data[ii, - :, - i, - element_id, - v])[1] - end - new_unstructured_data[i, new_id, v] = (vandermonde_i * temp_unstructured_data[i, - :, - new_id, - v])[1] + # Reshape data array for use in multiply_dimensionwise function + reshaped_data = reshape( + unstructured_data[:, :, :, v], 1, n_nodes_in, + n_nodes_in, n_elements + ) + + for element_id in 1:n_elements + # Extract level for convenience + level = levels[element_id] + + # Determine target indices + n_nodes_out = nvisnodes_per_level[level + 1] + first = lower_left_index[:, element_id] + last = first .+ (n_nodes_out - 1) + + # Interpolate data + vandermonde = vandermonde_per_level[level + 1] + structured[v][first[1]:last[1], first[2]:last[2]] .= ( + reshape( + multiply_dimensionwise( + vandermonde, + reshaped_data[ + :, + :, + :, + element_id, + ] + ), + n_nodes_out, + n_nodes_out + ) + ) end end - new_nodes[:, new_id] = original_nodes[slice_dimension, 1, 1, :, element_id] - end - - return get_data_1d(reshape(new_nodes[:, 1:new_id], 1, n_nodes_in, new_id), - new_unstructured_data[:, 1:new_id, :], nvisnodes) -end - -# Interpolate unstructured DG data to structured data (cell-centered) -# -# This function takes DG data in an unstructured, Cartesian layout and converts it to a uniformly -# distributed 2D layout. -# -# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may -# thus be changed in future releases. -function unstructured2structured(unstructured_data, normalized_coordinates, - levels, resolution, nvisnodes_per_level) - # Extract data shape information - n_nodes_in, _, n_elements, n_variables = size(unstructured_data) - - # Get node coordinates for DG locations on reference element - nodes_in, _ = gauss_lobatto_nodes_weights(n_nodes_in) - - # Calculate interpolation vandermonde matrices for each level - max_level = length(nvisnodes_per_level) - 1 - vandermonde_per_level = [] - for l in 0:max_level - n_nodes_out = nvisnodes_per_level[l + 1] - dx = 2 / n_nodes_out - nodes_out = collect(range(-1 + dx / 2, 1 - dx / 2, length = n_nodes_out)) - push!(vandermonde_per_level, - polynomial_interpolation_matrix(nodes_in, nodes_out)) + return structured end - # For each element, calculate index position at which to insert data in global data structure - lower_left_index = element2index(normalized_coordinates, levels, resolution, - nvisnodes_per_level) + # For a given normalized element coordinate, return the index of its lower left + # contribution to the global data structure + # + # Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may + # thus be changed in future releases. + function element2index(normalized_coordinates, levels, resolution, nvisnodes_per_level) + @assert size(normalized_coordinates, 1) == 2 "only works in 2D" - # Create output data structure - structured = [Matrix{Float64}(undef, resolution, resolution) for _ in 1:n_variables] + n_elements = length(levels) - # For each variable, interpolate element data and store to global data structure - for v in 1:n_variables - # Reshape data array for use in multiply_dimensionwise function - reshaped_data = reshape(unstructured_data[:, :, :, v], 1, n_nodes_in, - n_nodes_in, n_elements) + # First, determine lower left coordinate for all cells + dx = 2 / resolution + ndim = 2 + lower_left_coordinate = Array{Float64}(undef, ndim, n_elements) + for element_id in 1:n_elements + nvisnodes = nvisnodes_per_level[levels[element_id] + 1] + lower_left_coordinate[1, element_id] = ( + normalized_coordinates[1, element_id] - + (nvisnodes - 1) / 2 * dx + ) + lower_left_coordinate[2, element_id] = ( + normalized_coordinates[2, element_id] - + (nvisnodes - 1) / 2 * dx + ) + end + # Then, convert coordinate to global index + indices = coordinate2index(lower_left_coordinate, resolution) + + return indices + end + + # Find 2D array index for a 2-tuple of normalized, cell-centered coordinates (i.e., in [-1,1]) + # + # Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may + # thus be changed in future releases. + function coordinate2index(coordinate, resolution::Integer) + # Calculate 1D normalized coordinates + dx = 2 / resolution + mesh_coordinates = collect(range(-1 + dx / 2, 1 - dx / 2, length = resolution)) + + # Find index + id_x = searchsortedfirst.( + Ref(mesh_coordinates), coordinate[1, :], + lt = (x, y) -> x .< y .- dx / 2 + ) + id_y = searchsortedfirst.( + Ref(mesh_coordinates), coordinate[2, :], + lt = (x, y) -> x .< y .- dx / 2 + ) + return transpose(hcat(id_x, id_y)) + end + + # Calculate the vertices for each mesh cell such that it can be visualized as a closed box + # + # Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may + # thus be changed in future releases. + function calc_vertices(coordinates, levels, length_level_0) + ndim = size(coordinates, 1) + @assert ndim == 2 "only works in 2D" + + # Initialize output arrays + n_elements = length(levels) + n_points_per_element = 2^ndim + 2 + x = Vector{Float64}(undef, n_points_per_element * n_elements) + y = Vector{Float64}(undef, n_points_per_element * n_elements) + + # Calculate vertices for all coordinates at once for element_id in 1:n_elements - # Extract level for convenience - level = levels[element_id] - - # Determine target indices - n_nodes_out = nvisnodes_per_level[level + 1] - first = lower_left_index[:, element_id] - last = first .+ (n_nodes_out - 1) - - # Interpolate data - vandermonde = vandermonde_per_level[level + 1] - structured[v][first[1]:last[1], first[2]:last[2]] .= (reshape(multiply_dimensionwise(vandermonde, - reshaped_data[:, - :, - :, - element_id]), - n_nodes_out, - n_nodes_out)) + length = length_level_0 / 2^levels[element_id] + index = n_points_per_element * (element_id - 1) + x[index + 1] = coordinates[1, element_id] - 1 / 2 * length + x[index + 2] = coordinates[1, element_id] + 1 / 2 * length + x[index + 3] = coordinates[1, element_id] + 1 / 2 * length + x[index + 4] = coordinates[1, element_id] - 1 / 2 * length + x[index + 5] = coordinates[1, element_id] - 1 / 2 * length + x[index + 6] = NaN + + y[index + 1] = coordinates[2, element_id] - 1 / 2 * length + y[index + 2] = coordinates[2, element_id] - 1 / 2 * length + y[index + 3] = coordinates[2, element_id] + 1 / 2 * length + y[index + 4] = coordinates[2, element_id] + 1 / 2 * length + y[index + 5] = coordinates[2, element_id] - 1 / 2 * length + y[index + 6] = NaN end - end - return structured -end - -# For a given normalized element coordinate, return the index of its lower left -# contribution to the global data structure -# -# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may -# thus be changed in future releases. -function element2index(normalized_coordinates, levels, resolution, nvisnodes_per_level) - @assert size(normalized_coordinates, 1)==2 "only works in 2D" - - n_elements = length(levels) - - # First, determine lower left coordinate for all cells - dx = 2 / resolution - ndim = 2 - lower_left_coordinate = Array{Float64}(undef, ndim, n_elements) - for element_id in 1:n_elements - nvisnodes = nvisnodes_per_level[levels[element_id] + 1] - lower_left_coordinate[1, element_id] = (normalized_coordinates[1, element_id] - - (nvisnodes - 1) / 2 * dx) - lower_left_coordinate[2, element_id] = (normalized_coordinates[2, element_id] - - (nvisnodes - 1) / 2 * dx) + return x, y end - # Then, convert coordinate to global index - indices = coordinate2index(lower_left_coordinate, resolution) - - return indices -end - -# Find 2D array index for a 2-tuple of normalized, cell-centered coordinates (i.e., in [-1,1]) -# -# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may -# thus be changed in future releases. -function coordinate2index(coordinate, resolution::Integer) - # Calculate 1D normalized coordinates - dx = 2 / resolution - mesh_coordinates = collect(range(-1 + dx / 2, 1 - dx / 2, length = resolution)) - - # Find index - id_x = searchsortedfirst.(Ref(mesh_coordinates), coordinate[1, :], - lt = (x, y) -> x .< y .- dx / 2) - id_y = searchsortedfirst.(Ref(mesh_coordinates), coordinate[2, :], - lt = (x, y) -> x .< y .- dx / 2) - return transpose(hcat(id_x, id_y)) -end - -# Calculate the vertices for each mesh cell such that it can be visualized as a closed box -# -# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may -# thus be changed in future releases. -function calc_vertices(coordinates, levels, length_level_0) - ndim = size(coordinates, 1) - @assert ndim==2 "only works in 2D" - - # Initialize output arrays - n_elements = length(levels) - n_points_per_element = 2^ndim + 2 - x = Vector{Float64}(undef, n_points_per_element * n_elements) - y = Vector{Float64}(undef, n_points_per_element * n_elements) - - # Calculate vertices for all coordinates at once - for element_id in 1:n_elements - length = length_level_0 / 2^levels[element_id] - index = n_points_per_element * (element_id - 1) - x[index + 1] = coordinates[1, element_id] - 1 / 2 * length - x[index + 2] = coordinates[1, element_id] + 1 / 2 * length - x[index + 3] = coordinates[1, element_id] + 1 / 2 * length - x[index + 4] = coordinates[1, element_id] - 1 / 2 * length - x[index + 5] = coordinates[1, element_id] - 1 / 2 * length - x[index + 6] = NaN - - y[index + 1] = coordinates[2, element_id] - 1 / 2 * length - y[index + 2] = coordinates[2, element_id] - 1 / 2 * length - y[index + 3] = coordinates[2, element_id] + 1 / 2 * length - y[index + 4] = coordinates[2, element_id] + 1 / 2 * length - y[index + 5] = coordinates[2, element_id] - 1 / 2 * length - y[index + 6] = NaN - end + # Calculate the vertices to plot each grid line for StructuredMesh + # + # Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may + # thus be changed in future releases. + function calc_vertices(node_coordinates, mesh) + @unpack cells_per_dimension = mesh + @assert size(node_coordinates, 1) == 2 "only works in 2D" - return x, y -end - -# Calculate the vertices to plot each grid line for StructuredMesh -# -# Note: This is a low-level function that is not considered as part of Trixi.jl's interface and may -# thus be changed in future releases. -function calc_vertices(node_coordinates, mesh) - @unpack cells_per_dimension = mesh - @assert size(node_coordinates, 1)==2 "only works in 2D" - - linear_indices = LinearIndices(size(mesh)) - - # Initialize output arrays - n_lines = sum(cells_per_dimension) + 2 - max_length = maximum(cells_per_dimension) - n_nodes = size(node_coordinates, 2) - - # Create output as two matrices `x` and `y`, each holding the node locations for each of the `n_lines` grid lines - # The # of rows in the matrices must be sufficient to store the longest dimension (`max_length`), - # and for each the node locations without doubling the corner nodes (`n_nodes-1`), plus the final node (`+1`) - # Rely on Plots.jl to ignore `NaN`s (i.e., they are not plotted) to handle shorter lines - x = fill(NaN, max_length * (n_nodes - 1) + 1, n_lines) - y = fill(NaN, max_length * (n_nodes - 1) + 1, n_lines) - - line_index = 1 - # Lines in x-direction - # Bottom boundary - i = 1 - for cell_x in axes(mesh, 1) - for node in 1:(n_nodes - 1) - x[i, line_index] = node_coordinates[1, node, 1, linear_indices[cell_x, 1]] - y[i, line_index] = node_coordinates[2, node, 1, linear_indices[cell_x, 1]] - - i += 1 - end - end - # Last point on bottom boundary - x[i, line_index] = node_coordinates[1, end, 1, linear_indices[end, 1]] - y[i, line_index] = node_coordinates[2, end, 1, linear_indices[end, 1]] + linear_indices = LinearIndices(size(mesh)) - # Other lines in x-direction - line_index += 1 - for cell_y in axes(mesh, 2) + # Initialize output arrays + n_lines = sum(cells_per_dimension) + 2 + max_length = maximum(cells_per_dimension) + n_nodes = size(node_coordinates, 2) + + # Create output as two matrices `x` and `y`, each holding the node locations for each of the `n_lines` grid lines + # The # of rows in the matrices must be sufficient to store the longest dimension (`max_length`), + # and for each the node locations without doubling the corner nodes (`n_nodes-1`), plus the final node (`+1`) + # Rely on Plots.jl to ignore `NaN`s (i.e., they are not plotted) to handle shorter lines + x = fill(NaN, max_length * (n_nodes - 1) + 1, n_lines) + y = fill(NaN, max_length * (n_nodes - 1) + 1, n_lines) + + line_index = 1 + # Lines in x-direction + # Bottom boundary i = 1 for cell_x in axes(mesh, 1) for node in 1:(n_nodes - 1) - x[i, line_index] = node_coordinates[1, node, end, - linear_indices[cell_x, cell_y]] - y[i, line_index] = node_coordinates[2, node, end, - linear_indices[cell_x, cell_y]] + x[i, line_index] = node_coordinates[1, node, 1, linear_indices[cell_x, 1]] + y[i, line_index] = node_coordinates[2, node, 1, linear_indices[cell_x, 1]] i += 1 end end - # Last point on line - x[i, line_index] = node_coordinates[1, end, end, linear_indices[end, cell_y]] - y[i, line_index] = node_coordinates[2, end, end, linear_indices[end, cell_y]] + # Last point on bottom boundary + x[i, line_index] = node_coordinates[1, end, 1, linear_indices[end, 1]] + y[i, line_index] = node_coordinates[2, end, 1, linear_indices[end, 1]] + # Other lines in x-direction line_index += 1 - end - - # Lines in y-direction - # Left boundary - i = 1 - for cell_y in axes(mesh, 2) - for node in 1:(n_nodes - 1) - x[i, line_index] = node_coordinates[1, 1, node, linear_indices[1, cell_y]] - y[i, line_index] = node_coordinates[2, 1, node, linear_indices[1, cell_y]] + for cell_y in axes(mesh, 2) + i = 1 + for cell_x in axes(mesh, 1) + for node in 1:(n_nodes - 1) + x[i, line_index] = node_coordinates[ + 1, node, end, + linear_indices[cell_x, cell_y], + ] + y[i, line_index] = node_coordinates[ + 2, node, end, + linear_indices[cell_x, cell_y], + ] + + i += 1 + end + end + # Last point on line + x[i, line_index] = node_coordinates[1, end, end, linear_indices[end, cell_y]] + y[i, line_index] = node_coordinates[2, end, end, linear_indices[end, cell_y]] - i += 1 + line_index += 1 end - end - # Last point on left boundary - x[i, line_index] = node_coordinates[1, 1, end, linear_indices[1, end]] - y[i, line_index] = node_coordinates[2, 1, end, linear_indices[1, end]] - # Other lines in y-direction - line_index += 1 - for cell_x in axes(mesh, 1) + # Lines in y-direction + # Left boundary i = 1 for cell_y in axes(mesh, 2) for node in 1:(n_nodes - 1) - x[i, line_index] = node_coordinates[1, end, node, - linear_indices[cell_x, cell_y]] - y[i, line_index] = node_coordinates[2, end, node, - linear_indices[cell_x, cell_y]] + x[i, line_index] = node_coordinates[1, 1, node, linear_indices[1, cell_y]] + y[i, line_index] = node_coordinates[2, 1, node, linear_indices[1, cell_y]] i += 1 end end - # Last point on line - x[i, line_index] = node_coordinates[1, end, end, linear_indices[cell_x, end]] - y[i, line_index] = node_coordinates[2, end, end, linear_indices[cell_x, end]] + # Last point on left boundary + x[i, line_index] = node_coordinates[1, 1, end, linear_indices[1, end]] + y[i, line_index] = node_coordinates[2, 1, end, linear_indices[1, end]] + # Other lines in y-direction line_index += 1 + for cell_x in axes(mesh, 1) + i = 1 + for cell_y in axes(mesh, 2) + for node in 1:(n_nodes - 1) + x[i, line_index] = node_coordinates[ + 1, end, node, + linear_indices[cell_x, cell_y], + ] + y[i, line_index] = node_coordinates[ + 2, end, node, + linear_indices[cell_x, cell_y], + ] + + i += 1 + end + end + # Last point on line + x[i, line_index] = node_coordinates[1, end, end, linear_indices[cell_x, end]] + y[i, line_index] = node_coordinates[2, end, end, linear_indices[cell_x, end]] + + line_index += 1 + end + + return x, y end - return x, y -end - -# Convert `slice` to orientations (1 -> `x`, 2 -> `y`, 3 -> `z`) for the two axes in a 2D plot -function _get_orientations(mesh, slice) - if ndims(mesh) == 2 || (ndims(mesh) == 3 && slice === :xy) - orientation_x = 1 - orientation_y = 2 - elseif ndims(mesh) == 3 && slice === :xz - orientation_x = 1 - orientation_y = 3 - elseif ndims(mesh) == 3 && slice === :yz - orientation_x = 2 - orientation_y = 3 - else - orientation_x = 0 - orientation_y = 0 + # Convert `slice` to orientations (1 -> `x`, 2 -> `y`, 3 -> `z`) for the two axes in a 2D plot + function _get_orientations(mesh, slice) + if ndims(mesh) == 2 || (ndims(mesh) == 3 && slice === :xy) + orientation_x = 1 + orientation_y = 2 + elseif ndims(mesh) == 3 && slice === :xz + orientation_x = 1 + orientation_y = 3 + elseif ndims(mesh) == 3 && slice === :yz + orientation_x = 2 + orientation_y = 3 + else + orientation_x = 0 + orientation_y = 0 + end + return orientation_x, orientation_y end - return orientation_x, orientation_y -end - -# Convert `orientation` into a guide label (see also `_get_orientations`) -function _get_guide(orientation::Integer) - if orientation == 1 - return "\$x\$" - elseif orientation == 2 - return "\$y\$" - elseif orientation == 3 - return "\$z\$" - else - return "" + + # Convert `orientation` into a guide label (see also `_get_orientations`) + function _get_guide(orientation::Integer) + if orientation == 1 + return "\$x\$" + elseif orientation == 2 + return "\$y\$" + elseif orientation == 3 + return "\$z\$" + else + return "" + end end -end - -# plotting_interpolation_matrix(dg; kwargs...) -# -# Interpolation matrix which maps discretization nodes to a set of plotting nodes. -# Defaults to the identity matrix of size `length(solver.basis.nodes)`, and interpolates -# to equispaced nodes for DGSEM (set by kwarg `nvisnodes` in the plotting function). -# -# Example: -# ```julia -# A = plotting_interpolation_matrix(dg) -# A * dg.basis.nodes # => vector of nodes at which to plot the solution -# ``` -# -# Note: we cannot use UniformScaling to define the interpolation matrix since we use it with `kron` -# to define a multi-dimensional interpolation matrix later. -plotting_interpolation_matrix(dg; kwargs...) = I(length(dg.basis.nodes)) - -function face_plotting_interpolation_matrix(dg::DGSEM; - nvisnodes = 2 * length(dg.basis.nodes)) - return polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes)) -end - -function plotting_interpolation_matrix(dg::DGSEM; - nvisnodes = 2 * length(dg.basis.nodes)) - Vp1D = polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes)) - # For quadrilateral elements, interpolation to plotting nodes involves applying a 1D interpolation - # operator to each line of nodes. This is equivalent to multiplying the vector containing all node - # node coordinates on an element by a Kronecker product of the 1D interpolation operator (e.g., a - # multi-dimensional interpolation operator). - return kron(Vp1D, Vp1D) -end - -function reference_node_coordinates_2d(dg::DGSEM) - @unpack nodes = dg.basis - r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)]) - s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)]) - return r, s -end - -# Find element and triangle ids containing coordinates given as a matrix [ndims, npoints] -function get_ids_by_coordinates!(ids, coordinates, pd) - if length(ids) != 2 * size(coordinates, 2) - throw(DimensionMismatch("storage length for element ids does not match the number of coordinates")) + + # plotting_interpolation_matrix(dg; kwargs...) + # + # Interpolation matrix which maps discretization nodes to a set of plotting nodes. + # Defaults to the identity matrix of size `length(solver.basis.nodes)`, and interpolates + # to equispaced nodes for DGSEM (set by kwarg `nvisnodes` in the plotting function). + # + # Example: + # ```julia + # A = plotting_interpolation_matrix(dg) + # A * dg.basis.nodes # => vector of nodes at which to plot the solution + # ``` + # + # Note: we cannot use UniformScaling to define the interpolation matrix since we use it with `kron` + # to define a multi-dimensional interpolation matrix later. + plotting_interpolation_matrix(dg; kwargs...) = I(length(dg.basis.nodes)) + + function face_plotting_interpolation_matrix( + dg::DGSEM; + nvisnodes = 2 * length(dg.basis.nodes) + ) + return polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes)) + end + + function plotting_interpolation_matrix( + dg::DGSEM; + nvisnodes = 2 * length(dg.basis.nodes) + ) + Vp1D = polynomial_interpolation_matrix(dg.basis.nodes, LinRange(-1, 1, nvisnodes)) + # For quadrilateral elements, interpolation to plotting nodes involves applying a 1D interpolation + # operator to each line of nodes. This is equivalent to multiplying the vector containing all node + # node coordinates on an element by a Kronecker product of the 1D interpolation operator (e.g., a + # multi-dimensional interpolation operator). + return kron(Vp1D, Vp1D) + end + + function reference_node_coordinates_2d(dg::DGSEM) + @unpack nodes = dg.basis + r = vec([nodes[i] for i in eachnode(dg), j in eachnode(dg)]) + s = vec([nodes[j] for i in eachnode(dg), j in eachnode(dg)]) + return r, s + end + + # Find element and triangle ids containing coordinates given as a matrix [ndims, npoints] + function get_ids_by_coordinates!(ids, coordinates, pd) + if length(ids) != 2 * size(coordinates, 2) + throw(DimensionMismatch("storage length for element ids does not match the number of coordinates")) + end + + n_coordinates = size(coordinates, 2) + + for index in 1:n_coordinates + ids[index, :] .= find_element(coordinates[:, index], pd) + end + + return ids end - n_coordinates = size(coordinates, 2) + # Find the ids of elements and triangles containing given coordinates by using the triangulation in 'pd'. + function get_ids_by_coordinates(coordinates, pd) + ids = Matrix(undef, size(coordinates, 2), 2) + get_ids_by_coordinates!(ids, coordinates, pd) + return ids + end - for index in 1:n_coordinates - ids[index, :] .= find_element(coordinates[:, index], pd) + # Check if given 'point' is inside the triangle with corners corresponding to the coordinates of x and y. + function is_in_triangle(point, x, y) + a = SVector(x[1], y[1]) + b = SVector(x[2], y[2]) + c = SVector(x[3], y[3]) + return is_on_same_side(point, a, b, c) && is_on_same_side(point, b, c, a) && + is_on_same_side(point, c, a, b) end - return ids -end - -# Find the ids of elements and triangles containing given coordinates by using the triangulation in 'pd'. -function get_ids_by_coordinates(coordinates, pd) - ids = Matrix(undef, size(coordinates, 2), 2) - get_ids_by_coordinates!(ids, coordinates, pd) - return ids -end - -# Check if given 'point' is inside the triangle with corners corresponding to the coordinates of x and y. -function is_in_triangle(point, x, y) - a = SVector(x[1], y[1]) - b = SVector(x[2], y[2]) - c = SVector(x[3], y[3]) - return is_on_same_side(point, a, b, c) && is_on_same_side(point, b, c, a) && - is_on_same_side(point, c, a, b) -end - -# Create an axis through x and y to then check if 'point' is on the same side of the axis as z. -function is_on_same_side(point, x, y, z) - if (y[1] - x[1]) == 0 - return (point[1] - x[1]) * (z[1] - x[1]) >= 0 - else - a = (y[2] - x[2]) / (y[1] - x[1]) - b = x[2] - a * x[1] - return (z[2] - a * z[1] - b) * (point[2] - a * point[1] - b) >= 0 + # Create an axis through x and y to then check if 'point' is on the same side of the axis as z. + function is_on_same_side(point, x, y, z) + if (y[1] - x[1]) == 0 + return (point[1] - x[1]) * (z[1] - x[1]) >= 0 + else + a = (y[2] - x[2]) / (y[1] - x[1]) + b = x[2] - a * x[1] + return (z[2] - a * z[1] - b) * (point[2] - a * point[1] - b) >= 0 + end end -end - -# For a given 'point', return the id of the element it is contained in in; if not found return 0. -function find_element(point, pd) - n_tri = size(pd.t, 1) - n_elements = size(pd.x, 2) - - # Iterate over all elements. - for element in 1:n_elements - # Iterate over all triangles in given element. - for tri in 1:n_tri - if is_in_triangle(point, pd.x[pd.t[tri, :], element], - pd.y[pd.t[tri, :], element]) - return SVector(element, tri) + + # For a given 'point', return the id of the element it is contained in in; if not found return 0. + function find_element(point, pd) + n_tri = size(pd.t, 1) + n_elements = size(pd.x, 2) + + # Iterate over all elements. + for element in 1:n_elements + # Iterate over all triangles in given element. + for tri in 1:n_tri + if is_in_triangle( + point, pd.x[pd.t[tri, :], element], + pd.y[pd.t[tri, :], element] + ) + return SVector(element, tri) + end end end end -end - -# Interpolate from three corners of a triangle to a single point. -function triangle_interpolation(x_coordinates_in, y_coordinates_in, values_in, - coordinate_out) - A = hcat(x_coordinates_in, y_coordinates_in, SVector(1, 1, 1)) - c = A \ values_in - return c[1] * coordinate_out[1] + c[2] * coordinate_out[2] + c[3] -end - -# Create an axis. -function axis_curve(nodes_x, nodes_y, nodes_z, slice, point, n_points) - if n_points === nothing - n_points = 64 - end - dimensions = length(point) - curve = zeros(dimensions, n_points) - if slice == :x - xmin, xmax = extrema(nodes_x) - curve[1, :] .= range(xmin, xmax, length = n_points) - curve[2, :] .= point[2] - if dimensions === 3 - curve[3, :] .= point[3] - end - elseif slice == :y - ymin, ymax = extrema(nodes_y) - curve[1, :] .= point[1] - curve[2, :] .= range(ymin, ymax, length = n_points) - if dimensions === 3 - curve[3, :] .= point[3] - end - elseif slice == :z - zmin, zmax = extrema(nodes_z) - curve[1, :] .= point[1] - curve[2, :] .= point[2] - curve[3, :] .= range(zmin, zmax, length = n_points) - else - @assert false "Input for 'slice' is not supported here." + + # Interpolate from three corners of a triangle to a single point. + function triangle_interpolation( + x_coordinates_in, y_coordinates_in, values_in, + coordinate_out + ) + A = hcat(x_coordinates_in, y_coordinates_in, SVector(1, 1, 1)) + c = A \ values_in + return c[1] * coordinate_out[1] + c[2] * coordinate_out[2] + c[3] end - return curve -end + # Create an axis. + function axis_curve(nodes_x, nodes_y, nodes_z, slice, point, n_points) + if n_points === nothing + n_points = 64 + end + dimensions = length(point) + curve = zeros(dimensions, n_points) + if slice == :x + xmin, xmax = extrema(nodes_x) + curve[1, :] .= range(xmin, xmax, length = n_points) + curve[2, :] .= point[2] + if dimensions === 3 + curve[3, :] .= point[3] + end + elseif slice == :y + ymin, ymax = extrema(nodes_y) + curve[1, :] .= point[1] + curve[2, :] .= range(ymin, ymax, length = n_points) + if dimensions === 3 + curve[3, :] .= point[3] + end + elseif slice == :z + zmin, zmax = extrema(nodes_z) + curve[1, :] .= point[1] + curve[2, :] .= point[2] + curve[3, :] .= range(zmin, zmax, length = n_points) + else + @assert false "Input for 'slice' is not supported here." + end + + return curve + end end # @muladd diff --git a/src/visualization/visualization.jl b/src/visualization/visualization.jl index 94d2532cba..0b347768ab 100644 --- a/src/visualization/visualization.jl +++ b/src/visualization/visualization.jl @@ -3,14 +3,14 @@ # we need to opt-in explicitly. # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin -#! format: noindent + #! format: noindent -include("types.jl") -include("utilities.jl") -include("recipes_plots.jl") + include("types.jl") + include("utilities.jl") + include("recipes_plots.jl") -# Add function definitions here such that they can be exported from Trixi.jl and extended in the -# TrixiMakieExt package extension or by the Makie-specific code loaded by Requires.jl -function iplot end -function iplot! end + # Add function definitions here such that they can be exported from Trixi.jl and extended in the + # TrixiMakieExt package extension or by the Makie-specific code loaded by Requires.jl + function iplot end + function iplot! end end # @muladd diff --git a/test/runtests.jl b/test/runtests.jl index 836488d0d8..707e768b93 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -27,7 +27,7 @@ const TRIXI_NTHREADS = clamp(Sys.CPU_THREADS, 2, 3) # see the discussion at https://github.com/trixi-framework/Trixi.jl/pull/1062#issuecomment-1035901020 cmd = string(Base.julia_cmd()) coverage = occursin("--code-coverage", cmd) && - !occursin("--code-coverage=none", cmd) + !occursin("--code-coverage=none", cmd) if !(coverage && Sys.iswindows()) && !(coverage && Sys.isapple()) # We provide a `--heap-size-hint` to avoid/reduce out-of-memory errors during CI testing mpiexec() do cmd @@ -37,7 +37,7 @@ const TRIXI_NTHREADS = clamp(Sys.CPU_THREADS, 2, 3) end @time if TRIXI_TEST == "all" || TRIXI_TEST == "threaded" || - TRIXI_TEST == "threaded_legacy" + TRIXI_TEST == "threaded_legacy" # Do a dummy `@test true`: # If the process errors out the testset would error out as well, # cf. https://github.com/JuliaParallel/MPI.jl/pull/391 diff --git a/test/test_aqua.jl b/test/test_aqua.jl index 04c4a533d2..05c2f250af 100644 --- a/test/test_aqua.jl +++ b/test/test_aqua.jl @@ -8,20 +8,38 @@ using Trixi include("test_trixi.jl") @timed_testset "Aqua.jl" begin - Aqua.test_all(Trixi, - ambiguities = false, - # exceptions necessary for adding a new method `StartUpDG.estimate_h` - # in src/solvers/dgmulti/sbp.jl - piracies = (treat_as_own = [Trixi.StartUpDG.RefElemData, - Trixi.StartUpDG.MeshData],)) - @test isnothing(check_no_implicit_imports(Trixi, - skip = (Core, Base, Trixi.P4est, Trixi.T8code, - Trixi.EllipsisNotation))) - @test isnothing(check_no_stale_explicit_imports(Trixi, - ignore = (:derivative_operator, - :periodic_derivative_operator, - :upwind_operators, - Symbol("@batch")))) + Aqua.test_all( + Trixi, + ambiguities = false, + # exceptions necessary for adding a new method `StartUpDG.estimate_h` + # in src/solvers/dgmulti/sbp.jl + piracies = ( + treat_as_own = [ + Trixi.StartUpDG.RefElemData, + Trixi.StartUpDG.MeshData, + ], + ) + ) + @test isnothing( + check_no_implicit_imports( + Trixi, + skip = ( + Core, Base, Trixi.P4est, Trixi.T8code, + Trixi.EllipsisNotation, + ) + ) + ) + @test isnothing( + check_no_stale_explicit_imports( + Trixi, + ignore = ( + :derivative_operator, + :periodic_derivative_operator, + :upwind_operators, + Symbol("@batch"), + ) + ) + ) end end #module diff --git a/test/test_dgmulti_1d.jl b/test/test_dgmulti_1d.jl index 7ac3c73564..ea01aa7fc9 100644 --- a/test/test_dgmulti_1d.jl +++ b/test/test_dgmulti_1d.jl @@ -12,248 +12,280 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "DGMulti 1D" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_advection_gauss_sbp.jl " begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_gauss_sbp.jl"), - cells_per_dimension=(8,), - l2=[2.9953644500009865e-5], - linf=[4.467840577382365e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_gauss_sbp.jl " begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_gauss_sbp.jl"), + cells_per_dimension = (8,), + l2 = [2.9953644500009865e-5], + linf = [4.467840577382365e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_burgers_gauss_shock_capturing.jl " begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_burgers_gauss_shock_capturing.jl"), - cells_per_dimension=(8,), tspan=(0.0, 0.1), - l2=[0.445804588167854], - linf=[0.74780611426038]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_burgers_gauss_shock_capturing.jl " begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_burgers_gauss_shock_capturing.jl" + ), + cells_per_dimension = (8,), tspan = (0.0, 0.1), + l2 = [0.445804588167854], + linf = [0.74780611426038] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_flux_diff.jl " begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_flux_diff.jl"), - cells_per_dimension=(16,), - # division by sqrt(2.0) corresponds to normalization by the square root of the size of the domain - l2=[ - 7.853842541289665e-7, - 9.609905503440606e-7, - 2.832322219966481e-6, - ] ./ sqrt(2.0), - linf=[ - 1.5003758788711963e-6, - 1.802998748523521e-6, - 4.83599270806323e-6, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_flux_diff.jl " begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_flux_diff.jl"), + cells_per_dimension = (16,), + # division by sqrt(2.0) corresponds to normalization by the square root of the size of the domain + l2 = [ + 7.853842541289665e-7, + 9.609905503440606e-7, + 2.832322219966481e-6, + ] ./ sqrt(2.0), + linf = [ + 1.5003758788711963e-6, + 1.802998748523521e-6, + 4.83599270806323e-6, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shu_osher_gauss_shock_capturing.jl " begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_shu_osher_gauss_shock_capturing.jl"), - cells_per_dimension=(64,), tspan=(0.0, 1.0), - l2=[ - 1.673813320412685, - 5.980737909458242, - 21.587822949251173, - ], - linf=[ - 3.1388039126918064, - 10.630952212105246, - 37.682826521024865, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shu_osher_gauss_shock_capturing.jl " begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_shu_osher_gauss_shock_capturing.jl" + ), + cells_per_dimension = (64,), tspan = (0.0, 1.0), + l2 = [ + 1.673813320412685, + 5.980737909458242, + 21.587822949251173, + ], + linf = [ + 3.1388039126918064, + 10.630952212105246, + 37.682826521024865, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_flux_diff.jl (convergence)" begin - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, - "elixir_euler_flux_diff.jl"), 3) - @test isapprox(mean_convergence[:l2], - [4.1558759698638434, 3.977911306037128, 4.041421206468769], - rtol = 0.05) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_flux_diff.jl (convergence)" begin + mean_convergence = convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, + "elixir_euler_flux_diff.jl" + ), 3 + ) + @test isapprox( + mean_convergence[:l2], + [4.1558759698638434, 3.977911306037128, 4.041421206468769], + rtol = 0.05 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_flux_diff.jl (SBP) " begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_flux_diff.jl"), - cells_per_dimension=(16,), - approximation_type=SBP(), - l2=[ - 6.437827414849647e-6, - 2.1840558851820947e-6, - 1.3245669629438228e-5, - ], - linf=[ - 2.0715843751295537e-5, - 8.519520630301258e-6, - 4.2642194098885255e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_flux_diff.jl (SBP) " begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_flux_diff.jl"), + cells_per_dimension = (16,), + approximation_type = SBP(), + l2 = [ + 6.437827414849647e-6, + 2.1840558851820947e-6, + 1.3245669629438228e-5, + ], + linf = [ + 2.0715843751295537e-5, + 8.519520630301258e-6, + 4.2642194098885255e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_flux_diff.jl (FD SBP)" begin - global D = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order = 1, - accuracy_order = 4, - xmin = 0.0, xmax = 1.0, - N = 16) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_flux_diff.jl"), - cells_per_dimension=(4,), - approximation_type=D, - l2=[ - 1.8684509287853788e-5, - 1.0641411823379635e-5, - 5.178010291876143e-5, - ], - linf=[ - 6.933493585936645e-5, - 3.0277366229292113e-5, - 0.0002220020568932668, - ]) - show(stdout, semi.solver.basis) - show(stdout, MIME"text/plain"(), semi.solver.basis) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_flux_diff.jl (FD SBP)" begin + global D = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, + accuracy_order = 4, + xmin = 0.0, xmax = 1.0, + N = 16 + ) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_flux_diff.jl"), + cells_per_dimension = (4,), + approximation_type = D, + l2 = [ + 1.8684509287853788e-5, + 1.0641411823379635e-5, + 5.178010291876143e-5, + ], + linf = [ + 6.933493585936645e-5, + 3.0277366229292113e-5, + 0.0002220020568932668, + ] + ) + show(stdout, semi.solver.basis) + show(stdout, MIME"text/plain"(), semi.solver.basis) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_fdsbp_periodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), - l2=[ - 9.146929178341782e-7, 1.8997616876521201e-6, - 3.991417701005622e-6, - ], - linf=[ - 1.7321089882393892e-6, 3.3252888869128583e-6, - 6.525278767988141e-6, - ]) - show(stdout, semi.solver.basis) - show(stdout, MIME"text/plain"(), semi.solver.basis) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_fdsbp_periodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), + l2 = [ + 9.146929178341782e-7, 1.8997616876521201e-6, + 3.991417701005622e-6, + ], + linf = [ + 1.7321089882393892e-6, 3.3252888869128583e-6, + 6.525278767988141e-6, + ] + ) + show(stdout, semi.solver.basis) + show(stdout, MIME"text/plain"(), semi.solver.basis) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "DGMulti with periodic SBP unit test" begin - # see https://github.com/trixi-framework/Trixi.jl/pull/1013 - global D = periodic_derivative_operator(derivative_order = 1, - accuracy_order = 4, - xmin = -5.0, - xmax = 10.0, N = 50) - dg = DGMulti(element_type = Line(), approximation_type = D) - mesh = DGMultiMesh(dg) - @test mapreduce(isapprox, &, mesh.md.xyz, dg.basis.rst) - # check to make sure nodes are rescaled to [-1, 1] - @test minimum(dg.basis.rst[1]) ≈ -1 - @test maximum(dg.basis.rst[1])≈1 atol=0.35 -end + @trixi_testset "DGMulti with periodic SBP unit test" begin + # see https://github.com/trixi-framework/Trixi.jl/pull/1013 + global D = periodic_derivative_operator( + derivative_order = 1, + accuracy_order = 4, + xmin = -5.0, + xmax = 10.0, N = 50 + ) + dg = DGMulti(element_type = Line(), approximation_type = D) + mesh = DGMultiMesh(dg) + @test mapreduce(isapprox, &, mesh.md.xyz, dg.basis.rst) + # check to make sure nodes are rescaled to [-1, 1] + @test minimum(dg.basis.rst[1]) ≈ -1 + @test maximum(dg.basis.rst[1]) ≈ 1 atol = 0.35 + end -# test non-conservative systems -@trixi_testset "elixir_shallow_water_quasi_1d.jl (SBP) " begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallow_water_quasi_1d.jl"), - cells_per_dimension=(8,), - approximation_type=SBP(), - l2=[ - 3.03001101100507e-6, - 1.692177335948727e-5, - 3.002634351734614e-16, - 1.1636653574178203e-15, - ], - linf=[ - 1.2043401988570679e-5, - 5.346847010329059e-5, - 9.43689570931383e-16, - 2.220446049250313e-15, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # test non-conservative systems + @trixi_testset "elixir_shallow_water_quasi_1d.jl (SBP) " begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallow_water_quasi_1d.jl"), + cells_per_dimension = (8,), + approximation_type = SBP(), + l2 = [ + 3.03001101100507e-6, + 1.692177335948727e-5, + 3.002634351734614e-16, + 1.1636653574178203e-15, + ], + linf = [ + 1.2043401988570679e-5, + 5.346847010329059e-5, + 9.43689570931383e-16, + 2.220446049250313e-15, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_quasi_1d.jl (SBP) " begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_quasi_1d.jl"), - cells_per_dimension=(8,), - approximation_type=SBP(), - l2=[ - 1.633271343738687e-5, - 9.575385661756332e-6, - 1.2700331443128421e-5, - 0.0, - ], - linf=[ - 7.304984704381567e-5, - 5.2365944135601694e-5, - 6.469559594934893e-5, - 0.0, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_quasi_1d.jl (SBP) " begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_quasi_1d.jl"), + cells_per_dimension = (8,), + approximation_type = SBP(), + l2 = [ + 1.633271343738687e-5, + 9.575385661756332e-6, + 1.2700331443128421e-5, + 0.0, + ], + linf = [ + 7.304984704381567e-5, + 5.2365944135601694e-5, + 6.469559594934893e-5, + 0.0, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn isdir(outdir) && rm(outdir, recursive = true) diff --git a/test/test_dgmulti_2d.jl b/test/test_dgmulti_2d.jl index ab6b505e20..43675617d3 100644 --- a/test/test_dgmulti_2d.jl +++ b/test/test_dgmulti_2d.jl @@ -12,923 +12,1033 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "DGMulti 2D" begin -#! format: noindent - -@trixi_testset "elixir_euler_weakform.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - cells_per_dimension=(4, 4), - surface_integral=SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.0013536930300254945, - 0.0014315603442106193, - 0.001431560344211359, - 0.0047393341007602625, - ] ./ 2.0, - linf=[ - 0.001514260921466004, - 0.0020623991944839215, - 0.002062399194485476, - 0.004897700392503701, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + #! format: noindent + + @trixi_testset "elixir_euler_weakform.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + cells_per_dimension = (4, 4), + surface_integral = SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.0013536930300254945, + 0.0014315603442106193, + 0.001431560344211359, + 0.0047393341007602625, + ] ./ 2.0, + linf = [ + 0.001514260921466004, + 0.0020623991944839215, + 0.002062399194485476, + 0.004897700392503701, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (SBP)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - cells_per_dimension=(4, 4), - approximation_type=SBP(), - surface_integral=SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.0074706882014934735, - 0.005306220583603261, - 0.005306220583613591, - 0.014724842607716771, - ] ./ 2.0, - linf=[ - 0.021563604940952885, - 0.01359397832530762, - 0.013593978324845324, - 0.03270995869587523, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (SBP)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + cells_per_dimension = (4, 4), + approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.0074706882014934735, + 0.005306220583603261, + 0.005306220583613591, + 0.014724842607716771, + ] ./ 2.0, + linf = [ + 0.021563604940952885, + 0.01359397832530762, + 0.013593978324845324, + 0.03270995869587523, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (Quadrilateral elements)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - cells_per_dimension=(4, 4), - element_type=Quad(), - surface_integral=SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.00031892254415307093, - 0.00033637562986771894, - 0.0003363756298680649, - 0.0011100259064243145, - ] ./ 2.0, - linf=[ - 0.001073298211445639, - 0.0013568139808282087, - 0.0013568139808290969, - 0.0032249020004324613, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (Quadrilateral elements)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + cells_per_dimension = (4, 4), + element_type = Quad(), + surface_integral = SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.00031892254415307093, + 0.00033637562986771894, + 0.0003363756298680649, + 0.0011100259064243145, + ] ./ 2.0, + linf = [ + 0.001073298211445639, + 0.0013568139808282087, + 0.0013568139808290969, + 0.0032249020004324613, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (EC) " begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - cells_per_dimension=(4, 4), - volume_integral=VolumeIntegralFluxDifferencing(flux_ranocha), - surface_integral=SurfaceIntegralWeakForm(flux_ranocha), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.007801417730672109, - 0.00708583561714128, - 0.0070858356171393, - 0.015217574294198809, - ] ./ 2.0, - linf=[ - 0.011572828457858897, - 0.013965298735070686, - 0.01396529873508534, - 0.04227683691807904, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (EC) " begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + cells_per_dimension = (4, 4), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha), + surface_integral = SurfaceIntegralWeakForm(flux_ranocha), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.007801417730672109, + 0.00708583561714128, + 0.0070858356171393, + 0.015217574294198809, + ] ./ 2.0, + linf = [ + 0.011572828457858897, + 0.013965298735070686, + 0.01396529873508534, + 0.04227683691807904, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (SBP, EC)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - cells_per_dimension=(4, 4), - volume_integral=VolumeIntegralFluxDifferencing(flux_ranocha), - surface_integral=SurfaceIntegralWeakForm(flux_ranocha), - approximation_type=SBP(), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.01280067571168776, - 0.010607599608273302, - 0.010607599608239775, - 0.026408338014056548, - ] ./ 2.0, - linf=[ - 0.037983023185674814, - 0.05321027922533417, - 0.05321027922608157, - 0.13392025411844033, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (SBP, EC)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + cells_per_dimension = (4, 4), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha), + surface_integral = SurfaceIntegralWeakForm(flux_ranocha), + approximation_type = SBP(), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.01280067571168776, + 0.010607599608273302, + 0.010607599608239775, + 0.026408338014056548, + ] ./ 2.0, + linf = [ + 0.037983023185674814, + 0.05321027922533417, + 0.05321027922608157, + 0.13392025411844033, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (Quadrilateral elements, SBP, EC)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - cells_per_dimension=(4, 4), - element_type=Quad(), - volume_integral=VolumeIntegralFluxDifferencing(flux_ranocha), - surface_integral=SurfaceIntegralWeakForm(flux_ranocha), - approximation_type=SBP(), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.0029373718090697975, - 0.0030629360605489465, - 0.003062936060545615, - 0.0068486089344859755, - ] ./ 2.0, - linf=[ - 0.01360165305316885, - 0.01267402847925303, - 0.012674028479251254, - 0.02210545278615017, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (Quadrilateral elements, SBP, EC)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + cells_per_dimension = (4, 4), + element_type = Quad(), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha), + surface_integral = SurfaceIntegralWeakForm(flux_ranocha), + approximation_type = SBP(), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.0029373718090697975, + 0.0030629360605489465, + 0.003062936060545615, + 0.0068486089344859755, + ] ./ 2.0, + linf = [ + 0.01360165305316885, + 0.01267402847925303, + 0.012674028479251254, + 0.02210545278615017, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_bilinear.jl (Bilinear quadrilateral elements, SBP, flux differencing)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_bilinear.jl"), - l2=[ - 1.0259432774540821e-5, 9.014087689495575e-6, - 9.01408768888544e-6, 2.738953324859446e-5, - ], - linf=[ - 7.362605996297233e-5, 6.874189724781488e-5, - 6.874189703509614e-5, 0.00019124355334110277, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_bilinear.jl (Bilinear quadrilateral elements, SBP, flux differencing)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_bilinear.jl"), + l2 = [ + 1.0259432774540821e-5, 9.014087689495575e-6, + 9.01408768888544e-6, 2.738953324859446e-5, + ], + linf = [ + 7.362605996297233e-5, 6.874189724781488e-5, + 6.874189703509614e-5, 0.00019124355334110277, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_curved.jl (Quadrilateral elements, SBP, flux differencing)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), - l2=[ - 1.7204593127904542e-5, 1.5921547179522804e-5, - 1.5921547180107928e-5, 4.894071422525737e-5, - ], - linf=[ - 0.00010525416937667842, 0.00010003778102718464, - 0.00010003778071832059, 0.0003642628211952825, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_curved.jl (Quadrilateral elements, SBP, flux differencing)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), + l2 = [ + 1.7204593127904542e-5, 1.5921547179522804e-5, + 1.5921547180107928e-5, 4.894071422525737e-5, + ], + linf = [ + 0.00010525416937667842, 0.00010003778102718464, + 0.00010003778071832059, 0.0003642628211952825, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_curved.jl (Quadrilateral elements, GaussSBP, flux differencing)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), - approximation_type=GaussSBP(), - surface_integral=SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), - l2=[ - 3.4666312079259457e-6, - 3.4392774480368986e-6, - 3.439277447953705e-6, - 1.0965598424665836e-5, - ], - linf=[ - 1.1327280377004811e-5, - 1.1343911926253725e-5, - 1.1343911906935844e-5, - 3.679582619220412e-5, - ], - rtol=2 * sqrt(eps())) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_curved.jl (Quadrilateral elements, GaussSBP, flux differencing)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), + approximation_type = GaussSBP(), + surface_integral = SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), + l2 = [ + 3.4666312079259457e-6, + 3.4392774480368986e-6, + 3.439277447953705e-6, + 1.0965598424665836e-5, + ], + linf = [ + 1.1327280377004811e-5, + 1.1343911926253725e-5, + 1.1343911906935844e-5, + 3.679582619220412e-5, + ], + rtol = 2 * sqrt(eps()) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_curved.jl (Triangular elements, Polynomial, weak formulation)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), - element_type=Tri(), approximation_type=Polynomial(), - volume_integral=VolumeIntegralWeakForm(), - surface_integral=SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), - l2=[ - 7.905498158659466e-6, - 8.731690809663625e-6, - 8.731690811576996e-6, - 2.9113296018693953e-5, - ], - linf=[ - 3.298811230090237e-5, - 4.032272476939269e-5, - 4.032272526011127e-5, - 0.00012013725458537294, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_curved.jl (Triangular elements, Polynomial, weak formulation)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), + element_type = Tri(), approximation_type = Polynomial(), + volume_integral = VolumeIntegralWeakForm(), + surface_integral = SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), + l2 = [ + 7.905498158659466e-6, + 8.731690809663625e-6, + 8.731690811576996e-6, + 2.9113296018693953e-5, + ], + linf = [ + 3.298811230090237e-5, + 4.032272476939269e-5, + 4.032272526011127e-5, + 0.00012013725458537294, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_hohqmesh.jl (Quadrilateral elements, SBP, flux differencing)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_hohqmesh.jl"), - l2=[ - 0.0008153911341517156, - 0.0007768159701964676, - 0.00047902606811690694, - 0.0015551846076348535, - ], - linf=[ - 0.0029301131365355726, - 0.0034427051471457304, - 0.0028721569841545502, - 0.011125365074589944, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_hohqmesh.jl (Quadrilateral elements, SBP, flux differencing)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_hohqmesh.jl"), + l2 = [ + 0.0008153911341517156, + 0.0007768159701964676, + 0.00047902606811690694, + 0.0015551846076348535, + ], + linf = [ + 0.0029301131365355726, + 0.0034427051471457304, + 0.0028721569841545502, + 0.011125365074589944, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (convergence)" begin - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, - "elixir_euler_weakform.jl"), 2) - @test isapprox(mean_convergence[:l2], - [ - 4.243843382379403, - 4.128314378833922, - 4.128314378397532, - 4.081366752807379, - ], rtol = 0.05) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (convergence)" begin + mean_convergence = convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, + "elixir_euler_weakform.jl" + ), 2 + ) + @test isapprox( + mean_convergence[:l2], + [ + 4.243843382379403, + 4.128314378833922, + 4.128314378397532, + 4.081366752807379, + ], rtol = 0.05 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform_periodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.0007492755162295128, 0.0007641875305302599, - 0.0007641875305306243, 0.0024232389721009447, - ], - linf=[ - 0.0015060064614331736, 0.0019371156800773726, - 0.0019371156800769285, 0.004742431684202408, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform_periodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.0007492755162295128, 0.0007641875305302599, + 0.0007641875305306243, 0.0024232389721009447, + ], + linf = [ + 0.0015060064614331736, 0.0019371156800773726, + 0.0019371156800769285, 0.004742431684202408, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_triangulate_pkg_mesh.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_triangulate_pkg_mesh.jl"), - l2=[ - 2.344076909832665e-6, 1.8610002398709756e-6, - 2.4095132179484066e-6, 6.37330249340445e-6, - ], - linf=[ - 2.509979394305084e-5, 2.2683711321080935e-5, - 2.6180377720841363e-5, 5.575278031910713e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_triangulate_pkg_mesh.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_triangulate_pkg_mesh.jl"), + l2 = [ + 2.344076909832665e-6, 1.8610002398709756e-6, + 2.4095132179484066e-6, 6.37330249340445e-6, + ], + linf = [ + 2.509979394305084e-5, 2.2683711321080935e-5, + 2.6180377720841363e-5, 5.575278031910713e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability.jl"), - cells_per_dimension=(32, 32), tspan=(0.0, 0.2), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.11140378947116614, - 0.06598161188703612, - 0.10448953167839563, - 0.16023209181809595, - ] ./ 2.0, - linf=[ - 0.24033843177853664, - 0.1659992245272325, - 0.1235468309508845, - 0.26911424973147735, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability.jl" + ), + cells_per_dimension = (32, 32), tspan = (0.0, 0.2), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.11140378947116614, + 0.06598161188703612, + 0.10448953167839563, + 0.16023209181809595, + ] ./ 2.0, + linf = [ + 0.24033843177853664, + 0.1659992245272325, + 0.1235468309508845, + 0.26911424973147735, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl (Quadrilateral elements, GaussSBP)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability.jl"), - cells_per_dimension=(32, 32), element_type=Quad(), - approximation_type=GaussSBP(), tspan=(0.0, 0.2), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.11141270656347146, - 0.06598888014584121, - 0.1044902203749932, - 0.16023037364774995, - ] ./ 2.0, - linf=[ - 0.2414760062126462, - 0.1662111846065654, - 0.12344140473946856, - 0.26978428189564774, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl (Quadrilateral elements, GaussSBP)" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability.jl" + ), + cells_per_dimension = (32, 32), element_type = Quad(), + approximation_type = GaussSBP(), tspan = (0.0, 0.2), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.11141270656347146, + 0.06598888014584121, + 0.1044902203749932, + 0.16023037364774995, + ] ./ 2.0, + linf = [ + 0.2414760062126462, + 0.1662111846065654, + 0.12344140473946856, + 0.26978428189564774, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_rayleigh_taylor_instability.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_rayleigh_taylor_instability.jl"), - cells_per_dimension=(8, 8), tspan=(0.0, 0.2), - l2=[ - 0.07097806723891838, 0.005168550941966817, - 0.013820912272220933, 0.03243357220022434, - ], - linf=[ - 0.4783395896753895, 0.02244629340135818, - 0.04023357731088538, 0.08515807256615027, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_rayleigh_taylor_instability.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_rayleigh_taylor_instability.jl" + ), + cells_per_dimension = (8, 8), tspan = (0.0, 0.2), + l2 = [ + 0.07097806723891838, 0.005168550941966817, + 0.013820912272220933, 0.03243357220022434, + ], + linf = [ + 0.4783395896753895, 0.02244629340135818, + 0.04023357731088538, 0.08515807256615027, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_brown_minion_vortex.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_brown_minion_vortex.jl"), - cells_per_dimension=4, tspan=(0.0, 0.1), - l2=[ - 0.006680001611078062, - 0.02151676347585447, - 0.010696524235364626, - 0.15052841129694647, - ], - linf=[ - 0.01544756362800248, - 0.09517304772476806, - 0.021957154972646383, - 0.33773439650806303, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_brown_minion_vortex.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_brown_minion_vortex.jl"), + cells_per_dimension = 4, tspan = (0.0, 0.1), + l2 = [ + 0.006680001611078062, + 0.02151676347585447, + 0.010696524235364626, + 0.15052841129694647, + ], + linf = [ + 0.01544756362800248, + 0.09517304772476806, + 0.021957154972646383, + 0.33773439650806303, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing.jl"), - cells_per_dimension=4, tspan=(0.0, 0.1), - l2=[ - 0.05685148333985476, - 0.04308122135907089, - 0.043081221359070915, - 0.21098131003847664, - ], - linf=[ - 0.2360672306096051, - 0.16684417686971842, - 0.1668441768697189, - 0.8572572782118661, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing.jl"), + cells_per_dimension = 4, tspan = (0.0, 0.1), + l2 = [ + 0.05685148333985476, + 0.04308122135907089, + 0.043081221359070915, + 0.21098131003847664, + ], + linf = [ + 0.2360672306096051, + 0.16684417686971842, + 0.1668441768697189, + 0.8572572782118661, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_curved.jl"), - cells_per_dimension=4, tspan=(0.0, 0.1), - l2=[ - 0.05565849298766252, - 0.042322816017256494, - 0.042322816017256466, - 0.2064212098324083, - ], - linf=[ - 0.23633287875008924, - 0.16930148707515683, - 0.16930148707515688, - 0.8587706761131937, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing_curved.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_curved.jl"), + cells_per_dimension = 4, tspan = (0.0, 0.1), + l2 = [ + 0.05565849298766252, + 0.042322816017256494, + 0.042322816017256466, + 0.2064212098324083, + ], + linf = [ + 0.23633287875008924, + 0.16930148707515683, + 0.16930148707515688, + 0.8587706761131937, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (FD SBP)" begin - global D = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order = 1, - accuracy_order = 4, - xmin = 0.0, xmax = 1.0, - N = 12) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - cells_per_dimension=(2, 2), - element_type=Quad(), - cfl=1.0, - approximation_type=D, - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.0008966318978421226, - 0.0011418826379110242, - 0.001141882637910878, - 0.0030918374335671393, - ] ./ 2.0, - linf=[ - 0.0015281525343109337, - 0.00162430960401716, - 0.0016243096040242655, - 0.004447503691245913, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (FD SBP)" begin + global D = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, + accuracy_order = 4, + xmin = 0.0, xmax = 1.0, + N = 12 + ) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + cells_per_dimension = (2, 2), + element_type = Quad(), + cfl = 1.0, + approximation_type = D, + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.0008966318978421226, + 0.0011418826379110242, + 0.001141882637910878, + 0.0030918374335671393, + ] ./ 2.0, + linf = [ + 0.0015281525343109337, + 0.00162430960401716, + 0.0016243096040242655, + 0.004447503691245913, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (FD SBP, EC)" begin - global D = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order = 1, - accuracy_order = 4, - xmin = 0.0, xmax = 1.0, - N = 12) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - cells_per_dimension=(2, 2), - element_type=Quad(), - cfl=1.0, - approximation_type=D, - volume_integral=VolumeIntegralFluxDifferencing(flux_ranocha), - surface_integral=SurfaceIntegralWeakForm(flux_ranocha), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[ - 0.0014018725496871129, - 0.0015887007320868913, - 0.001588700732086329, - 0.003870926821031202, - ] ./ 2.0, - linf=[ - 0.0029541996523780867, - 0.0034520465226108854, - 0.003452046522624652, - 0.007677153211004928, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (FD SBP, EC)" begin + global D = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, + accuracy_order = 4, + xmin = 0.0, xmax = 1.0, + N = 12 + ) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + cells_per_dimension = (2, 2), + element_type = Quad(), + cfl = 1.0, + approximation_type = D, + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha), + surface_integral = SurfaceIntegralWeakForm(flux_ranocha), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.0014018725496871129, + 0.0015887007320868913, + 0.001588700732086329, + 0.003870926821031202, + ] ./ 2.0, + linf = [ + 0.0029541996523780867, + 0.0034520465226108854, + 0.003452046522624652, + 0.007677153211004928, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_fdsbp_periodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), - l2=[ - 1.333332033888785e-6, 2.044834627786368e-6, - 2.0448346278315884e-6, 5.282189803437435e-6, - ], - linf=[ - 2.7000151703315822e-6, 3.988595025372632e-6, - 3.9885950240403645e-6, 8.848583036513702e-6, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_fdsbp_periodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), + l2 = [ + 1.333332033888785e-6, 2.044834627786368e-6, + 2.0448346278315884e-6, 5.282189803437435e-6, + ], + linf = [ + 2.7000151703315822e-6, 3.988595025372632e-6, + 3.9885950240403645e-6, 8.848583036513702e-6, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_fdsbp_periodic.jl (arbitrary reference domain)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), - xmin=-200.0, xmax=100.0, #= parameters for reference interval =# - surface_flux=FluxHLL(min_max_speed_naive), - l2=[ - 1.333332034149886e-6, - 2.0448346280892024e-6, - 2.0448346279766305e-6, - 5.282189803510037e-6, - ], - linf=[ - 2.700015170553627e-6, - 3.988595024262409e-6, - 3.988595024928543e-6, - 8.84858303740188e-6, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_fdsbp_periodic.jl (arbitrary reference domain)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), + xmin = -200.0, xmax = 100.0, #= parameters for reference interval =# + surface_flux = FluxHLL(min_max_speed_naive), + l2 = [ + 1.333332034149886e-6, + 2.0448346280892024e-6, + 2.0448346279766305e-6, + 5.282189803510037e-6, + ], + linf = [ + 2.700015170553627e-6, + 3.988595024262409e-6, + 3.988595024928543e-6, + 8.84858303740188e-6, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_fdsbp_periodic.jl (arbitrary reference and physical domains)" begin - global D = periodic_derivative_operator(derivative_order = 1, - accuracy_order = 4, - xmin = -200.0, - xmax = 100.0, - N = 100) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), - approximation_type=D, - coordinates_min=(-3.0, -4.0), coordinates_max=(0.0, -1.0), - surface_flux=FluxHLL(min_max_speed_naive), - l2=[ - 0.07318831033918516, - 0.10039910610067465, - 0.1003991061006748, - 0.2642450566234564, - ], - linf=[ - 0.36081081739439735, - 0.5244468027020845, - 0.5244468027020814, - 1.2210130256735705, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_fdsbp_periodic.jl (arbitrary reference and physical domains)" begin + global D = periodic_derivative_operator( + derivative_order = 1, + accuracy_order = 4, + xmin = -200.0, + xmax = 100.0, + N = 100 + ) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), + approximation_type = D, + coordinates_min = (-3.0, -4.0), coordinates_max = (0.0, -1.0), + surface_flux = FluxHLL(min_max_speed_naive), + l2 = [ + 0.07318831033918516, + 0.10039910610067465, + 0.1003991061006748, + 0.2642450566234564, + ], + linf = [ + 0.36081081739439735, + 0.5244468027020845, + 0.5244468027020814, + 1.2210130256735705, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_fdsbp_periodic.jl (CGSEM)" begin - D_local = SummationByPartsOperators.legendre_derivative_operator(xmin = 0.0, - xmax = 1.0, - N = 4) - mesh_local = SummationByPartsOperators.UniformPeriodicMesh1D(xmin = -1.0, - xmax = 1.0, - Nx = 10) - global D = SummationByPartsOperators.couple_continuously(D_local, mesh_local) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), - approximation_type=D, - surface_flux=FluxHLL(min_max_speed_naive), - l2=[ - 1.5440402410017893e-5, - 1.4913189903083485e-5, - 1.4913189902797073e-5, - 2.6104615985156992e-5, - ], - linf=[ - 4.16334345412217e-5, - 5.067812788173143e-5, - 5.067812786885284e-5, - 9.887976803746312e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_fdsbp_periodic.jl (CGSEM)" begin + D_local = SummationByPartsOperators.legendre_derivative_operator( + xmin = 0.0, + xmax = 1.0, + N = 4 + ) + mesh_local = SummationByPartsOperators.UniformPeriodicMesh1D( + xmin = -1.0, + xmax = 1.0, + Nx = 10 + ) + global D = SummationByPartsOperators.couple_continuously(D_local, mesh_local) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), + approximation_type = D, + surface_flux = FluxHLL(min_max_speed_naive), + l2 = [ + 1.5440402410017893e-5, + 1.4913189903083485e-5, + 1.4913189902797073e-5, + 2.6104615985156992e-5, + ], + linf = [ + 4.16334345412217e-5, + 5.067812788173143e-5, + 5.067812786885284e-5, + 9.887976803746312e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_weak_blast_wave.jl (Quad)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_weak_blast_wave.jl"), - cells_per_dimension=4, - l2=[0.03906769915509508, 0.04923079758984701, - 0.049230797589847136, 0.02660348840973283, - 0.18054907061740028, 0.019195256934309846, - 0.019195256934310016, 0.027856113419468087, - 0.0016567799774264065], - linf=[0.16447597822733662, 0.244157345789029, - 0.24415734578903472, 0.11982440036793476, - 0.7450328339751362, 0.06357382685763713, 0.0635738268576378, - 0.1058830287485999, - 0.005740591170062146]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_weak_blast_wave.jl (Quad)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_weak_blast_wave.jl"), + cells_per_dimension = 4, + l2 = [ + 0.03906769915509508, 0.04923079758984701, + 0.049230797589847136, 0.02660348840973283, + 0.18054907061740028, 0.019195256934309846, + 0.019195256934310016, 0.027856113419468087, + 0.0016567799774264065, + ], + linf = [ + 0.16447597822733662, 0.244157345789029, + 0.24415734578903472, 0.11982440036793476, + 0.7450328339751362, 0.06357382685763713, 0.0635738268576378, + 0.1058830287485999, + 0.005740591170062146, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_weak_blast_wave.jl (Tri)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_weak_blast_wave.jl"), - cells_per_dimension=4, element_type=Tri(), - l2=[0.03372468091254386, 0.03971626483409167, - 0.03971626483409208, 0.021427571421535722, - 0.15079331840847413, 0.015716300366650286, - 0.015716300366652128, 0.022365252076279075, - 0.0009232971979900358], - linf=[0.16290247390873458, 0.2256891306641319, - 0.2256891306641336, 0.09476017042552534, - 0.6906308908961734, 0.05349939593012487, - 0.05349939593013042, 0.08830587480616725, - 0.0029551359803035027]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_weak_blast_wave.jl (Tri)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_weak_blast_wave.jl"), + cells_per_dimension = 4, element_type = Tri(), + l2 = [ + 0.03372468091254386, 0.03971626483409167, + 0.03971626483409208, 0.021427571421535722, + 0.15079331840847413, 0.015716300366650286, + 0.015716300366652128, 0.022365252076279075, + 0.0009232971979900358, + ], + linf = [ + 0.16290247390873458, 0.2256891306641319, + 0.2256891306641336, 0.09476017042552534, + 0.6906308908961734, 0.05349939593012487, + 0.05349939593013042, 0.08830587480616725, + 0.0029551359803035027, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_weak_blast_wave_SBP.jl (Quad)" begin - # These setups do not pass CI reliably, see - # https://github.com/trixi-framework/Trixi.jl/pull/880 and - # https://github.com/trixi-framework/Trixi.jl/issues/881 - @test_skip @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_mhd_weak_blast_wave_SBP.jl"), - cells_per_dimension=4, - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[0.15825983698241494, 0.19897219694837923, - 0.19784182473275247, 0.10482833997417325, - 0.7310752391255246, 0.07374056714564853, - 0.07371172293240634, 0.10782032253431281, - 0.004921676235111545] ./ 2.0, - linf=[0.1765644464978685, 0.2627803272865769, - 0.26358136695848144, 0.12347681727447984, - 0.7733289736898254, 0.06695360844467957, - 0.06650382120802623, 0.10885097000919097, - 0.007212567638078835]) -end + @trixi_testset "elixir_mhd_weak_blast_wave_SBP.jl (Quad)" begin + # These setups do not pass CI reliably, see + # https://github.com/trixi-framework/Trixi.jl/pull/880 and + # https://github.com/trixi-framework/Trixi.jl/issues/881 + @test_skip @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_mhd_weak_blast_wave_SBP.jl" + ), + cells_per_dimension = 4, + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.15825983698241494, 0.19897219694837923, + 0.19784182473275247, 0.10482833997417325, + 0.7310752391255246, 0.07374056714564853, + 0.07371172293240634, 0.10782032253431281, + 0.004921676235111545, + ] ./ 2.0, + linf = [ + 0.1765644464978685, 0.2627803272865769, + 0.26358136695848144, 0.12347681727447984, + 0.7733289736898254, 0.06695360844467957, + 0.06650382120802623, 0.10885097000919097, + 0.007212567638078835, + ] + ) + end -@trixi_testset "elixir_mhd_weak_blast_wave_SBP.jl (Tri)" begin - # These setups do not pass CI reliably, see - # https://github.com/trixi-framework/Trixi.jl/pull/880 and - # https://github.com/trixi-framework/Trixi.jl/issues/881 - @test_skip @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_mhd_weak_blast_wave_SBP.jl"), - cells_per_dimension=4, element_type=Tri(), - tspan=(0.0, 0.2), - # division by 2.0 corresponds to normalization by the square root of the size of the domain - l2=[0.13825044764021147, 0.15472815448314997, - 0.1549093274293255, 0.053103596213755405, - 0.7246162776815603, 0.07730777596615901, - 0.07733438386480523, 0.109893463921706, - 0.00617678167062838] ./ 2.0, - linf=[0.22701306227317952, 0.2905255794821543, - 0.2912409425436937, 0.08051361477962096, - 1.0842974228656006, 0.07866000368926784, - 0.0786646354518149, 0.1614896380292925, - 0.010358210347485542]) -end + @trixi_testset "elixir_mhd_weak_blast_wave_SBP.jl (Tri)" begin + # These setups do not pass CI reliably, see + # https://github.com/trixi-framework/Trixi.jl/pull/880 and + # https://github.com/trixi-framework/Trixi.jl/issues/881 + @test_skip @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_mhd_weak_blast_wave_SBP.jl" + ), + cells_per_dimension = 4, element_type = Tri(), + tspan = (0.0, 0.2), + # division by 2.0 corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.13825044764021147, 0.15472815448314997, + 0.1549093274293255, 0.053103596213755405, + 0.7246162776815603, 0.07730777596615901, + 0.07733438386480523, 0.109893463921706, + 0.00617678167062838, + ] ./ 2.0, + linf = [ + 0.22701306227317952, 0.2905255794821543, + 0.2912409425436937, 0.08051361477962096, + 1.0842974228656006, 0.07866000368926784, + 0.0786646354518149, 0.1614896380292925, + 0.010358210347485542, + ] + ) + end -@trixi_testset "elixir_mhd_reflective_wall.jl (Quad)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_reflective_wall.jl"), - cells_per_dimension=4, - l2=[ - 0.0036019536614619687, - 0.001734097206958611, - 0.008375221008997178, - 0.0, - 0.028596796602124414, - 0.0018573693138866614, - 0.0020807798141551166, - 0.0, - 5.301188920230166e-5, - ], - linf=[ - 0.01692601228199253, - 0.009369662298436778, - 0.04145169295835428, - 0.0, - 0.11569908670112738, - 0.00984964453299233, - 0.01141708032148614, - 0.0, - 0.0002992631411931389, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_reflective_wall.jl (Quad)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_reflective_wall.jl"), + cells_per_dimension = 4, + l2 = [ + 0.0036019536614619687, + 0.001734097206958611, + 0.008375221008997178, + 0.0, + 0.028596796602124414, + 0.0018573693138866614, + 0.0020807798141551166, + 0.0, + 5.301188920230166e-5, + ], + linf = [ + 0.01692601228199253, + 0.009369662298436778, + 0.04145169295835428, + 0.0, + 0.11569908670112738, + 0.00984964453299233, + 0.01141708032148614, + 0.0, + 0.0002992631411931389, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl (Quad, SBP)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - cells_per_dimension=8, element_type=Quad(), - approximation_type=SBP(), - l2=[ - 0.0020316462913319046, - 0.023669019044882247, - 0.03446194752754684, - 1.9333465252381796e-15, - ], - linf=[ - 0.010385010095182778, - 0.08750628939565086, - 0.12088392994348407, - 9.325873406851315e-15, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl (Quad, SBP)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + cells_per_dimension = 8, element_type = Quad(), + approximation_type = SBP(), + l2 = [ + 0.0020316462913319046, + 0.023669019044882247, + 0.03446194752754684, + 1.9333465252381796e-15, + ], + linf = [ + 0.010385010095182778, + 0.08750628939565086, + 0.12088392994348407, + 9.325873406851315e-15, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl (Tri, SBP)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - cells_per_dimension=8, element_type=Tri(), - approximation_type=SBP(), - l2=[ - 0.004180680322490383, - 0.07026192411558974, - 0.11815151697006446, - 2.329788936151192e-15, - ], - linf=[ - 0.02076003852980346, - 0.29169601664914424, - 0.5674183379872275, - 1.1546319456101628e-14, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl (Tri, SBP)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + cells_per_dimension = 8, element_type = Tri(), + approximation_type = SBP(), + l2 = [ + 0.004180680322490383, + 0.07026192411558974, + 0.11815151697006446, + 2.329788936151192e-15, + ], + linf = [ + 0.02076003852980346, + 0.29169601664914424, + 0.5674183379872275, + 1.1546319456101628e-14, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl (Tri, Polynomial)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - cells_per_dimension=8, element_type=Tri(), - approximation_type=Polynomial(), - # The last l2, linf error are the L2 projection error in approximating `b`, so they are not - # zero for general non-collocated quadrature rules (e.g., for `element_type=Tri()`, `polydeg > 2`). - l2=[ - 0.0008309356912456799, - 0.01522451288799231, - 0.016033969387208476, - 1.2820247308150876e-5, - ], - linf=[ - 0.001888045014140971, - 0.05466838692127718, - 0.06345885709961152, - 3.3989933098554914e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl (Tri, Polynomial)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + cells_per_dimension = 8, element_type = Tri(), + approximation_type = Polynomial(), + # The last l2, linf error are the L2 projection error in approximating `b`, so they are not + # zero for general non-collocated quadrature rules (e.g., for `element_type=Tri()`, `polydeg > 2`). + l2 = [ + 0.0008309356912456799, + 0.01522451288799231, + 0.016033969387208476, + 1.2820247308150876e-5, + ], + linf = [ + 0.001888045014140971, + 0.05466838692127718, + 0.06345885709961152, + 3.3989933098554914e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl (Quad, Polynomial)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - cells_per_dimension=8, element_type=Quad(), - approximation_type=Polynomial(), - # The last l2, linf error are the L2 projection error in approximating `b`. However, this is zero - # for `Quad()` elements with `Polynomial()` approximations because the quadrature rule defaults to - # a `(polydeg + 1)`-point Gauss quadrature rule in each coordinate (in general, StartUpDG.jl defaults - # to the quadrature rule with the fewest number of points which exactly integrates the mass matrix). - l2=[ - 7.460461950323111e-5, - 0.003685589808444905, - 0.0039101604749887785, - 2.0636891126652983e-15, - ], - linf=[ - 0.000259995400729629, - 0.0072236204211630906, - 0.010364675200833062, - 1.021405182655144e-14, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl (Quad, Polynomial)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + cells_per_dimension = 8, element_type = Quad(), + approximation_type = Polynomial(), + # The last l2, linf error are the L2 projection error in approximating `b`. However, this is zero + # for `Quad()` elements with `Polynomial()` approximations because the quadrature rule defaults to + # a `(polydeg + 1)`-point Gauss quadrature rule in each coordinate (in general, StartUpDG.jl defaults + # to the quadrature rule with the fewest number of points which exactly integrates the mass matrix). + l2 = [ + 7.460461950323111e-5, + 0.003685589808444905, + 0.0039101604749887785, + 2.0636891126652983e-15, + ], + linf = [ + 0.000259995400729629, + 0.0072236204211630906, + 0.010364675200833062, + 1.021405182655144e-14, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn isdir(outdir) && rm(outdir, recursive = true) diff --git a/test/test_dgmulti_3d.jl b/test/test_dgmulti_3d.jl index d0671c0bfd..d92a3f02ac 100644 --- a/test/test_dgmulti_3d.jl +++ b/test/test_dgmulti_3d.jl @@ -12,385 +12,417 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "DGMulti 3D" begin -#! format: noindent + #! format: noindent -# 3d tet/hex tests -@trixi_testset "elixir_euler_weakform.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - l2=[ - 0.000354593110864001, 0.00041301573702385284, - 0.00037934556184883277, 0.0003525767114354012, - 0.0013917457634530887, - ], - linf=[ - 0.0036608123230692513, 0.005625540942772123, - 0.0030565781898950206, 0.004158099048202857, - 0.01932716837214299, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # 3d tet/hex tests + @trixi_testset "elixir_euler_weakform.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + l2 = [ + 0.000354593110864001, 0.00041301573702385284, + 0.00037934556184883277, 0.0003525767114354012, + 0.0013917457634530887, + ], + linf = [ + 0.0036608123230692513, 0.005625540942772123, + 0.0030565781898950206, 0.004158099048202857, + 0.01932716837214299, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (EC)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - surface_integral=SurfaceIntegralWeakForm(flux_ranocha), - volume_integral=VolumeIntegralFluxDifferencing(flux_ranocha), - # division by sqrt(8.0) corresponds to normalization by the square root of the size of the domain - l2=[ - 0.014932088450136542, - 0.017080219613061526, - 0.016589517840793006, - 0.015905000907070196, - 0.03903416208587798, - ] ./ sqrt(8), - linf=[ - 0.06856547797256729, - 0.08225664880340489, - 0.06925055630951782, - 0.06913016119820181, - 0.19161418499621874, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (EC)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + surface_integral = SurfaceIntegralWeakForm(flux_ranocha), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha), + # division by sqrt(8.0) corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.014932088450136542, + 0.017080219613061526, + 0.016589517840793006, + 0.015905000907070196, + 0.03903416208587798, + ] ./ sqrt(8), + linf = [ + 0.06856547797256729, + 0.08225664880340489, + 0.06925055630951782, + 0.06913016119820181, + 0.19161418499621874, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform.jl (Hexahedral elements)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), - element_type=Hex(), - surface_integral=SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), - # division by sqrt(8.0) corresponds to normalization by the square root of the size of the domain - l2=[ - 0.00030580190715769566, - 0.00040146357607439464, - 0.00040146357607564597, - 0.000401463576075708, - 0.0015749412434154315, - ] ./ sqrt(8), - linf=[ - 0.00036910287847780054, - 0.00042659774184228283, - 0.0004265977427213574, - 0.00042659774250686233, - 0.00143803344597071, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform.jl (Hexahedral elements)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform.jl"), + element_type = Hex(), + surface_integral = SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), + # division by sqrt(8.0) corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.00030580190715769566, + 0.00040146357607439464, + 0.00040146357607564597, + 0.000401463576075708, + 0.0015749412434154315, + ] ./ sqrt(8), + linf = [ + 0.00036910287847780054, + 0.00042659774184228283, + 0.0004265977427213574, + 0.00042659774250686233, + 0.00143803344597071, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_curved.jl (Hex elements, SBP, flux differencing)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), - l2=[ - 0.0019393929700612259, - 0.003213659298633126, - 0.003203104361527826, - 0.0019407707245105426, - 0.0109274471764788, - ], - linf=[ - 0.01914151956454324, - 0.0270195960766606, - 0.026891238631389536, - 0.019817504336972602, - 0.09645660501766873, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_curved.jl (Hex elements, SBP, flux differencing)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), + l2 = [ + 0.0019393929700612259, + 0.003213659298633126, + 0.003203104361527826, + 0.0019407707245105426, + 0.0109274471764788, + ], + linf = [ + 0.01914151956454324, + 0.0270195960766606, + 0.026891238631389536, + 0.019817504336972602, + 0.09645660501766873, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_curved.jl (Hex elements, GaussSBP, flux differencing)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), - approximation_type=GaussSBP(), - l2=[ - 0.0026311315195097097, 0.002914422404496567, - 0.0029138891106640368, 0.002615140832315232, - 0.006881528610616624, - ], - linf=[ - 0.02099611487415931, 0.021314522450152307, - 0.021288322783027613, 0.020273381695449455, - 0.05259874039006007, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_curved.jl (Hex elements, GaussSBP, flux differencing)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_curved.jl"), + approximation_type = GaussSBP(), + l2 = [ + 0.0026311315195097097, 0.002914422404496567, + 0.0029138891106640368, 0.002615140832315232, + 0.006881528610616624, + ], + linf = [ + 0.02099611487415931, 0.021314522450152307, + 0.021288322783027613, 0.020273381695449455, + 0.05259874039006007, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform_periodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), - l2=[ - 0.00036475807571383924, 0.00043404536371780537, - 0.0003985850214093045, 0.0003683451584072326, - 0.00143503620472638, - ], - linf=[ - 0.0032278615418719347, 0.005620238272054934, - 0.0030514261010661237, 0.0039871165455998, - 0.019282771780667396, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform_periodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), + l2 = [ + 0.00036475807571383924, 0.00043404536371780537, + 0.0003985850214093045, 0.0003683451584072326, + 0.00143503620472638, + ], + linf = [ + 0.0032278615418719347, 0.005620238272054934, + 0.0030514261010661237, 0.0039871165455998, + 0.019282771780667396, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform_periodic.jl (Hexahedral elements)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), - element_type=Hex(), - surface_integral=SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), - # division by sqrt(8.0) corresponds to normalization by the square root of the size of the domain - l2=[ - 0.00034230612468547436, - 0.00044397204714598747, - 0.0004439720471461567, - 0.0004439720471464591, - 0.0016639410646990126, - ] ./ sqrt(8), - linf=[ - 0.0003674374460325147, - 0.0004253921341716982, - 0.0004253921340786615, - 0.0004253921340831024, - 0.0014333414071048267, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform_periodic.jl (Hexahedral elements)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), + element_type = Hex(), + surface_integral = SurfaceIntegralWeakForm(FluxHLL(min_max_speed_naive)), + # division by sqrt(8.0) corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.00034230612468547436, + 0.00044397204714598747, + 0.0004439720471461567, + 0.0004439720471464591, + 0.0016639410646990126, + ] ./ sqrt(8), + linf = [ + 0.0003674374460325147, + 0.0004253921341716982, + 0.0004253921340786615, + 0.0004253921340831024, + 0.0014333414071048267, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform_periodic.jl (Hexahedral elements, SBP, EC)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), - element_type=Hex(), - volume_integral=VolumeIntegralFluxDifferencing(flux_ranocha), - surface_integral=SurfaceIntegralWeakForm(flux_ranocha), - approximation_type=SBP(), - # division by sqrt(8.0) corresponds to normalization by the square root of the size of the domain - l2=[ - 0.001712443468716032, - 0.002491315550718859, - 0.0024913155507195303, - 0.002491315550720031, - 0.008585818982343299, - ] ./ sqrt(8), - linf=[ - 0.003810078279323559, - 0.004998778644230928, - 0.004998778643986235, - 0.0049987786444081195, - 0.016455044373650196, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform_periodic.jl (Hexahedral elements, SBP, EC)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), + element_type = Hex(), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha), + surface_integral = SurfaceIntegralWeakForm(flux_ranocha), + approximation_type = SBP(), + # division by sqrt(8.0) corresponds to normalization by the square root of the size of the domain + l2 = [ + 0.001712443468716032, + 0.002491315550718859, + 0.0024913155507195303, + 0.002491315550720031, + 0.008585818982343299, + ] ./ sqrt(8), + linf = [ + 0.003810078279323559, + 0.004998778644230928, + 0.004998778643986235, + 0.0049987786444081195, + 0.016455044373650196, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_taylor_green_vortex.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_taylor_green_vortex.jl"), - polydeg=3, tspan=(0.0, 1.0), cells_per_dimension=(2, 2, 2), - l2=[ - 0.0003612827827560599, - 0.06219350883951729, - 0.062193508839503864, - 0.08121963221634831, - 0.07082703570808184, - ], - linf=[ - 0.0007893509649821162, - 0.1481953939988877, - 0.14819539399791176, - 0.14847291108358926, - 0.21313533492212855, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_taylor_green_vortex.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_taylor_green_vortex.jl"), + polydeg = 3, tspan = (0.0, 1.0), cells_per_dimension = (2, 2, 2), + l2 = [ + 0.0003612827827560599, + 0.06219350883951729, + 0.062193508839503864, + 0.08121963221634831, + 0.07082703570808184, + ], + linf = [ + 0.0007893509649821162, + 0.1481953939988877, + 0.14819539399791176, + 0.14847291108358926, + 0.21313533492212855, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_taylor_green_vortex.jl (GaussSBP)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_taylor_green_vortex.jl"), - polydeg=3, approximation_type=GaussSBP(), tspan=(0.0, 1.0), - cells_per_dimension=(2, 2, 2), - l2=[ - 0.00036128278275524326, - 0.062193508839511434, - 0.06219350883949677, - 0.08121963221635205, - 0.07082703570765223, - ], - linf=[ - 0.000789350964946367, - 0.14819539399525805, - 0.14819539399590542, - 0.14847291107658706, - 0.21313533492059378, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_taylor_green_vortex.jl (GaussSBP)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_taylor_green_vortex.jl"), + polydeg = 3, approximation_type = GaussSBP(), tspan = (0.0, 1.0), + cells_per_dimension = (2, 2, 2), + l2 = [ + 0.00036128278275524326, + 0.062193508839511434, + 0.06219350883949677, + 0.08121963221635205, + 0.07082703570765223, + ], + linf = [ + 0.000789350964946367, + 0.14819539399525805, + 0.14819539399590542, + 0.14847291107658706, + 0.21313533492059378, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform_periodic.jl (FD SBP)" begin - global D = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order = 1, - accuracy_order = 2, - xmin = 0.0, xmax = 1.0, - N = 8) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), - element_type=Hex(), - cells_per_dimension=(2, 2, 2), - approximation_type=D, - l2=[ - 0.0024092707138829925, - 0.003318758964118284, - 0.0033187589641182386, - 0.003318758964118252, - 0.012689348410504253, - ], - linf=[ - 0.006118565824207778, - 0.008486456080185167, - 0.008486456080180282, - 0.008486456080185611, - 0.035113544599208346, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform_periodic.jl (FD SBP)" begin + global D = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, + accuracy_order = 2, + xmin = 0.0, xmax = 1.0, + N = 8 + ) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), + element_type = Hex(), + cells_per_dimension = (2, 2, 2), + approximation_type = D, + l2 = [ + 0.0024092707138829925, + 0.003318758964118284, + 0.0033187589641182386, + 0.003318758964118252, + 0.012689348410504253, + ], + linf = [ + 0.006118565824207778, + 0.008486456080185167, + 0.008486456080180282, + 0.008486456080185611, + 0.035113544599208346, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weakform_periodic.jl (FD SBP, EC)" begin - global D = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order = 1, - accuracy_order = 2, - xmin = 0.0, xmax = 1.0, - N = 8) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), - element_type=Hex(), - cells_per_dimension=(2, 2, 2), - approximation_type=D, - volume_integral=VolumeIntegralFluxDifferencing(flux_ranocha), - surface_integral=SurfaceIntegralWeakForm(flux_ranocha), - l2=[ - 0.0034543609010604407, - 0.004944363692625066, - 0.0049443636926250435, - 0.004944363692625037, - 0.01788695279620914, - ], - linf=[ - 0.013861851418853988, - 0.02126572106620328, - 0.021265721066209053, - 0.021265721066210386, - 0.0771455289446683, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weakform_periodic.jl (FD SBP, EC)" begin + global D = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, + accuracy_order = 2, + xmin = 0.0, xmax = 1.0, + N = 8 + ) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weakform_periodic.jl"), + element_type = Hex(), + cells_per_dimension = (2, 2, 2), + approximation_type = D, + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha), + surface_integral = SurfaceIntegralWeakForm(flux_ranocha), + l2 = [ + 0.0034543609010604407, + 0.004944363692625066, + 0.0049443636926250435, + 0.004944363692625037, + 0.01788695279620914, + ], + linf = [ + 0.013861851418853988, + 0.02126572106620328, + 0.021265721066209053, + 0.021265721066210386, + 0.0771455289446683, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_fdsbp_periodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), - l2=[ - 7.561896970325353e-5, - 6.884047859361093e-5, - 6.884047859363204e-5, - 6.884047859361148e-5, - 0.000201107274617457, - ], - linf=[ - 0.0001337520020225913, - 0.00011571467799287305, - 0.0001157146779990903, - 0.00011571467799376123, - 0.0003446082308800058, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_fdsbp_periodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_fdsbp_periodic.jl"), + l2 = [ + 7.561896970325353e-5, + 6.884047859361093e-5, + 6.884047859363204e-5, + 6.884047859361148e-5, + 0.000201107274617457, + ], + linf = [ + 0.0001337520020225913, + 0.00011571467799287305, + 0.0001157146779990903, + 0.00011571467799376123, + 0.0003446082308800058, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_tensor_wedge.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_tensor_wedge.jl"), - l2=[2.30487910e-04], - linf=[6.31795281e-04]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_tensor_wedge.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_tensor_wedge.jl"), + l2 = [2.3048791e-4], + linf = [6.31795281e-4] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn isdir(outdir) && rm(outdir, recursive = true) diff --git a/test/test_mpi_p4est_2d.jl b/test/test_mpi_p4est_2d.jl index 29de4efc6d..50567eb3f6 100644 --- a/test/test_mpi_p4est_2d.jl +++ b/test/test_mpi_p4est_2d.jl @@ -8,153 +8,179 @@ include("test_trixi.jl") const EXAMPLES_DIR = pkgdir(Trixi, "examples", "p4est_2d_dgsem") @testset "P4estMesh MPI 2D" begin -#! format: noindent - -# Run basic tests -@testset "Examples 2D" begin - # Linear scalar advection - @trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5]) - - @testset "error-based step size control" begin - Trixi.mpi_isroot() && println("-"^100) - Trixi.mpi_isroot() && - println("elixir_advection_basic.jl with error-based step size control") - - sol = solve(ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, - ode_default_options()..., callback = callbacks) - summary_callback() - errors = analysis_callback(sol) - if Trixi.mpi_isroot() - @test errors.l2≈[3.3022040342579066e-5] rtol=1.0e-4 - @test errors.linf≈[0.00011787417954578494] rtol=1.0e-4 + #! format: noindent + + # Run basic tests + @testset "Examples 2D" begin + # Linear scalar advection + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5] + ) + + @testset "error-based step size control" begin + Trixi.mpi_isroot() && println("-"^100) + Trixi.mpi_isroot() && + println("elixir_advection_basic.jl with error-based step size control") + + sol = solve( + ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, + ode_default_options()..., callback = callbacks + ) + summary_callback() + errors = analysis_callback(sol) + if Trixi.mpi_isroot() + @test errors.l2 ≈ [3.3022040342579066e-5] rtol = 1.0e-4 + @test errors.linf ≈ [0.00011787417954578494] rtol = 1.0e-4 + end end - end - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_nonconforming_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_nonconforming_flag.jl"), - l2=[3.198940059144588e-5], - linf=[0.00030636069494005547]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_nonconforming_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_nonconforming_flag.jl" + ), + l2 = [3.198940059144588e-5], + linf = [0.00030636069494005547] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_unstructured_flag.jl"), - l2=[0.0005379687442422346], - linf=[0.007438525029884735]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_unstructured_flag.jl" + ), + l2 = [0.0005379687442422346], + linf = [0.007438525029884735] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_amr_solution_independent.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_solution_independent.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[4.949660644033807e-5], - linf=[0.0004867846262313763], - coverage_override=(maxiters = 6,)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_solution_independent.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_solution_independent.jl" + ), + # Expected errors are exactly the same as with TreeMesh! + l2 = [4.949660644033807e-5], + linf = [0.0004867846262313763], + coverage_override = (maxiters = 6,) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_amr_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_unstructured_flag.jl"), - l2=[0.0012808538770535593], - linf=[0.01752690016659812], - coverage_override=(maxiters = 6,)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_unstructured_flag.jl" + ), + l2 = [0.0012808538770535593], + linf = [0.01752690016659812], + coverage_override = (maxiters = 6,) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_restart.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - l2=[4.507575525876275e-6], - linf=[6.21489667023134e-5], - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + l2 = [4.507575525876275e-6], + linf = [6.21489667023134e-5], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), - l2=[ - 0.0034516244508588046, - 0.0023420334036925493, - 0.0024261923964557187, - 0.004731710454271893, - ], - linf=[ - 0.04155789011775046, - 0.024772109862748914, - 0.03759938693042297, - 0.08039824959535657, - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" + ), + l2 = [ + 0.0034516244508588046, + 0.0023420334036925493, + 0.0024261923964557187, + 0.004731710454271893, + ], + linf = [ + 0.04155789011775046, + 0.024772109862748914, + 0.03759938693042297, + 0.08039824959535657, + ] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # P4estMesh MPI end # module diff --git a/test/test_mpi_p4est_3d.jl b/test/test_mpi_p4est_3d.jl index 4f9465b85d..39fc34f44f 100644 --- a/test/test_mpi_p4est_3d.jl +++ b/test/test_mpi_p4est_3d.jl @@ -8,234 +8,266 @@ include("test_trixi.jl") const EXAMPLES_DIR = pkgdir(Trixi, "examples", "p4est_3d_dgsem") @testset "P4estMesh MPI 3D" begin -#! format: noindent - -# Run basic tests -@testset "Examples 3D" begin - # Linear scalar advection - @trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[0.00016263963870641478], - linf=[0.0014537194925779984]) - - @testset "error-based step size control" begin - Trixi.mpi_isroot() && println("-"^100) - Trixi.mpi_isroot() && - println("elixir_advection_basic.jl with error-based step size control") - - sol = solve(ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, - ode_default_options()..., callback = callbacks) - summary_callback() - errors = analysis_callback(sol) - if Trixi.mpi_isroot() - @test errors.l2≈[0.00016800412839949264] rtol=1.0e-4 - @test errors.linf≈[0.0014548839020096516] rtol=1.0e-4 + #! format: noindent + + # Run basic tests + @testset "Examples 3D" begin + # Linear scalar advection + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [0.00016263963870641478], + linf = [0.0014537194925779984] + ) + + @testset "error-based step size control" begin + Trixi.mpi_isroot() && println("-"^100) + Trixi.mpi_isroot() && + println("elixir_advection_basic.jl with error-based step size control") + + sol = solve( + ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, + ode_default_options()..., callback = callbacks + ) + summary_callback() + errors = analysis_callback(sol) + if Trixi.mpi_isroot() + @test errors.l2 ≈ [0.00016800412839949264] rtol = 1.0e-4 + @test errors.linf ≈ [0.0014548839020096516] rtol = 1.0e-4 + end end - end - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[9.773852895157622e-6], - linf=[0.0005853874124926162], - # override values are different from the serial tests to ensure each process holds at least - # one element, otherwise OrdinaryDiffEq fails during initialization - coverage_override=(maxiters = 6, - initial_refinement_level = 2, - base_level = 2, med_level = 3, - max_level = 4)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [9.773852895157622e-6], + linf = [0.0005853874124926162], + # override values are different from the serial tests to ensure each process holds at least + # one element, otherwise OrdinaryDiffEq fails during initialization + coverage_override = ( + maxiters = 6, + initial_refinement_level = 2, + base_level = 2, med_level = 3, + max_level = 4, + ) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_amr_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_unstructured_curved.jl"), - l2=[1.6163120948209677e-5], - linf=[0.0010572201890564834], - tspan=(0.0, 1.0), - coverage_override=(maxiters = 6, - initial_refinement_level = 0, - base_level = 0, med_level = 1, - max_level = 2)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_unstructured_curved.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_unstructured_curved.jl" + ), + l2 = [1.6163120948209677e-5], + linf = [0.0010572201890564834], + tspan = (0.0, 1.0), + coverage_override = ( + maxiters = 6, + initial_refinement_level = 0, + base_level = 0, med_level = 1, + max_level = 2, + ) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_restart.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - l2=[0.002590388934758452], - linf=[0.01840757696885409], - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + l2 = [0.002590388934758452], + linf = [0.01840757696885409], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_cubed_sphere.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_cubed_sphere.jl"), - l2=[0.002006918015656413], - linf=[0.027655117058380085]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_cubed_sphere.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_cubed_sphere.jl"), + l2 = [0.002006918015656413], + linf = [0.027655117058380085] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - # Compressible Euler - @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonconforming_unstructured_curved.jl"), - l2=[ - 4.070355207909268e-5, - 4.4993257426833716e-5, - 5.10588457841744e-5, - 5.102840924036687e-5, - 0.00019986264001630542, - ], - linf=[ - 0.0016987332417202072, - 0.003622956808262634, - 0.002029576258317789, - 0.0024206977281964193, - 0.008526972236273522, - ], - tspan=(0.0, 0.01)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Compressible Euler + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" + ), + l2 = [ + 4.070355207909268e-5, + 4.4993257426833716e-5, + 5.10588457841744e-5, + 5.102840924036687e-5, + 0.00019986264001630542, + ], + linf = [ + 0.0016987332417202072, + 0.003622956808262634, + 0.002029576258317789, + 0.0024206977281964193, + 0.008526972236273522, + ], + tspan = (0.0, 0.01) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 0.0015106060984283647, - 0.0014733349038567685, - 0.00147333490385685, - 0.001473334903856929, - 0.0028149479453087093, - ], - linf=[ - 0.008070806335238156, - 0.009007245083113125, - 0.009007245083121784, - 0.009007245083102688, - 0.01562861968368434, - ], - tspan=(0.0, 1.0)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 0.0015106060984283647, + 0.0014733349038567685, + 0.00147333490385685, + 0.001473334903856929, + 0.0028149479453087093, + ], + linf = [ + 0.008070806335238156, + 0.009007245083113125, + 0.009007245083121784, + 0.009007245083102688, + 0.01562861968368434, + ], + tspan = (0.0, 1.0) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.010380390326164493, - 0.006192950051354618, - 0.005970674274073704, - 0.005965831290564327, - 0.02628875593094754, - ], - linf=[ - 0.3326911600075694, - 0.2824952141320467, - 0.41401037398065543, - 0.45574161423218573, - 0.8099577682187109, - ], - tspan=(0.0, 0.2), - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.010380390326164493, + 0.006192950051354618, + 0.005970674274073704, + 0.005965831290564327, + 0.02628875593094754, + ], + linf = [ + 0.3326911600075694, + 0.2824952141320467, + 0.41401037398065543, + 0.45574161423218573, + 0.8099577682187109, + ], + tspan = (0.0, 0.2), + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_euler_source_terms_nonperiodic_hohqmesh.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic_hohqmesh.jl"), - l2=[ - 0.0042023406458005464, - 0.004122532789279737, - 0.0042448149597303616, - 0.0036361316700401765, - 0.007389845952982495, - ], - linf=[ - 0.04530610539892499, - 0.02765695110527666, - 0.05670295599308606, - 0.048396544302230504, - 0.1154589758186293, - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic_hohqmesh.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic_hohqmesh.jl" + ), + l2 = [ + 0.0042023406458005464, + 0.004122532789279737, + 0.0042448149597303616, + 0.0036361316700401765, + 0.007389845952982495, + ], + linf = [ + 0.04530610539892499, + 0.02765695110527666, + 0.05670295599308606, + 0.048396544302230504, + 0.1154589758186293, + ] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # P4estMesh MPI end # module diff --git a/test/test_mpi_t8code_2d.jl b/test/test_mpi_t8code_2d.jl index 75e65c8c38..7f27788bd8 100644 --- a/test/test_mpi_t8code_2d.jl +++ b/test/test_mpi_t8code_2d.jl @@ -8,136 +8,160 @@ include("test_trixi.jl") const EXAMPLES_DIR = pkgdir(Trixi, "examples", "t8code_2d_dgsem") @testset "T8codeMesh MPI 2D" begin -#! format: noindent - -# Run basic tests -@testset "Examples 2D" begin - # Linear scalar advection - @trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5]) - - @testset "error-based step size control" begin - Trixi.mpi_isroot() && println("-"^100) - Trixi.mpi_isroot() && - println("elixir_advection_basic.jl with error-based step size control") - - sol = solve(ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, - ode_default_options()..., callback = callbacks) - summary_callback() - errors = analysis_callback(sol) - if Trixi.mpi_isroot() - @test errors.l2≈[3.3022040342579066e-5] rtol=1.0e-4 - @test errors.linf≈[0.00011787417954578494] rtol=1.0e-4 + #! format: noindent + + # Run basic tests + @testset "Examples 2D" begin + # Linear scalar advection + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5] + ) + + @testset "error-based step size control" begin + Trixi.mpi_isroot() && println("-"^100) + Trixi.mpi_isroot() && + println("elixir_advection_basic.jl with error-based step size control") + + sol = solve( + ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, + ode_default_options()..., callback = callbacks + ) + summary_callback() + errors = analysis_callback(sol) + if Trixi.mpi_isroot() + @test errors.l2 ≈ [3.3022040342579066e-5] rtol = 1.0e-4 + @test errors.linf ≈ [0.00011787417954578494] rtol = 1.0e-4 + end end - end - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_nonconforming_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_nonconforming_flag.jl"), - l2=[3.198940059144588e-5], - linf=[0.00030636069494005547]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_nonconforming_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_nonconforming_flag.jl" + ), + l2 = [3.198940059144588e-5], + linf = [0.00030636069494005547] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_unstructured_flag.jl"), - l2=[0.0005379687442422346], - linf=[0.007438525029884735]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_unstructured_flag.jl" + ), + l2 = [0.0005379687442422346], + linf = [0.007438525029884735] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_amr_solution_independent.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_solution_independent.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[4.933027431215839e-5], - linf=[0.00048678461161243136], - coverage_override=(maxiters = 6,)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_solution_independent.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_solution_independent.jl" + ), + # Expected errors are exactly the same as with TreeMesh! + l2 = [4.933027431215839e-5], + linf = [0.00048678461161243136], + coverage_override = (maxiters = 6,) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_amr_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_unstructured_flag.jl"), - l2=[0.002019623611753929], - linf=[0.03542375961299987], - dynamic_load_balancing=false, - coverage_override=(maxiters = 6,)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_unstructured_flag.jl" + ), + l2 = [0.002019623611753929], + linf = [0.03542375961299987], + dynamic_load_balancing = false, + coverage_override = (maxiters = 6,) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), - l2=[ - 0.0034516244508588046, - 0.0023420334036925493, - 0.0024261923964557187, - 0.004731710454271893, - ], - linf=[ - 0.04155789011775046, - 0.024772109862748914, - 0.03759938693042297, - 0.08039824959535657, - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" + ), + l2 = [ + 0.0034516244508588046, + 0.0023420334036925493, + 0.0024261923964557187, + 0.004731710454271893, + ], + linf = [ + 0.04155789011775046, + 0.024772109862748914, + 0.03759938693042297, + 0.08039824959535657, + ] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # T8codeMesh MPI end # module diff --git a/test/test_mpi_t8code_3d.jl b/test/test_mpi_t8code_3d.jl index 2e837f79ad..437e8c1b04 100644 --- a/test/test_mpi_t8code_3d.jl +++ b/test/test_mpi_t8code_3d.jl @@ -8,173 +8,197 @@ include("test_trixi.jl") const EXAMPLES_DIR = pkgdir(Trixi, "examples", "t8code_3d_dgsem") @testset "T8codeMesh MPI 3D" begin -#! format: noindent - -# Run basic tests -@testset "Examples 3D" begin - # Linear scalar advection - @trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[0.00016263963870641478], - linf=[0.0014537194925779984]) - - @testset "error-based step size control" begin - Trixi.mpi_isroot() && println("-"^100) - Trixi.mpi_isroot() && - println("elixir_advection_basic.jl with error-based step size control") - - sol = solve(ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, - ode_default_options()..., callback = callbacks) - summary_callback() - errors = analysis_callback(sol) - if Trixi.mpi_isroot() - @test errors.l2≈[0.00016800412839949264] rtol=1.0e-4 - @test errors.linf≈[0.0014548839020096516] rtol=1.0e-4 + #! format: noindent + + # Run basic tests + @testset "Examples 3D" begin + # Linear scalar advection + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [0.00016263963870641478], + linf = [0.0014537194925779984] + ) + + @testset "error-based step size control" begin + Trixi.mpi_isroot() && println("-"^100) + Trixi.mpi_isroot() && + println("elixir_advection_basic.jl with error-based step size control") + + sol = solve( + ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, + ode_default_options()..., callback = callbacks + ) + summary_callback() + errors = analysis_callback(sol) + if Trixi.mpi_isroot() + @test errors.l2 ≈ [0.00016800412839949264] rtol = 1.0e-4 + @test errors.linf ≈ [0.0014548839020096516] rtol = 1.0e-4 + end end - end - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[1.1302812803902801e-5], - linf=[0.0007889950196294793], - # override values are different from the serial tests to ensure each process holds at least - # one element, otherwise OrdinaryDiffEq fails during initialization - coverage_override=(maxiters = 6, - initial_refinement_level = 2, - base_level = 2, med_level = 3, - max_level = 4)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [1.1302812803902801e-5], + linf = [0.0007889950196294793], + # override values are different from the serial tests to ensure each process holds at least + # one element, otherwise OrdinaryDiffEq fails during initialization + coverage_override = ( + maxiters = 6, + initial_refinement_level = 2, + base_level = 2, med_level = 3, + max_level = 4, + ) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_advection_amr_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_unstructured_curved.jl"), - l2=[2.0535121347526814e-5], - linf=[0.0010586603797777504], - tspan=(0.0, 1.0), - coverage_override=(maxiters = 6, - initial_refinement_level = 0, - base_level = 0, med_level = 1, - max_level = 2)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_unstructured_curved.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_unstructured_curved.jl" + ), + l2 = [2.0535121347526814e-5], + linf = [0.0010586603797777504], + tspan = (0.0, 1.0), + coverage_override = ( + maxiters = 6, + initial_refinement_level = 0, + base_level = 0, med_level = 1, + max_level = 2, + ) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - # Compressible Euler - @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonconforming_unstructured_curved.jl"), - l2=[ - 4.070355207909268e-5, - 4.4993257426833716e-5, - 5.10588457841744e-5, - 5.102840924036687e-5, - 0.00019986264001630542, - ], - linf=[ - 0.0016987332417202072, - 0.003622956808262634, - 0.002029576258317789, - 0.0024206977281964193, - 0.008526972236273522, - ], - tspan=(0.0, 0.01)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Compressible Euler + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" + ), + l2 = [ + 4.070355207909268e-5, + 4.4993257426833716e-5, + 5.10588457841744e-5, + 5.102840924036687e-5, + 0.00019986264001630542, + ], + linf = [ + 0.0016987332417202072, + 0.003622956808262634, + 0.002029576258317789, + 0.0024206977281964193, + 0.008526972236273522, + ], + tspan = (0.0, 0.01) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 0.0015106060984283647, - 0.0014733349038567685, - 0.00147333490385685, - 0.001473334903856929, - 0.0028149479453087093, - ], - linf=[ - 0.008070806335238156, - 0.009007245083113125, - 0.009007245083121784, - 0.009007245083102688, - 0.01562861968368434, - ], - tspan=(0.0, 1.0)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 0.0015106060984283647, + 0.0014733349038567685, + 0.00147333490385685, + 0.001473334903856929, + 0.0028149479453087093, + ], + linf = [ + 0.008070806335238156, + 0.009007245083113125, + 0.009007245083121784, + 0.009007245083102688, + 0.01562861968368434, + ], + tspan = (0.0, 1.0) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - end - @trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.010380390326164493, - 0.006192950051354618, - 0.005970674274073704, - 0.005965831290564327, - 0.02628875593094754, - ], - linf=[ - 0.3326911600075694, - 0.2824952141320467, - 0.41401037398065543, - 0.45574161423218573, - 0.8099577682187109, - ], - tspan=(0.0, 0.2), - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.010380390326164493, + 0.006192950051354618, + 0.005970674274073704, + 0.005965831290564327, + 0.02628875593094754, + ], + linf = [ + 0.3326911600075694, + 0.2824952141320467, + 0.41401037398065543, + 0.45574161423218573, + 0.8099577682187109, + ], + tspan = (0.0, 0.2), + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # T8codeMesh MPI end # module diff --git a/test/test_mpi_tree.jl b/test/test_mpi_tree.jl index 6114e453e5..af27a20ef4 100644 --- a/test/test_mpi_tree.jl +++ b/test/test_mpi_tree.jl @@ -11,342 +11,406 @@ const EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") CI_ON_WINDOWS = (get(ENV, "GITHUB_ACTIONS", false) == "true") && Sys.iswindows() @testset "TreeMesh MPI" begin -#! format: noindent + #! format: noindent -# Run basic tests -@testset "Examples 2D" begin - # Linear scalar advection - @trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as in the serial test! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5]) - end + # Run basic tests + @testset "Examples 2D" begin + # Linear scalar advection + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as in the serial test! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5] + ) + end - @trixi_testset "elixir_advection_restart.jl" begin - using OrdinaryDiffEq: RDPK3SpFSAL49 - Trixi.mpi_isroot() && println("═"^100) - Trixi.mpi_isroot() && - println(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl")) - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - alg = RDPK3SpFSAL49(), tspan = (0.0, 10.0)) - l2_expected, linf_expected = analysis_callback(sol) + @trixi_testset "elixir_advection_restart.jl" begin + using OrdinaryDiffEq: RDPK3SpFSAL49 + Trixi.mpi_isroot() && println("═"^100) + Trixi.mpi_isroot() && + println(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl")) + trixi_include( + @__MODULE__, + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + alg = RDPK3SpFSAL49(), tspan = (0.0, 10.0) + ) + l2_expected, linf_expected = analysis_callback(sol) - Trixi.mpi_isroot() && println("═"^100) - Trixi.mpi_isroot() && - println(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl")) - # Errors are exactly the same as in the elixir_advection_extended.jl - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - alg = RDPK3SpFSAL49()) - l2_actual, linf_actual = analysis_callback(sol) + Trixi.mpi_isroot() && println("═"^100) + Trixi.mpi_isroot() && + println(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl")) + # Errors are exactly the same as in the elixir_advection_extended.jl + trixi_include( + @__MODULE__, + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + alg = RDPK3SpFSAL49() + ) + l2_actual, linf_actual = analysis_callback(sol) - Trixi.mpi_isroot() && @test l2_actual == l2_expected - Trixi.mpi_isroot() && @test linf_actual == linf_expected - end + Trixi.mpi_isroot() && @test l2_actual == l2_expected + Trixi.mpi_isroot() && @test linf_actual == linf_expected + end - @trixi_testset "elixir_advection_mortar.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_mortar.jl"), - # Expected errors are exactly the same as in the serial test! - l2=[0.0015188466707237375], - linf=[0.008446655719187679]) - end + @trixi_testset "elixir_advection_mortar.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_mortar.jl"), + # Expected errors are exactly the same as in the serial test! + l2 = [0.0015188466707237375], + linf = [0.008446655719187679] + ) + end - @trixi_testset "elixir_advection_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), - # Expected errors are exactly the same as in the serial test! - l2=[4.913300828257469e-5], - linf=[0.00045263895394385967], - coverage_override=(maxiters = 6,)) - end + @trixi_testset "elixir_advection_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), + # Expected errors are exactly the same as in the serial test! + l2 = [4.913300828257469e-5], + linf = [0.00045263895394385967], + coverage_override = (maxiters = 6,) + ) + end - @trixi_testset "elixir_advection_amr_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_nonperiodic.jl"), - # Expected errors are exactly the same as in the serial test! - l2=[3.2207388565869075e-5], - linf=[0.0007508059772436404], - coverage_override=(maxiters = 6,)) - end + @trixi_testset "elixir_advection_amr_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_nonperiodic.jl" + ), + # Expected errors are exactly the same as in the serial test! + l2 = [3.2207388565869075e-5], + linf = [0.0007508059772436404], + coverage_override = (maxiters = 6,) + ) + end - @trixi_testset "elixir_advection_restart_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_restart_amr.jl"), - l2=[8.018498574373939e-5], - linf=[0.0007307237754662355]) - end + @trixi_testset "elixir_advection_restart_amr.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_restart_amr.jl" + ), + l2 = [8.018498574373939e-5], + linf = [0.0007307237754662355] + ) + end - # Linear scalar advection with AMR - # These example files are only for testing purposes and have no practical use - @trixi_testset "elixir_advection_amr_refine_twice.jl" begin - # Here, we also test that SaveSolutionCallback prints multiple mesh files with AMR - # Start with a clean environment: remove Trixi.jl output directory if it exists - outdir = "out" - Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive = true) - Trixi.MPI.Barrier(Trixi.mpi_comm()) - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_refine_twice.jl"), - l2=[0.00020547512522578292], - linf=[0.007831753383083506], - coverage_override=(maxiters = 6,)) - meshfiles = filter(file -> endswith(file, ".h5") && startswith(file, "mesh"), - readdir(outdir)) - @test length(meshfiles) > 1 - end + # Linear scalar advection with AMR + # These example files are only for testing purposes and have no practical use + @trixi_testset "elixir_advection_amr_refine_twice.jl" begin + # Here, we also test that SaveSolutionCallback prints multiple mesh files with AMR + # Start with a clean environment: remove Trixi.jl output directory if it exists + outdir = "out" + Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive = true) + Trixi.MPI.Barrier(Trixi.mpi_comm()) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_refine_twice.jl" + ), + l2 = [0.00020547512522578292], + linf = [0.007831753383083506], + coverage_override = (maxiters = 6,) + ) + meshfiles = filter( + file -> endswith(file, ".h5") && startswith(file, "mesh"), + readdir(outdir) + ) + @test length(meshfiles) > 1 + end - @trixi_testset "elixir_advection_amr_coarsen_twice.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_coarsen_twice.jl"), - l2=[0.0014321062757891826], - linf=[0.0253454486893413], - coverage_override=(maxiters = 6,)) - end + @trixi_testset "elixir_advection_amr_coarsen_twice.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_coarsen_twice.jl" + ), + l2 = [0.0014321062757891826], + linf = [0.0253454486893413], + coverage_override = (maxiters = 6,) + ) + end - # Hyperbolic diffusion - if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` - @trixi_testset "elixir_hypdiff_lax_friedrichs.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_hypdiff_lax_friedrichs.jl"), - l2=[ - 0.00015687751816056159, - 0.001025986772217084, - 0.0010259867722169909, - ], - linf=[ - 0.0011986956416591976, - 0.006423873516411049, - 0.006423873516411049, - ]) + # Hyperbolic diffusion + if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` + @trixi_testset "elixir_hypdiff_lax_friedrichs.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_hypdiff_lax_friedrichs.jl" + ), + l2 = [ + 0.00015687751816056159, + 0.001025986772217084, + 0.0010259867722169909, + ], + linf = [ + 0.0011986956416591976, + 0.006423873516411049, + 0.006423873516411049, + ] + ) + end end - end - @trixi_testset "elixir_hypdiff_harmonic_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_hypdiff_harmonic_nonperiodic.jl"), - l2=[ - 8.61813235543625e-8, - 5.619399844542781e-7, - 5.6193998447443e-7, - ], - linf=[ - 1.124861862180196e-6, - 8.622436471039663e-6, - 8.622436470151484e-6, - ]) - end + @trixi_testset "elixir_hypdiff_harmonic_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_hypdiff_harmonic_nonperiodic.jl" + ), + l2 = [ + 8.61813235543625e-8, + 5.619399844542781e-7, + 5.6193998447443e-7, + ], + linf = [ + 1.124861862180196e-6, + 8.622436471039663e-6, + 8.622436470151484e-6, + ] + ) + end - @trixi_testset "elixir_hypdiff_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), - l2=[ - 8.523077653955306e-6, - 2.8779323653065056e-5, - 5.4549427691297846e-5, - ], - linf=[ - 5.5227409524905013e-5, - 0.0001454489597927185, - 0.00032396328684569653, - ]) - end + @trixi_testset "elixir_hypdiff_nonperiodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), + l2 = [ + 8.523077653955306e-6, + 2.8779323653065056e-5, + 5.4549427691297846e-5, + ], + linf = [ + 5.5227409524905013e-5, + 0.0001454489597927185, + 0.00032396328684569653, + ] + ) + end - if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` - @trixi_testset "elixir_hypdiff_godunov.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_godunov.jl"), - l2=[ - 5.868147556427088e-6, - 3.80517927324465e-5, - 3.805179273249344e-5, - ], - linf=[ - 3.701965498725812e-5, - 0.0002122422943138247, - 0.00021224229431116015, - ], - atol=2.0e-12) #= required for CI on macOS =# + if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` + @trixi_testset "elixir_hypdiff_godunov.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_godunov.jl"), + l2 = [ + 5.868147556427088e-6, + 3.80517927324465e-5, + 3.805179273249344e-5, + ], + linf = [ + 3.701965498725812e-5, + 0.0002122422943138247, + 0.00021224229431116015, + ], + atol = 2.0e-12 + ) #= required for CI on macOS =# + end end - end - # Compressible Euler - # Note: Some tests here have manually increased relative tolerances since reduction via MPI can - # slightly change the L2 error norms (different floating point truncation errors) - if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` - @trixi_testset "elixir_euler_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), - l2=[ - 9.321181253186009e-7, - 1.4181210743438511e-6, - 1.4181210743487851e-6, - 4.824553091276693e-6, - ], - linf=[ - 9.577246529612893e-6, - 1.1707525976012434e-5, - 1.1707525976456523e-5, - 4.8869615580926506e-5, - ], - rtol=2000 * sqrt(eps())) + # Compressible Euler + # Note: Some tests here have manually increased relative tolerances since reduction via MPI can + # slightly change the L2 error norms (different floating point truncation errors) + if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` + @trixi_testset "elixir_euler_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), + l2 = [ + 9.321181253186009e-7, + 1.4181210743438511e-6, + 1.4181210743487851e-6, + 4.824553091276693e-6, + ], + linf = [ + 9.577246529612893e-6, + 1.1707525976012434e-5, + 1.1707525976456523e-5, + 4.8869615580926506e-5, + ], + rtol = 2000 * sqrt(eps()) + ) + end end - end - # This example file is only for testing purposes and has no practical use - if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` - @trixi_testset "elixir_euler_source_terms_amr_refine_coarsen.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_amr_refine_coarsen.jl"), - l2=[ - 4.8226610349853444e-5, - 4.117706709270575e-5, - 4.1177067092959676e-5, - 0.00012205252427437389, - ], - linf=[ - 0.0003543874851490436, - 0.0002973166773747593, - 0.0002973166773760916, - 0.001154106793870291, - ], - # Let this test run until the end to cover the time-dependent lines - # of the indicator and the MPI-specific AMR code. - coverage_override=(maxiters = 10^5,)) + # This example file is only for testing purposes and has no practical use + if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` + @trixi_testset "elixir_euler_source_terms_amr_refine_coarsen.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_amr_refine_coarsen.jl" + ), + l2 = [ + 4.8226610349853444e-5, + 4.117706709270575e-5, + 4.1177067092959676e-5, + 0.00012205252427437389, + ], + linf = [ + 0.0003543874851490436, + 0.0002973166773747593, + 0.0002973166773760916, + 0.001154106793870291, + ], + # Let this test run until the end to cover the time-dependent lines + # of the indicator and the MPI-specific AMR code. + coverage_override = (maxiters = 10^5,) + ) + end end - end - if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` - @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 2.259440511766445e-6, - 2.318888155713922e-6, - 2.3188881557894307e-6, - 6.3327863238858925e-6, - ], - linf=[ - 1.498738264560373e-5, - 1.9182011928187137e-5, - 1.918201192685487e-5, - 6.0526717141407005e-5, - ], - rtol=0.001) + if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 2.259440511766445e-6, + 2.318888155713922e-6, + 2.3188881557894307e-6, + 6.3327863238858925e-6, + ], + linf = [ + 1.498738264560373e-5, + 1.9182011928187137e-5, + 1.918201192685487e-5, + 6.0526717141407005e-5, + ], + rtol = 0.001 + ) + end end - end - if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` - @trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.061751715597716854, - 0.05018223615408711, - 0.05018989446443463, - 0.225871559730513, - ], - linf=[ - 0.29347582879608825, - 0.31081249232844693, - 0.3107380389947736, - 1.0540358049885143, - ]) + if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.061751715597716854, + 0.05018223615408711, + 0.05018989446443463, + 0.225871559730513, + ], + linf = [ + 0.29347582879608825, + 0.31081249232844693, + 0.3107380389947736, + 1.0540358049885143, + ] + ) - @testset "error-based step size control" begin - Trixi.mpi_isroot() && println("-"^100) - Trixi.mpi_isroot() && - println("elixir_euler_ec.jl with error-based step size control") + @testset "error-based step size control" begin + Trixi.mpi_isroot() && println("-"^100) + Trixi.mpi_isroot() && + println("elixir_euler_ec.jl with error-based step size control") - sol = solve(ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, - ode_default_options()..., callback = callbacks) - summary_callback() - errors = analysis_callback(sol) - if Trixi.mpi_isroot() - @test errors.l2≈[ - 0.061653630426688116, - 0.05006930431098764, - 0.05007694316484242, - 0.22550689872331683, - ] rtol=1.0e-4 - @test errors.linf≈[ - 0.28516937484583693, - 0.2983633696512788, - 0.297812036335975, - 1.027368795517512, - ] rtol=1.0e-4 + sol = solve( + ode, RDPK3SpFSAL35(); abstol = 1.0e-4, reltol = 1.0e-4, + ode_default_options()..., callback = callbacks + ) + summary_callback() + errors = analysis_callback(sol) + if Trixi.mpi_isroot() + @test errors.l2 ≈ [ + 0.061653630426688116, + 0.05006930431098764, + 0.05007694316484242, + 0.22550689872331683, + ] rtol = 1.0e-4 + @test errors.linf ≈ [ + 0.28516937484583693, + 0.2983633696512788, + 0.297812036335975, + 1.027368795517512, + ] rtol = 1.0e-4 + end end end end - end - @trixi_testset "elixir_euler_vortex.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex.jl"), - l2=[ - 0.00013492249515826863, - 0.006615696236378061, - 0.006782108219800376, - 0.016393831451740604, - ], - linf=[ - 0.0020782600954247776, - 0.08150078921935999, - 0.08663621974991986, - 0.2829930622010579, - ], - rtol=0.001) - end + @trixi_testset "elixir_euler_vortex.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex.jl"), + l2 = [ + 0.00013492249515826863, + 0.006615696236378061, + 0.006782108219800376, + 0.016393831451740604, + ], + linf = [ + 0.0020782600954247776, + 0.08150078921935999, + 0.08663621974991986, + 0.2829930622010579, + ], + rtol = 0.001 + ) + end - @trixi_testset "elixir_euler_vortex_mortar.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex_mortar.jl"), - # Expected errors are exactly the same as in the serial test! - l2=[ - 0.0017208369388227673, - 0.09628684992237334, - 0.09620157717330868, - 0.1758809552387432, - ], - linf=[ - 0.021869936355319086, - 0.9956698009442038, - 1.0002507727219028, - 2.223249697515648, - ]) - end + @trixi_testset "elixir_euler_vortex_mortar.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex_mortar.jl"), + # Expected errors are exactly the same as in the serial test! + l2 = [ + 0.0017208369388227673, + 0.09628684992237334, + 0.09620157717330868, + 0.1758809552387432, + ], + linf = [ + 0.021869936355319086, + 0.9956698009442038, + 1.0002507727219028, + 2.223249697515648, + ] + ) + end - @trixi_testset "elixir_euler_vortex_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex_amr.jl"), - # Expected errors are exactly the same as in the serial test! - l2=[ - 5.051719943432265e-5, - 0.0022574259317084747, - 0.0021755998463189713, - 0.004346492398617521, - ], - linf=[ - 0.0012880114865917447, - 0.03857193149447702, - 0.031090457959835893, - 0.12125130332971423, - ], - coverage_override=(maxiters = 6,)) - end + @trixi_testset "elixir_euler_vortex_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex_amr.jl"), + # Expected errors are exactly the same as in the serial test! + l2 = [ + 5.051719943432265e-5, + 0.0022574259317084747, + 0.0021755998463189713, + 0.004346492398617521, + ], + linf = [ + 0.0012880114865917447, + 0.03857193149447702, + 0.031090457959835893, + 0.12125130332971423, + ], + coverage_override = (maxiters = 6,) + ) + end - if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` - @trixi_testset "elixir_euler_vortex_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_vortex_shockcapturing.jl"), - l2=[ - 0.0017158367642679273, - 0.09619888722871434, - 0.09616432767924141, - 0.17553381166255197, - ], - linf=[ - 0.021853862449723982, - 0.9878047229255944, - 0.9880191167111795, - 2.2154030488035588, - ], - rtol=0.001) + if !CI_ON_WINDOWS # see comment on `CI_ON_WINDOWS` in `test/test_mpi.jl` + @trixi_testset "elixir_euler_vortex_shockcapturing.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_vortex_shockcapturing.jl" + ), + l2 = [ + 0.0017158367642679273, + 0.09619888722871434, + 0.09616432767924141, + 0.17553381166255197, + ], + linf = [ + 0.021853862449723982, + 0.9878047229255944, + 0.9880191167111795, + 2.2154030488035588, + ], + rtol = 0.001 + ) + end end end -end end # TreeMesh MPI end # module diff --git a/test/test_p4est_2d.jl b/test/test_p4est_2d.jl index aad6337ffc..61d20f91c6 100644 --- a/test/test_p4est_2d.jl +++ b/test/test_p4est_2d.jl @@ -12,443 +12,579 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "P4estMesh2D" begin -#! format: noindent - -@trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + #! format: noindent + + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_nonconforming_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_nonconforming_flag.jl"), - l2=[3.198940059144588e-5], - linf=[0.00030636069494005547]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_nonconforming_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_nonconforming_flag.jl" + ), + l2 = [3.198940059144588e-5], + linf = [0.00030636069494005547] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_unstructured_flag.jl"), - l2=[0.0005379687442422346], - linf=[0.007438525029884735]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_unstructured_flag.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_unstructured_flag.jl"), + l2 = [0.0005379687442422346], + linf = [0.007438525029884735] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr_solution_independent.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_solution_independent.jl"), - # Expected errors are exactly the same as with StructuredMesh! - l2=[4.949660644033807e-5], - linf=[0.0004867846262313763], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_solution_independent.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_solution_independent.jl" + ), + # Expected errors are exactly the same as with StructuredMesh! + l2 = [4.949660644033807e-5], + linf = [0.0004867846262313763], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_unstructured_flag.jl"), - l2=[0.0012808538770535593], - linf=[0.01752690016659812], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_unstructured_flag.jl" + ), + l2 = [0.0012808538770535593], + linf = [0.01752690016659812], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_restart.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - l2=[4.507575525876275e-6], - linf=[6.21489667023134e-5], - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 25,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + l2 = [4.507575525876275e-6], + linf = [6.21489667023134e-5], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 25,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_restart_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart_amr.jl"), - l2=[2.869137983727866e-6], - linf=[3.8353423270964804e-5], - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 25,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart_amr.jl"), + l2 = [2.869137983727866e-6], + linf = [3.8353423270964804e-5], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 25,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), - l2=[ - 0.0034516244508588046, - 0.0023420334036925493, - 0.0024261923964557187, - 0.004731710454271893, - ], - linf=[ - 0.04155789011775046, - 0.024772109862748914, - 0.03759938693042297, - 0.08039824959535657, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" + ), + l2 = [ + 0.0034516244508588046, + 0.0023420334036925493, + 0.0024261923964557187, + 0.004731710454271893, + ], + linf = [ + 0.04155789011775046, + 0.024772109862748914, + 0.03759938693042297, + 0.08039824959535657, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), - l2=[ - 2.063350241405049e-15, - 1.8571016296925367e-14, - 3.1769447886391905e-14, - 1.4104095258528071e-14, - ], - linf=[1.9539925233402755e-14, 2e-12, 4.8e-12, 4e-12], - atol=2.0e-12,) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + l2 = [ + 2.063350241405049e-15, + 1.8571016296925367e-14, + 3.1769447886391905e-14, + 1.4104095258528071e-14, + ], + linf = [1.9539925233402755e-14, 2.0e-12, 4.8e-12, 4.0e-12], + atol = 2.0e-12, + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_ec.jl"), - l2=[ - 9.53984675e-02, - 1.05633455e-01, - 1.05636158e-01, - 3.50747237e-01, - ], - linf=[ - 2.94357464e-01, - 4.07893014e-01, - 3.97334516e-01, - 1.08142520e+00, - ], - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_ec.jl"), + l2 = [ + 9.53984675e-2, + 1.05633455e-1, + 1.05636158e-1, + 3.50747237e-1, + ], + linf = [ + 2.94357464e-1, + 4.07893014e-1, + 3.97334516e-1, + 1.0814252e+0, + ], + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing_ec.jl (flux_chandrashekar)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_ec.jl"), - l2=[ - 0.09527896382082567, - 0.10557894830184737, - 0.10559379376154387, - 0.3503791205165925, - ], - linf=[ - 0.2733486454092644, - 0.3877283966722886, - 0.38650482703821426, - 1.0053712251056308, - ], - tspan=(0.0, 1.0), - volume_flux=flux_chandrashekar) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing_ec.jl (flux_chandrashekar)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_ec.jl"), + l2 = [ + 0.09527896382082567, + 0.10557894830184737, + 0.10559379376154387, + 0.3503791205165925, + ], + linf = [ + 0.2733486454092644, + 0.3877283966722886, + 0.38650482703821426, + 1.0053712251056308, + ], + tspan = (0.0, 1.0), + volume_flux = flux_chandrashekar + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing_ec_float32.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_shockcapturing_ec_float32.jl"), - l2=[ - 0.09539953f0, - 0.10563527f0, - 0.105637245f0, - 0.3507514f0, - ], - linf=[ - 0.2942562f0, - 0.4079147f0, - 0.3972956f0, - 1.0810697f0, - ], - tspan=(0.0f0, 1.0f0), - rtol=10 * sqrt(eps(Float32)), # to make CI pass - RealT=Float32) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing_ec_float32.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_shockcapturing_ec_float32.jl" + ), + l2 = [ + 0.09539953f0, + 0.10563527f0, + 0.105637245f0, + 0.3507514f0, + ], + linf = [ + 0.2942562f0, + 0.4079147f0, + 0.3972956f0, + 1.0810697f0, + ], + tspan = (0.0f0, 1.0f0), + rtol = 10 * sqrt(eps(Float32)), # to make CI pass + RealT = Float32 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 3.76149952e-01, - 2.46970327e-01, - 2.46970327e-01, - 1.28889042e+00, - ], - linf=[ - 1.22139001e+00, - 1.17742626e+00, - 1.17742626e+00, - 6.20638482e+00, - ], - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 3.76149952e-1, + 2.46970327e-1, + 2.46970327e-1, + 1.28889042e+0, + ], + linf = [ + 1.22139001e+0, + 1.17742626e+0, + 1.17742626e+0, + 6.20638482e+0, + ], + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave_sc_subcell.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_sedov_blast_wave_sc_subcell.jl"), - l2=[ - 0.4573787784168518, - 0.28520972760728397, - 0.28527281808006966, - 1.2881460122982442, - ], - linf=[ - 1.644411040701827, - 1.6743368119653912, - 1.6760847977977988, - 6.268843623142863, - ], - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_euler_sedov_blast_wave_sc_subcell.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_sedov_blast_wave_sc_subcell.jl" + ), + l2 = [ + 0.4573787784168518, + 0.28520972760728397, + 0.28527281808006966, + 1.2881460122982442, + ], + linf = [ + 1.644411040701827, + 1.6743368119653912, + 1.6760847977977988, + 6.268843623142863, + ], + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end + end + + @trixi_testset "elixir_euler_sedov.jl with HLLC Flux" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 0.4229948321239887, + 0.2559038337457483, + 0.2559038337457484, + 1.2990046683564136, + ], + linf = [ + 1.4989357969730492, + 1.325456585141623, + 1.3254565851416251, + 6.331283015053501, + ], + surface_flux = flux_hllc, + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl with HLLC Flux" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 0.4229948321239887, - 0.2559038337457483, - 0.2559038337457484, - 1.2990046683564136, - ], - linf=[ - 1.4989357969730492, - 1.325456585141623, - 1.3254565851416251, - 6.331283015053501, - ], - surface_flux=flux_hllc, - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov.jl (HLLE)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 0.40853279043747015, + 0.25356771650524296, + 0.2535677165052422, + 1.2984601729572691, + ], + linf = [ + 1.3840909333784284, + 1.3077772519086124, + 1.3077772519086157, + 6.298798630968632, + ], + surface_flux = flux_hlle, + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl (HLLE)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 0.40853279043747015, - 0.25356771650524296, - 0.2535677165052422, - 1.2984601729572691, - ], - linf=[ - 1.3840909333784284, - 1.3077772519086124, - 1.3077772519086157, - 6.298798630968632, - ], - surface_flux=flux_hlle, - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_blast_wave_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr.jl"), + l2 = [ + 0.6321850210104147, + 0.38691446170269167, + 0.3868695626809587, + 1.0657553825683956, + ], + linf = [ + 2.7602280007469666, + 2.3265993814913672, + 2.3258078438689673, + 2.1577683028925416, + ], + tspan = (0.0, 0.3), + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end + + @trixi_testset "elixir_euler_wall_bc_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_wall_bc_amr.jl"), + l2 = [ + 0.02026685991647352, + 0.017467584076280237, + 0.011378371604813321, + 0.05138942558296091, + ], + linf = [ + 0.35924402060711524, + 0.32068389566068806, + 0.2361141752119986, + 0.9289840057748628, + ], + tspan = (0.0, 0.15) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_blast_wave_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr.jl"), - l2=[ - 0.6321850210104147, - 0.38691446170269167, - 0.3868695626809587, - 1.0657553825683956, - ], - linf=[ - 2.7602280007469666, - 2.3265993814913672, - 2.3258078438689673, - 2.1577683028925416, - ], - tspan=(0.0, 0.3), - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_forward_step_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_forward_step_amr.jl"), + l2 = [ + 0.004191480950848891, + 0.003781298410569231, + 0.0013470418422981045, + 0.03262817609394949, + ], + linf = [ + 2.0581500751947113, + 2.2051301367971288, + 3.8502467979250254, + 17.750333649853616, + ], + tspan = (0.0, 0.0001), + rtol = 1.0e-7, + skip_coverage = true + ) + if @isdefined sol # Skipped in coverage run + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end end -end -@trixi_testset "elixir_euler_wall_bc_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_wall_bc_amr.jl"), - l2=[ - 0.02026685991647352, - 0.017467584076280237, - 0.011378371604813321, - 0.05138942558296091, - ], - linf=[ - 0.35924402060711524, - 0.32068389566068806, - 0.2361141752119986, - 0.9289840057748628, - ], - tspan=(0.0, 0.15)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_double_mach_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_double_mach_amr.jl"), + l2 = [ + 0.051359355290192046, + 0.4266034859911273, + 0.2438304855475594, + 4.11487176105527, + ], + linf = [ + 6.902000373057003, + 53.95714139820832, + 24.241610279839758, + 561.0630401858057, + ], + tspan = (0.0, 0.0001), + skip_coverage = true + ) + if @isdefined sol # Skipped in coverage run + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end end -end -@trixi_testset "elixir_euler_forward_step_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_forward_step_amr.jl"), - l2=[ - 0.004191480950848891, - 0.003781298410569231, - 0.0013470418422981045, - 0.03262817609394949, - ], - linf=[ - 2.0581500751947113, - 2.2051301367971288, - 3.8502467979250254, - 17.750333649853616, - ], - tspan=(0.0, 0.0001), - rtol=1.0e-7, - skip_coverage=true) - if @isdefined sol # Skipped in coverage run + @trixi_testset "elixir_euler_supersonic_cylinder.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_supersonic_cylinder.jl"), + l2 = [ + 0.02676082999794676, + 0.05110830068968181, + 0.03205164257040607, + 0.1965981012724311, + ], + linf = [ + 3.6830683476364476, + 4.284442685012427, + 6.857777546171545, + 31.749285097390576, + ], + tspan = (0.0, 0.001), + skip_coverage = true + ) + if @isdefined sol # Skipped in coverage run + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end + end + + @trixi_testset "elixir_euler_supersonic_cylinder_sc_subcell.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_supersonic_cylinder_sc_subcell.jl" + ), + l2 = [ + 0.11085870166618325, + 0.23309905989870722, + 0.13505351590735631, + 0.7932047512585592, + ], + linf = [ + 2.9808773737943564, + 4.209364526217892, + 6.265341002817672, + 24.077904874883338, + ], + tspan = (0.0, 0.02) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let t = sol.t[end] u_ode = sol.u[end] du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 end end -end -@trixi_testset "elixir_euler_double_mach_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_double_mach_amr.jl"), - l2=[ - 0.051359355290192046, - 0.4266034859911273, - 0.2438304855475594, - 4.11487176105527, - ], - linf=[ - 6.902000373057003, - 53.95714139820832, - 24.241610279839758, - 561.0630401858057, - ], - tspan=(0.0, 0.0001), - skip_coverage=true) - if @isdefined sol # Skipped in coverage run + @trixi_testset "elixir_euler_NACA6412airfoil_mach2.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_NACA6412airfoil_mach2.jl"), + l2 = [ + 0.19107654776276498, 0.3545913719444839, + 0.18492730895077583, 0.817927213517244, + ], + linf = [ + 2.5397624311491946, 2.7075156425517917, 2.200980534211764, + 9.031153939238115, + ], + tspan = (0.0, 0.1) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -458,25 +594,24 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end -end -@trixi_testset "elixir_euler_supersonic_cylinder.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_supersonic_cylinder.jl"), - l2=[ - 0.02676082999794676, - 0.05110830068968181, - 0.03205164257040607, - 0.1965981012724311, - ], - linf=[ - 3.6830683476364476, - 4.284442685012427, - 6.857777546171545, - 31.749285097390576, - ], - tspan=(0.0, 0.001), - skip_coverage=true) - if @isdefined sol # Skipped in coverage run + @trixi_testset "elixir_eulergravity_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), + l2 = [ + 0.00024871265138964204, + 0.0003370077102132591, + 0.0003370077102131964, + 0.0007231525513793697, + ], + linf = [ + 0.0015813032944647087, + 0.0020494288423820173, + 0.0020494288423824614, + 0.004793821195083758, + ], + tspan = (0.0, 0.1) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -486,321 +621,288 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end -end -@trixi_testset "elixir_euler_supersonic_cylinder_sc_subcell.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_supersonic_cylinder_sc_subcell.jl"), - l2=[ - 0.11085870166618325, - 0.23309905989870722, - 0.13505351590735631, - 0.7932047512585592, - ], - linf=[ - 2.9808773737943564, - 4.209364526217892, - 6.265341002817672, - 24.077904874883338, - ], - tspan=(0.0, 0.02)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_shallowwater_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 9.168126407325352e-5, + 0.0009795410115453788, + 0.002546408320320785, + 3.941189812642317e-6, + ], + linf = [ + 0.0009903782521019089, + 0.0059752684687262025, + 0.010941106525454103, + 1.2129488214718265e-5, + ], + tspan = (0.0, 0.1) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_NACA6412airfoil_mach2.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_NACA6412airfoil_mach2.jl"), - l2=[ - 0.19107654776276498, 0.3545913719444839, - 0.18492730895077583, 0.817927213517244, - ], - linf=[ - 2.5397624311491946, 2.7075156425517917, 2.200980534211764, - 9.031153939238115, - ], - tspan=(0.0, 0.1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 1.0513414461545583e-5, 1.0517900957166411e-6, + 1.0517900957304043e-6, 1.511816606372376e-6, + 1.0443997728645063e-6, 7.879639064990798e-7, + 7.879639065049896e-7, 1.0628631669056271e-6, + 4.3382328912336153e-7, + ], + linf = [ + 4.255466285174592e-5, 1.0029706745823264e-5, + 1.0029706747467781e-5, 1.2122265939010224e-5, + 5.4791097160444835e-6, 5.18922042269665e-6, + 5.189220422141538e-6, 9.552667261422676e-6, + 1.4237578427628152e-6, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulergravity_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), - l2=[ - 0.00024871265138964204, - 0.0003370077102132591, - 0.0003370077102131964, - 0.0007231525513793697, - ], - linf=[ - 0.0015813032944647087, - 0.0020494288423820173, - 0.0020494288423824614, - 0.004793821195083758, - ], - tspan=(0.0, 0.1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_rotor.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_rotor.jl"), + l2 = [ + 0.4552094211937344, 0.8918052934760807, 0.8324715234680768, + 0.0, + 0.9801268321975978, 0.10475722739111007, + 0.15551326369033164, + 0.0, + 2.0602990858239632e-5, + ], + linf = [ + 10.19421969147307, 18.254409357804683, 10.031954811332596, + 0.0, + 19.646870938371492, 1.3938679692894465, 1.8725058401937984, + 0.0, + 0.0016201762010257296, + ], + tspan = (0.0, 0.02) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 9.168126407325352e-5, - 0.0009795410115453788, - 0.002546408320320785, - 3.941189812642317e-6, - ], - linf=[ - 0.0009903782521019089, - 0.0059752684687262025, - 0.010941106525454103, - 1.2129488214718265e-5, - ], - tspan=(0.0, 0.1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_linearizedeuler_gaussian_source.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_linearizedeuler_gaussian_source.jl" + ), + l2 = [ + 0.006047938590548741, + 0.0040953286019907035, + 0.004222698522497298, + 0.006269492499336128, + ], + linf = [ + 0.06386175207349379, + 0.0378926444850457, + 0.041759728067967065, + 0.06430136016259067, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[1.0513414461545583e-5, 1.0517900957166411e-6, - 1.0517900957304043e-6, 1.511816606372376e-6, - 1.0443997728645063e-6, 7.879639064990798e-7, - 7.879639065049896e-7, 1.0628631669056271e-6, - 4.3382328912336153e-7], - linf=[4.255466285174592e-5, 1.0029706745823264e-5, - 1.0029706747467781e-5, 1.2122265939010224e-5, - 5.4791097160444835e-6, 5.18922042269665e-6, - 5.189220422141538e-6, 9.552667261422676e-6, - 1.4237578427628152e-6]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end + @trixi_testset "elixir_euler_subsonic_cylinder.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_subsonic_cylinder.jl"), + l2 = [ + 0.00011914390523852561, + 0.00010776028621724485, + 6.139954358305467e-5, + 0.0003067693731825959, + ], + linf = [ + 0.1653075586200805, + 0.1868437275544909, + 0.09772818519679008, + 0.4311796171737692, + ], tspan = (0.0, 0.001) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end -@trixi_testset "elixir_mhd_rotor.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_rotor.jl"), - l2=[0.4552094211937344, 0.8918052934760807, 0.8324715234680768, - 0.0, - 0.9801268321975978, 0.10475722739111007, - 0.15551326369033164, - 0.0, - 2.0602990858239632e-5], - linf=[10.19421969147307, 18.254409357804683, 10.031954811332596, - 0.0, - 19.646870938371492, 1.3938679692894465, 1.8725058401937984, - 0.0, - 0.0016201762010257296], - tspan=(0.0, 0.02)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) # Just a placeholder in this case + + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + drag = Trixi.analyze( + drag_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache, semi + ) + lift = Trixi.analyze( + lift_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache, semi + ) + + @test isapprox(lift, -6.501138753497174e-15, atol = 1.0e-13) + @test isapprox(drag, 2.588589856781827, atol = 1.0e-13) + end + + # Forces computation test in an AMR code + @trixi_testset "elixir_euler_NACA0012airfoil_mach085.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_NACA0012airfoil_mach085.jl" + ), + l2 = [ + 5.56114097044427e-7, 6.62284247153255e-6, + 1.0823259724601275e-5, 0.000659804574787503, + ], + linf = [ + 0.002157589754528455, 0.039163189253511164, + 0.038386804399707625, 2.6685831417913914, + ], + amr_interval = 1, + base_level = 0, med_level = 1, max_level = 1, + tspan = (0.0, 0.0001), + adapt_initial_condition = false, + adapt_initial_condition_only_refine = false, + # With the default `maxiters = 1` in coverage tests, + # the values for `drag` and `lift` below would differ. + coverage_override = (maxiters = 100_000,) + ) -@trixi_testset "elixir_linearizedeuler_gaussian_source.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_linearizedeuler_gaussian_source.jl"), - l2=[ - 0.006047938590548741, - 0.0040953286019907035, - 0.004222698522497298, - 0.006269492499336128, - ], - linf=[ - 0.06386175207349379, - 0.0378926444850457, - 0.041759728067967065, - 0.06430136016259067, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end -@trixi_testset "elixir_euler_subsonic_cylinder.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_subsonic_cylinder.jl"), - l2=[ - 0.00011914390523852561, - 0.00010776028621724485, - 6.139954358305467e-5, - 0.0003067693731825959, - ], - linf=[ - 0.1653075586200805, - 0.1868437275544909, - 0.09772818519679008, - 0.4311796171737692, - ], tspan=(0.0, 0.001)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end - - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) # Just a placeholder in this case - - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - drag = Trixi.analyze(drag_coefficient, du, u, tspan[2], mesh, equations, solver, - semi.cache, semi) - lift = Trixi.analyze(lift_coefficient, du, u, tspan[2], mesh, equations, solver, - semi.cache, semi) - - @test isapprox(lift, -6.501138753497174e-15, atol = 1e-13) - @test isapprox(drag, 2.588589856781827, atol = 1e-13) -end + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) # Just a placeholder in this case + + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + drag = Trixi.analyze( + drag_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache, semi + ) + lift = Trixi.analyze( + lift_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache, semi + ) + + @test isapprox(lift, 0.029094009322876882, atol = 1.0e-13) + @test isapprox(drag, 0.13579200776643238, atol = 1.0e-13) + end + + @trixi_testset "elixir_euler_blast_wave_pure_fv.jl" begin + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "tree_2d_dgsem"), + "elixir_euler_blast_wave_pure_fv.jl" + ), + l2 = [ + 0.39957047631960346, + 0.21006912294983154, + 0.21006903549932, + 0.6280328163981136, + ], + linf = [ + 2.20417889887697, + 1.5487238480003327, + 1.5486788679247812, + 2.4656795949035857, + ], + tspan = (0.0, 0.5), + mesh = P4estMesh( + (64, 64), polydeg = 3, + coordinates_min = (-2.0, -2.0), + coordinates_max = (2.0, 2.0) + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end -# Forces computation test in an AMR code -@trixi_testset "elixir_euler_NACA0012airfoil_mach085.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_NACA0012airfoil_mach085.jl"), - l2=[ - 5.56114097044427e-7, 6.62284247153255e-6, - 1.0823259724601275e-5, 0.000659804574787503, - ], - linf=[ - 0.002157589754528455, 0.039163189253511164, - 0.038386804399707625, 2.6685831417913914, - ], - amr_interval=1, - base_level=0, med_level=1, max_level=1, - tspan=(0.0, 0.0001), - adapt_initial_condition=false, - adapt_initial_condition_only_refine=false, - # With the default `maxiters = 1` in coverage tests, - # the values for `drag` and `lift` below would differ. - coverage_override=(maxiters = 100_000,)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end - - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) # Just a placeholder in this case - - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - drag = Trixi.analyze(drag_coefficient, du, u, tspan[2], mesh, equations, solver, - semi.cache, semi) - lift = Trixi.analyze(lift_coefficient, du, u, tspan[2], mesh, equations, solver, - semi.cache, semi) - - @test isapprox(lift, 0.029094009322876882, atol = 1e-13) - @test isapprox(drag, 0.13579200776643238, atol = 1e-13) -end + @trixi_testset "elixir_euler_weak_blast_wave_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weak_blast_wave_amr.jl"), + l2 = [ + 0.11134260363848127, + 0.11752357091804219, + 0.11829112104640764, + 0.7557891142955036, + ], + linf = [ + 0.5728647031475109, + 0.8353132977670252, + 0.8266797080712205, + 3.9792506230548317, + ], + tspan = (0.0, 0.1), + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + # Check for conservation + state_integrals = Trixi.integrate(sol.u[2], semi) + initial_state_integrals = analysis_callback.affect!.initial_state_integrals -@trixi_testset "elixir_euler_blast_wave_pure_fv.jl" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "tree_2d_dgsem"), - "elixir_euler_blast_wave_pure_fv.jl"), - l2=[ - 0.39957047631960346, - 0.21006912294983154, - 0.21006903549932, - 0.6280328163981136, - ], - linf=[ - 2.20417889887697, - 1.5487238480003327, - 1.5486788679247812, - 2.4656795949035857, - ], - tspan=(0.0, 0.5), - mesh=P4estMesh((64, 64), polydeg = 3, - coordinates_min = (-2.0, -2.0), - coordinates_max = (2.0, 2.0))) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1.0e-13) + @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1.0e-13) + @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1.0e-13) + @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1.0e-13) end end -@trixi_testset "elixir_euler_weak_blast_wave_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weak_blast_wave_amr.jl"), - l2=[ - 0.11134260363848127, - 0.11752357091804219, - 0.11829112104640764, - 0.7557891142955036, - ], - linf=[ - 0.5728647031475109, - 0.8353132977670252, - 0.8266797080712205, - 3.9792506230548317, - ], - tspan=(0.0, 0.1), - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end - # Check for conservation - state_integrals = Trixi.integrate(sol.u[2], semi) - initial_state_integrals = analysis_callback.affect!.initial_state_integrals - - @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1e-13) - @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1e-13) - @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1e-13) - @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1e-13) -end -end - # Clean up afterwards: delete Trixi.jl output directory @test_nowarn rm(outdir, recursive = true) diff --git a/test/test_p4est_3d.jl b/test/test_p4est_3d.jl index 3432bd69b2..672426c2b9 100644 --- a/test/test_p4est_3d.jl +++ b/test/test_p4est_3d.jl @@ -12,646 +12,736 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "P4estMesh3D" begin -#! format: noindent - -@trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[0.00016263963870641478], - linf=[0.0014537194925779984]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + #! format: noindent + + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [0.00016263963870641478], + linf = [0.0014537194925779984] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_unstructured_curved.jl"), - l2=[0.0004750004258546538], - linf=[0.026527551737137167]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_unstructured_curved.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_unstructured_curved.jl" + ), + l2 = [0.0004750004258546538], + linf = [0.026527551737137167] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_nonconforming.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_nonconforming.jl"), - l2=[0.00253595715323843], - linf=[0.016486952252155795]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_nonconforming.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_nonconforming.jl"), + l2 = [0.00253595715323843], + linf = [0.016486952252155795] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[9.773852895157622e-6], - linf=[0.0005853874124926162], - coverage_override=(maxiters = 6, initial_refinement_level = 1, - base_level = 1, med_level = 2, max_level = 3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [9.773852895157622e-6], + linf = [0.0005853874124926162], + coverage_override = ( + maxiters = 6, initial_refinement_level = 1, + base_level = 1, med_level = 2, max_level = 3, + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_unstructured_curved.jl"), - l2=[1.6163120948209677e-5], - linf=[0.0010572201890564834], - tspan=(0.0, 1.0), - coverage_override=(maxiters = 6, initial_refinement_level = 0, - base_level = 0, med_level = 1, max_level = 2)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_unstructured_curved.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_unstructured_curved.jl" + ), + l2 = [1.6163120948209677e-5], + linf = [0.0010572201890564834], + tspan = (0.0, 1.0), + coverage_override = ( + maxiters = 6, initial_refinement_level = 0, + base_level = 0, med_level = 1, max_level = 2, + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_cubed_sphere.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_cubed_sphere.jl"), - l2=[0.002006918015656413], - linf=[0.027655117058380085]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_cubed_sphere.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_cubed_sphere.jl"), + l2 = [0.002006918015656413], + linf = [0.027655117058380085] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_restart.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - l2=[0.002590388934758452], - linf=[0.01840757696885409], - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + l2 = [0.002590388934758452], + linf = [0.01840757696885409], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonconforming_unstructured_curved.jl"), - l2=[ - 4.070355207909268e-5, - 4.4993257426833716e-5, - 5.10588457841744e-5, - 5.102840924036687e-5, - 0.00019986264001630542, - ], - linf=[ - 0.0016987332417202072, - 0.003622956808262634, - 0.002029576258317789, - 0.0024206977281964193, - 0.008526972236273522, - ], - tspan=(0.0, 0.01)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" + ), + l2 = [ + 4.070355207909268e-5, + 4.4993257426833716e-5, + 5.10588457841744e-5, + 5.102840924036687e-5, + 0.00019986264001630542, + ], + linf = [ + 0.0016987332417202072, + 0.003622956808262634, + 0.002029576258317789, + 0.0024206977281964193, + 0.008526972236273522, + ], + tspan = (0.0, 0.01) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 0.0015106060984283647, - 0.0014733349038567685, - 0.00147333490385685, - 0.001473334903856929, - 0.0028149479453087093, - ], - linf=[ - 0.008070806335238156, - 0.009007245083113125, - 0.009007245083121784, - 0.009007245083102688, - 0.01562861968368434, - ], - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 0.0015106060984283647, + 0.0014733349038567685, + 0.00147333490385685, + 0.001473334903856929, + 0.0028149479453087093, + ], + linf = [ + 0.008070806335238156, + 0.009007245083113125, + 0.009007245083121784, + 0.009007245083102688, + 0.01562861968368434, + ], + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), - l2=[ - 5.162664597942288e-15, - 1.941857343642486e-14, - 2.0232366394187278e-14, - 2.3381518645408552e-14, - 7.083114561232324e-14, - ], - linf=[ - 7.269740365245525e-13, - 3.289868377720495e-12, - 4.440087186807773e-12, - 3.8686831516088205e-12, - 9.412914891981927e-12, - ], - tspan=(0.0, 0.03)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + l2 = [ + 5.162664597942288e-15, + 1.941857343642486e-14, + 2.0232366394187278e-14, + 2.3381518645408552e-14, + 7.083114561232324e-14, + ], + linf = [ + 7.269740365245525e-13, + 3.289868377720495e-12, + 4.440087186807773e-12, + 3.8686831516088205e-12, + 9.412914891981927e-12, + ], + tspan = (0.0, 0.03) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream_extruded.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream_extruded.jl"), - l2=[ - 8.444868392439035e-16, - 4.889826056731442e-15, - 2.2921260987087585e-15, - 4.268460455702414e-15, - 1.1356712092620279e-14, - ], - linf=[ - 7.749356711883593e-14, - 2.8792246364872653e-13, - 1.1121659149182506e-13, - 3.3228975127030935e-13, - 9.592326932761353e-13, - ], - tspan=(0.0, 0.1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream_extruded.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream_extruded.jl"), + l2 = [ + 8.444868392439035e-16, + 4.889826056731442e-15, + 2.2921260987087585e-15, + 4.268460455702414e-15, + 1.1356712092620279e-14, + ], + linf = [ + 7.749356711883593e-14, + 2.8792246364872653e-13, + 1.1121659149182506e-13, + 3.3228975127030935e-13, + 9.592326932761353e-13, + ], + tspan = (0.0, 0.1) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream_boundaries.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_free_stream_boundaries.jl"), - l2=[ - 6.530157034651212e-16, 1.6057829680004379e-15, - 3.31107455378537e-15, 3.908829498281281e-15, - 5.048390610424672e-15, - ], - linf=[ - 4.884981308350689e-15, 1.1921019726912618e-14, - 1.5432100042289676e-14, 2.298161660974074e-14, - 6.039613253960852e-14, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream_boundaries.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_free_stream_boundaries.jl" + ), + l2 = [ + 6.530157034651212e-16, 1.6057829680004379e-15, + 3.31107455378537e-15, 3.908829498281281e-15, + 5.048390610424672e-15, + ], + linf = [ + 4.884981308350689e-15, 1.1921019726912618e-14, + 1.5432100042289676e-14, 2.298161660974074e-14, + 6.039613253960852e-14, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream_boundaries_float32.jl" begin - # Expected errors are taken from elixir_euler_free_stream_boundaries.jl - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_free_stream_boundaries_float32.jl"), - l2=[ - Float32(6.530157034651212e-16), - Float32(1.6057829680004379e-15), - Float32(3.31107455378537e-15), - Float32(3.908829498281281e-15), - Float32(5.048390610424672e-15), - ], - linf=[ - Float32(4.884981308350689e-15), - Float32(1.1921019726912618e-14), - Float32(1.5432100042289676e-14), - Float32(2.298161660974074e-14), - Float32(6.039613253960852e-14), - ], - RealT=Float32) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream_boundaries_float32.jl" begin + # Expected errors are taken from elixir_euler_free_stream_boundaries.jl + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_free_stream_boundaries_float32.jl" + ), + l2 = [ + Float32(6.530157034651212e-16), + Float32(1.6057829680004379e-15), + Float32(3.31107455378537e-15), + Float32(3.908829498281281e-15), + Float32(5.048390610424672e-15), + ], + linf = [ + Float32(4.884981308350689e-15), + Float32(1.1921019726912618e-14), + Float32(1.5432100042289676e-14), + Float32(2.298161660974074e-14), + Float32(6.039613253960852e-14), + ], + RealT = Float32 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream_extruded.jl with HLLC FLux" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream_extruded.jl"), - l2=[ - 8.444868392439035e-16, - 4.889826056731442e-15, - 2.2921260987087585e-15, - 4.268460455702414e-15, - 1.1356712092620279e-14, - ], - linf=[ - 7.749356711883593e-14, - 4.513472928735496e-13, - 2.9790059308254513e-13, - 1.057154364048074e-12, - 1.6271428648906294e-12, - ], - tspan=(0.0, 0.1), - surface_flux=flux_hllc) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream_extruded.jl with HLLC FLux" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream_extruded.jl"), + l2 = [ + 8.444868392439035e-16, + 4.889826056731442e-15, + 2.2921260987087585e-15, + 4.268460455702414e-15, + 1.1356712092620279e-14, + ], + linf = [ + 7.749356711883593e-14, + 4.513472928735496e-13, + 2.9790059308254513e-13, + 1.057154364048074e-12, + 1.6271428648906294e-12, + ], + tspan = (0.0, 0.1), + surface_flux = flux_hllc + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.010380390326164493, - 0.006192950051354618, - 0.005970674274073704, - 0.005965831290564327, - 0.02628875593094754, - ], - linf=[ - 0.3326911600075694, - 0.2824952141320467, - 0.41401037398065543, - 0.45574161423218573, - 0.8099577682187109, - ], - tspan=(0.0, 0.2), - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.010380390326164493, + 0.006192950051354618, + 0.005970674274073704, + 0.005965831290564327, + 0.02628875593094754, + ], + linf = [ + 0.3326911600075694, + 0.2824952141320467, + 0.41401037398065543, + 0.45574161423218573, + 0.8099577682187109, + ], + tspan = (0.0, 0.2), + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl (flux_chandrashekar)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.010368548525287055, - 0.006216054794583285, - 0.006020401857347216, - 0.006019175682769779, - 0.026228080232814154, - ], - linf=[ - 0.3169376449662026, - 0.28950510175646726, - 0.4402523227566396, - 0.4869168122387365, - 0.7999141641954051, - ], - tspan=(0.0, 0.2), - volume_flux=flux_chandrashekar, - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl (flux_chandrashekar)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.010368548525287055, + 0.006216054794583285, + 0.006020401857347216, + 0.006019175682769779, + 0.026228080232814154, + ], + linf = [ + 0.3169376449662026, + 0.28950510175646726, + 0.4402523227566396, + 0.4869168122387365, + 0.7999141641954051, + ], + tspan = (0.0, 0.2), + volume_flux = flux_chandrashekar, + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 7.82070951e-02, - 4.33260474e-02, - 4.33260474e-02, - 4.33260474e-02, - 3.75260911e-01, - ], - linf=[ - 7.45329845e-01, - 3.21754792e-01, - 3.21754792e-01, - 3.21754792e-01, - 4.76151527e+00, - ], - tspan=(0.0, 0.3), - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 7.82070951e-2, + 4.33260474e-2, + 4.33260474e-2, + 4.33260474e-2, + 3.75260911e-1, + ], + linf = [ + 7.45329845e-1, + 3.21754792e-1, + 3.21754792e-1, + 3.21754792e-1, + 4.76151527e+0, + ], + tspan = (0.0, 0.3), + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl (HLLE)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 0.09946224487902565, - 0.04863386374672001, - 0.048633863746720116, - 0.04863386374672032, - 0.3751015774232693, - ], - linf=[ - 0.789241521871487, - 0.42046970270100276, - 0.42046970270100276, - 0.4204697027010028, - 4.730877375538398, - ], - tspan=(0.0, 0.3), - surface_flux=flux_hlle) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov.jl (HLLE)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 0.09946224487902565, + 0.04863386374672001, + 0.048633863746720116, + 0.04863386374672032, + 0.3751015774232693, + ], + linf = [ + 0.789241521871487, + 0.42046970270100276, + 0.42046970270100276, + 0.4204697027010028, + 4.730877375538398, + ], + tspan = (0.0, 0.3), + surface_flux = flux_hlle + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonconforming_earth.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonconforming_earth.jl"), - l2=[ - 6.040180337738628e-6, - 5.4254175153621895e-6, - 5.677698851333843e-6, - 5.8017136892469794e-6, - 1.3637854615117974e-5, - ], - linf=[ - 0.00013996924184311865, - 0.00013681539559939893, - 0.00013681539539733834, - 0.00013681539541021692, - 0.00016833038543762058, - ], - # Decrease tolerance of adaptive time stepping to get similar results across different systems - abstol=1.0e-11, reltol=1.0e-11, - coverage_override=(trees_per_cube_face = (1, 1), polydeg = 3)) # Prevent long compile time in CI - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonconforming_earth.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_earth.jl" + ), + l2 = [ + 6.040180337738628e-6, + 5.4254175153621895e-6, + 5.677698851333843e-6, + 5.8017136892469794e-6, + 1.3637854615117974e-5, + ], + linf = [ + 0.00013996924184311865, + 0.00013681539559939893, + 0.00013681539539733834, + 0.00013681539541021692, + 0.00016833038543762058, + ], + # Decrease tolerance of adaptive time stepping to get similar results across different systems + abstol = 1.0e-11, reltol = 1.0e-11, + coverage_override = (trees_per_cube_face = (1, 1), polydeg = 3) + ) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_circular_wind_nonconforming.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_circular_wind_nonconforming.jl"), - l2=[ - 1.5737711609657832e-7, - 3.8630261900166194e-5, - 3.8672287531936816e-5, - 3.6865116098660796e-5, - 0.05508620970403884, - ], - linf=[ - 2.268845333053271e-6, - 0.000531462302113539, - 0.0005314624461298934, - 0.0005129931254772464, - 0.7942778058932163, - ], - tspan=(0.0, 2e2), - coverage_override=(trees_per_cube_face = (1, 1), polydeg = 3)) # Prevent long compile time in CI - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_circular_wind_nonconforming.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_circular_wind_nonconforming.jl" + ), + l2 = [ + 1.5737711609657832e-7, + 3.8630261900166194e-5, + 3.8672287531936816e-5, + 3.6865116098660796e-5, + 0.05508620970403884, + ], + linf = [ + 2.268845333053271e-6, + 0.000531462302113539, + 0.0005314624461298934, + 0.0005129931254772464, + 0.7942778058932163, + ], + tspan = (0.0, 2.0e2), + coverage_override = (trees_per_cube_face = (1, 1), polydeg = 3) + ) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_baroclinic_instability.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_baroclinic_instability.jl"), - l2=[ - 6.725093801700048e-7, - 0.00021710076010951073, - 0.0004386796338203878, - 0.00020836270267103122, - 0.07601887903440395, - ], - linf=[ - 1.9107530539574924e-5, - 0.02980358831035801, - 0.048476331898047564, - 0.02200137344113612, - 4.848310144356219, - ], - tspan=(0.0, 1e2), - # Decrease tolerance of adaptive time stepping to get similar results across different systems - abstol=1.0e-9, reltol=1.0e-9, - coverage_override=(trees_per_cube_face = (1, 1), polydeg = 3)) # Prevent long compile time in CI - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_baroclinic_instability.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_baroclinic_instability.jl" + ), + l2 = [ + 6.725093801700048e-7, + 0.00021710076010951073, + 0.0004386796338203878, + 0.00020836270267103122, + 0.07601887903440395, + ], + linf = [ + 1.9107530539574924e-5, + 0.02980358831035801, + 0.048476331898047564, + 0.02200137344113612, + 4.848310144356219, + ], + tspan = (0.0, 1.0e2), + # Decrease tolerance of adaptive time stepping to get similar results across different systems + abstol = 1.0e-9, reltol = 1.0e-9, + coverage_override = (trees_per_cube_face = (1, 1), polydeg = 3) + ) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonperiodic_hohqmesh.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic_hohqmesh.jl"), - l2=[ - 0.0042023406458005464, - 0.004122532789279737, - 0.0042448149597303616, - 0.0036361316700401765, - 0.007389845952982495, - ], - linf=[ - 0.04530610539892499, - 0.02765695110527666, - 0.05670295599308606, - 0.048396544302230504, - 0.1154589758186293, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic_hohqmesh.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic_hohqmesh.jl" + ), + l2 = [ + 0.0042023406458005464, + 0.004122532789279737, + 0.0042448149597303616, + 0.0036361316700401765, + 0.007389845952982495, + ], + linf = [ + 0.04530610539892499, + 0.02765695110527666, + 0.05670295599308606, + 0.048396544302230504, + 0.1154589758186293, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave_nonconforming.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_mhd_alfven_wave_nonconforming.jl"), - l2=[0.00019018725889431733, 0.0006523517707148006, - 0.0002401595437705759, 0.0007796920661427565, - 0.0007095787460334334, 0.0006558819731628876, - 0.0003565026134076906, 0.0007904654548841712, - 9.437300326448332e-7], - linf=[0.0012482306861187897, 0.006408776208178299, - 0.0016845452099629663, 0.0068711236542984555, - 0.004626581522263695, 0.006614624811393632, - 0.0030068344747734566, 0.008277825749754025, - 1.3475027166309006e-5], - tspan=(0.0, 0.25), - coverage_override=(trees_per_dimension = (1, 1, 1),)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave_nonconforming.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_mhd_alfven_wave_nonconforming.jl" + ), + l2 = [ + 0.00019018725889431733, 0.0006523517707148006, + 0.0002401595437705759, 0.0007796920661427565, + 0.0007095787460334334, 0.0006558819731628876, + 0.0003565026134076906, 0.0007904654548841712, + 9.437300326448332e-7, + ], + linf = [ + 0.0012482306861187897, 0.006408776208178299, + 0.0016845452099629663, 0.0068711236542984555, + 0.004626581522263695, 0.006614624811393632, + 0.0030068344747734566, 0.008277825749754025, + 1.3475027166309006e-5, + ], + tspan = (0.0, 0.25), + coverage_override = (trees_per_dimension = (1, 1, 1),) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_shockcapturing_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_shockcapturing_amr.jl"), - l2=[0.006297229188299052, 0.0064363477630573936, - 0.007109134822960387, 0.0065295379843073945, - 0.02061487028361094, 0.005561406556868266, - 0.007570747563219415, 0.005571060186624124, - 3.910359570546058e-6], - linf=[0.20904050617411984, 0.18630026905465372, - 0.23476537952044518, 0.19430178061639747, - 0.6858488631108304, 0.15169972134884624, - 0.22431157069631724, 0.16823638724229162, - 0.0005352202836463904], - tspan=(0.0, 0.04), - coverage_override=(maxiters = 6, initial_refinement_level = 1, - base_level = 1, max_level = 2)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_shockcapturing_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_shockcapturing_amr.jl"), + l2 = [ + 0.006297229188299052, 0.0064363477630573936, + 0.007109134822960387, 0.0065295379843073945, + 0.02061487028361094, 0.005561406556868266, + 0.007570747563219415, 0.005571060186624124, + 3.910359570546058e-6, + ], + linf = [ + 0.20904050617411984, 0.18630026905465372, + 0.23476537952044518, 0.19430178061639747, + 0.6858488631108304, 0.15169972134884624, + 0.22431157069631724, 0.16823638724229162, + 0.0005352202836463904, + ], + tspan = (0.0, 0.04), + coverage_override = ( + maxiters = 6, initial_refinement_level = 1, + base_level = 1, max_level = 2, + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_linearizedeuler_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_linearizedeuler_convergence.jl"), - l2=[ - 0.04452389418193219, 0.03688186699434862, - 0.03688186699434861, 0.03688186699434858, - 0.044523894181932186, - ], - linf=[ - 0.2295447498696467, 0.058369658071546704, - 0.05836965807154648, 0.05836965807154648, 0.2295447498696468, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_linearizedeuler_convergence.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_linearizedeuler_convergence.jl" + ), + l2 = [ + 0.04452389418193219, 0.03688186699434862, + 0.03688186699434861, 0.03688186699434858, + 0.044523894181932186, + ], + linf = [ + 0.2295447498696467, 0.058369658071546704, + 0.05836965807154648, 0.05836965807154648, 0.2295447498696468, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weak_blast_wave_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weak_blast_wave_amr.jl"), - l2=[ - 0.011345993108796831, - 0.018525073963833696, - 0.019102348105917946, - 0.01920515438943838, - 0.15060493968460148, - ], - linf=[ - 0.2994949779783401, - 0.5530175050084679, - 0.5335803757792128, - 0.5647252867336123, - 3.6462732329242566, - ], - tspan=(0.0, 0.025), - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_weak_blast_wave_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weak_blast_wave_amr.jl"), + l2 = [ + 0.011345993108796831, + 0.018525073963833696, + 0.019102348105917946, + 0.01920515438943838, + 0.15060493968460148, + ], + linf = [ + 0.2994949779783401, + 0.5530175050084679, + 0.5335803757792128, + 0.5647252867336123, + 3.6462732329242566, + ], + tspan = (0.0, 0.025), + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + # Check for conservation + state_integrals = Trixi.integrate(sol.u[2], semi) + initial_state_integrals = analysis_callback.affect!.initial_state_integrals + + @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1.0e-13) + @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1.0e-13) + @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1.0e-13) + @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1.0e-13) + @test isapprox(state_integrals[5], initial_state_integrals[5], atol = 1.0e-13) end - # Check for conservation - state_integrals = Trixi.integrate(sol.u[2], semi) - initial_state_integrals = analysis_callback.affect!.initial_state_integrals - - @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1e-13) - @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1e-13) - @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1e-13) - @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1e-13) - @test isapprox(state_integrals[5], initial_state_integrals[5], atol = 1e-13) -end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_paper_self_gravitating_gas_dynamics.jl b/test/test_paper_self_gravitating_gas_dynamics.jl index 10b4f93ad7..b8dc71e62b 100644 --- a/test/test_paper_self_gravitating_gas_dynamics.jl +++ b/test/test_paper_self_gravitating_gas_dynamics.jl @@ -13,344 +13,384 @@ const EXAMPLES_DIR = pkgdir(Trixi, "examples", "paper_self_gravitating_gas_dynam # Numerical examples from the Euler-gravity paper @testset "paper_self_gravitating_gas_dynamics" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_euler_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 0.0001740977055972079, - 0.0003369355182519592, - 0.0003369355182518708, - 0.0006099171220334989, - ], - linf=[ - 0.001079347149189669, - 0.0018836938381321389, - 0.001883693838132583, - 0.003971575376718217, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 0.0001740977055972079, + 0.0003369355182519592, + 0.0003369355182518708, + 0.0006099171220334989, + ], + linf = [ + 0.001079347149189669, + 0.0018836938381321389, + 0.001883693838132583, + 0.003971575376718217, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_convergence.jl with polydeg=4" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 1.7187201161597772e-5, - 2.678065111772951e-5, - 2.678065111783027e-5, - 4.952504160091526e-5, - ], - linf=[ - 0.0001501749544159381, - 0.00016549482504535362, - 0.00016549482504601976, - 0.0004372960291432193, - ], - polydeg=4) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_convergence.jl with polydeg=4" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 1.7187201161597772e-5, + 2.678065111772951e-5, + 2.678065111783027e-5, + 4.952504160091526e-5, + ], + linf = [ + 0.0001501749544159381, + 0.00016549482504535362, + 0.00016549482504601976, + 0.0004372960291432193, + ], + polydeg = 4 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_hypdiff_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_convergence.jl"), - l2=[ - 0.003154024896093942, - 0.012394432074951856, - 0.02185973823794725, - ], - linf=[ - 0.01731850928579215, - 0.07843510773347553, - 0.11242300176349201, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_hypdiff_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_convergence.jl"), + l2 = [ + 0.003154024896093942, + 0.012394432074951856, + 0.02185973823794725, + ], + linf = [ + 0.01731850928579215, + 0.07843510773347553, + 0.11242300176349201, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_hypdiff_convergence.jl with polydeg=4" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_convergence.jl"), - l2=[ - 0.0002511283012128458, - 0.0008808243846610255, - 0.0016313343228567005, - ], - linf=[ - 0.0017290715087938668, - 0.003129184465704738, - 0.01000728849316701, - ], - polydeg=4) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_hypdiff_convergence.jl with polydeg=4" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_convergence.jl"), + l2 = [ + 0.0002511283012128458, + 0.0008808243846610255, + 0.0016313343228567005, + ], + linf = [ + 0.0017290715087938668, + 0.003129184465704738, + 0.01000728849316701, + ], + polydeg = 4 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulergravity_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), - l2=[ - 0.00024871265138964204, - 0.0003370077102132591, - 0.0003370077102131964, - 0.0007231525513793697, - ], - linf=[ - 0.0015813032944647087, - 0.0020494288423820173, - 0.0020494288423824614, - 0.004793821195083758, - ], - tspan=(0.0, 0.1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), + l2 = [ + 0.00024871265138964204, + 0.0003370077102132591, + 0.0003370077102131964, + 0.0007231525513793697, + ], + linf = [ + 0.0015813032944647087, + 0.0020494288423820173, + 0.0020494288423824614, + 0.004793821195083758, + ], + tspan = (0.0, 0.1) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulergravity_convergence.jl with polydeg=4" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), - l2=[ - 1.9537712148648045e-5, - 2.7564396197947587e-5, - 2.7564396197967635e-5, - 5.688838772067586e-5, - ], - linf=[ - 0.00012335710672761735, - 0.00020086268350816283, - 0.00020086268350727465, - 0.0004962155455632278, - ], - tspan=(0.0, 0.1), polydeg=4) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_convergence.jl with polydeg=4" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), + l2 = [ + 1.9537712148648045e-5, + 2.7564396197947587e-5, + 2.7564396197967635e-5, + 5.688838772067586e-5, + ], + linf = [ + 0.00012335710672761735, + 0.00020086268350816283, + 0.00020086268350727465, + 0.0004962155455632278, + ], + tspan = (0.0, 0.1), polydeg = 4 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulergravity_convergence.jl with 1st order RK3S*" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), - l2=[ - 0.00024871265138959434, - 0.000337007710281087, - 0.0003370077102811394, - 0.0007231525515231289, - ], - linf=[ - 0.0015813032941613958, - 0.002049428843978518, - 0.0020494288439798503, - 0.004793821198143977, - ], - tspan=(0.0, 0.1), - timestep_gravity=Trixi.timestep_gravity_erk51_3Sstar!) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_convergence.jl with 1st order RK3S*" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), + l2 = [ + 0.00024871265138959434, + 0.000337007710281087, + 0.0003370077102811394, + 0.0007231525515231289, + ], + linf = [ + 0.0015813032941613958, + 0.002049428843978518, + 0.0020494288439798503, + 0.004793821198143977, + ], + tspan = (0.0, 0.1), + timestep_gravity = Trixi.timestep_gravity_erk51_3Sstar! + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulergravity_convergence.jl with 3rd order RK3S*" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), - l2=[ - 0.0002487126513894034, - 0.00033700771023049785, - 0.00033700771023048245, - 0.0007231525514158737, - ], - linf=[ - 0.0015813032943847727, - 0.002049428842844314, - 0.0020494288428452023, - 0.004793821195971937, - ], - tspan=(0.0, 0.1), - timestep_gravity=Trixi.timestep_gravity_erk53_3Sstar!) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_convergence.jl with 3rd order RK3S*" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), + l2 = [ + 0.0002487126513894034, + 0.00033700771023049785, + 0.00033700771023048245, + 0.0007231525514158737, + ], + linf = [ + 0.0015813032943847727, + 0.002049428842844314, + 0.0020494288428452023, + 0.004793821195971937, + ], + tspan = (0.0, 0.1), + timestep_gravity = Trixi.timestep_gravity_erk53_3Sstar! + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulergravity_jeans_instability.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulergravity_jeans_instability.jl"), - l2=[ - 10733.63378538114, - 13356.780607423452, - 1.6722844879795038e-6, - 26834.076821148774, - ], - linf=[ - 15194.296424901113, - 18881.481685044182, - 6.809726988008751e-6, - 37972.99700513482, - ], - tspan=(0.0, 0.1), - atol=4.0e-6) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_jeans_instability.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulergravity_jeans_instability.jl" + ), + l2 = [ + 10733.63378538114, + 13356.780607423452, + 1.6722844879795038e-6, + 26834.076821148774, + ], + linf = [ + 15194.296424901113, + 18881.481685044182, + 6.809726988008751e-6, + 37972.99700513482, + ], + tspan = (0.0, 0.1), + atol = 4.0e-6 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulergravity_jeans_instability.jl with RK3S*" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulergravity_jeans_instability.jl"), - l2=[ - 10734.598193238024, - 13358.217234481384, - 1.911011743371934e-6, - 26836.487841241516, - ], - linf=[ - 15195.661004798487, - 18883.512035906537, - 7.867948710816926e-6, - 37976.408478975296, - ], - tspan=(0.0, 0.1), - atol=4.0e-6, # the background field is reatively large, so this corresponds to our usual atol - parameters=ParametersEulerGravity(background_density = 1.5e7, - gravitational_constant = 6.674e-8, - cfl = 2.4, - resid_tol = 1.0e-4, - n_iterations_max = 1000, - timestep_gravity = timestep_gravity_erk52_3Sstar!)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_jeans_instability.jl with RK3S*" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulergravity_jeans_instability.jl" + ), + l2 = [ + 10734.598193238024, + 13358.217234481384, + 1.911011743371934e-6, + 26836.487841241516, + ], + linf = [ + 15195.661004798487, + 18883.512035906537, + 7.867948710816926e-6, + 37976.408478975296, + ], + tspan = (0.0, 0.1), + atol = 4.0e-6, # the background field is reatively large, so this corresponds to our usual atol + parameters = ParametersEulerGravity( + background_density = 1.5e7, + gravitational_constant = 6.674e-8, + cfl = 2.4, + resid_tol = 1.0e-4, + n_iterations_max = 1000, + timestep_gravity = timestep_gravity_erk52_3Sstar! + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "Printing" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulergravity_jeans_instability.jl"), - tspan=(0.0, 1.0e-5), - parameters=ParametersEulerGravity(background_density = 1.5e7, - gravitational_constant = 6.674e-8, - cfl = 2.4, - resid_tol = 1.0e-4, - n_iterations_max = 1000, - timestep_gravity = timestep_gravity_erk52_3Sstar!)) + @trixi_testset "Printing" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulergravity_jeans_instability.jl" + ), + tspan = (0.0, 1.0e-5), + parameters = ParametersEulerGravity( + background_density = 1.5e7, + gravitational_constant = 6.674e-8, + cfl = 2.4, + resid_tol = 1.0e-4, + n_iterations_max = 1000, + timestep_gravity = timestep_gravity_erk52_3Sstar! + ) + ) - show(stdout, parameters) - show(stdout, semi) - show(stdout, semi_euler.boundary_conditions) - show(stdout, TrivialCallback()) - show(stdout, equations_euler) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + show(stdout, parameters) + show(stdout, semi) + show(stdout, semi_euler.boundary_conditions) + show(stdout, TrivialCallback()) + show(stdout, equations_euler) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulergravity_sedov_blast_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulergravity_sedov_blast_wave.jl"), - l2=[ - 0.046315994852653024, - 0.0650818006233669, - 0.06508180062336677, - 0.4896707211656037, - ], - linf=[ - 2.3874843337593776, - 4.07876384374792, - 4.07876384374792, - 16.23914384809855, - ], - tspan=(0.0, 0.05), - coverage_override=(maxiters = 2,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_sedov_blast_wave.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulergravity_sedov_blast_wave.jl" + ), + l2 = [ + 0.046315994852653024, + 0.0650818006233669, + 0.06508180062336677, + 0.4896707211656037, + ], + linf = [ + 2.3874843337593776, + 4.07876384374792, + 4.07876384374792, + 16.23914384809855, + ], + tspan = (0.0, 0.05), + coverage_override = (maxiters = 2,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulergravity_sedov_blast_wave.jl with ref-level=8 and no AMR" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulergravity_sedov_blast_wave.jl"), - l2=[ - 0.00289222135995042, - 0.013724813590853825, - 0.013724813590853832, - 0.05822904710548214, - ], - linf=[ - 0.26361780693997594, - 1.3908873830688688, - 1.3908873830688688, - 4.066701303607613, - ], - tspan=(0.0, 0.005), initial_refinement_level=8, - amr_callback=TrivialCallback()) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_sedov_blast_wave.jl with ref-level=8 and no AMR" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulergravity_sedov_blast_wave.jl" + ), + l2 = [ + 0.00289222135995042, + 0.013724813590853825, + 0.013724813590853832, + 0.05822904710548214, + ], + linf = [ + 0.26361780693997594, + 1.3908873830688688, + 1.3908873830688688, + 4.066701303607613, + ], + tspan = (0.0, 0.005), initial_refinement_level = 8, + amr_callback = TrivialCallback() + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn rm(outdir, recursive = true) diff --git a/test/test_parabolic_1d.jl b/test/test_parabolic_1d.jl index 38bebdcce1..6c506c4c77 100644 --- a/test/test_parabolic_1d.jl +++ b/test/test_parabolic_1d.jl @@ -10,210 +10,258 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "SemidiscretizationHyperbolicParabolic (1D)" begin -#! format: noindent + #! format: noindent -@trixi_testset "TreeMesh1D: elixir_advection_diffusion.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem", - "elixir_advection_diffusion.jl"), - initial_refinement_level=4, tspan=(0.0, 0.4), polydeg=3, - l2=[8.389498188525518e-06], - linf=[2.847421658558336e-05]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh1D: elixir_advection_diffusion.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_1d_dgsem", + "elixir_advection_diffusion.jl" + ), + initial_refinement_level = 4, tspan = (0.0, 0.4), polydeg = 3, + l2 = [8.389498188525518e-6], + linf = [2.847421658558336e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh1D: elixir_advection_diffusion.jl (AMR)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem", - "elixir_advection_diffusion.jl"), - tspan=(0.0, 0.0), initial_refinement_level=5) - tspan = (0.0, 1.0) - ode = semidiscretize(semi, tspan) - amr_controller = ControllerThreeLevel(semi, IndicatorMax(semi, variable = first), - base_level = 4, - med_level = 5, med_threshold = 0.1, - max_level = 6, max_threshold = 0.6) - amr_callback = AMRCallback(semi, amr_controller, - interval = 5, - adapt_initial_condition = true) + @trixi_testset "TreeMesh1D: elixir_advection_diffusion.jl (AMR)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_1d_dgsem", + "elixir_advection_diffusion.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 5 + ) + tspan = (0.0, 1.0) + ode = semidiscretize(semi, tspan) + amr_controller = ControllerThreeLevel( + semi, IndicatorMax(semi, variable = first), + base_level = 4, + med_level = 5, med_threshold = 0.1, + max_level = 6, max_threshold = 0.6 + ) + amr_callback = AMRCallback( + semi, amr_controller, + interval = 5, + adapt_initial_condition = true + ) - # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver - callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, - amr_callback) - sol = solve(ode, KenCarp4(autodiff = false), abstol = time_abs_tol, - reltol = time_int_tol, - save_everystep = false, callback = callbacks) - l2_error, linf_error = analysis_callback(sol) - @test l2_error ≈ [6.4878111416468355e-6] - @test linf_error ≈ [3.258075790424364e-5] - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Create a CallbackSet to collect all callbacks such that they can be passed to the ODE solver + callbacks = CallbackSet( + summary_callback, analysis_callback, alive_callback, + amr_callback + ) + sol = solve( + ode, KenCarp4(autodiff = false), abstol = time_abs_tol, + reltol = time_int_tol, + save_everystep = false, callback = callbacks + ) + l2_error, linf_error = analysis_callback(sol) + @test l2_error ≈ [6.4878111416468355e-6] + @test linf_error ≈ [3.258075790424364e-5] + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_periodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem", - "elixir_navierstokes_convergence_periodic.jl"), - l2=[ - 0.0001133835907077494, - 6.226282245610444e-5, - 0.0002820171699999139, - ], - linf=[ - 0.0006255102377159538, - 0.00036195501456059986, - 0.0016147729485886941, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_periodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_1d_dgsem", + "elixir_navierstokes_convergence_periodic.jl" + ), + l2 = [ + 0.0001133835907077494, + 6.226282245610444e-5, + 0.0002820171699999139, + ], + linf = [ + 0.0006255102377159538, + 0.00036195501456059986, + 0.0016147729485886941, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_periodic.jl: GradientVariablesEntropy" begin - @test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem", - "elixir_navierstokes_convergence_periodic.jl"), - equations_parabolic=CompressibleNavierStokesDiffusion1D(equations, - mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesEntropy()), - l2=[ - 0.00011310615871043463, - 6.216495207074201e-5, - 0.00028195843110817814, - ], - linf=[ - 0.0006240837363233886, - 0.0003616694320713876, - 0.0016147339542413874, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_periodic.jl: GradientVariablesEntropy" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_1d_dgsem", + "elixir_navierstokes_convergence_periodic.jl" + ), + equations_parabolic = CompressibleNavierStokesDiffusion1D( + equations, + mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesEntropy() + ), + l2 = [ + 0.00011310615871043463, + 6.216495207074201e-5, + 0.00028195843110817814, + ], + linf = [ + 0.0006240837363233886, + 0.0003616694320713876, + 0.0016147339542413874, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_walls.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem", - "elixir_navierstokes_convergence_walls.jl"), - l2=[ - 0.00047023310868269237, - 0.00032181736027057234, - 0.0014966266486095025, - ], - linf=[ - 0.002996375101363302, - 0.0028639041695096433, - 0.012691132694550689, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_walls.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_1d_dgsem", + "elixir_navierstokes_convergence_walls.jl" + ), + l2 = [ + 0.00047023310868269237, + 0.00032181736027057234, + 0.0014966266486095025, + ], + linf = [ + 0.002996375101363302, + 0.0028639041695096433, + 0.012691132694550689, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_walls.jl: GradientVariablesEntropy" begin - @test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem", - "elixir_navierstokes_convergence_walls.jl"), - equations_parabolic=CompressibleNavierStokesDiffusion1D(equations, - mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesEntropy()), - l2=[ - 0.0004608500483647771, - 0.00032431091222851285, - 0.0015159733360626845, - ], - linf=[ - 0.002754803146635787, - 0.0028567713744625124, - 0.012941793784197131, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_walls.jl: GradientVariablesEntropy" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_1d_dgsem", + "elixir_navierstokes_convergence_walls.jl" + ), + equations_parabolic = CompressibleNavierStokesDiffusion1D( + equations, + mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesEntropy() + ), + l2 = [ + 0.0004608500483647771, + 0.00032431091222851285, + 0.0015159733360626845, + ], + linf = [ + 0.002754803146635787, + 0.0028567713744625124, + 0.012941793784197131, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_walls_amr.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem", - "elixir_navierstokes_convergence_walls_amr.jl"), - equations_parabolic=CompressibleNavierStokesDiffusion1D(equations, - mu = mu(), - Prandtl = prandtl_number()), - l2=[ - 2.5278845598681636e-5, - 2.5540145802666872e-5, - 0.0001211867535580826, - ], - linf=[ - 0.0001466387202588848, - 0.00019422419092429135, - 0.0009556449835592673, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_walls_amr.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_1d_dgsem", + "elixir_navierstokes_convergence_walls_amr.jl" + ), + equations_parabolic = CompressibleNavierStokesDiffusion1D( + equations, + mu = mu(), + Prandtl = prandtl_number() + ), + l2 = [ + 2.5278845598681636e-5, + 2.5540145802666872e-5, + 0.0001211867535580826, + ], + linf = [ + 0.0001466387202588848, + 0.00019422419092429135, + 0.0009556449835592673, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_walls_amr.jl: GradientVariablesEntropy" begin - @test_trixi_include(joinpath(examples_dir(), "tree_1d_dgsem", - "elixir_navierstokes_convergence_walls_amr.jl"), - equations_parabolic=CompressibleNavierStokesDiffusion1D(equations, - mu = mu(), - Prandtl = prandtl_number(), - gradient_variables = GradientVariablesEntropy()), - l2=[ - 2.4593521887223632e-5, - 2.3928212900127102e-5, - 0.00011252332663824173, - ], - linf=[ - 0.00011850494672183132, - 0.00018987676556476442, - 0.0009597461727750556, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh1D: elixir_navierstokes_convergence_walls_amr.jl: GradientVariablesEntropy" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_1d_dgsem", + "elixir_navierstokes_convergence_walls_amr.jl" + ), + equations_parabolic = CompressibleNavierStokesDiffusion1D( + equations, + mu = mu(), + Prandtl = prandtl_number(), + gradient_variables = GradientVariablesEntropy() + ), + l2 = [ + 2.4593521887223632e-5, + 2.3928212900127102e-5, + 0.00011252332663824173, + ], + linf = [ + 0.00011850494672183132, + 0.00018987676556476442, + 0.0009597461727750556, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end # Clean up afterwards: delete Trixi output directory @test_nowarn isdir(outdir) && rm(outdir, recursive = true) diff --git a/test/test_parabolic_2d.jl b/test/test_parabolic_2d.jl index d038354f88..21e8ab7400 100644 --- a/test/test_parabolic_2d.jl +++ b/test/test_parabolic_2d.jl @@ -10,752 +10,922 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "SemidiscretizationHyperbolicParabolic (2D)" begin -#! format: noindent - -@trixi_testset "DGMulti 2D rhs_parabolic!" begin - dg = DGMulti(polydeg = 2, element_type = Quad(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_central), - volume_integral = VolumeIntegralWeakForm()) - cells_per_dimension = (2, 2) - mesh = DGMultiMesh(dg, cells_per_dimension) - - # test with polynomial initial condition x^2 * y - # test if we recover the exact second derivative - initial_condition = (x, t, equations) -> SVector(x[1]^2 * x[2]) - - equations = LinearScalarAdvectionEquation2D(1.0, 1.0) - equations_parabolic = LaplaceDiffusion2D(1.0, equations) - - semi = SemidiscretizationHyperbolicParabolic(mesh, equations, equations_parabolic, - initial_condition, dg) - @test_nowarn_mod show(stdout, semi) - @test_nowarn_mod show(stdout, MIME"text/plain"(), semi) - @test_nowarn_mod show(stdout, boundary_condition_do_nothing) - - @test nvariables(semi) == nvariables(equations) - @test Base.ndims(semi) == Base.ndims(mesh) - @test Base.real(semi) == Base.real(dg) - - ode = semidiscretize(semi, (0.0, 0.01)) - u0 = similar(ode.u0) - Trixi.compute_coefficients!(u0, 0.0, semi) - @test u0 ≈ ode.u0 - - # test "do nothing" BC just returns first argument - @test boundary_condition_do_nothing(u0, nothing) == u0 - - @unpack cache, cache_parabolic, equations_parabolic = semi - @unpack gradients = cache_parabolic - for dim in eachindex(gradients) - fill!(gradients[dim], zero(eltype(gradients[dim]))) + #! format: noindent + + @trixi_testset "DGMulti 2D rhs_parabolic!" begin + dg = DGMulti( + polydeg = 2, element_type = Quad(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_central), + volume_integral = VolumeIntegralWeakForm() + ) + cells_per_dimension = (2, 2) + mesh = DGMultiMesh(dg, cells_per_dimension) + + # test with polynomial initial condition x^2 * y + # test if we recover the exact second derivative + initial_condition = (x, t, equations) -> SVector(x[1]^2 * x[2]) + + equations = LinearScalarAdvectionEquation2D(1.0, 1.0) + equations_parabolic = LaplaceDiffusion2D(1.0, equations) + + semi = SemidiscretizationHyperbolicParabolic( + mesh, equations, equations_parabolic, + initial_condition, dg + ) + @test_nowarn_mod show(stdout, semi) + @test_nowarn_mod show(stdout, MIME"text/plain"(), semi) + @test_nowarn_mod show(stdout, boundary_condition_do_nothing) + + @test nvariables(semi) == nvariables(equations) + @test Base.ndims(semi) == Base.ndims(mesh) + @test Base.real(semi) == Base.real(dg) + + ode = semidiscretize(semi, (0.0, 0.01)) + u0 = similar(ode.u0) + Trixi.compute_coefficients!(u0, 0.0, semi) + @test u0 ≈ ode.u0 + + # test "do nothing" BC just returns first argument + @test boundary_condition_do_nothing(u0, nothing) == u0 + + @unpack cache, cache_parabolic, equations_parabolic = semi + @unpack gradients = cache_parabolic + for dim in eachindex(gradients) + fill!(gradients[dim], zero(eltype(gradients[dim]))) + end + + t = 0.0 + # pass in `boundary_condition_periodic` to skip boundary flux/integral evaluation + Trixi.calc_gradient!( + gradients, ode.u0, t, mesh, equations_parabolic, + boundary_condition_periodic, dg, cache, cache_parabolic + ) + @unpack x, y, xq, yq = mesh.md + @test getindex.(gradients[1], 1) ≈ 2 * xq .* yq + @test getindex.(gradients[2], 1) ≈ xq .^ 2 + + u_flux = similar.(gradients) + Trixi.calc_viscous_fluxes!( + u_flux, ode.u0, gradients, mesh, equations_parabolic, + dg, cache, cache_parabolic + ) + @test u_flux[1] ≈ gradients[1] + @test u_flux[2] ≈ gradients[2] + + du = similar(ode.u0) + Trixi.calc_divergence!( + du, ode.u0, t, u_flux, mesh, equations_parabolic, + boundary_condition_periodic, + dg, semi.solver_parabolic, cache, cache_parabolic + ) + @test getindex.(du, 1) ≈ 2 * y end - t = 0.0 - # pass in `boundary_condition_periodic` to skip boundary flux/integral evaluation - Trixi.calc_gradient!(gradients, ode.u0, t, mesh, equations_parabolic, - boundary_condition_periodic, dg, cache, cache_parabolic) - @unpack x, y, xq, yq = mesh.md - @test getindex.(gradients[1], 1) ≈ 2 * xq .* yq - @test getindex.(gradients[2], 1) ≈ xq .^ 2 - - u_flux = similar.(gradients) - Trixi.calc_viscous_fluxes!(u_flux, ode.u0, gradients, mesh, equations_parabolic, - dg, cache, cache_parabolic) - @test u_flux[1] ≈ gradients[1] - @test u_flux[2] ≈ gradients[2] - - du = similar(ode.u0) - Trixi.calc_divergence!(du, ode.u0, t, u_flux, mesh, equations_parabolic, - boundary_condition_periodic, - dg, semi.solver_parabolic, cache, cache_parabolic) - @test getindex.(du, 1) ≈ 2 * y -end - -@trixi_testset "DGMulti: elixir_advection_diffusion.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_advection_diffusion.jl"), - cells_per_dimension=(4, 4), tspan=(0.0, 0.1), - l2=[0.2485803335154642], - linf=[1.079606969242132]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "DGMulti: elixir_advection_diffusion.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_advection_diffusion.jl" + ), + cells_per_dimension = (4, 4), tspan = (0.0, 0.1), + l2 = [0.2485803335154642], + linf = [1.079606969242132] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "DGMulti: elixir_advection_diffusion_periodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_advection_diffusion_periodic.jl"), - cells_per_dimension=(4, 4), tspan=(0.0, 0.1), - l2=[0.03180371984888462], - linf=[0.2136821621370909]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "DGMulti: elixir_advection_diffusion_periodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_advection_diffusion_periodic.jl" + ), + cells_per_dimension = (4, 4), tspan = (0.0, 0.1), + l2 = [0.03180371984888462], + linf = [0.2136821621370909] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "DGMulti: elixir_advection_diffusion_nonperiodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_advection_diffusion_nonperiodic.jl"), - cells_per_dimension=(4, 4), tspan=(0.0, 0.1), - l2=[0.002123168335604323], - linf=[0.00963640423513712]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "DGMulti: elixir_advection_diffusion_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_advection_diffusion_nonperiodic.jl" + ), + cells_per_dimension = (4, 4), tspan = (0.0, 0.1), + l2 = [0.002123168335604323], + linf = [0.00963640423513712] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "DGMulti: elixir_navierstokes_convergence.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_navierstokes_convergence.jl"), - cells_per_dimension=(4, 4), tspan=(0.0, 0.1), - l2=[ - 0.0015355076812510957, - 0.0033843168272696756, - 0.0036531858107443434, - 0.009948436427519214, - ], - linf=[ - 0.005522560467190019, - 0.013425258500730508, - 0.013962115643482154, - 0.027483102120502423, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "DGMulti: elixir_navierstokes_convergence.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_navierstokes_convergence.jl" + ), + cells_per_dimension = (4, 4), tspan = (0.0, 0.1), + l2 = [ + 0.0015355076812510957, + 0.0033843168272696756, + 0.0036531858107443434, + 0.009948436427519214, + ], + linf = [ + 0.005522560467190019, + 0.013425258500730508, + 0.013962115643482154, + 0.027483102120502423, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "DGMulti: elixir_navierstokes_convergence_curved.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_navierstokes_convergence_curved.jl"), - cells_per_dimension=(4, 4), tspan=(0.0, 0.1), - l2=[ - 0.004255101916146187, - 0.011118488923215765, - 0.011281831283462686, - 0.03573656447388509, - ], - linf=[ - 0.015071710669706473, - 0.04103132025858458, - 0.03990424085750277, - 0.1309401718598764, - ],) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "DGMulti: elixir_navierstokes_convergence_curved.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_navierstokes_convergence_curved.jl" + ), + cells_per_dimension = (4, 4), tspan = (0.0, 0.1), + l2 = [ + 0.004255101916146187, + 0.011118488923215765, + 0.011281831283462686, + 0.03573656447388509, + ], + linf = [ + 0.015071710669706473, + 0.04103132025858458, + 0.03990424085750277, + 0.1309401718598764, + ], + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "DGMulti: elixir_navierstokes_lid_driven_cavity.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_navierstokes_lid_driven_cavity.jl"), - cells_per_dimension=(4, 4), tspan=(0.0, 0.5), - l2=[ - 0.00022156125227115747, - 0.028318325921401, - 0.009509168701070296, - 0.028267900513550506, - ], - linf=[ - 0.001562278941298234, - 0.14886653390744856, - 0.0716323565533752, - 0.19472785105241996, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "DGMulti: elixir_navierstokes_lid_driven_cavity.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_navierstokes_lid_driven_cavity.jl" + ), + cells_per_dimension = (4, 4), tspan = (0.0, 0.5), + l2 = [ + 0.00022156125227115747, + 0.028318325921401, + 0.009509168701070296, + 0.028267900513550506, + ], + linf = [ + 0.001562278941298234, + 0.14886653390744856, + 0.0716323565533752, + 0.19472785105241996, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh2D: elixir_advection_diffusion.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_diffusion.jl"), - initial_refinement_level=2, tspan=(0.0, 0.4), polydeg=5, - l2=[4.0915532997994255e-6], - linf=[2.3040850347877395e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_advection_diffusion.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_diffusion.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.4), polydeg = 5, + l2 = [4.0915532997994255e-6], + linf = [2.3040850347877395e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh2D: elixir_advection_diffusion.jl (Refined mesh)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_diffusion.jl"), - tspan=(0.0, 0.0)) - LLID = Trixi.local_leaf_cells(mesh.tree) - num_leaves = length(LLID) - @assert num_leaves % 8 == 0 - Trixi.refine!(mesh.tree, LLID[1:Int(num_leaves / 8)]) - tspan = (0.0, 1.5) - semi = SemidiscretizationHyperbolicParabolic(mesh, - (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic)) - ode = semidiscretize(semi, tspan) - analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) - sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - ode_default_options()..., callback = callbacks) - l2_error, linf_error = analysis_callback(sol) - @test l2_error ≈ [1.67452550744728e-6] - @test linf_error ≈ [7.905059166368744e-6] - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 100 - @test (@allocated Trixi.rhs_parabolic!(du_ode, u_ode, semi, t)) < 100 + @trixi_testset "TreeMesh2D: elixir_advection_diffusion.jl (Refined mesh)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_diffusion.jl" + ), + tspan = (0.0, 0.0) + ) + LLID = Trixi.local_leaf_cells(mesh.tree) + num_leaves = length(LLID) + @assert num_leaves % 8 == 0 + Trixi.refine!(mesh.tree, LLID[1:Int(num_leaves / 8)]) + tspan = (0.0, 1.5) + semi = SemidiscretizationHyperbolicParabolic( + mesh, + (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ) + ) + ode = semidiscretize(semi, tspan) + analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) + sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + ode_default_options()..., callback = callbacks + ) + l2_error, linf_error = analysis_callback(sol) + @test l2_error ≈ [1.67452550744728e-6] + @test linf_error ≈ [7.905059166368744e-6] + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 100 + @test (@allocated Trixi.rhs_parabolic!(du_ode, u_ode, semi, t)) < 100 + end end -end -@trixi_testset "TreeMesh2D: elixir_advection_diffusion_nonperiodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_diffusion_nonperiodic.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - l2=[0.007646800618485118], - linf=[0.10067621050468958]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_advection_diffusion_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_diffusion_nonperiodic.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + l2 = [0.007646800618485118], + linf = [0.10067621050468958] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - analysis_callback=AnalysisCallback(semi, - interval = analysis_interval, - extra_analysis_integrals = (energy_kinetic, - energy_internal, - enstrophy)), - l2=[ - 0.002111672530658797, - 0.0034322351490857846, - 0.0038742528195910416, - 0.012469246082568561, - ], - linf=[ - 0.012006418939223495, - 0.035520871209746126, - 0.024512747492231427, - 0.11191122588756564, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + analysis_callback = AnalysisCallback( + semi, + interval = analysis_interval, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + enstrophy, + ) + ), + l2 = [ + 0.002111672530658797, + 0.0034322351490857846, + 0.0038742528195910416, + 0.012469246082568561, + ], + linf = [ + 0.012006418939223495, + 0.035520871209746126, + 0.024512747492231427, + 0.11191122588756564, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (isothermal walls)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - heat_bc_top_bottom=Isothermal((x, t, equations) -> Trixi.temperature(initial_condition_navier_stokes_convergence_test(x, - t, - equations), - equations)), - l2=[ - 0.002103629650383915, - 0.003435843933396454, - 0.00386735987813341, - 0.012670355349235728, - ], - linf=[ - 0.012006261793147788, - 0.03550212518982032, - 0.025107947319661185, - 0.11647078036571124, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (isothermal walls)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + heat_bc_top_bottom = Isothermal( + (x, t, equations) -> Trixi.temperature( + initial_condition_navier_stokes_convergence_test( + x, + t, + equations + ), + equations + ) + ), + l2 = [ + 0.002103629650383915, + 0.003435843933396454, + 0.00386735987813341, + 0.012670355349235728, + ], + linf = [ + 0.012006261793147788, + 0.03550212518982032, + 0.025107947319661185, + 0.11647078036571124, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (Entropy gradient variables)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - gradient_variables=GradientVariablesEntropy(), - l2=[ - 0.0021403742517389513, - 0.0034258287094908572, - 0.0038915122886898517, - 0.012506862343013842, - ], - linf=[ - 0.012244412004628336, - 0.03507559186162224, - 0.024580892345558894, - 0.11425600758350107, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (Entropy gradient variables)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + gradient_variables = GradientVariablesEntropy(), + l2 = [ + 0.0021403742517389513, + 0.0034258287094908572, + 0.0038915122886898517, + 0.012506862343013842, + ], + linf = [ + 0.012244412004628336, + 0.03507559186162224, + 0.024580892345558894, + 0.11425600758350107, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (Entropy gradient variables, isothermal walls)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - gradient_variables=GradientVariablesEntropy(), - heat_bc_top_bottom=Isothermal((x, t, equations) -> Trixi.temperature(initial_condition_navier_stokes_convergence_test(x, - t, - equations), - equations)), - l2=[ - 0.0021349737347844907, - 0.0034301388278203033, - 0.0038928324474291572, - 0.012693611436230873, - ], - linf=[ - 0.01224423627586213, - 0.035054066314102905, - 0.025099598504931965, - 0.11795616324751634, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (Entropy gradient variables, isothermal walls)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + gradient_variables = GradientVariablesEntropy(), + heat_bc_top_bottom = Isothermal( + (x, t, equations) -> Trixi.temperature( + initial_condition_navier_stokes_convergence_test( + x, + t, + equations + ), + equations + ) + ), + l2 = [ + 0.0021349737347844907, + 0.0034301388278203033, + 0.0038928324474291572, + 0.012693611436230873, + ], + linf = [ + 0.01224423627586213, + 0.035054066314102905, + 0.025099598504931965, + 0.11795616324751634, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (flux differencing)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - volume_integral=VolumeIntegralFluxDifferencing(flux_central), - l2=[ - 0.0021116725306633594, - 0.0034322351490827557, - 0.0038742528196093542, - 0.012469246082526909, - ], - linf=[ - 0.012006418939291663, - 0.035520871209594115, - 0.024512747491801577, - 0.11191122588591007, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (flux differencing)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + volume_integral = VolumeIntegralFluxDifferencing(flux_central), + l2 = [ + 0.0021116725306633594, + 0.0034322351490827557, + 0.0038742528196093542, + 0.012469246082526909, + ], + linf = [ + 0.012006418939291663, + 0.035520871209594115, + 0.024512747491801577, + 0.11191122588591007, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (Refined mesh)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_navierstokes_convergence.jl"), - tspan=(0.0, 0.0), initial_refinement_level=3) - LLID = Trixi.local_leaf_cells(mesh.tree) - num_leaves = length(LLID) - @assert num_leaves % 4 == 0 - Trixi.refine!(mesh.tree, LLID[1:Int(num_leaves / 4)]) - tspan = (0.0, 0.5) - semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) - ode = semidiscretize(semi, tspan) - analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) - sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - dt = 1e-5, - ode_default_options()..., callback = callbacks) - l2_error, linf_error = analysis_callback(sol) - @test l2_error ≈ - [0.00024296959173852447; 0.0002093263158670915; 0.0005390572390977262; - 0.00026753561392341537] - @test linf_error ≈ - [0.0016210102053424436; 0.002593287648655501; 0.002953907343823712; - 0.002077119120180271] - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_navierstokes_convergence.jl (Refined mesh)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 3 + ) + LLID = Trixi.local_leaf_cells(mesh.tree) + num_leaves = length(LLID) + @assert num_leaves % 4 == 0 + Trixi.refine!(mesh.tree, LLID[1:Int(num_leaves / 4)]) + tspan = (0.0, 0.5) + semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test + ) + ode = semidiscretize(semi, tspan) + analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) + sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + dt = 1.0e-5, + ode_default_options()..., callback = callbacks + ) + l2_error, linf_error = analysis_callback(sol) + @test l2_error ≈ + [ + 0.00024296959173852447; 0.0002093263158670915; 0.0005390572390977262; + 0.00026753561392341537 + ] + @test linf_error ≈ + [ + 0.0016210102053424436; 0.002593287648655501; 0.002953907343823712; + 0.002077119120180271 + ] + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh2D: elixir_navierstokes_lid_driven_cavity.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_navierstokes_lid_driven_cavity.jl"), - initial_refinement_level=2, tspan=(0.0, 0.5), - l2=[ - 0.00015144571529699053, - 0.018766076072331623, - 0.007065070765652574, - 0.0208399005734258, - ], - linf=[ - 0.0014523369373669048, - 0.12366779944955864, - 0.05532450997115432, - 0.16099927805328207, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_navierstokes_lid_driven_cavity.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_navierstokes_lid_driven_cavity.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.5), + l2 = [ + 0.00015144571529699053, + 0.018766076072331623, + 0.007065070765652574, + 0.0208399005734258, + ], + linf = [ + 0.0014523369373669048, + 0.12366779944955864, + 0.05532450997115432, + 0.16099927805328207, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end - -@trixi_testset "TreeMesh2D: elixir_navierstokes_shearlayer_amr.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_navierstokes_shearlayer_amr.jl"), - l2=[ - 0.005155557460409018, - 0.4048446934219344, - 0.43040068852937047, - 1.1255130552079322, - ], - linf=[ - 0.03287305649809613, - 1.1656793717431393, - 1.3917196016246969, - 8.146587380114653, - ], - tspan=(0.0, 0.7)) -end -@trixi_testset "TreeMesh2D: elixir_navierstokes_taylor_green_vortex_sutherland.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_navierstokes_taylor_green_vortex_sutherland.jl"), - l2=[ - 0.001452856280034929, - 0.0007538775539989481, - 0.0007538775539988681, - 0.011035506549989587, - ], - linf=[ - 0.003291912841311362, - 0.002986462478096974, - 0.0029864624780958637, - 0.0231954665514138, - ], - tspan=(0.0, 1.0)) -end - -@trixi_testset "P4estMesh2D: elixir_advection_diffusion_periodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_advection_diffusion_periodic.jl"), - trees_per_dimension=(1, 1), initial_refinement_level=2, - tspan=(0.0, 0.5), - l2=[0.0023754695605828443], - linf=[0.008154128363741964]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_navierstokes_shearlayer_amr.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_navierstokes_shearlayer_amr.jl" + ), + l2 = [ + 0.005155557460409018, + 0.4048446934219344, + 0.43040068852937047, + 1.1255130552079322, + ], + linf = [ + 0.03287305649809613, + 1.1656793717431393, + 1.3917196016246969, + 8.146587380114653, + ], + tspan = (0.0, 0.7) + ) end -end -@trixi_testset "P4estMesh2D: elixir_advection_diffusion_periodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_advection_diffusion_periodic.jl"), - trees_per_dimension=(1, 1), initial_refinement_level=2, - tspan=(0.0, 0.5), - l2=[0.0023754695605828443], - linf=[0.008154128363741964]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh2D: elixir_navierstokes_taylor_green_vortex_sutherland.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_navierstokes_taylor_green_vortex_sutherland.jl" + ), + l2 = [ + 0.001452856280034929, + 0.0007538775539989481, + 0.0007538775539988681, + 0.011035506549989587, + ], + linf = [ + 0.003291912841311362, + 0.002986462478096974, + 0.0029864624780958637, + 0.0231954665514138, + ], + tspan = (0.0, 1.0) + ) end -end -@trixi_testset "P4estMesh2D: elixir_advection_diffusion_periodic_curved.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_advection_diffusion_periodic_curved.jl"), - trees_per_dimension=(1, 1), initial_refinement_level=2, - tspan=(0.0, 0.5), - l2=[0.006708147442490916], - linf=[0.04807038397976693]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh2D: elixir_advection_diffusion_periodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_advection_diffusion_periodic.jl" + ), + trees_per_dimension = (1, 1), initial_refinement_level = 2, + tspan = (0.0, 0.5), + l2 = [0.0023754695605828443], + linf = [0.008154128363741964] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh2D: elixir_advection_diffusion_periodic_amr.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_advection_diffusion_periodic_amr.jl"), - tspan=(0.0, 0.01), - l2=[0.014715887539773128], - linf=[0.2285802791900049]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh2D: elixir_advection_diffusion_periodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_advection_diffusion_periodic.jl" + ), + trees_per_dimension = (1, 1), initial_refinement_level = 2, + tspan = (0.0, 0.5), + l2 = [0.0023754695605828443], + linf = [0.008154128363741964] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh2D: elixir_advection_diffusion_nonperiodic_amr.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_advection_diffusion_nonperiodic_amr.jl"), - tspan=(0.0, 0.01), - l2=[0.007934195641974433], - linf=[0.11030265194954081]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh2D: elixir_advection_diffusion_periodic_curved.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_advection_diffusion_periodic_curved.jl" + ), + trees_per_dimension = (1, 1), initial_refinement_level = 2, + tspan = (0.0, 0.5), + l2 = [0.006708147442490916], + linf = [0.04807038397976693] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh2D: elixir_advection_diffusion_nonperiodic_curved.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_advection_diffusion_nonperiodic_curved.jl"), - trees_per_dimension=(1, 1), initial_refinement_level=2, - tspan=(0.0, 0.5), - l2=[0.00919917034843865], - linf=[0.14186297438393505]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh2D: elixir_advection_diffusion_periodic_amr.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_advection_diffusion_periodic_amr.jl" + ), + tspan = (0.0, 0.01), + l2 = [0.014715887539773128], + linf = [0.2285802791900049] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh2D: elixir_navierstokes_convergence.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=1, tspan=(0.0, 0.2), - l2=[ - 0.0003811978985836709, - 0.0005874314969169538, - 0.0009142898787923481, - 0.0011613918899727263, - ], - linf=[ - 0.0021633623982135752, - 0.009484348274135372, - 0.004231572066492217, - 0.011661660275365193, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh2D: elixir_advection_diffusion_nonperiodic_amr.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_advection_diffusion_nonperiodic_amr.jl" + ), + tspan = (0.0, 0.01), + l2 = [0.007934195641974433], + linf = [0.11030265194954081] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh2D: elixir_navierstokes_convergence_nonperiodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_navierstokes_convergence_nonperiodic.jl"), - initial_refinement_level=1, tspan=(0.0, 0.2), - l2=[ - 0.00040364962558511795, - 0.0005869762481506936, - 0.00091488537427274, - 0.0011984191566376762, - ], - linf=[ - 0.0024993634941723464, - 0.009487866203944725, - 0.004505829506628117, - 0.011634902776245681, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh2D: elixir_advection_diffusion_nonperiodic_curved.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_advection_diffusion_nonperiodic_curved.jl" + ), + trees_per_dimension = (1, 1), initial_refinement_level = 2, + tspan = (0.0, 0.5), + l2 = [0.00919917034843865], + linf = [0.14186297438393505] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh2D: elixir_navierstokes_lid_driven_cavity.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_navierstokes_lid_driven_cavity.jl"), - initial_refinement_level=2, tspan=(0.0, 0.5), - l2=[ - 0.00028716166408816073, - 0.08101204560401647, - 0.02099595625377768, - 0.05008149754143295, - ], - linf=[ - 0.014804500261322406, - 0.9513271652357098, - 0.7223919625994717, - 1.4846907331004786, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh2D: elixir_navierstokes_convergence.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 1, tspan = (0.0, 0.2), + l2 = [ + 0.0003811978985836709, + 0.0005874314969169538, + 0.0009142898787923481, + 0.0011613918899727263, + ], + linf = [ + 0.0021633623982135752, + 0.009484348274135372, + 0.004231572066492217, + 0.011661660275365193, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh2D: elixir_navierstokes_lid_driven_cavity_amr.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_navierstokes_lid_driven_cavity_amr.jl"), - tspan=(0.0, 1.0), - l2=[ - 0.0005323841980601085, 0.07892044543547208, - 0.02909671646389337, 0.11717468256112017, - ], - linf=[ - 0.006045292737899444, 0.9233292581786228, - 0.7982129977236198, 1.6864546235292153, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh2D: elixir_navierstokes_convergence_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_navierstokes_convergence_nonperiodic.jl" + ), + initial_refinement_level = 1, tspan = (0.0, 0.2), + l2 = [ + 0.00040364962558511795, + 0.0005869762481506936, + 0.00091488537427274, + 0.0011984191566376762, + ], + linf = [ + 0.0024993634941723464, + 0.009487866203944725, + 0.004505829506628117, + 0.011634902776245681, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_navierstokes_NACA0012airfoil_mach08.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_navierstokes_NACA0012airfoil_mach08.jl"), - l2=[0.000186486564226516, - 0.0005076712323400374, - 0.00038074588984354107, - 0.002128177239782089], - linf=[0.5153387072802718, - 1.199362305026636, - 0.9077214424040279, - 5.666071182328691], tspan=(0.0, 0.001), - initial_refinement_level=0, - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 10_000,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh2D: elixir_navierstokes_lid_driven_cavity.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_navierstokes_lid_driven_cavity.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.5), + l2 = [ + 0.00028716166408816073, + 0.08101204560401647, + 0.02099595625377768, + 0.05008149754143295, + ], + linf = [ + 0.014804500261322406, + 0.9513271652357098, + 0.7223919625994717, + 1.4846907331004786, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) # Just a placeholder in this case - - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - - drag_p = Trixi.analyze(drag_coefficient, du, u, tspan[2], mesh, equations, solver, - semi.cache, semi) - lift_p = Trixi.analyze(lift_coefficient, du, u, tspan[2], mesh, equations, solver, - semi.cache, semi) - - drag_f = Trixi.analyze(drag_coefficient_shear_force, du, u, tspan[2], mesh, - equations, equations_parabolic, solver, - semi.cache, semi, semi.cache_parabolic) - lift_f = Trixi.analyze(lift_coefficient_shear_force, du, u, tspan[2], mesh, - equations, equations_parabolic, solver, - semi.cache, semi, semi.cache_parabolic) - - @test isapprox(drag_p, 0.17963843913309516, atol = 1e-13) - @test isapprox(lift_p, 0.26462588007949367, atol = 1e-13) + @trixi_testset "P4estMesh2D: elixir_navierstokes_lid_driven_cavity_amr.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_navierstokes_lid_driven_cavity_amr.jl" + ), + tspan = (0.0, 1.0), + l2 = [ + 0.0005323841980601085, 0.07892044543547208, + 0.02909671646389337, 0.11717468256112017, + ], + linf = [ + 0.006045292737899444, 0.9233292581786228, + 0.7982129977236198, 1.6864546235292153, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end - @test isapprox(drag_f, 1.5427441885921553, atol = 1e-13) - @test isapprox(lift_f, 0.005621910087395724, atol = 1e-13) -end + @trixi_testset "elixir_navierstokes_NACA0012airfoil_mach08.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_navierstokes_NACA0012airfoil_mach08.jl" + ), + l2 = [ + 0.000186486564226516, + 0.0005076712323400374, + 0.00038074588984354107, + 0.002128177239782089, + ], + linf = [ + 0.5153387072802718, + 1.199362305026636, + 0.9077214424040279, + 5.666071182328691, + ], tspan = (0.0, 0.001), + initial_refinement_level = 0, + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 10_000,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) # Just a placeholder in this case + + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + + drag_p = Trixi.analyze( + drag_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache, semi + ) + lift_p = Trixi.analyze( + lift_coefficient, du, u, tspan[2], mesh, equations, solver, + semi.cache, semi + ) + + drag_f = Trixi.analyze( + drag_coefficient_shear_force, du, u, tspan[2], mesh, + equations, equations_parabolic, solver, + semi.cache, semi, semi.cache_parabolic + ) + lift_f = Trixi.analyze( + lift_coefficient_shear_force, du, u, tspan[2], mesh, + equations, equations_parabolic, solver, + semi.cache, semi, semi.cache_parabolic + ) + + @test isapprox(drag_p, 0.17963843913309516, atol = 1.0e-13) + @test isapprox(lift_p, 0.26462588007949367, atol = 1.0e-13) + + @test isapprox(drag_f, 1.5427441885921553, atol = 1.0e-13) + @test isapprox(lift_f, 0.005621910087395724, atol = 1.0e-13) + end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_parabolic_3d.jl b/test/test_parabolic_3d.jl index 863daeeaf3..87c4e5c1f9 100644 --- a/test/test_parabolic_3d.jl +++ b/test/test_parabolic_3d.jl @@ -10,502 +10,596 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "SemidiscretizationHyperbolicParabolic (3D)" begin -#! format: noindent + #! format: noindent -@trixi_testset "DGMulti: elixir_navierstokes_convergence.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_3d", - "elixir_navierstokes_convergence.jl"), - cells_per_dimension=(4, 4, 4), tspan=(0.0, 0.1), - l2=[ - 0.0005532847115849239, - 0.000659263490965341, - 0.0007776436127362806, - 0.0006592634909662951, - 0.0038073628897809185, - ], - linf=[ - 0.0017039861523615585, - 0.002628561703560073, - 0.003531057425112172, - 0.0026285617036090336, - 0.015587829540351095, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "DGMulti: elixir_navierstokes_convergence.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_3d", + "elixir_navierstokes_convergence.jl" + ), + cells_per_dimension = (4, 4, 4), tspan = (0.0, 0.1), + l2 = [ + 0.0005532847115849239, + 0.000659263490965341, + 0.0007776436127362806, + 0.0006592634909662951, + 0.0038073628897809185, + ], + linf = [ + 0.0017039861523615585, + 0.002628561703560073, + 0.003531057425112172, + 0.0026285617036090336, + 0.015587829540351095, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "DGMulti: elixir_navierstokes_convergence_curved.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_3d", - "elixir_navierstokes_convergence_curved.jl"), - cells_per_dimension=(4, 4, 4), tspan=(0.0, 0.1), - l2=[ - 0.0014027227251207474, - 0.0021322235533273513, - 0.0027873741447455194, - 0.0024587473070627423, - 0.00997836818019202, - ], - linf=[ - 0.006341750402837576, - 0.010306014252246865, - 0.01520740250924979, - 0.010968264045485565, - 0.047454389831591115, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "DGMulti: elixir_navierstokes_convergence_curved.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_3d", + "elixir_navierstokes_convergence_curved.jl" + ), + cells_per_dimension = (4, 4, 4), tspan = (0.0, 0.1), + l2 = [ + 0.0014027227251207474, + 0.0021322235533273513, + 0.0027873741447455194, + 0.0024587473070627423, + 0.00997836818019202, + ], + linf = [ + 0.006341750402837576, + 0.010306014252246865, + 0.01520740250924979, + 0.010968264045485565, + 0.047454389831591115, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "DGMulti: elixir_navierstokes_taylor_green_vortex.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_3d", - "elixir_navierstokes_taylor_green_vortex.jl"), - cells_per_dimension=(4, 4, 4), tspan=(0.0, 0.25), - l2=[ - 0.0001825713444029892, - 0.015589736382772248, - 0.015589736382771884, - 0.021943924667273653, - 0.01927370280244222, - ], - linf=[ - 0.0006268463584697681, - 0.03218881662749007, - 0.03218881662697948, - 0.053872495395614256, - 0.05183822000984151, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "DGMulti: elixir_navierstokes_taylor_green_vortex.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_3d", + "elixir_navierstokes_taylor_green_vortex.jl" + ), + cells_per_dimension = (4, 4, 4), tspan = (0.0, 0.25), + l2 = [ + 0.0001825713444029892, + 0.015589736382772248, + 0.015589736382771884, + 0.021943924667273653, + 0.01927370280244222, + ], + linf = [ + 0.0006268463584697681, + 0.03218881662749007, + 0.03218881662697948, + 0.053872495395614256, + 0.05183822000984151, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - l2=[ - 0.0019582188528512257, - 0.002653449504302844, - 0.002898264205184629, - 0.002653449504302853, - 0.009511572365085706, - ], - linf=[ - 0.013680656759085918, - 0.0356910450154318, - 0.023526343547736236, - 0.035691045015431855, - 0.11482570604041165, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + l2 = [ + 0.0019582188528512257, + 0.002653449504302844, + 0.002898264205184629, + 0.002653449504302853, + 0.009511572365085706, + ], + linf = [ + 0.013680656759085918, + 0.0356910450154318, + 0.023526343547736236, + 0.035691045015431855, + 0.11482570604041165, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (isothermal walls)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - heat_bc_top_bottom=Isothermal((x, t, equations) -> Trixi.temperature(initial_condition_navier_stokes_convergence_test(x, - t, - equations), - equations)), - l2=[ - 0.00195468651965362, - 0.0026554367897028506, - 0.002892730402724066, - 0.002655436789702817, - 0.009596351796609566, - ], - linf=[ - 0.013680508110645473, - 0.035673446359424356, - 0.024024936779729028, - 0.03567344635942474, - 0.11839497110809383, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (isothermal walls)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + heat_bc_top_bottom = Isothermal( + (x, t, equations) -> Trixi.temperature( + initial_condition_navier_stokes_convergence_test( + x, + t, + equations + ), + equations + ) + ), + l2 = [ + 0.00195468651965362, + 0.0026554367897028506, + 0.002892730402724066, + 0.002655436789702817, + 0.009596351796609566, + ], + linf = [ + 0.013680508110645473, + 0.035673446359424356, + 0.024024936779729028, + 0.03567344635942474, + 0.11839497110809383, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (Entropy gradient variables)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - gradient_variables=GradientVariablesEntropy(), - l2=[ - 0.0019770444875099307, - 0.0026524750946399327, - 0.00290860030832445, - 0.0026524750946399396, - 0.009509568981439294, - ], - linf=[ - 0.01387936112914212, - 0.03526260609304053, - 0.023554197097368997, - 0.035262606093040896, - 0.11719963716509518, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (Entropy gradient variables)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + gradient_variables = GradientVariablesEntropy(), + l2 = [ + 0.0019770444875099307, + 0.0026524750946399327, + 0.00290860030832445, + 0.0026524750946399396, + 0.009509568981439294, + ], + linf = [ + 0.01387936112914212, + 0.03526260609304053, + 0.023554197097368997, + 0.035262606093040896, + 0.11719963716509518, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (Entropy gradient variables, isothermal walls)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - gradient_variables=GradientVariablesEntropy(), - heat_bc_top_bottom=Isothermal((x, t, equations) -> Trixi.temperature(initial_condition_navier_stokes_convergence_test(x, - t, - equations), - equations)), - l2=[ - 0.001974631423398113, - 0.002654768259143932, - 0.002907031063651286, - 0.002654768259143901, - 0.009587792882971452, - ], - linf=[ - 0.01387919380137137, - 0.035244084526358944, - 0.02398614622061363, - 0.03524408452635828, - 0.12005056512506407, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (Entropy gradient variables, isothermal walls)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + gradient_variables = GradientVariablesEntropy(), + heat_bc_top_bottom = Isothermal( + (x, t, equations) -> Trixi.temperature( + initial_condition_navier_stokes_convergence_test( + x, + t, + equations + ), + equations + ) + ), + l2 = [ + 0.001974631423398113, + 0.002654768259143932, + 0.002907031063651286, + 0.002654768259143901, + 0.009587792882971452, + ], + linf = [ + 0.01387919380137137, + 0.035244084526358944, + 0.02398614622061363, + 0.03524408452635828, + 0.12005056512506407, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (flux differencing)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - volume_integral=VolumeIntegralFluxDifferencing(flux_central), - l2=[ - 0.0019582188528180213, - 0.002653449504301736, - 0.0028982642051960006, - 0.0026534495043017384, - 0.009511572364811033, - ], - linf=[ - 0.013680656758949583, - 0.035691045015224444, - 0.02352634354676752, - 0.035691045015223424, - 0.11482570603751441, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (flux differencing)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + volume_integral = VolumeIntegralFluxDifferencing(flux_central), + l2 = [ + 0.0019582188528180213, + 0.002653449504301736, + 0.0028982642051960006, + 0.0026534495043017384, + 0.009511572364811033, + ], + linf = [ + 0.013680656758949583, + 0.035691045015224444, + 0.02352634354676752, + 0.035691045015223424, + 0.11482570603751441, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (Refined mesh)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_navierstokes_convergence.jl"), - tspan=(0.0, 0.0)) - LLID = Trixi.local_leaf_cells(mesh.tree) - num_leaves = length(LLID) - @assert num_leaves % 16 == 0 - Trixi.refine!(mesh.tree, LLID[1:Int(num_leaves / 16)]) - tspan = (0.0, 0.25) - semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver; - boundary_conditions = (boundary_conditions, - boundary_conditions_parabolic), - source_terms = source_terms_navier_stokes_convergence_test) - ode = semidiscretize(semi, tspan) - analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) - sol = solve(ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, - dt = 1e-5, - ode_default_options()..., callback = callbacks) - l2_error, linf_error = analysis_callback(sol) - @test l2_error ≈ [ - 0.0003109336253407314, - 0.0006473493036803503, - 0.0007705277238213672, - 0.0006280517917198335, - 0.000903927789884075, - ] - @test linf_error ≈ [ - 0.0023694155365339142, - 0.010634932622402863, - 0.006772070862236412, - 0.010640551561726901, - 0.019256819038719897, - ] - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh3D: elixir_navierstokes_convergence.jl (Refined mesh)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + tspan = (0.0, 0.0) + ) + LLID = Trixi.local_leaf_cells(mesh.tree) + num_leaves = length(LLID) + @assert num_leaves % 16 == 0 + Trixi.refine!(mesh.tree, LLID[1:Int(num_leaves / 16)]) + tspan = (0.0, 0.25) + semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver; + boundary_conditions = ( + boundary_conditions, + boundary_conditions_parabolic, + ), + source_terms = source_terms_navier_stokes_convergence_test + ) + ode = semidiscretize(semi, tspan) + analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) + sol = solve( + ode, RDPK3SpFSAL49(); abstol = time_int_tol, reltol = time_int_tol, + dt = 1.0e-5, + ode_default_options()..., callback = callbacks + ) + l2_error, linf_error = analysis_callback(sol) + @test l2_error ≈ [ + 0.0003109336253407314, + 0.0006473493036803503, + 0.0007705277238213672, + 0.0006280517917198335, + 0.000903927789884075, + ] + @test linf_error ≈ [ + 0.0023694155365339142, + 0.010634932622402863, + 0.006772070862236412, + 0.010640551561726901, + 0.019256819038719897, + ] + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_navierstokes_taylor_green_vortex.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_navierstokes_taylor_green_vortex.jl"), - initial_refinement_level=2, tspan=(0.0, 0.25), - l2=[ - 0.00024173250389635442, - 0.015684268393762454, - 0.01568426839376248, - 0.021991909545192333, - 0.02825413672911425, - ], - linf=[ - 0.0008410587892853094, - 0.04740176181772552, - 0.04740176181772507, - 0.07483494924031157, - 0.150181591534448, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh3D: elixir_navierstokes_taylor_green_vortex.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_navierstokes_taylor_green_vortex.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.25), + l2 = [ + 0.00024173250389635442, + 0.015684268393762454, + 0.01568426839376248, + 0.021991909545192333, + 0.02825413672911425, + ], + linf = [ + 0.0008410587892853094, + 0.04740176181772552, + 0.04740176181772507, + 0.07483494924031157, + 0.150181591534448, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_navierstokes_taylor_green_vortex.jl (Refined mesh)" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_navierstokes_taylor_green_vortex.jl"), - tspan=(0.0, 0.0)) - LLID = Trixi.local_leaf_cells(mesh.tree) - num_leaves = length(LLID) - @assert num_leaves % 32 == 0 - Trixi.refine!(mesh.tree, LLID[1:Int(num_leaves / 32)]) - tspan = (0.0, 0.1) - semi = SemidiscretizationHyperbolicParabolic(mesh, (equations, equations_parabolic), - initial_condition, solver) - ode = semidiscretize(semi, tspan) - analysis_callback = AnalysisCallback(semi, interval = analysis_interval, - save_analysis = true, - extra_analysis_integrals = (energy_kinetic, - energy_internal, - enstrophy)) - callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) - # Use CarpenterKennedy2N54 since `RDPK3SpFSAL49` gives slightly different results on different machines - sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - dt = 5e-3, - save_everystep = false, callback = callbacks) - l2_error, linf_error = analysis_callback(sol) - @test l2_error ≈ [ - 7.314319856736271e-5, - 0.006266480163542894, - 0.006266489911815533, - 0.008829222305770226, - 0.0032859166842329228, - ] - @test linf_error ≈ [ - 0.0002943968186086554, - 0.013876261980614757, - 0.013883619864959451, - 0.025201279960491936, - 0.018679364985388247, - ] - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 100 - @test (@allocated Trixi.rhs_parabolic!(du_ode, u_ode, semi, t)) < 100 + @trixi_testset "TreeMesh3D: elixir_navierstokes_taylor_green_vortex.jl (Refined mesh)" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_navierstokes_taylor_green_vortex.jl" + ), + tspan = (0.0, 0.0) + ) + LLID = Trixi.local_leaf_cells(mesh.tree) + num_leaves = length(LLID) + @assert num_leaves % 32 == 0 + Trixi.refine!(mesh.tree, LLID[1:Int(num_leaves / 32)]) + tspan = (0.0, 0.1) + semi = SemidiscretizationHyperbolicParabolic( + mesh, (equations, equations_parabolic), + initial_condition, solver + ) + ode = semidiscretize(semi, tspan) + analysis_callback = AnalysisCallback( + semi, interval = analysis_interval, + save_analysis = true, + extra_analysis_integrals = ( + energy_kinetic, + energy_internal, + enstrophy, + ) + ) + callbacks = CallbackSet(summary_callback, alive_callback, analysis_callback) + # Use CarpenterKennedy2N54 since `RDPK3SpFSAL49` gives slightly different results on different machines + sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + dt = 5.0e-3, + save_everystep = false, callback = callbacks + ) + l2_error, linf_error = analysis_callback(sol) + @test l2_error ≈ [ + 7.314319856736271e-5, + 0.006266480163542894, + 0.006266489911815533, + 0.008829222305770226, + 0.0032859166842329228, + ] + @test linf_error ≈ [ + 0.0002943968186086554, + 0.013876261980614757, + 0.013883619864959451, + 0.025201279960491936, + 0.018679364985388247, + ] + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 100 + @test (@allocated Trixi.rhs_parabolic!(du_ode, u_ode, semi, t)) < 100 + end end -end -@trixi_testset "P4estMesh3D: elixir_navierstokes_convergence.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_3d_dgsem", - "elixir_navierstokes_convergence.jl"), - initial_refinement_level=2, tspan=(0.0, 0.1), - l2=[ - 0.00026599105554982194, - 0.000461877794472316, - 0.0005424899076052261, - 0.0004618777944723191, - 0.0015846392581126832, - ], - linf=[ - 0.0025241668929956163, - 0.006308461681816373, - 0.004334939663169113, - 0.006308461681804009, - 0.03176343480493493, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh3D: elixir_navierstokes_convergence.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_3d_dgsem", + "elixir_navierstokes_convergence.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.1), + l2 = [ + 0.00026599105554982194, + 0.000461877794472316, + 0.0005424899076052261, + 0.0004618777944723191, + 0.0015846392581126832, + ], + linf = [ + 0.0025241668929956163, + 0.006308461681816373, + 0.004334939663169113, + 0.006308461681804009, + 0.03176343480493493, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh3D: elixir_navierstokes_taylor_green_vortex.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_3d_dgsem", - "elixir_navierstokes_taylor_green_vortex.jl"), - initial_refinement_level=2, tspan=(0.0, 0.25), - surface_flux=FluxHLL(min_max_speed_naive), - l2=[ - 0.0001547509861140407, - 0.015637861347119624, - 0.015637861347119687, - 0.022024699158522523, - 0.009711013505930812, - ], - linf=[ - 0.0006696415247340326, - 0.03442565722527785, - 0.03442565722577423, - 0.06295407168705314, - 0.032857472756916195, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh3D: elixir_navierstokes_taylor_green_vortex.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_3d_dgsem", + "elixir_navierstokes_taylor_green_vortex.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.25), + surface_flux = FluxHLL(min_max_speed_naive), + l2 = [ + 0.0001547509861140407, + 0.015637861347119624, + 0.015637861347119687, + 0.022024699158522523, + 0.009711013505930812, + ], + linf = [ + 0.0006696415247340326, + 0.03442565722527785, + 0.03442565722577423, + 0.06295407168705314, + 0.032857472756916195, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_advection_diffusion_amr.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_advection_diffusion_amr.jl"), - l2=[0.000355780485397024], - linf=[0.0010810770271614256]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh3D: elixir_advection_diffusion_amr.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_advection_diffusion_amr.jl" + ), + l2 = [0.000355780485397024], + linf = [0.0010810770271614256] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "TreeMesh3D: elixir_advection_diffusion_nonperiodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_advection_diffusion_nonperiodic.jl"), - l2=[0.0009808996243280868], - linf=[0.01732621559135459]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "TreeMesh3D: elixir_advection_diffusion_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_advection_diffusion_nonperiodic.jl" + ), + l2 = [0.0009808996243280868], + linf = [0.01732621559135459] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh3D: elixir_navierstokes_taylor_green_vortex_amr.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_3d_dgsem", - "elixir_navierstokes_taylor_green_vortex_amr.jl"), - initial_refinement_level=0, tspan=(0.0, 0.5), - l2=[ - 0.0016588740573444188, - 0.03437058632045721, - 0.03437058632045671, - 0.041038898400430075, - 0.30978593009044153, - ], - linf=[ - 0.004173569912012121, - 0.09168674832979556, - 0.09168674832975021, - 0.12129218723807476, - 0.8433893297612087, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh3D: elixir_navierstokes_taylor_green_vortex_amr.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_3d_dgsem", + "elixir_navierstokes_taylor_green_vortex_amr.jl" + ), + initial_refinement_level = 0, tspan = (0.0, 0.5), + l2 = [ + 0.0016588740573444188, + 0.03437058632045721, + 0.03437058632045671, + 0.041038898400430075, + 0.30978593009044153, + ], + linf = [ + 0.004173569912012121, + 0.09168674832979556, + 0.09168674832975021, + 0.12129218723807476, + 0.8433893297612087, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "P4estMesh3D: elixir_navierstokes_blast_wave_amr.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_3d_dgsem", - "elixir_navierstokes_blast_wave_amr.jl"), - tspan=(0.0, 0.01), - l2=[ - 0.009472104410520866, 0.0017883742549557149, - 0.0017883742549557147, 0.0017883742549557196, - 0.024388540048562748, - ], - linf=[ - 0.6782397526873181, 0.17663702154066238, - 0.17663702154066266, 0.17663702154066238, 1.7327849844825238, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "P4estMesh3D: elixir_navierstokes_blast_wave_amr.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_3d_dgsem", + "elixir_navierstokes_blast_wave_amr.jl" + ), + tspan = (0.0, 0.01), + l2 = [ + 0.009472104410520866, 0.0017883742549557149, + 0.0017883742549557147, 0.0017883742549557196, + 0.024388540048562748, + ], + linf = [ + 0.6782397526873181, 0.17663702154066238, + 0.17663702154066266, 0.17663702154066238, 1.7327849844825238, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn isdir(outdir) && rm(outdir, recursive = true) diff --git a/test/test_performance_specializations_2d.jl b/test/test_performance_specializations_2d.jl index 4fd39c78f6..d840fafd1d 100644 --- a/test/test_performance_specializations_2d.jl +++ b/test/test_performance_specializations_2d.jl @@ -10,166 +10,198 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "Performance specializations 2D" begin -#! format: noindent - -@timed_testset "TreeMesh2D, flux_shima_etal_turbo" begin - trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl"), - initial_refinement_level = 0, tspan = (0.0, 0.0), polydeg = 3, - volume_flux = flux_shima_etal_turbo, - surface_flux = flux_shima_etal_turbo) - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) - - # Preserve original memory since it will be `unsafe_wrap`ped and might - # thus otherwise be garbage collected - GC.@preserve u_ode du_ode begin - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) - - # Call the optimized default version - du .= 0 - Trixi.flux_differencing_kernel!(du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, - semi.solver, semi.cache, true) - du_specialized = du[:, :, :, 1] - - # Call the plain version - note the argument type `Function` of - # `semi.solver.volume_integral.volume_flux` - du .= 0 - invoke(Trixi.flux_differencing_kernel!, - Tuple{typeof(du), typeof(u), Integer, typeof(semi.mesh), - typeof(nonconservative_terms), typeof(semi.equations), - Function, typeof(semi.solver), typeof(semi.cache), Bool}, - du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true) - du_baseline = du[:, :, :, 1] - - @test du_specialized ≈ du_baseline + #! format: noindent + + @timed_testset "TreeMesh2D, flux_shima_etal_turbo" begin + trixi_include( + @__MODULE__, + joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl"), + initial_refinement_level = 0, tspan = (0.0, 0.0), polydeg = 3, + volume_flux = flux_shima_etal_turbo, + surface_flux = flux_shima_etal_turbo + ) + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) + + # Preserve original memory since it will be `unsafe_wrap`ped and might + # thus otherwise be garbage collected + GC.@preserve u_ode du_ode begin + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) + + # Call the optimized default version + du .= 0 + Trixi.flux_differencing_kernel!( + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, + semi.solver, semi.cache, true + ) + du_specialized = du[:, :, :, 1] + + # Call the plain version - note the argument type `Function` of + # `semi.solver.volume_integral.volume_flux` + du .= 0 + invoke( + Trixi.flux_differencing_kernel!, + Tuple{ + typeof(du), typeof(u), Integer, typeof(semi.mesh), + typeof(nonconservative_terms), typeof(semi.equations), + Function, typeof(semi.solver), typeof(semi.cache), Bool, + }, + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true + ) + du_baseline = du[:, :, :, 1] + + @test du_specialized ≈ du_baseline + end end -end -@timed_testset "TreeMesh2D, flux_ranocha_turbo" begin - trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl"), - initial_refinement_level = 0, tspan = (0.0, 0.0), polydeg = 3, - volume_flux = flux_ranocha_turbo, surface_flux = flux_ranocha_turbo) - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) - - # Preserve original memory since it will be `unsafe_wrap`ped and might - # thus otherwise be garbage collected - GC.@preserve u_ode du_ode begin - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) - - # Call the optimized default version - du .= 0 - Trixi.flux_differencing_kernel!(du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, - semi.solver, semi.cache, true) - du_specialized = du[:, :, :, 1] - - # Call the plain version - note the argument type `Function` of - # `semi.solver.volume_integral.volume_flux` - du .= 0 - invoke(Trixi.flux_differencing_kernel!, - Tuple{typeof(du), typeof(u), Integer, typeof(semi.mesh), - typeof(nonconservative_terms), typeof(semi.equations), - Function, typeof(semi.solver), typeof(semi.cache), Bool}, - du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true) - du_baseline = du[:, :, :, 1] - - @test du_specialized ≈ du_baseline + @timed_testset "TreeMesh2D, flux_ranocha_turbo" begin + trixi_include( + @__MODULE__, + joinpath(examples_dir(), "tree_2d_dgsem", "elixir_euler_ec.jl"), + initial_refinement_level = 0, tspan = (0.0, 0.0), polydeg = 3, + volume_flux = flux_ranocha_turbo, surface_flux = flux_ranocha_turbo + ) + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) + + # Preserve original memory since it will be `unsafe_wrap`ped and might + # thus otherwise be garbage collected + GC.@preserve u_ode du_ode begin + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) + + # Call the optimized default version + du .= 0 + Trixi.flux_differencing_kernel!( + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, + semi.solver, semi.cache, true + ) + du_specialized = du[:, :, :, 1] + + # Call the plain version - note the argument type `Function` of + # `semi.solver.volume_integral.volume_flux` + du .= 0 + invoke( + Trixi.flux_differencing_kernel!, + Tuple{ + typeof(du), typeof(u), Integer, typeof(semi.mesh), + typeof(nonconservative_terms), typeof(semi.equations), + Function, typeof(semi.solver), typeof(semi.cache), Bool, + }, + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true + ) + du_baseline = du[:, :, :, 1] + + @test du_specialized ≈ du_baseline + end end -end -@timed_testset "StructuredMesh2D, flux_shima_etal_turbo" begin - trixi_include(@__MODULE__, - joinpath(examples_dir(), "structured_2d_dgsem", "elixir_euler_ec.jl"), - cells_per_dimension = (1, 1), tspan = (0.0, 0.0), polydeg = 3, - volume_flux = flux_shima_etal_turbo, - surface_flux = flux_shima_etal_turbo) - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) - - # Preserve original memory since it will be `unsafe_wrap`ped and might - # thus otherwise be garbage collected - GC.@preserve u_ode du_ode begin - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) - - # Call the optimized default version - du .= 0 - Trixi.flux_differencing_kernel!(du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, - semi.solver, semi.cache, true) - du_specialized = du[:, :, :, 1] - - # Call the plain version - note the argument type `Function` of - # `semi.solver.volume_integral.volume_flux` - du .= 0 - invoke(Trixi.flux_differencing_kernel!, - Tuple{typeof(du), typeof(u), Integer, typeof(semi.mesh), - typeof(nonconservative_terms), typeof(semi.equations), - Function, typeof(semi.solver), typeof(semi.cache), Bool}, - du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true) - du_baseline = du[:, :, :, 1] - - @test du_specialized ≈ du_baseline + @timed_testset "StructuredMesh2D, flux_shima_etal_turbo" begin + trixi_include( + @__MODULE__, + joinpath(examples_dir(), "structured_2d_dgsem", "elixir_euler_ec.jl"), + cells_per_dimension = (1, 1), tspan = (0.0, 0.0), polydeg = 3, + volume_flux = flux_shima_etal_turbo, + surface_flux = flux_shima_etal_turbo + ) + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) + + # Preserve original memory since it will be `unsafe_wrap`ped and might + # thus otherwise be garbage collected + GC.@preserve u_ode du_ode begin + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) + + # Call the optimized default version + du .= 0 + Trixi.flux_differencing_kernel!( + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, + semi.solver, semi.cache, true + ) + du_specialized = du[:, :, :, 1] + + # Call the plain version - note the argument type `Function` of + # `semi.solver.volume_integral.volume_flux` + du .= 0 + invoke( + Trixi.flux_differencing_kernel!, + Tuple{ + typeof(du), typeof(u), Integer, typeof(semi.mesh), + typeof(nonconservative_terms), typeof(semi.equations), + Function, typeof(semi.solver), typeof(semi.cache), Bool, + }, + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true + ) + du_baseline = du[:, :, :, 1] + + @test du_specialized ≈ du_baseline + end end -end -@timed_testset "StructuredMesh2D, flux_ranocha_turbo" begin - trixi_include(@__MODULE__, - joinpath(examples_dir(), "structured_2d_dgsem", "elixir_euler_ec.jl"), - cells_per_dimension = (1, 1), tspan = (0.0, 0.0), polydeg = 3, - volume_flux = flux_ranocha_turbo, surface_flux = flux_ranocha_turbo) - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) - - # Preserve original memory since it will be `unsafe_wrap`ped and might - # thus otherwise be garbage collected - GC.@preserve u_ode du_ode begin - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) - - # Call the optimized default version - du .= 0 - Trixi.flux_differencing_kernel!(du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, - semi.solver, semi.cache, true) - du_specialized = du[:, :, :, 1] - - # Call the plain version - note the argument type `Function` of - # `semi.solver.volume_integral.volume_flux` - du .= 0 - invoke(Trixi.flux_differencing_kernel!, - Tuple{typeof(du), typeof(u), Integer, typeof(semi.mesh), - typeof(nonconservative_terms), typeof(semi.equations), - Function, typeof(semi.solver), typeof(semi.cache), Bool}, - du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true) - du_baseline = du[:, :, :, 1] - - @test du_specialized ≈ du_baseline + @timed_testset "StructuredMesh2D, flux_ranocha_turbo" begin + trixi_include( + @__MODULE__, + joinpath(examples_dir(), "structured_2d_dgsem", "elixir_euler_ec.jl"), + cells_per_dimension = (1, 1), tspan = (0.0, 0.0), polydeg = 3, + volume_flux = flux_ranocha_turbo, surface_flux = flux_ranocha_turbo + ) + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) + + # Preserve original memory since it will be `unsafe_wrap`ped and might + # thus otherwise be garbage collected + GC.@preserve u_ode du_ode begin + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) + + # Call the optimized default version + du .= 0 + Trixi.flux_differencing_kernel!( + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, + semi.solver, semi.cache, true + ) + du_specialized = du[:, :, :, 1] + + # Call the plain version - note the argument type `Function` of + # `semi.solver.volume_integral.volume_flux` + du .= 0 + invoke( + Trixi.flux_differencing_kernel!, + Tuple{ + typeof(du), typeof(u), Integer, typeof(semi.mesh), + typeof(nonconservative_terms), typeof(semi.equations), + Function, typeof(semi.solver), typeof(semi.cache), Bool, + }, + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true + ) + du_baseline = du[:, :, :, 1] + + @test du_specialized ≈ du_baseline + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn rm(outdir, recursive = true) diff --git a/test/test_performance_specializations_3d.jl b/test/test_performance_specializations_3d.jl index 929fc7e362..08c33d1f51 100644 --- a/test/test_performance_specializations_3d.jl +++ b/test/test_performance_specializations_3d.jl @@ -10,166 +10,198 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "Performance specializations 3D" begin -#! format: noindent - -@timed_testset "TreeMesh3D, flux_shima_etal_turbo" begin - trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_3d_dgsem", "elixir_euler_ec.jl"), - initial_refinement_level = 0, tspan = (0.0, 0.0), polydeg = 3, - volume_flux = flux_shima_etal_turbo, - surface_flux = flux_shima_etal_turbo) - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) - - # Preserve original memory since it will be `unsafe_wrap`ped and might - # thus otherwise be garbage collected - GC.@preserve u_ode du_ode begin - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) - - # Call the optimized default version - du .= 0 - Trixi.flux_differencing_kernel!(du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, - semi.solver, semi.cache, true) - du_specialized = du[:, :, :, :, 1] - - # Call the plain version - note the argument type `Function` of - # `semi.solver.volume_integral.volume_flux` - du .= 0 - invoke(Trixi.flux_differencing_kernel!, - Tuple{typeof(du), typeof(u), Integer, typeof(semi.mesh), - typeof(nonconservative_terms), typeof(semi.equations), - Function, typeof(semi.solver), typeof(semi.cache), Bool}, - du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true) - du_baseline = du[:, :, :, :, 1] - - @test du_specialized ≈ du_baseline + #! format: noindent + + @timed_testset "TreeMesh3D, flux_shima_etal_turbo" begin + trixi_include( + @__MODULE__, + joinpath(examples_dir(), "tree_3d_dgsem", "elixir_euler_ec.jl"), + initial_refinement_level = 0, tspan = (0.0, 0.0), polydeg = 3, + volume_flux = flux_shima_etal_turbo, + surface_flux = flux_shima_etal_turbo + ) + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) + + # Preserve original memory since it will be `unsafe_wrap`ped and might + # thus otherwise be garbage collected + GC.@preserve u_ode du_ode begin + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) + + # Call the optimized default version + du .= 0 + Trixi.flux_differencing_kernel!( + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, + semi.solver, semi.cache, true + ) + du_specialized = du[:, :, :, :, 1] + + # Call the plain version - note the argument type `Function` of + # `semi.solver.volume_integral.volume_flux` + du .= 0 + invoke( + Trixi.flux_differencing_kernel!, + Tuple{ + typeof(du), typeof(u), Integer, typeof(semi.mesh), + typeof(nonconservative_terms), typeof(semi.equations), + Function, typeof(semi.solver), typeof(semi.cache), Bool, + }, + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true + ) + du_baseline = du[:, :, :, :, 1] + + @test du_specialized ≈ du_baseline + end end -end -@timed_testset "TreeMesh3D, flux_ranocha_turbo" begin - trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_3d_dgsem", "elixir_euler_ec.jl"), - initial_refinement_level = 0, tspan = (0.0, 0.0), polydeg = 3, - volume_flux = flux_ranocha_turbo, surface_flux = flux_ranocha_turbo) - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) - - # Preserve original memory since it will be `unsafe_wrap`ped and might - # thus otherwise be garbage collected - GC.@preserve u_ode du_ode begin - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) - - # Call the optimized default version - du .= 0 - Trixi.flux_differencing_kernel!(du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, - semi.solver, semi.cache, true) - du_specialized = du[:, :, :, :, 1] - - # Call the plain version - note the argument type `Function` of - # `semi.solver.volume_integral.volume_flux` - du .= 0 - invoke(Trixi.flux_differencing_kernel!, - Tuple{typeof(du), typeof(u), Integer, typeof(semi.mesh), - typeof(nonconservative_terms), typeof(semi.equations), - Function, typeof(semi.solver), typeof(semi.cache), Bool}, - du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true) - du_baseline = du[:, :, :, :, 1] - - @test du_specialized ≈ du_baseline + @timed_testset "TreeMesh3D, flux_ranocha_turbo" begin + trixi_include( + @__MODULE__, + joinpath(examples_dir(), "tree_3d_dgsem", "elixir_euler_ec.jl"), + initial_refinement_level = 0, tspan = (0.0, 0.0), polydeg = 3, + volume_flux = flux_ranocha_turbo, surface_flux = flux_ranocha_turbo + ) + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) + + # Preserve original memory since it will be `unsafe_wrap`ped and might + # thus otherwise be garbage collected + GC.@preserve u_ode du_ode begin + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) + + # Call the optimized default version + du .= 0 + Trixi.flux_differencing_kernel!( + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, + semi.solver, semi.cache, true + ) + du_specialized = du[:, :, :, :, 1] + + # Call the plain version - note the argument type `Function` of + # `semi.solver.volume_integral.volume_flux` + du .= 0 + invoke( + Trixi.flux_differencing_kernel!, + Tuple{ + typeof(du), typeof(u), Integer, typeof(semi.mesh), + typeof(nonconservative_terms), typeof(semi.equations), + Function, typeof(semi.solver), typeof(semi.cache), Bool, + }, + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true + ) + du_baseline = du[:, :, :, :, 1] + + @test du_specialized ≈ du_baseline + end end -end -@timed_testset "StructuredMesh3D, flux_shima_etal_turbo" begin - trixi_include(@__MODULE__, - joinpath(examples_dir(), "structured_3d_dgsem", "elixir_euler_ec.jl"), - cells_per_dimension = (1, 1, 1), tspan = (0.0, 0.0), polydeg = 3, - volume_flux = flux_shima_etal_turbo, - surface_flux = flux_shima_etal_turbo) - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) - - # Preserve original memory since it will be `unsafe_wrap`ped and might - # thus otherwise be garbage collected - GC.@preserve u_ode du_ode begin - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) - - # Call the optimized default version - du .= 0 - Trixi.flux_differencing_kernel!(du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, - semi.solver, semi.cache, true) - du_specialized = du[:, :, :, :, 1] - - # Call the plain version - note the argument type `Function` of - # `semi.solver.volume_integral.volume_flux` - du .= 0 - invoke(Trixi.flux_differencing_kernel!, - Tuple{typeof(du), typeof(u), Integer, typeof(semi.mesh), - typeof(nonconservative_terms), typeof(semi.equations), - Function, typeof(semi.solver), typeof(semi.cache), Bool}, - du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true) - du_baseline = du[:, :, :, :, 1] - - @test du_specialized ≈ du_baseline + @timed_testset "StructuredMesh3D, flux_shima_etal_turbo" begin + trixi_include( + @__MODULE__, + joinpath(examples_dir(), "structured_3d_dgsem", "elixir_euler_ec.jl"), + cells_per_dimension = (1, 1, 1), tspan = (0.0, 0.0), polydeg = 3, + volume_flux = flux_shima_etal_turbo, + surface_flux = flux_shima_etal_turbo + ) + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) + + # Preserve original memory since it will be `unsafe_wrap`ped and might + # thus otherwise be garbage collected + GC.@preserve u_ode du_ode begin + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) + + # Call the optimized default version + du .= 0 + Trixi.flux_differencing_kernel!( + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, + semi.solver, semi.cache, true + ) + du_specialized = du[:, :, :, :, 1] + + # Call the plain version - note the argument type `Function` of + # `semi.solver.volume_integral.volume_flux` + du .= 0 + invoke( + Trixi.flux_differencing_kernel!, + Tuple{ + typeof(du), typeof(u), Integer, typeof(semi.mesh), + typeof(nonconservative_terms), typeof(semi.equations), + Function, typeof(semi.solver), typeof(semi.cache), Bool, + }, + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true + ) + du_baseline = du[:, :, :, :, 1] + + @test du_specialized ≈ du_baseline + end end -end -@timed_testset "StructuredMesh3D, flux_ranocha_turbo" begin - trixi_include(@__MODULE__, - joinpath(examples_dir(), "structured_3d_dgsem", "elixir_euler_ec.jl"), - cells_per_dimension = (1, 1, 1), tspan = (0.0, 0.0), polydeg = 3, - volume_flux = flux_ranocha_turbo, surface_flux = flux_ranocha_turbo) - u_ode = copy(sol.u[end]) - du_ode = zero(u_ode) - - # Preserve original memory since it will be `unsafe_wrap`ped and might - # thus otherwise be garbage collected - GC.@preserve u_ode du_ode begin - u = Trixi.wrap_array(u_ode, semi) - du = Trixi.wrap_array(du_ode, semi) - nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) - - # Call the optimized default version - du .= 0 - Trixi.flux_differencing_kernel!(du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, - semi.solver, semi.cache, true) - du_specialized = du[:, :, :, :, 1] - - # Call the plain version - note the argument type `Function` of - # `semi.solver.volume_integral.volume_flux` - du .= 0 - invoke(Trixi.flux_differencing_kernel!, - Tuple{typeof(du), typeof(u), Integer, typeof(semi.mesh), - typeof(nonconservative_terms), typeof(semi.equations), - Function, typeof(semi.solver), typeof(semi.cache), Bool}, - du, u, 1, semi.mesh, - nonconservative_terms, semi.equations, - semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true) - du_baseline = du[:, :, :, :, 1] - - @test du_specialized ≈ du_baseline + @timed_testset "StructuredMesh3D, flux_ranocha_turbo" begin + trixi_include( + @__MODULE__, + joinpath(examples_dir(), "structured_3d_dgsem", "elixir_euler_ec.jl"), + cells_per_dimension = (1, 1, 1), tspan = (0.0, 0.0), polydeg = 3, + volume_flux = flux_ranocha_turbo, surface_flux = flux_ranocha_turbo + ) + u_ode = copy(sol.u[end]) + du_ode = zero(u_ode) + + # Preserve original memory since it will be `unsafe_wrap`ped and might + # thus otherwise be garbage collected + GC.@preserve u_ode du_ode begin + u = Trixi.wrap_array(u_ode, semi) + du = Trixi.wrap_array(du_ode, semi) + nonconservative_terms = Trixi.have_nonconservative_terms(semi.equations) + + # Call the optimized default version + du .= 0 + Trixi.flux_differencing_kernel!( + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, + semi.solver, semi.cache, true + ) + du_specialized = du[:, :, :, :, 1] + + # Call the plain version - note the argument type `Function` of + # `semi.solver.volume_integral.volume_flux` + du .= 0 + invoke( + Trixi.flux_differencing_kernel!, + Tuple{ + typeof(du), typeof(u), Integer, typeof(semi.mesh), + typeof(nonconservative_terms), typeof(semi.equations), + Function, typeof(semi.solver), typeof(semi.cache), Bool, + }, + du, u, 1, semi.mesh, + nonconservative_terms, semi.equations, + semi.solver.volume_integral.volume_flux, semi.solver, semi.cache, true + ) + du_baseline = du[:, :, :, :, 1] + + @test du_specialized ≈ du_baseline + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn rm(outdir, recursive = true) diff --git a/test/test_special_elixirs.jl b/test/test_special_elixirs.jl index 277ade9bd5..98ec0e5be7 100644 --- a/test/test_special_elixirs.jl +++ b/test/test_special_elixirs.jl @@ -18,336 +18,460 @@ cmd = string(Base.julia_cmd()) coverage = occursin("--code-coverage", cmd) && !occursin("--code-coverage=none", cmd) @testset "Special elixirs" begin -#! format: noindent - -@testset "Convergence test" begin - if !coverage - @timed_testset "tree_2d_dgsem" begin - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_advection_extended.jl"), - 3, initial_refinement_level = 2) - @test isapprox(mean_convergence[:l2], [4.0], rtol = 0.05) - end + #! format: noindent + + @testset "Convergence test" begin + if !coverage + @timed_testset "tree_2d_dgsem" begin + mean_convergence = convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_advection_extended.jl" + ), + 3, initial_refinement_level = 2 + ) + @test isapprox(mean_convergence[:l2], [4.0], rtol = 0.05) + end - @timed_testset "structured_2d_dgsem" begin - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, - "structured_2d_dgsem", - "elixir_advection_extended.jl"), - 3, cells_per_dimension = (5, 9)) - @test isapprox(mean_convergence[:l2], [4.0], rtol = 0.05) - end + @timed_testset "structured_2d_dgsem" begin + mean_convergence = convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, + "structured_2d_dgsem", + "elixir_advection_extended.jl" + ), + 3, cells_per_dimension = (5, 9) + ) + @test isapprox(mean_convergence[:l2], [4.0], rtol = 0.05) + end - @timed_testset "structured_2d_dgsem coupled" begin - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, - "structured_2d_dgsem", - "elixir_advection_coupled.jl"), - 3) - @test isapprox(mean_convergence[1][:l2], [4.0], rtol = 0.05) - @test isapprox(mean_convergence[2][:l2], [4.0], rtol = 0.05) - end + @timed_testset "structured_2d_dgsem coupled" begin + mean_convergence = convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, + "structured_2d_dgsem", + "elixir_advection_coupled.jl" + ), + 3 + ) + @test isapprox(mean_convergence[1][:l2], [4.0], rtol = 0.05) + @test isapprox(mean_convergence[2][:l2], [4.0], rtol = 0.05) + end - @timed_testset "p4est_2d_dgsem" begin - # Run convergence test on unrefined mesh - no_refine = @cfunction((p4est, which_tree, quadrant)->Cint(0), Cint, - (Ptr{Trixi.p4est_t}, Ptr{Trixi.p4est_topidx_t}, - Ptr{Trixi.p4est_quadrant_t})) - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "p4est_2d_dgsem", - "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), - 2, refine_fn_c = no_refine) - @test isapprox(mean_convergence[:linf], [3.2, 3.2, 4.0, 3.7], rtol = 0.05) - end + @timed_testset "p4est_2d_dgsem" begin + # Run convergence test on unrefined mesh + no_refine = @cfunction( + (p4est, which_tree, quadrant) -> Cint(0), Cint, + ( + Ptr{Trixi.p4est_t}, Ptr{Trixi.p4est_topidx_t}, + Ptr{Trixi.p4est_quadrant_t}, + ) + ) + mean_convergence = convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "p4est_2d_dgsem", + "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" + ), + 2, refine_fn_c = no_refine + ) + @test isapprox(mean_convergence[:linf], [3.2, 3.2, 4.0, 3.7], rtol = 0.05) + end - @timed_testset "structured_3d_dgsem" begin - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, - "structured_3d_dgsem", - "elixir_advection_basic.jl"), - 2, cells_per_dimension = (7, 4, 5)) - @test isapprox(mean_convergence[:l2], [4.0], rtol = 0.05) - end + @timed_testset "structured_3d_dgsem" begin + mean_convergence = convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, + "structured_3d_dgsem", + "elixir_advection_basic.jl" + ), + 2, cells_per_dimension = (7, 4, 5) + ) + @test isapprox(mean_convergence[:l2], [4.0], rtol = 0.05) + end - @timed_testset "p4est_3d_dgsem" begin - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "p4est_3d_dgsem", - "elixir_advection_unstructured_curved.jl"), - 2, initial_refinement_level = 0) - @test isapprox(mean_convergence[:l2], [2.7], rtol = 0.05) - end + @timed_testset "p4est_3d_dgsem" begin + mean_convergence = convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "p4est_3d_dgsem", + "elixir_advection_unstructured_curved.jl" + ), + 2, initial_refinement_level = 0 + ) + @test isapprox(mean_convergence[:l2], [2.7], rtol = 0.05) + end - @timed_testset "paper_self_gravitating_gas_dynamics" begin - mean_convergence = convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, - "paper_self_gravitating_gas_dynamics", - "elixir_eulergravity_convergence.jl"), - 2, tspan = (0.0, 0.25), - initial_refinement_level = 1) - @test isapprox(mean_convergence[:l2], 4 * ones(4), atol = 0.4) + @timed_testset "paper_self_gravitating_gas_dynamics" begin + mean_convergence = convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, + "paper_self_gravitating_gas_dynamics", + "elixir_eulergravity_convergence.jl" + ), + 2, tspan = (0.0, 0.25), + initial_refinement_level = 1 + ) + @test isapprox(mean_convergence[:l2], 4 * ones(4), atol = 0.4) + end + else + # Without coverage, just run simple convergence tests to cover + # the convergence test logic + @test_nowarn_mod convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_advection_basic.jl" + ), 2 + ) + @test_nowarn_mod convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_advection_extended.jl" + ), 2, + initial_refinement_level = 0, + tspan = (0.0, 0.1) + ) + @test_nowarn_mod convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "structured_2d_dgsem", + "elixir_advection_basic.jl" + ), 2 + ) + @test_nowarn_mod convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "structured_2d_dgsem", + "elixir_advection_coupled.jl" + ), 2 + ) + @test_nowarn_mod convergence_test( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "structured_2d_dgsem", + "elixir_advection_extended.jl" + ), 2, + cells_per_dimension = (1, 1), + tspan = (0.0, 0.1) + ) end - else - # Without coverage, just run simple convergence tests to cover - # the convergence test logic - @test_nowarn_mod convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_advection_basic.jl"), 2) - @test_nowarn_mod convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_advection_extended.jl"), 2, - initial_refinement_level = 0, - tspan = (0.0, 0.1)) - @test_nowarn_mod convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "structured_2d_dgsem", - "elixir_advection_basic.jl"), 2) - @test_nowarn_mod convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "structured_2d_dgsem", - "elixir_advection_coupled.jl"), 2) - @test_nowarn_mod convergence_test(@__MODULE__, - joinpath(EXAMPLES_DIR, "structured_2d_dgsem", - "elixir_advection_extended.jl"), 2, - cells_per_dimension = (1, 1), - tspan = (0.0, 0.1)) end -end - -@timed_testset "Test linear structure (2D)" begin - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_advection_extended.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 2) - A, b = linear_structure(semi) - λ = eigvals(Matrix(A)) - @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) - - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_hypdiff_lax_friedrichs.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 2) - A, b = linear_structure(semi) - λ = eigvals(Matrix(A)) - @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) - - # check whether the user can modify `b` without changing `A` - x = vec(ode.u0) - Ax = A * x - @. b = 2 * b + x - @test A * x ≈ Ax -end -@testset "Test Jacobian of DG (2D)" begin - @timed_testset "Linear advection" begin - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_advection_extended.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 2) - A, _ = linear_structure(semi) - - J = jacobian_ad_forward(semi) - @test Matrix(A) ≈ J - λ = eigvals(J) + @timed_testset "Test linear structure (2D)" begin + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_advection_extended.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 2 + ) + A, b = linear_structure(semi) + λ = eigvals(Matrix(A)) @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) - J = jacobian_fd(semi) - @test Matrix(A) ≈ J - λ = eigvals(J) + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_hypdiff_lax_friedrichs.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 2 + ) + A, b = linear_structure(semi) + λ = eigvals(Matrix(A)) @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) - end - - @timed_testset "Linear advection-diffusion" begin - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_advection_diffusion.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 2) - J = jacobian_ad_forward(semi) - λ = eigvals(J) - @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) + # check whether the user can modify `b` without changing `A` + x = vec(ode.u0) + Ax = A * x + @. b = 2 * b + x + @test A * x ≈ Ax end - @timed_testset "Compressible Euler equations" begin - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_euler_density_wave.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 1) + @testset "Test Jacobian of DG (2D)" begin + @timed_testset "Linear advection" begin + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_advection_extended.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 2 + ) + A, _ = linear_structure(semi) - J = jacobian_ad_forward(semi) - λ = eigvals(J) - @test maximum(real, λ) < 7.0e-7 + J = jacobian_ad_forward(semi) + @test Matrix(A) ≈ J + λ = eigvals(J) + @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) - J = jacobian_fd(semi) - λ = eigvals(J) - @test maximum(real, λ) < 7.0e-3 - - # This does not work yet because of the indicators... - @test_skip begin - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_euler_shockcapturing.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 1) - jacobian_ad_forward(semi) + J = jacobian_fd(semi) + @test Matrix(A) ≈ J + λ = eigvals(J) + @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) end - @timed_testset "DGMulti (weak form)" begin - gamma = 1.4 - equations = CompressibleEulerEquations2D(gamma) - initial_condition = initial_condition_density_wave + @timed_testset "Linear advection-diffusion" begin + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_advection_diffusion.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 2 + ) - solver = DGMulti(polydeg = 5, element_type = Quad(), - approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(flux_central), - volume_integral = VolumeIntegralWeakForm()) - - # DGMultiMesh is on [-1, 1]^ndims by default - cells_per_dimension = (2, 2) - mesh = DGMultiMesh(solver, cells_per_dimension, - periodicity = (true, true)) + J = jacobian_ad_forward(semi) + λ = eigvals(J) + @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) + end - semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, - solver) + @timed_testset "Compressible Euler equations" begin + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_euler_density_wave.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 1 + ) J = jacobian_ad_forward(semi) λ = eigvals(J) @test maximum(real, λ) < 7.0e-7 - end - @timed_testset "DGMulti (SBP, flux differencing)" begin - gamma = 1.4 - equations = CompressibleEulerEquations2D(gamma) - initial_condition = initial_condition_density_wave + J = jacobian_fd(semi) + λ = eigvals(J) + @test maximum(real, λ) < 7.0e-3 + + # This does not work yet because of the indicators... + @test_skip begin + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_euler_shockcapturing.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 1 + ) + jacobian_ad_forward(semi) + end - solver = DGMulti(polydeg = 5, element_type = Quad(), - approximation_type = SBP(), - surface_integral = SurfaceIntegralWeakForm(flux_central), - volume_integral = VolumeIntegralFluxDifferencing(flux_central)) + @timed_testset "DGMulti (weak form)" begin + gamma = 1.4 + equations = CompressibleEulerEquations2D(gamma) + initial_condition = initial_condition_density_wave + + solver = DGMulti( + polydeg = 5, element_type = Quad(), + approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(flux_central), + volume_integral = VolumeIntegralWeakForm() + ) + + # DGMultiMesh is on [-1, 1]^ndims by default + cells_per_dimension = (2, 2) + mesh = DGMultiMesh( + solver, cells_per_dimension, + periodicity = (true, true) + ) + + semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, + solver + ) + + J = jacobian_ad_forward(semi) + λ = eigvals(J) + @test maximum(real, λ) < 7.0e-7 + end - # DGMultiMesh is on [-1, 1]^ndims by default - cells_per_dimension = (2, 2) - mesh = DGMultiMesh(solver, cells_per_dimension, - periodicity = (true, true)) + @timed_testset "DGMulti (SBP, flux differencing)" begin + gamma = 1.4 + equations = CompressibleEulerEquations2D(gamma) + initial_condition = initial_condition_density_wave + + solver = DGMulti( + polydeg = 5, element_type = Quad(), + approximation_type = SBP(), + surface_integral = SurfaceIntegralWeakForm(flux_central), + volume_integral = VolumeIntegralFluxDifferencing(flux_central) + ) + + # DGMultiMesh is on [-1, 1]^ndims by default + cells_per_dimension = (2, 2) + mesh = DGMultiMesh( + solver, cells_per_dimension, + periodicity = (true, true) + ) + + semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, + solver + ) + + J = jacobian_ad_forward(semi) + λ = eigvals(J) + @test maximum(real, λ) < 7.0e-7 + end + end - semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, - solver) + @timed_testset "Navier-Stokes" begin + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_navierstokes_taylor_green_vortex.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 2 + ) J = jacobian_ad_forward(semi) λ = eigvals(J) - @test maximum(real, λ) < 7.0e-7 + @test maximum(real, λ) < 0.2 end - end - @timed_testset "Navier-Stokes" begin - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_navierstokes_taylor_green_vortex.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 2) - - J = jacobian_ad_forward(semi) - λ = eigvals(J) - @test maximum(real, λ) < 0.2 + @timed_testset "MHD" begin + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_2d_dgsem", + "elixir_mhd_alfven_wave.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 0 + ) + @test_nowarn jacobian_ad_forward(semi) + end end - @timed_testset "MHD" begin - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_2d_dgsem", - "elixir_mhd_alfven_wave.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 0) - @test_nowarn jacobian_ad_forward(semi) + @timed_testset "Test linear structure (3D)" begin + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_3d_dgsem", + "elixir_advection_extended.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 1 + ) + A, b = linear_structure(semi) + λ = eigvals(Matrix(A)) + @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) end -end - -@timed_testset "Test linear structure (3D)" begin - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_3d_dgsem", - "elixir_advection_extended.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 1) - A, b = linear_structure(semi) - λ = eigvals(Matrix(A)) - @test maximum(real, λ) < 10 * sqrt(eps(real(semi))) -end -@timed_testset "Test Jacobian of DG (3D)" begin - trixi_include(@__MODULE__, - joinpath(EXAMPLES_DIR, "tree_3d_dgsem", - "elixir_advection_extended.jl"), - tspan = (0.0, 0.0), initial_refinement_level = 1) - A, _ = linear_structure(semi) + @timed_testset "Test Jacobian of DG (3D)" begin + trixi_include( + @__MODULE__, + joinpath( + EXAMPLES_DIR, "tree_3d_dgsem", + "elixir_advection_extended.jl" + ), + tspan = (0.0, 0.0), initial_refinement_level = 1 + ) + A, _ = linear_structure(semi) - J = jacobian_ad_forward(semi) - @test Matrix(A) ≈ J + J = jacobian_ad_forward(semi) + @test Matrix(A) ≈ J - J = jacobian_fd(semi) - @test Matrix(A) ≈ J -end + J = jacobian_fd(semi) + @test Matrix(A) ≈ J + end -@testset "AD using ForwardDiff" begin - @timed_testset "Euler equations 1D" begin - function entropy_at_final_time(k) # k is the wave number of the initial condition - equations = CompressibleEulerEquations1D(1.4) - mesh = TreeMesh((-1.0,), (1.0,), initial_refinement_level = 3, - n_cells_max = 10^4) - solver = DGSEM(3, FluxHLL(min_max_speed_naive), - VolumeIntegralFluxDifferencing(flux_ranocha)) - initial_condition = (x, t, equations) -> begin - rho = 2 + sinpi(k * sum(x)) - v1 = 0.1 - p = 10.0 - return prim2cons(SVector(rho, v1, p), equations) + @testset "AD using ForwardDiff" begin + @timed_testset "Euler equations 1D" begin + function entropy_at_final_time(k) # k is the wave number of the initial condition + equations = CompressibleEulerEquations1D(1.4) + mesh = TreeMesh( + (-1.0,), (1.0,), initial_refinement_level = 3, + n_cells_max = 10^4 + ) + solver = DGSEM( + 3, FluxHLL(min_max_speed_naive), + VolumeIntegralFluxDifferencing(flux_ranocha) + ) + initial_condition = (x, t, equations) -> begin + rho = 2 + sinpi(k * sum(x)) + v1 = 0.1 + p = 10.0 + return prim2cons(SVector(rho, v1, p), equations) + end + semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, + solver, + uEltype = typeof(k) + ) + ode = semidiscretize(semi, (0.0, 1.0)) + summary_callback = SummaryCallback() + analysis_interval = 100 + analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + alive_callback = AliveCallback(analysis_interval = analysis_interval) + callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback + ) + sol = solve(ode, SSPRK43(), callback = callbacks) + Trixi.integrate(entropy, sol.u[end], semi) end - semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, - solver, - uEltype = typeof(k)) - ode = semidiscretize(semi, (0.0, 1.0)) - summary_callback = SummaryCallback() - analysis_interval = 100 - analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - alive_callback = AliveCallback(analysis_interval = analysis_interval) - callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback) - sol = solve(ode, SSPRK43(), callback = callbacks) - Trixi.integrate(entropy, sol.u[end], semi) + ForwardDiff.derivative(entropy_at_final_time, 1.0) ≈ -0.4524664696235628 end - ForwardDiff.derivative(entropy_at_final_time, 1.0) ≈ -0.4524664696235628 - end - @timed_testset "Linear advection 2D" begin - function energy_at_final_time(k) # k is the wave number of the initial condition - equations = LinearScalarAdvectionEquation2D(0.2, -0.7) - mesh = TreeMesh((-1.0, -1.0), (1.0, 1.0), initial_refinement_level = 3, - n_cells_max = 10^4) - solver = DGSEM(3, flux_lax_friedrichs) - initial_condition = (x, t, equation) -> begin - x_trans = Trixi.x_trans_periodic_2d(x - equation.advection_velocity * t) - return SVector(sinpi(k * sum(x_trans))) + @timed_testset "Linear advection 2D" begin + function energy_at_final_time(k) # k is the wave number of the initial condition + equations = LinearScalarAdvectionEquation2D(0.2, -0.7) + mesh = TreeMesh( + (-1.0, -1.0), (1.0, 1.0), initial_refinement_level = 3, + n_cells_max = 10^4 + ) + solver = DGSEM(3, flux_lax_friedrichs) + initial_condition = (x, t, equation) -> begin + x_trans = Trixi.x_trans_periodic_2d(x - equation.advection_velocity * t) + return SVector(sinpi(k * sum(x_trans))) + end + semi = SemidiscretizationHyperbolic( + mesh, equations, initial_condition, + solver, + uEltype = typeof(k) + ) + ode = semidiscretize(semi, (0.0, 1.0)) + summary_callback = SummaryCallback() + analysis_interval = 100 + analysis_callback = AnalysisCallback(semi, interval = analysis_interval) + alive_callback = AliveCallback(analysis_interval = analysis_interval) + stepsize_callback = StepsizeCallback(cfl = 1.6) + callbacks = CallbackSet( + summary_callback, + analysis_callback, + alive_callback, + stepsize_callback + ) + sol = solve( + ode, CarpenterKennedy2N54(williamson_condition = false), + save_everystep = false, adaptive = false, dt = 1.0, + callback = callbacks + ) + Trixi.integrate(energy_total, sol.u[end], semi) end - semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, - solver, - uEltype = typeof(k)) - ode = semidiscretize(semi, (0.0, 1.0)) - summary_callback = SummaryCallback() - analysis_interval = 100 - analysis_callback = AnalysisCallback(semi, interval = analysis_interval) - alive_callback = AliveCallback(analysis_interval = analysis_interval) - stepsize_callback = StepsizeCallback(cfl = 1.6) - callbacks = CallbackSet(summary_callback, - analysis_callback, - alive_callback, - stepsize_callback) - sol = solve(ode, CarpenterKennedy2N54(williamson_condition = false), - save_everystep = false, adaptive = false, dt = 1.0, - callback = callbacks) - Trixi.integrate(energy_total, sol.u[end], semi) + ForwardDiff.derivative(energy_at_final_time, 1.0) ≈ 1.4388628342896945e-5 end - ForwardDiff.derivative(energy_at_final_time, 1.0) ≈ 1.4388628342896945e-5 - end - @timed_testset "elixir_euler_ad.jl" begin - @test_nowarn_mod trixi_include(joinpath(examples_dir(), "special_elixirs", - "elixir_euler_ad.jl")) + @timed_testset "elixir_euler_ad.jl" begin + @test_nowarn_mod trixi_include( + joinpath( + examples_dir(), "special_elixirs", + "elixir_euler_ad.jl" + ) + ) + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn rm(outdir, recursive = true) diff --git a/test/test_structured_1d.jl b/test/test_structured_1d.jl index 78230e5cf0..e4d914c087 100644 --- a/test/test_structured_1d.jl +++ b/test/test_structured_1d.jl @@ -12,187 +12,215 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "StructuredMesh1D" begin -#! format: noindent - -@trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[6.0388296447998465e-6], - linf=[3.217887726258972e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + #! format: noindent + + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [6.0388296447998465e-6], + linf = [3.217887726258972e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_nonperiodic.jl"), - l2=[5.641921365468918e-5], - linf=[0.00021049780975179733]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_nonperiodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_nonperiodic.jl"), + l2 = [5.641921365468918e-5], + linf = [0.00021049780975179733] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_shockcapturing.jl"), - l2=[0.08015029105233593], - linf=[0.610709468736576], - atol=1.0e-5) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_shockcapturing.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_shockcapturing.jl"), + l2 = [0.08015029105233593], + linf = [0.610709468736576], + atol = 1.0e-5 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[3.67478226e-01, 3.49491179e-01, 8.08910759e-01], - linf=[1.58971947e+00, 1.59812384e+00, 1.94732969e+00], - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [3.67478226e-1, 3.49491179e-1, 8.08910759e-1], + linf = [1.58971947e+0, 1.59812384e+0, 1.94732969e+0], + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_hll_davis.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[1.278661029299215, 0.0663853410742763, 0.9585741943783386], - linf=[ - 3.1661064228547255, - 0.16256363944708607, - 2.667676158812806, - ], - tspan=(0.0, 12.5), - surface_flux=FluxHLL(min_max_speed_davis)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_hll_davis.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [1.278661029299215, 0.0663853410742763, 0.9585741943783386], + linf = [ + 3.1661064228547255, + 0.16256363944708607, + 2.667676158812806, + ], + tspan = (0.0, 12.5), + surface_flux = FluxHLL(min_max_speed_davis) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[ - 2.2527950196212703e-8, - 1.8187357193835156e-8, - 7.705669939973104e-8, - ], - linf=[ - 1.6205433861493646e-7, - 1.465427772462391e-7, - 5.372255111879554e-7, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [ + 2.2527950196212703e-8, + 1.8187357193835156e-8, + 7.705669939973104e-8, + ], + linf = [ + 1.6205433861493646e-7, + 1.465427772462391e-7, + 5.372255111879554e-7, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 3.8099996914101204e-6, - 1.6745575717106341e-6, - 7.732189531480852e-6, - ], - linf=[ - 1.2971473393186272e-5, - 9.270328934274374e-6, - 3.092514399671842e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 3.8099996914101204e-6, + 1.6745575717106341e-6, + 7.732189531480852e-6, + ], + linf = [ + 1.2971473393186272e-5, + 9.270328934274374e-6, + 3.092514399671842e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_linearizedeuler_characteristic_system.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_linearizedeuler_characteristic_system.jl"), - l2=[2.9318078842789714e-6, 0.0, 0.0], - linf=[4.291208715723194e-5, 0.0, 0.0]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_linearizedeuler_characteristic_system.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_linearizedeuler_characteristic_system.jl" + ), + l2 = [2.9318078842789714e-6, 0.0, 0.0], + linf = [4.291208715723194e-5, 0.0, 0.0] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_traffic_flow_lwr_greenlight.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_traffic_flow_lwr_greenlight.jl"), - l2=[0.2005523261652845], - linf=[0.5052827913468407]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_traffic_flow_lwr_greenlight.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_traffic_flow_lwr_greenlight.jl" + ), + l2 = [0.2005523261652845], + linf = [0.5052827913468407] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_convergence_pure_fv.jl" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "tree_1d_dgsem"), - "elixir_euler_convergence_pure_fv.jl"), - mesh=StructuredMesh(16, (0.0,), (2.0,)), - l2=[ - 0.019355699748523896, - 0.022326984561234497, - 0.02523665947241734, - ], - linf=[ - 0.02895961127645519, - 0.03293442484199227, - 0.04246098278632804, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_convergence_pure_fv.jl" begin + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "tree_1d_dgsem"), + "elixir_euler_convergence_pure_fv.jl" + ), + mesh = StructuredMesh(16, (0.0,), (2.0,)), + l2 = [ + 0.019355699748523896, + 0.022326984561234497, + 0.02523665947241734, + ], + linf = [ + 0.02895961127645519, + 0.03293442484199227, + 0.04246098278632804, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn rm(outdir, recursive = true) diff --git a/test/test_structured_2d.jl b/test/test_structured_2d.jl index 82c76cc5d2..e17332f845 100644 --- a/test/test_structured_2d.jl +++ b/test/test_structured_2d.jl @@ -12,70 +12,119 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "StructuredMesh2D" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_float32.jl" begin - # Expected errors are taken from elixir_advection_basic.jl - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_float32.jl"), - # Expected errors are taken from elixir_advection_basic.jl - l2=[Float32(8.311947673061856e-6)], - linf=[Float32(6.627000273229378e-5)], - RealT=Float32) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_float32.jl" begin + # Expected errors are taken from elixir_advection_basic.jl + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_float32.jl"), + # Expected errors are taken from elixir_advection_basic.jl + l2 = [Float32(8.311947673061856e-6)], + linf = [Float32(6.627000273229378e-5)], + RealT = Float32 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_coupled.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_coupled.jl"), - l2=[ - 7.816742843336293e-6, - 7.816742843340186e-6, - 7.816742843025513e-6, - 7.816742843061526e-6, - ], - linf=[ - 6.314906965276812e-5, - 6.314906965187994e-5, - 6.31490696496595e-5, - 6.314906965032563e-5, - ], - coverage_override=(maxiters = 10^5,)) + @trixi_testset "elixir_advection_coupled.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_coupled.jl"), + l2 = [ + 7.816742843336293e-6, + 7.816742843340186e-6, + 7.816742843025513e-6, + 7.816742843061526e-6, + ], + linf = [ + 6.314906965276812e-5, + 6.314906965187994e-5, + 6.31490696496595e-5, + 6.314906965032563e-5, + ], + coverage_override = (maxiters = 10^5,) + ) - @testset "analysis_callback(sol) for AnalysisCallbackCoupled" begin - errors = analysis_callback(sol) - @test errors.l2≈[ - 7.816742843336293e-6, - 7.816742843340186e-6, - 7.816742843025513e-6, - 7.816742843061526e-6, - ] rtol=1.0e-4 - @test errors.linf≈[ - 6.314906965276812e-5, - 6.314906965187994e-5, - 6.31490696496595e-5, - 6.314906965032563e-5, - ] rtol=1.0e-4 + @testset "analysis_callback(sol) for AnalysisCallbackCoupled" begin + errors = analysis_callback(sol) + @test errors.l2 ≈ [ + 7.816742843336293e-6, + 7.816742843340186e-6, + 7.816742843025513e-6, + 7.816742843061526e-6, + ] rtol = 1.0e-4 + @test errors.linf ≈ [ + 6.314906965276812e-5, + 6.314906965187994e-5, + 6.31490696496595e-5, + 6.314906965032563e-5, + ] rtol = 1.0e-4 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end + end + + @trixi_testset "elixir_advection_meshview.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_meshview.jl"), + l2 = [ + 8.311947673083206e-6, + 8.311947673068427e-6, + ], + linf = [ + 6.627000273318195e-5, + 6.62700027264096e-5, + ], + coverage_override = (maxiters = 10^5,) + ) + + @testset "analysis_callback(sol) for AnalysisCallbackCoupled" begin + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end + end + + @trixi_testset "elixir_advection_extended.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [4.220397559713772e-6], + linf = [3.477948874874848e-5] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -85,21 +134,17 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end -end - -@trixi_testset "elixir_advection_meshview.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_meshview.jl"), - l2=[ - 8.311947673083206e-6, - 8.311947673068427e-6, - ], - linf=[ - 6.627000273318195e-5, - 6.62700027264096e-5, - ], - coverage_override=(maxiters = 10^5,)) - @testset "analysis_callback(sol) for AnalysisCallbackCoupled" begin + @trixi_testset "elixir_advection_extended.jl with polydeg=4" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [5.32996976442737e-7], + linf = [4.1344662966569246e-6], + atol = 1.0e-12, # required to make CI tests pass on macOS + cells_per_dimension = (16, 23), + polydeg = 4, + cfl = 1.4 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -109,47 +154,70 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end -end -@trixi_testset "elixir_advection_extended.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[4.220397559713772e-6], - linf=[3.477948874874848e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end + @testset "elixir_advection_rotated.jl" begin + @trixi_testset "elixir_advection_rotated.jl with α = 0.0" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_rotated.jl"), + # Expected errors are exactly the same as in elixir_advection_basic! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5], + alpha = 0.0 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end -@trixi_testset "elixir_advection_extended.jl with polydeg=4" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[5.32996976442737e-7], - linf=[4.1344662966569246e-6], - atol=1e-12, # required to make CI tests pass on macOS - cells_per_dimension=(16, 23), - polydeg=4, - cfl=1.4) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_rotated.jl with α = 0.1" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_rotated.jl"), + # Expected errors differ only slightly from elixir_advection_basic! + l2 = [8.3122750550501e-6], + linf = [6.626802581322089e-5], + alpha = 0.1 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end + + @trixi_testset "elixir_advection_rotated.jl with α = 0.5 * pi" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_rotated.jl"), + # Expected errors are exactly the same as in elixir_advection_basic! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5], + alpha = 0.5 * pi + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end end -end -@testset "elixir_advection_rotated.jl" begin - @trixi_testset "elixir_advection_rotated.jl with α = 0.0" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_rotated.jl"), - # Expected errors are exactly the same as in elixir_advection_basic! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5], - alpha=0.0) + @trixi_testset "elixir_advection_parallelogram.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_parallelogram.jl"), + # Expected errors are exactly the same as in elixir_advection_basic! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -160,12 +228,12 @@ end end end - @trixi_testset "elixir_advection_rotated.jl with α = 0.1" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_rotated.jl"), - # Expected errors differ only slightly from elixir_advection_basic! - l2=[8.3122750550501e-6], - linf=[6.626802581322089e-5], - alpha=0.1) + @trixi_testset "elixir_advection_waving_flag.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_waving_flag.jl"), + l2 = [0.00018553859900545866], + linf = [0.0016167719118129753] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -176,12 +244,12 @@ end end end - @trixi_testset "elixir_advection_rotated.jl with α = 0.5 * pi" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_rotated.jl"), - # Expected errors are exactly the same as in elixir_advection_basic! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5], - alpha=0.5 * pi) + @trixi_testset "elixir_advection_free_stream.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_free_stream.jl"), + l2 = [6.8925194184204476e-15], + linf = [9.903189379656396e-14] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -191,190 +259,285 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end -end -@trixi_testset "elixir_advection_parallelogram.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_parallelogram.jl"), - # Expected errors are exactly the same as in elixir_advection_basic! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_nonperiodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_nonperiodic.jl"), + l2 = [0.00025552740731641223], + linf = [0.007252625722805939] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_waving_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_waving_flag.jl"), - l2=[0.00018553859900545866], - linf=[0.0016167719118129753]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + l2 = [4.219208035582454e-6], + linf = [3.438434404412494e-5], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_free_stream.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_free_stream.jl"), - l2=[6.8925194184204476e-15], - linf=[9.903189379656396e-14]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart.jl with waving flag mesh" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + l2 = [0.00016265538265929818], + linf = [0.0015194252169410394], + rtol = 5.0e-5, # Higher tolerance to make tests pass in CI (in particular with macOS) + elixir_file = "elixir_advection_waving_flag.jl", + restart_file = "restart_000000021.h5", + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_nonperiodic.jl"), - l2=[0.00025552740731641223], - linf=[0.007252625722805939]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart.jl with free stream mesh" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + l2 = [7.841217436552029e-15], + linf = [1.0857981180834031e-13], + elixir_file = "elixir_advection_free_stream.jl", + restart_file = "restart_000000036.h5", + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_restart.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - l2=[4.219208035582454e-6], - linf=[3.438434404412494e-5], - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulermulti_convergence_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_ec.jl"), + l2 = [ + 1.5123651627525257e-5, + 1.51236516273878e-5, + 2.4544918394022538e-5, + 5.904791661362391e-6, + 1.1809583322724782e-5, + ], + linf = [ + 8.393471747591974e-5, + 8.393471748258108e-5, + 0.00015028562494778797, + 3.504466610437795e-5, + 7.00893322087559e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_restart.jl with waving flag mesh" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - l2=[0.00016265538265929818], - linf=[0.0015194252169410394], - rtol=5.0e-5, # Higher tolerance to make tests pass in CI (in particular with macOS) - elixir_file="elixir_advection_waving_flag.jl", - restart_file="restart_000000021.h5", - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [ + 9.321181253186009e-7, + 1.4181210743438511e-6, + 1.4181210743487851e-6, + 4.824553091276693e-6, + ], + linf = [ + 9.577246529612893e-6, + 1.1707525976012434e-5, + 1.1707525976456523e-5, + 4.8869615580926506e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_restart.jl with free stream mesh" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - l2=[7.841217436552029e-15], - linf=[1.0857981180834031e-13], - elixir_file="elixir_advection_free_stream.jl", - restart_file="restart_000000036.h5", - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end + @testset "elixir_euler_source_terms_rotated.jl" begin + @trixi_testset "elixir_euler_source_terms_rotated.jl with α = 0.0" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_rotated.jl" + ), + # Expected errors are exactly the same as in elixir_euler_source_terms! + l2 = [ + 9.321181253186009e-7, + 1.4181210743438511e-6, + 1.4181210743487851e-6, + 4.824553091276693e-6, + ], + linf = [ + 9.577246529612893e-6, + 1.1707525976012434e-5, + 1.1707525976456523e-5, + 4.8869615580926506e-5, + ], + alpha = 0.0 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end -@trixi_testset "elixir_eulermulti_convergence_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_ec.jl"), - l2=[ - 1.5123651627525257e-5, - 1.51236516273878e-5, - 2.4544918394022538e-5, - 5.904791661362391e-6, - 1.1809583322724782e-5, - ], - linf=[ - 8.393471747591974e-5, - 8.393471748258108e-5, - 0.00015028562494778797, - 3.504466610437795e-5, - 7.00893322087559e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end + @trixi_testset "elixir_euler_source_terms_rotated.jl with α = 0.1" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_rotated.jl" + ), + # Expected errors differ only slightly from elixir_euler_source_terms! + l2 = [ + 9.321188057029291e-7, + 1.3195106906473365e-6, + 1.510307360354032e-6, + 4.82455408101712e-6, + ], + linf = [ + 9.57723626271445e-6, + 1.0480225511866337e-5, + 1.2817828088262928e-5, + 4.886962393513272e-5, + ], + alpha = 0.1 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end -@trixi_testset "elixir_euler_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[ - 9.321181253186009e-7, - 1.4181210743438511e-6, - 1.4181210743487851e-6, - 4.824553091276693e-6, - ], - linf=[ - 9.577246529612893e-6, - 1.1707525976012434e-5, - 1.1707525976456523e-5, - 4.8869615580926506e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_rotated.jl with α = 0.2 * pi" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_rotated.jl" + ), + # Expected errors differ only slightly from elixir_euler_source_terms! + l2 = [ + 9.32127973957391e-7, + 8.477824799744325e-7, + 1.8175286311402784e-6, + 4.824562453521076e-6, + ], + linf = [ + 9.576898420737834e-6, + 5.057704352218195e-6, + 1.635260719945464e-5, + 4.886978754825577e-5, + ], + alpha = 0.2 * pi + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end + + @trixi_testset "elixir_euler_source_terms_rotated.jl with α = 0.5 * pi" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_rotated.jl" + ), + # Expected errors are exactly the same as in elixir_euler_source_terms! + l2 = [ + 9.321181253186009e-7, + 1.4181210743438511e-6, + 1.4181210743487851e-6, + 4.824553091276693e-6, + ], + linf = [ + 9.577246529612893e-6, + 1.1707525976012434e-5, + 1.1707525976456523e-5, + 4.8869615580926506e-5, + ], + alpha = 0.5 * pi + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end end -end -@testset "elixir_euler_source_terms_rotated.jl" begin - @trixi_testset "elixir_euler_source_terms_rotated.jl with α = 0.0" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_rotated.jl"), - # Expected errors are exactly the same as in elixir_euler_source_terms! - l2=[ - 9.321181253186009e-7, - 1.4181210743438511e-6, - 1.4181210743487851e-6, - 4.824553091276693e-6, - ], - linf=[ - 9.577246529612893e-6, - 1.1707525976012434e-5, - 1.1707525976456523e-5, - 4.8869615580926506e-5, - ], - alpha=0.0) + @trixi_testset "elixir_euler_source_terms_parallelogram.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_parallelogram.jl" + ), + l2 = [ + 1.1167802955144833e-5, + 1.0805775514153104e-5, + 1.953188337010932e-5, + 5.5033856574857146e-5, + ], + linf = [ + 8.297006495561199e-5, + 8.663281475951301e-5, + 0.00012264160606778596, + 0.00041818802502024965, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -385,23 +548,25 @@ end end end - @trixi_testset "elixir_euler_source_terms_rotated.jl with α = 0.1" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_rotated.jl"), - # Expected errors differ only slightly from elixir_euler_source_terms! - l2=[ - 9.321188057029291e-7, - 1.3195106906473365e-6, - 1.510307360354032e-6, - 4.82455408101712e-6, - ], - linf=[ - 9.57723626271445e-6, - 1.0480225511866337e-5, - 1.2817828088262928e-5, - 4.886962393513272e-5, - ], - alpha=0.1) + @trixi_testset "elixir_euler_source_terms_waving_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_waving_flag.jl" + ), + l2 = [ + 2.991891317562739e-5, + 3.6063177168283174e-5, + 2.7082941743640572e-5, + 0.00011414695350996946, + ], + linf = [ + 0.0002437454930492855, + 0.0003438936171968887, + 0.00024217622945688078, + 0.001266380414757684, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -412,23 +577,23 @@ end end end - @trixi_testset "elixir_euler_source_terms_rotated.jl with α = 0.2 * pi" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_rotated.jl"), - # Expected errors differ only slightly from elixir_euler_source_terms! - l2=[ - 9.32127973957391e-7, - 8.477824799744325e-7, - 1.8175286311402784e-6, - 4.824562453521076e-6, - ], - linf=[ - 9.576898420737834e-6, - 5.057704352218195e-6, - 1.635260719945464e-5, - 4.886978754825577e-5, - ], - alpha=0.2 * pi) + @trixi_testset "elixir_euler_free_stream.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + l2 = [ + 2.063350241405049e-15, + 1.8571016296925367e-14, + 3.1769447886391905e-14, + 1.4104095258528071e-14, + ], + linf = [ + 1.9539925233402755e-14, + 2.9791447087035294e-13, + 6.502853810985698e-13, + 2.7000623958883807e-13, + ], + atol = 7.0e-13 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -439,23 +604,24 @@ end end end - @trixi_testset "elixir_euler_source_terms_rotated.jl with α = 0.5 * pi" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_rotated.jl"), - # Expected errors are exactly the same as in elixir_euler_source_terms! - l2=[ - 9.321181253186009e-7, - 1.4181210743438511e-6, - 1.4181210743487851e-6, - 4.824553091276693e-6, - ], - linf=[ - 9.577246529612893e-6, - 1.1707525976012434e-5, - 1.1707525976456523e-5, - 4.8869615580926506e-5, - ], - alpha=0.5 * pi) + @trixi_testset "elixir_euler_free_stream.jl with FluxRotated(flux_lax_friedrichs)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + surface_flux = FluxRotated(flux_lax_friedrichs), + l2 = [ + 2.063350241405049e-15, + 1.8571016296925367e-14, + 3.1769447886391905e-14, + 1.4104095258528071e-14, + ], + linf = [ + 1.9539925233402755e-14, + 2.9791447087035294e-13, + 6.502853810985698e-13, + 2.7000623958883807e-13, + ], + atol = 7.0e-13 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -465,600 +631,512 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end -end - -@trixi_testset "elixir_euler_source_terms_parallelogram.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_parallelogram.jl"), - l2=[ - 1.1167802955144833e-5, - 1.0805775514153104e-5, - 1.953188337010932e-5, - 5.5033856574857146e-5, - ], - linf=[ - 8.297006495561199e-5, - 8.663281475951301e-5, - 0.00012264160606778596, - 0.00041818802502024965, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end - -@trixi_testset "elixir_euler_source_terms_waving_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_waving_flag.jl"), - l2=[ - 2.991891317562739e-5, - 3.6063177168283174e-5, - 2.7082941743640572e-5, - 0.00011414695350996946, - ], - linf=[ - 0.0002437454930492855, - 0.0003438936171968887, - 0.00024217622945688078, - 0.001266380414757684, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end - -@trixi_testset "elixir_euler_free_stream.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), - l2=[ - 2.063350241405049e-15, - 1.8571016296925367e-14, - 3.1769447886391905e-14, - 1.4104095258528071e-14, - ], - linf=[ - 1.9539925233402755e-14, - 2.9791447087035294e-13, - 6.502853810985698e-13, - 2.7000623958883807e-13, - ], - atol=7.0e-13) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end - -@trixi_testset "elixir_euler_free_stream.jl with FluxRotated(flux_lax_friedrichs)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), - surface_flux=FluxRotated(flux_lax_friedrichs), - l2=[ - 2.063350241405049e-15, - 1.8571016296925367e-14, - 3.1769447886391905e-14, - 1.4104095258528071e-14, - ], - linf=[ - 1.9539925233402755e-14, - 2.9791447087035294e-13, - 6.502853810985698e-13, - 2.7000623958883807e-13, - ], - atol=7.0e-13) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end - -@trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 2.259440511901724e-6, - 2.3188881559075347e-6, - 2.3188881559568146e-6, - 6.332786324137878e-6, - ], - linf=[ - 1.4987382622067003e-5, - 1.918201192063762e-5, - 1.918201192019353e-5, - 6.052671713430158e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end -@trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.03774907669925568, - 0.02845190575242045, - 0.028262802829412605, - 0.13785915638851698, - ], - linf=[ - 0.3368296929764073, - 0.27644083771519773, - 0.27990039685141377, - 1.1971436487402016, - ], - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 2.259440511901724e-6, + 2.3188881559075347e-6, + 2.3188881559568146e-6, + 6.332786324137878e-6, + ], + linf = [ + 1.4987382622067003e-5, + 1.918201192063762e-5, + 1.918201192019353e-5, + 6.052671713430158e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 3.69856202e-01, - 2.35242180e-01, - 2.41444928e-01, - 1.28807120e+00, - ], - linf=[ - 1.82786223e+00, - 1.30452904e+00, - 1.40347257e+00, - 6.21791658e+00, - ], - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.03774907669925568, + 0.02845190575242045, + 0.028262802829412605, + 0.13785915638851698, + ], + linf = [ + 0.3368296929764073, + 0.27644083771519773, + 0.27990039685141377, + 1.1971436487402016, + ], + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave_sc_subcell.jl (local bounds)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_sedov_blast_wave_sc_subcell.jl"), - l2=[ - 0.6337774834710513, - 0.30377119245852724, - 0.3111372568571772, - 1.2976221893997268, - ], - linf=[ - 2.2064877103138207, - 1.541067099687334, - 1.5487587769900337, - 6.271271639873466, - ], - tspan=(0.0, 0.5)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 10000 + @trixi_testset "elixir_euler_sedov.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 3.69856202e-1, + 2.3524218e-1, + 2.41444928e-1, + 1.2880712e+0, + ], + linf = [ + 1.82786223e+0, + 1.30452904e+0, + 1.40347257e+0, + 6.21791658e+0, + ], + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave_sc_subcell.jl (global bounds)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_sedov_blast_wave_sc_subcell.jl"), - positivity_variables_cons=["rho"], - positivity_variables_nonlinear=[pressure], - local_twosided_variables_cons=[], - local_onesided_variables_nonlinear=[], - l2=[ - 0.7869912572385168, - 0.39170886758882073, - 0.39613257454431977, - 1.2951760266455101, - ], - linf=[ - 5.156044534854053, - 3.6261667239538986, - 3.1807681416546085, - 6.3028422220287235, - ], - tspan=(0.0, 0.5)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 10000 + @trixi_testset "elixir_euler_sedov_blast_wave_sc_subcell.jl (local bounds)" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_sedov_blast_wave_sc_subcell.jl" + ), + l2 = [ + 0.6337774834710513, + 0.30377119245852724, + 0.3111372568571772, + 1.2976221893997268, + ], + linf = [ + 2.2064877103138207, + 1.541067099687334, + 1.5487587769900337, + 6.271271639873466, + ], + tspan = (0.0, 0.5) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 10000 + end end -end -@trixi_testset "elixir_euler_rayleigh_taylor_instability.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_rayleigh_taylor_instability.jl"), - l2=[ - 0.06365630515019809, 0.007166887172039836, - 0.0028787103533600804, 0.010247678008197966, - ], - linf=[ - 0.47992143569849377, 0.02459548251933757, - 0.02059810091623976, 0.0319077000843877, - ], - cells_per_dimension=(8, 8), - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave_sc_subcell.jl (global bounds)" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_sedov_blast_wave_sc_subcell.jl" + ), + positivity_variables_cons = ["rho"], + positivity_variables_nonlinear = [pressure], + local_twosided_variables_cons = [], + local_onesided_variables_nonlinear = [], + l2 = [ + 0.7869912572385168, + 0.39170886758882073, + 0.39613257454431977, + 1.2951760266455101, + ], + linf = [ + 5.156044534854053, + 3.6261667239538986, + 3.1807681416546085, + 6.3028422220287235, + ], + tspan = (0.0, 0.5) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 10000 + end end -end -@trixi_testset "elixir_euler_warm_bubble.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_warm_bubble.jl"), - l2=[ - 0.00019387402388722496, - 0.03086514388623955, - 0.04541427917165, - 43.892826583444716, - ], - linf=[ - 0.0015942305974430138, - 0.17449778969139373, - 0.3729704262394843, - 307.6706958565337, - ], - cells_per_dimension=(32, 16), - tspan=(0.0, 10.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 100 + @trixi_testset "elixir_euler_rayleigh_taylor_instability.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_rayleigh_taylor_instability.jl" + ), + l2 = [ + 0.06365630515019809, 0.007166887172039836, + 0.0028787103533600804, 0.010247678008197966, + ], + linf = [ + 0.47992143569849377, 0.02459548251933757, + 0.02059810091623976, 0.0319077000843877, + ], + cells_per_dimension = (8, 8), + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulerpolytropic_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulerpolytropic_convergence.jl"), - l2=[ - 0.00166898321776379, 0.00259202637930991, - 0.0032810744946276406, - ], - linf=[ - 0.010994883201888683, 0.013309526619369905, - 0.020080326611175536, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_warm_bubble.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_warm_bubble.jl" + ), + l2 = [ + 0.00019387402388722496, + 0.03086514388623955, + 0.04541427917165, + 43.892826583444716, + ], + linf = [ + 0.0015942305974430138, + 0.17449778969139373, + 0.3729704262394843, + 307.6706958565337, + ], + cells_per_dimension = (32, 16), + tspan = (0.0, 10.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 100 + end end -end -@trixi_testset "elixir_eulerpolytropic_convergence.jl with FluxHLL(min_max_speed_naive)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulerpolytropic_convergence.jl"), - solver=DGSEM(polydeg = 3, - surface_flux = FluxHLL(min_max_speed_naive), - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)), - l2=[ - 0.001668882059653298, 0.002592168188567654, - 0.0032809503514328307, - ], - linf=[ - 0.01099467966437917, 0.013311978456333584, - 0.020080117011337606, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulerpolytropic_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulerpolytropic_convergence.jl"), + l2 = [ + 0.00166898321776379, 0.00259202637930991, + 0.0032810744946276406, + ], + linf = [ + 0.010994883201888683, 0.013309526619369905, + 0.020080326611175536, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulerpolytropic_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulerpolytropic_ec.jl"), - l2=[ - 0.03647890611450939, - 0.025284915444045052, - 0.025340697771609126, - ], - linf=[ - 0.32516731565355583, - 0.37509762516540046, - 0.29812843284727336, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulerpolytropic_convergence.jl with FluxHLL(min_max_speed_naive)" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulerpolytropic_convergence.jl" + ), + solver = DGSEM( + polydeg = 3, + surface_flux = FluxHLL(min_max_speed_naive), + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) + ), + l2 = [ + 0.001668882059653298, 0.002592168188567654, + 0.0032809503514328307, + ], + linf = [ + 0.01099467966437917, 0.013311978456333584, + 0.020080117011337606, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulerpolytropic_isothermal_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulerpolytropic_isothermal_wave.jl"), - l2=[ - 0.004998778512795407, 0.004998916021367992, - 8.991558055435833e-17, - ], - linf=[ - 0.010001103632831354, 0.010051165055185603, - 7.60697457718599e-16, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulerpolytropic_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulerpolytropic_ec.jl"), + l2 = [ + 0.03647890611450939, + 0.025284915444045052, + 0.025340697771609126, + ], + linf = [ + 0.32516731565355583, + 0.37509762516540046, + 0.29812843284727336, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_eulerpolytropic_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulerpolytropic_wave.jl"), - l2=[ - 0.23642871172548174, 0.2090519382039672, - 8.778842676292274e-17, - ], - linf=[ - 0.4852276879687425, 0.25327870807625175, - 5.533921691832115e-16, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulerpolytropic_isothermal_wave.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulerpolytropic_isothermal_wave.jl" + ), + l2 = [ + 0.004998778512795407, 0.004998916021367992, + 8.991558055435833e-17, + ], + linf = [ + 0.010001103632831354, 0.010051165055185603, + 7.60697457718599e-16, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_hypdiff_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), - l2=[0.8799744480157664, 0.8535008397034816, 0.7851383019164209], - linf=[1.0771947577311836, 1.9143913544309838, 2.149549109115789], - tspan=(0.0, 0.1), - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_eulerpolytropic_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulerpolytropic_wave.jl"), + l2 = [ + 0.23642871172548174, 0.2090519382039672, + 8.778842676292274e-17, + ], + linf = [ + 0.4852276879687425, 0.25327870807625175, + 5.533921691832115e-16, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_hypdiff_harmonic_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_hypdiff_harmonic_nonperiodic.jl"), - l2=[ - 0.19357947606509474, - 0.47041398037626814, - 0.4704139803762686, - ], - linf=[ - 0.35026352556630114, - 0.8344372248051408, - 0.8344372248051408, - ], - tspan=(0.0, 0.1), - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_hypdiff_nonperiodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), + l2 = [0.8799744480157664, 0.8535008397034816, 0.7851383019164209], + linf = [1.0771947577311836, 1.9143913544309838, 2.149549109115789], + tspan = (0.0, 0.1), + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end -end -@trixi_testset "elixir_mhd_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), - l2=[0.04937480811868297, 0.06117033019988596, - 0.060998028674664716, 0.03155145889799417, - 0.2319175391388658, 0.02476283192966346, - 0.024483244374818587, 0.035439957899127385, - 0.0016022148194667542], - linf=[0.24749024430983746, 0.2990608279625713, - 0.3966937932860247, 0.22265033744519683, - 0.9757376320946505, 0.12123736788315098, - 0.12837436699267113, 0.17793825293524734, - 0.03460761690059514], - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_hypdiff_harmonic_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_hypdiff_harmonic_nonperiodic.jl" + ), + l2 = [ + 0.19357947606509474, + 0.47041398037626814, + 0.4704139803762686, + ], + linf = [ + 0.35026352556630114, + 0.8344372248051408, + 0.8344372248051408, + ], + tspan = (0.0, 0.1), + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[0.02890769490562535, 0.0062599448721613205, - 0.005650300017676721, 0.007334415940022972, - 0.00490446035599909, 0.007202284100220619, - 0.007003258686714405, 0.006734267830082687, - 0.004253003868791559], - linf=[0.17517380432288565, 0.06197353710696667, - 0.038494840938641646, 0.05293345499813148, - 0.03817506476831778, 0.042847170999492534, - 0.03761563456810613, 0.048184237474911844, - 0.04114666955364693], - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), + l2 = [ + 0.04937480811868297, 0.06117033019988596, + 0.060998028674664716, 0.03155145889799417, + 0.2319175391388658, 0.02476283192966346, + 0.024483244374818587, 0.035439957899127385, + 0.0016022148194667542, + ], + linf = [ + 0.24749024430983746, 0.2990608279625713, + 0.3966937932860247, 0.22265033744519683, + 0.9757376320946505, 0.12123736788315098, + 0.12837436699267113, 0.17793825293524734, + 0.03460761690059514, + ], + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.0017286908591070864, - 0.025585037307655684, - 0.028374244567802766, - 6.274146767730866e-5, - ], - linf=[ - 0.012973752001194772, - 0.10829375385832263, - 0.15832858475438094, - 0.00018196759554722775, - ], - tspan=(0.0, 0.05)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 0.02890769490562535, 0.0062599448721613205, + 0.005650300017676721, 0.007334415940022972, + 0.00490446035599909, 0.007202284100220619, + 0.007003258686714405, 0.006734267830082687, + 0.004253003868791559, + ], + linf = [ + 0.17517380432288565, 0.06197353710696667, + 0.038494840938641646, 0.05293345499813148, + 0.03817506476831778, 0.042847170999492534, + 0.03761563456810613, 0.048184237474911844, + 0.04114666955364693, + ], + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 0.7920927046419308, - 9.92129670988898e-15, - 1.0118635033124588e-14, - 0.7920927046419308, - ], - linf=[ - 2.408429868800133, - 5.5835419986809516e-14, - 5.448874313931364e-14, - 2.4084298688001335, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.0017286908591070864, + 0.025585037307655684, + 0.028374244567802766, + 6.274146767730866e-5, + ], + linf = [ + 0.012973752001194772, + 0.10829375385832263, + 0.15832858475438094, + 0.00018196759554722775, + ], + tspan = (0.0, 0.05) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_ec_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec_shockcapturing.jl"), - l2=[0.0364192725149364, 0.0426667193422069, 0.04261673001449095, - 0.025884071405646924, - 0.16181626564020496, 0.017346518770783536, - 0.017291573200291104, 0.026856206495339655, - 0.0007443858043598808], - linf=[0.25144373906033013, 0.32881947152723745, - 0.3053266801502693, 0.20989755319972866, - 0.9927517314507455, 0.1105172121361323, 0.1257708104676617, - 0.1628334844841588, - 0.02624301627479052]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 0.7920927046419308, + 9.92129670988898e-15, + 1.0118635033124588e-14, + 0.7920927046419308, + ], + linf = [ + 2.408429868800133, + 5.5835419986809516e-14, + 5.448874313931364e-14, + 2.4084298688001335, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_coupled.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_coupled.jl"), - l2=[ - 1.0743426980507015e-7, 0.030901698521864966, - 0.030901698662039206, 0.04370160129981656, - 8.259193827852516e-8, 0.03090169908364623, - 0.030901699039770684, 0.04370160128147447, - 8.735923402748945e-9, 1.0743426996067106e-7, - 0.03090169852186498, 0.030901698662039206, - 0.04370160129981657, 8.259193829690747e-8, - 0.03090169908364624, 0.030901699039770726, - 0.04370160128147445, 8.73592340076897e-9, - ], - linf=[ - 9.021023431587949e-7, 0.043701454182710486, - 0.043701458294527366, 0.061803146322536154, - 9.487023335807976e-7, 0.043701561010342616, - 0.04370147392153734, 0.06180318786081025, - 3.430673132525334e-8, 9.02102342825728e-7, - 0.043701454182710764, 0.043701458294525895, - 0.06180314632253597, 9.487023254761695e-7, - 0.04370156101034084, 0.04370147392153745, - 0.06180318786081015, 3.430672973680963e-8, - ], - coverage_override=(maxiters = 10^5,)) - - @testset "analysis_callback(sol) for AnalysisCallbackCoupled" begin - errors = analysis_callback(sol) - @test errors.l2≈[ - 1.0743426980507015e-7, 0.030901698521864966, 0.030901698662039206, - 0.04370160129981656, 8.259193827852516e-8, 0.03090169908364623, - 0.030901699039770684, 0.04370160128147447, 8.735923402748945e-9, - 1.0743426996067106e-7, 0.03090169852186498, 0.030901698662039206, - 0.04370160129981657, 8.259193829690747e-8, 0.03090169908364624, - 0.030901699039770726, 0.04370160128147445, 8.73592340076897e-9, - ] rtol=1.0e-4 - @test errors.linf≈[ - 9.021023431587949e-7, 0.043701454182710486, 0.043701458294527366, - 0.061803146322536154, 9.487023335807976e-7, 0.043701561010342616, - 0.04370147392153734, 0.06180318786081025, 3.430673132525334e-8, - 9.02102342825728e-7, 0.043701454182710764, 0.043701458294525895, - 0.06180314632253597, 9.487023254761695e-7, 0.04370156101034084, - 0.04370147392153745, 0.06180318786081015, 3.430672973680963e-8, - ] rtol=1.0e-4 + @trixi_testset "elixir_mhd_ec_shockcapturing.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec_shockcapturing.jl"), + l2 = [ + 0.0364192725149364, 0.0426667193422069, 0.04261673001449095, + 0.025884071405646924, + 0.16181626564020496, 0.017346518770783536, + 0.017291573200291104, 0.026856206495339655, + 0.0007443858043598808, + ], + linf = [ + 0.25144373906033013, 0.32881947152723745, + 0.3053266801502693, 0.20989755319972866, + 0.9927517314507455, 0.1105172121361323, 0.1257708104676617, + 0.1628334844841588, + 0.02624301627479052, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -1068,7 +1146,63 @@ end @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 end end -end + + @trixi_testset "elixir_mhd_coupled.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_coupled.jl"), + l2 = [ + 1.0743426980507015e-7, 0.030901698521864966, + 0.030901698662039206, 0.04370160129981656, + 8.259193827852516e-8, 0.03090169908364623, + 0.030901699039770684, 0.04370160128147447, + 8.735923402748945e-9, 1.0743426996067106e-7, + 0.03090169852186498, 0.030901698662039206, + 0.04370160129981657, 8.259193829690747e-8, + 0.03090169908364624, 0.030901699039770726, + 0.04370160128147445, 8.73592340076897e-9, + ], + linf = [ + 9.021023431587949e-7, 0.043701454182710486, + 0.043701458294527366, 0.061803146322536154, + 9.487023335807976e-7, 0.043701561010342616, + 0.04370147392153734, 0.06180318786081025, + 3.430673132525334e-8, 9.02102342825728e-7, + 0.043701454182710764, 0.043701458294525895, + 0.06180314632253597, 9.487023254761695e-7, + 0.04370156101034084, 0.04370147392153745, + 0.06180318786081015, 3.430672973680963e-8, + ], + coverage_override = (maxiters = 10^5,) + ) + + @testset "analysis_callback(sol) for AnalysisCallbackCoupled" begin + errors = analysis_callback(sol) + @test errors.l2 ≈ [ + 1.0743426980507015e-7, 0.030901698521864966, 0.030901698662039206, + 0.04370160129981656, 8.259193827852516e-8, 0.03090169908364623, + 0.030901699039770684, 0.04370160128147447, 8.735923402748945e-9, + 1.0743426996067106e-7, 0.03090169852186498, 0.030901698662039206, + 0.04370160129981657, 8.259193829690747e-8, 0.03090169908364624, + 0.030901699039770726, 0.04370160128147445, 8.73592340076897e-9, + ] rtol = 1.0e-4 + @test errors.linf ≈ [ + 9.021023431587949e-7, 0.043701454182710486, 0.043701458294527366, + 0.061803146322536154, 9.487023335807976e-7, 0.043701561010342616, + 0.04370147392153734, 0.06180318786081025, 3.430673132525334e-8, + 9.02102342825728e-7, 0.043701454182710764, 0.043701458294525895, + 0.06180314632253597, 9.487023254761695e-7, 0.04370156101034084, + 0.04370147392153745, 0.06180318786081015, 3.430672973680963e-8, + ] rtol = 1.0e-4 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end + end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_structured_3d.jl b/test/test_structured_3d.jl index a52c459d6b..4847925d86 100644 --- a/test/test_structured_3d.jl +++ b/test/test_structured_3d.jl @@ -12,329 +12,377 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "Structured mesh" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[0.00016263963870641478], - linf=[0.0014537194925779984]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [0.00016263963870641478], + linf = [0.0014537194925779984] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_free_stream.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_free_stream.jl"), - l2=[1.2908196366970896e-14], - linf=[1.0262901639634947e-12], - atol=8e-13,) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_free_stream.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_free_stream.jl"), + l2 = [1.2908196366970896e-14], + linf = [1.0262901639634947e-12], + atol = 8.0e-13, + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_nonperiodic_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_nonperiodic_curved.jl"), - l2=[0.0004483892474201268], - linf=[0.009201820593762955]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_nonperiodic_curved.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_nonperiodic_curved.jl" + ), + l2 = [0.0004483892474201268], + linf = [0.009201820593762955] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_restart.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - l2=[0.0025903889347585777], - linf=[0.018407576968841655], - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + l2 = [0.0025903889347585777], + linf = [0.018407576968841655], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[ - 0.010385936842224346, - 0.009776048833895767, - 0.00977604883389591, - 0.009776048833895733, - 0.01506687097416608, - ], - linf=[ - 0.03285848350791731, - 0.0321792316408982, - 0.032179231640894645, - 0.032179231640895534, - 0.0655408023333299, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [ + 0.010385936842224346, + 0.009776048833895767, + 0.00977604883389591, + 0.009776048833895733, + 0.01506687097416608, + ], + linf = [ + 0.03285848350791731, + 0.0321792316408982, + 0.032179231640894645, + 0.032179231640895534, + 0.0655408023333299, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), - l2=[ - 2.8815700334367128e-15, - 9.361915278236651e-15, - 9.95614203619935e-15, - 1.6809941842374106e-14, - 1.4815037041566735e-14, - ], - linf=[ - 4.1300296516055823e-14, - 2.0444756998472258e-13, - 1.0133560657266116e-13, - 2.0627943797535409e-13, - 2.8954616482224083e-13, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + l2 = [ + 2.8815700334367128e-15, + 9.361915278236651e-15, + 9.95614203619935e-15, + 1.6809941842374106e-14, + 1.4815037041566735e-14, + ], + linf = [ + 4.1300296516055823e-14, + 2.0444756998472258e-13, + 1.0133560657266116e-13, + 2.0627943797535409e-13, + 2.8954616482224083e-13, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream.jl with FluxRotated(flux_lax_friedrichs)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), - surface_flux=FluxRotated(flux_lax_friedrichs), - l2=[ - 2.8815700334367128e-15, - 9.361915278236651e-15, - 9.95614203619935e-15, - 1.6809941842374106e-14, - 1.4815037041566735e-14, - ], - linf=[ - 4.1300296516055823e-14, - 2.0444756998472258e-13, - 1.0133560657266116e-13, - 2.0627943797535409e-13, - 2.8954616482224083e-13, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream.jl with FluxRotated(flux_lax_friedrichs)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + surface_flux = FluxRotated(flux_lax_friedrichs), + l2 = [ + 2.8815700334367128e-15, + 9.361915278236651e-15, + 9.95614203619935e-15, + 1.6809941842374106e-14, + 1.4815037041566735e-14, + ], + linf = [ + 4.1300296516055823e-14, + 2.0444756998472258e-13, + 1.0133560657266116e-13, + 2.0627943797535409e-13, + 2.8954616482224083e-13, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonperiodic_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic_curved.jl"), - l2=[ - 0.0032940531178824463, - 0.003275679548217804, - 0.0030020672748714084, - 0.00324007343451744, - 0.005721986362580164, - ], - linf=[ - 0.03156756290660656, - 0.033597629023726316, - 0.02095783702361409, - 0.03353574465232212, - 0.05873635745032857, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic_curved.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic_curved.jl" + ), + l2 = [ + 0.0032940531178824463, + 0.003275679548217804, + 0.0030020672748714084, + 0.00324007343451744, + 0.005721986362580164, + ], + linf = [ + 0.03156756290660656, + 0.033597629023726316, + 0.02095783702361409, + 0.03353574465232212, + 0.05873635745032857, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.011367083018614027, - 0.007022020327490176, - 0.006759580335962235, - 0.006820337637760632, - 0.02912659127566544, - ], - linf=[ - 0.2761764220925329, - 0.20286331858055706, - 0.18763944865434593, - 0.19313636558790004, - 0.707563913727584, - ], - tspan=(0.0, 0.25), - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.011367083018614027, + 0.007022020327490176, + 0.006759580335962235, + 0.006820337637760632, + 0.02912659127566544, + ], + linf = [ + 0.2761764220925329, + 0.20286331858055706, + 0.18763944865434593, + 0.19313636558790004, + 0.707563913727584, + ], + tspan = (0.0, 0.25), + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 5.30310390e-02, - 2.53167260e-02, - 2.64276438e-02, - 2.52195992e-02, - 3.56830295e-01, - ], - linf=[ - 6.16356950e-01, - 2.50600049e-01, - 2.74796377e-01, - 2.46448217e-01, - 4.77888479e+00, - ], - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 5.3031039e-2, + 2.5316726e-2, + 2.64276438e-2, + 2.52195992e-2, + 3.56830295e-1, + ], + linf = [ + 6.1635695e-1, + 2.50600049e-1, + 2.74796377e-1, + 2.46448217e-1, + 4.77888479e+0, + ], + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), - l2=[0.009082353036644902, 0.007128360240528109, - 0.006970330025996491, 0.006898850266874514, - 0.03302008823756457, 0.003203389099143526, - 0.003077498677885352, 0.0030740006760477624, - 4.192129696970217e-5], - linf=[0.2883946030582689, 0.25956437344015054, - 0.2614364943543665, 0.24617277938134657, - 1.1370443512475847, 0.1278041831463388, 0.13347391885068594, - 0.1457563463643099, - 0.0021174246048172563], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), + l2 = [ + 0.009082353036644902, 0.007128360240528109, + 0.006970330025996491, 0.006898850266874514, + 0.03302008823756457, 0.003203389099143526, + 0.003077498677885352, 0.0030740006760477624, + 4.192129696970217e-5, + ], + linf = [ + 0.2883946030582689, 0.25956437344015054, + 0.2614364943543665, 0.24617277938134657, + 1.1370443512475847, 0.1278041831463388, 0.13347391885068594, + 0.1457563463643099, + 0.0021174246048172563, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[0.003015476175153681, 0.00145499403283373, - 0.0009125744757935803, 0.0017703080480578979, - 0.0013046447673965966, 0.0014564863387645508, - 0.0013332311430907598, 0.001647832598455728, - 0.0013647609788548722], - linf=[0.027510637768610846, 0.02797062834945721, - 0.01274249949295704, 0.038940694415543736, - 0.02200825678588325, 0.03167600959583505, - 0.021420957993862344, 0.03386589835999665, - 0.01888303191983353], - # Use same polydeg as everything else to prevent long compile times in CI - coverage_override=(polydeg = 3,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 0.003015476175153681, 0.00145499403283373, + 0.0009125744757935803, 0.0017703080480578979, + 0.0013046447673965966, 0.0014564863387645508, + 0.0013332311430907598, 0.001647832598455728, + 0.0013647609788548722, + ], + linf = [ + 0.027510637768610846, 0.02797062834945721, + 0.01274249949295704, 0.038940694415543736, + 0.02200825678588325, 0.03167600959583505, + 0.021420957993862344, 0.03386589835999665, + 0.01888303191983353, + ], + # Use same polydeg as everything else to prevent long compile times in CI + coverage_override = (polydeg = 3,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl with flux_lax_friedrichs" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[0.003047854479955232, 0.0014572199588782184, - 0.0009093737183251411, 0.0017937548694553895, - 0.0013010437110755424, 0.0014545607744895874, - 0.001328514015121245, 0.001671342529206066, - 0.0013653963058149186], - linf=[0.027719103797310463, 0.027570111789910784, - 0.012561901006903103, 0.03903568568480584, - 0.021311996934554767, 0.03154849824135775, - 0.020996033645485412, 0.03403185137382961, - 0.019488952445771597], - surface_flux=(flux_lax_friedrichs, flux_nonconservative_powell), - # Use same polydeg as everything else to prevent long compile times in CI - coverage_override=(polydeg = 3,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl with flux_lax_friedrichs" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 0.003047854479955232, 0.0014572199588782184, + 0.0009093737183251411, 0.0017937548694553895, + 0.0013010437110755424, 0.0014545607744895874, + 0.001328514015121245, 0.001671342529206066, + 0.0013653963058149186, + ], + linf = [ + 0.027719103797310463, 0.027570111789910784, + 0.012561901006903103, 0.03903568568480584, + 0.021311996934554767, 0.03154849824135775, + 0.020996033645485412, 0.03403185137382961, + 0.019488952445771597, + ], + surface_flux = (flux_lax_friedrichs, flux_nonconservative_powell), + # Use same polydeg as everything else to prevent long compile times in CI + coverage_override = (polydeg = 3,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_ec_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec_shockcapturing.jl"), - l2=[0.009352631220872144, 0.008058649103542618, - 0.008027041293333663, 0.008071417851552725, - 0.034909149665869485, 0.00393019428600812, - 0.0039219074393817, 0.003906321245184237, - 4.197255300781248e-5], - linf=[0.30749098250807516, 0.2679008863509767, - 0.271243087484388, 0.26545396569129537, - 0.9620950892188596, 0.18163281157498123, - 0.15995708312378454, 0.17918221526906408, - 0.015138346608166353], - tspan=(0.0, 0.25), - # Use same polydeg as everything else to prevent long compile times in CI - coverage_override=(polydeg = 3,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ec_shockcapturing.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec_shockcapturing.jl"), + l2 = [ + 0.009352631220872144, 0.008058649103542618, + 0.008027041293333663, 0.008071417851552725, + 0.034909149665869485, 0.00393019428600812, + 0.0039219074393817, 0.003906321245184237, + 4.197255300781248e-5, + ], + linf = [ + 0.30749098250807516, 0.2679008863509767, + 0.271243087484388, 0.26545396569129537, + 0.9620950892188596, 0.18163281157498123, + 0.15995708312378454, 0.17918221526906408, + 0.015138346608166353, + ], + tspan = (0.0, 0.25), + # Use same polydeg as everything else to prevent long compile times in CI + coverage_override = (polydeg = 3,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn rm(outdir, recursive = true) diff --git a/test/test_t8code_2d.jl b/test/test_t8code_2d.jl index c1fcc35521..e012530c71 100644 --- a/test/test_t8code_2d.jl +++ b/test/test_t8code_2d.jl @@ -13,352 +13,402 @@ isdir(outdir) && rm(outdir, recursive = true) mkdir(outdir) @testset "T8codeMesh2D" begin -#! format: noindent + #! format: noindent -@trixi_testset "test save_mesh_file" begin - @test_throws Exception begin - # Save mesh file support will be added in the future. The following - # lines of code are here for satisfying code coverage. + @trixi_testset "test save_mesh_file" begin + @test_throws Exception begin + # Save mesh file support will be added in the future. The following + # lines of code are here for satisfying code coverage. - # Create dummy mesh. - mesh = T8codeMesh((1, 1), polydeg = 1, - mapping = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)), - initial_refinement_level = 1) + # Create dummy mesh. + mesh = T8codeMesh( + (1, 1), polydeg = 1, + mapping = Trixi.coordinates2mapping((-1.0, -1.0), (1.0, 1.0)), + initial_refinement_level = 1 + ) - # This call throws an error. - Trixi.save_mesh_file(mesh, "dummy") + # This call throws an error. + Trixi.save_mesh_file(mesh, "dummy") + end end -end -@trixi_testset "test load mesh from path" begin - mktempdir() do path - @test_throws "Unknown file extension: .unknown_ext" begin - mesh = T8codeMesh(touch(joinpath(path, "dummy.unknown_ext")), 2) + @trixi_testset "test load mesh from path" begin + mktempdir() do path + @test_throws "Unknown file extension: .unknown_ext" begin + mesh = T8codeMesh(touch(joinpath(path, "dummy.unknown_ext")), 2) + end end end -end -@trixi_testset "test check_for_negative_volumes" begin - @test_throws "Discovered negative volumes" begin - # Unstructured mesh with six cells which have left-handed node ordering. - mesh_file = Trixi.download("https://gist.githubusercontent.com/jmark/bfe0d45f8e369298d6cc637733819013/raw/cecf86edecc736e8b3e06e354c494b2052d41f7a/rectangle_with_negative_volumes.msh", - joinpath(EXAMPLES_DIR, - "rectangle_with_negative_volumes.msh")) + @trixi_testset "test check_for_negative_volumes" begin + @test_throws "Discovered negative volumes" begin + # Unstructured mesh with six cells which have left-handed node ordering. + mesh_file = Trixi.download( + "https://gist.githubusercontent.com/jmark/bfe0d45f8e369298d6cc637733819013/raw/cecf86edecc736e8b3e06e354c494b2052d41f7a/rectangle_with_negative_volumes.msh", + joinpath( + EXAMPLES_DIR, + "rectangle_with_negative_volumes.msh" + ) + ) - # This call should throw a warning about negative volumes detected. - mesh = T8codeMesh(mesh_file, 2) + # This call should throw a warning about negative volumes detected. + mesh = T8codeMesh(mesh_file, 2) + end end -end -@trixi_testset "test t8code mesh from p4est connectivity" begin - @test begin - # Here we use the connectivity constructor from `P4est.jl` since the - # method dispatch works only on `Ptr{p4est_connectivity}` which - # actually is `Ptr{P4est.LibP4est.p4est_connectivity}`. - conn = Trixi.P4est.LibP4est.p4est_connectivity_new_brick(2, 3, 1, 1) - mesh = T8codeMesh(conn) - all(size(mesh.tree_node_coordinates) .== (2, 2, 2, 6)) + @trixi_testset "test t8code mesh from p4est connectivity" begin + @test begin + # Here we use the connectivity constructor from `P4est.jl` since the + # method dispatch works only on `Ptr{p4est_connectivity}` which + # actually is `Ptr{P4est.LibP4est.p4est_connectivity}`. + conn = Trixi.P4est.LibP4est.p4est_connectivity_new_brick(2, 3, 1, 1) + mesh = T8codeMesh(conn) + all(size(mesh.tree_node_coordinates) .== (2, 2, 2, 6)) + end end -end -@trixi_testset "test t8code mesh from ABAQUS HOHQMesh file" begin - @test begin - # Unstructured ABAQUS mesh file created with HOHQMesh.. - file_path = Trixi.download("https://gist.githubusercontent.com/jmark/9e0da4306e266617eeb19bc56b0e7feb/raw/e6856e1deb648a807f6bb6d6dcacff9e55d94e2a/round_2d_tank.inp", - joinpath(EXAMPLES_DIR, "round_2d_tank.inp")) - mesh = T8codeMesh(file_path, 2) - all(size(mesh.tree_node_coordinates) .== (2, 4, 4, 340)) + @trixi_testset "test t8code mesh from ABAQUS HOHQMesh file" begin + @test begin + # Unstructured ABAQUS mesh file created with HOHQMesh.. + file_path = Trixi.download( + "https://gist.githubusercontent.com/jmark/9e0da4306e266617eeb19bc56b0e7feb/raw/e6856e1deb648a807f6bb6d6dcacff9e55d94e2a/round_2d_tank.inp", + joinpath(EXAMPLES_DIR, "round_2d_tank.inp") + ) + mesh = T8codeMesh(file_path, 2) + all(size(mesh.tree_node_coordinates) .== (2, 4, 4, 340)) + end end -end -@trixi_testset "elixir_advection_basic.jl" begin - # This test is identical to the one in `test_p4est_2d.jl`. - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_basic.jl" begin + # This test is identical to the one in `test_p4est_2d.jl`. + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_nonconforming_flag.jl" begin - # This test is identical to the one in `test_p4est_2d.jl`. - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_nonconforming_flag.jl"), - l2=[3.198940059144588e-5], - linf=[0.00030636069494005547]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_nonconforming_flag.jl" begin + # This test is identical to the one in `test_p4est_2d.jl`. + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_nonconforming_flag.jl" + ), + l2 = [3.198940059144588e-5], + linf = [0.00030636069494005547] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_unstructured_flag.jl" begin - # This test is identical to the one in `test_p4est_2d.jl`. - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_unstructured_flag.jl"), - l2=[0.0005379687442422346], - linf=[0.007438525029884735]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_unstructured_flag.jl" begin + # This test is identical to the one in `test_p4est_2d.jl`. + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_unstructured_flag.jl"), + l2 = [0.0005379687442422346], + linf = [0.007438525029884735] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr_unstructured_flag.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_unstructured_flag.jl"), - l2=[0.002019623611753929], - linf=[0.03542375961299987], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_unstructured_flag.jl" + ), + l2 = [0.002019623611753929], + linf = [0.03542375961299987], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr_solution_independent.jl" begin - # This test is identical to the one in `test_p4est_2d.jl`. - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_solution_independent.jl"), - # Expected errors are exactly the same as with StructuredMesh! - l2=[4.949660644033807e-5], - linf=[0.0004867846262313763], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_solution_independent.jl" begin + # This test is identical to the one in `test_p4est_2d.jl`. + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_solution_independent.jl" + ), + # Expected errors are exactly the same as with StructuredMesh! + l2 = [4.949660644033807e-5], + linf = [0.0004867846262313763], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin - # This test is identical to the one in `test_p4est_2d.jl`. - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), - l2=[ - 0.0034516244508588046, - 0.0023420334036925493, - 0.0024261923964557187, - 0.004731710454271893, - ], - linf=[ - 0.04155789011775046, - 0.024772109862748914, - 0.03759938693042297, - 0.08039824959535657, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin + # This test is identical to the one in `test_p4est_2d.jl`. + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" + ), + l2 = [ + 0.0034516244508588046, + 0.0023420334036925493, + 0.0024261923964557187, + 0.004731710454271893, + ], + linf = [ + 0.04155789011775046, + 0.024772109862748914, + 0.03759938693042297, + 0.08039824959535657, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream.jl" begin - # This test is identical to the one in `test_p4est_2d.jl`. - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), - l2=[ - 2.063350241405049e-15, - 1.8571016296925367e-14, - 3.1769447886391905e-14, - 1.4104095258528071e-14, - ], - linf=[1.9539925233402755e-14, 2e-12, 4.8e-12, 4e-12], - atol=2.0e-12,) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream.jl" begin + # This test is identical to the one in `test_p4est_2d.jl`. + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + l2 = [ + 2.063350241405049e-15, + 1.8571016296925367e-14, + 3.1769447886391905e-14, + 1.4104095258528071e-14, + ], + linf = [1.9539925233402755e-14, 2.0e-12, 4.8e-12, 4.0e-12], + atol = 2.0e-12, + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing_ec.jl" begin - # This test is identical to the one in `test_p4est_2d.jl`. - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_ec.jl"), - l2=[ - 9.53984675e-02, - 1.05633455e-01, - 1.05636158e-01, - 3.50747237e-01, - ], - linf=[ - 2.94357464e-01, - 4.07893014e-01, - 3.97334516e-01, - 1.08142520e+00, - ], - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing_ec.jl" begin + # This test is identical to the one in `test_p4est_2d.jl`. + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_ec.jl"), + l2 = [ + 9.53984675e-2, + 1.05633455e-1, + 1.05636158e-1, + 3.50747237e-1, + ], + linf = [ + 2.94357464e-1, + 4.07893014e-1, + 3.97334516e-1, + 1.0814252e+0, + ], + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl" begin - # This test is identical to the one in `test_p4est_2d.jl` besides minor - # deviations in the expected error norms. - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 3.76149952e-01, - 2.46970327e-01, - 2.46970327e-01, - 1.28889042e+00, - ], - linf=[ - 1.22139001e+00, - 1.17742626e+00, - 1.17742626e+00, - 6.20638482e+00, - ], - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov.jl" begin + # This test is identical to the one in `test_p4est_2d.jl` besides minor + # deviations in the expected error norms. + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 3.76149952e-1, + 2.46970327e-1, + 2.46970327e-1, + 1.28889042e+0, + ], + linf = [ + 1.22139001e+0, + 1.17742626e+0, + 1.17742626e+0, + 6.20638482e+0, + ], + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl" begin - # This test is identical to the one in `test_p4est_2d.jl`. - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 9.168126407325352e-5, - 0.0009795410115453788, - 0.002546408320320785, - 3.941189812642317e-6, - ], - linf=[ - 0.0009903782521019089, - 0.0059752684687262025, - 0.010941106525454103, - 1.2129488214718265e-5, - ], - tspan=(0.0, 0.1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl" begin + # This test is identical to the one in `test_p4est_2d.jl`. + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 9.168126407325352e-5, + 0.0009795410115453788, + 0.002546408320320785, + 3.941189812642317e-6, + ], + linf = [ + 0.0009903782521019089, + 0.0059752684687262025, + 0.010941106525454103, + 1.2129488214718265e-5, + ], + tspan = (0.0, 0.1) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl" begin - # This test is identical to the one in `test_p4est_2d.jl`. - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[1.0513414461545583e-5, 1.0517900957166411e-6, - 1.0517900957304043e-6, 1.511816606372376e-6, - 1.0443997728645063e-6, 7.879639064990798e-7, - 7.879639065049896e-7, 1.0628631669056271e-6, - 4.3382328912336153e-7], - linf=[4.255466285174592e-5, 1.0029706745823264e-5, - 1.0029706747467781e-5, 1.2122265939010224e-5, - 5.4791097160444835e-6, 5.18922042269665e-6, - 5.189220422141538e-6, 9.552667261422676e-6, - 1.4237578427628152e-6]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl" begin + # This test is identical to the one in `test_p4est_2d.jl`. + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 1.0513414461545583e-5, 1.0517900957166411e-6, + 1.0517900957304043e-6, 1.511816606372376e-6, + 1.0443997728645063e-6, 7.879639064990798e-7, + 7.879639065049896e-7, 1.0628631669056271e-6, + 4.3382328912336153e-7, + ], + linf = [ + 4.255466285174592e-5, 1.0029706745823264e-5, + 1.0029706747467781e-5, 1.2122265939010224e-5, + 5.4791097160444835e-6, 5.18922042269665e-6, + 5.189220422141538e-6, 9.552667261422676e-6, + 1.4237578427628152e-6, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_rotor.jl" begin - # This test is identical to the one in `test_p4est_2d.jl` besides minor - # deviations in the expected error norms. - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_rotor.jl"), - l2=[0.44207324634847545, 0.8804644301177857, 0.8262542320669426, - 0.0, - 0.9615023124189027, 0.10386709616755131, 0.1540308191628843, - 0.0, - 2.8350276854372125e-5], - linf=[10.04548675437385, 17.998696852394836, 9.575802136190026, - 0.0, - 19.431290746184473, 1.3821685018474321, 1.8186235976551453, - 0.0, - 0.002309422702635547], - tspan=(0.0, 0.02)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_rotor.jl" begin + # This test is identical to the one in `test_p4est_2d.jl` besides minor + # deviations in the expected error norms. + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_rotor.jl"), + l2 = [ + 0.44207324634847545, 0.8804644301177857, 0.8262542320669426, + 0.0, + 0.9615023124189027, 0.10386709616755131, 0.1540308191628843, + 0.0, + 2.8350276854372125e-5, + ], + linf = [ + 10.04548675437385, 17.998696852394836, 9.575802136190026, + 0.0, + 19.431290746184473, 1.3821685018474321, 1.8186235976551453, + 0.0, + 0.002309422702635547, + ], + tspan = (0.0, 0.02) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_weak_blast_wave_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weak_blast_wave_amr.jl"), - l2=[ - 0.10823279736983638, - 0.1158152939803735, - 0.11633970342992006, - 0.751152651902375, - ], - linf=[ - 0.5581611332828653, - 0.8354026029724041, - 0.834485181423738, - 3.923553028014343, - ], - tspan=(0.0, 0.1), - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end - # Check for conservation - state_integrals = Trixi.integrate(sol.u[2], semi) - initial_state_integrals = analysis_callback.affect!.initial_state_integrals + @trixi_testset "elixir_euler_weak_blast_wave_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weak_blast_wave_amr.jl"), + l2 = [ + 0.10823279736983638, + 0.1158152939803735, + 0.11633970342992006, + 0.751152651902375, + ], + linf = [ + 0.5581611332828653, + 0.8354026029724041, + 0.834485181423738, + 3.923553028014343, + ], + tspan = (0.0, 0.1), + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + # Check for conservation + state_integrals = Trixi.integrate(sol.u[2], semi) + initial_state_integrals = analysis_callback.affect!.initial_state_integrals - @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1e-13) - @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1e-13) - @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1e-13) - @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1e-13) -end + @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1.0e-13) + @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1.0e-13) + @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1.0e-13) + @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1.0e-13) + end end # Clean up afterwards: delete Trixi.jl output directory diff --git a/test/test_t8code_3d.jl b/test/test_t8code_3d.jl index 940d2c4337..8556a855d6 100644 --- a/test/test_t8code_3d.jl +++ b/test/test_t8code_3d.jl @@ -26,10 +26,12 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl`. @trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[0.00016263963870641478], - linf=[0.0014537194925779984]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [0.00016263963870641478], + linf = [0.0014537194925779984] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -42,10 +44,14 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl`. @trixi_testset "elixir_advection_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_unstructured_curved.jl"), - l2=[0.0004750004258546538], - linf=[0.026527551737137167]) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_unstructured_curved.jl" + ), + l2 = [0.0004750004258546538], + linf = [0.026527551737137167] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -58,9 +64,11 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl`. @trixi_testset "elixir_advection_nonconforming.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_nonconforming.jl"), - l2=[0.00253595715323843], - linf=[0.016486952252155795]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_nonconforming.jl"), + l2 = [0.00253595715323843], + linf = [0.016486952252155795] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -74,12 +82,16 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl` besides minor # deviations from the expected error norms. @trixi_testset "elixir_advection_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), - # Expected errors are exactly the same as with TreeMesh! - l2=[1.1302812803902801e-5], - linf=[0.0007889950196294793], - coverage_override=(maxiters = 6, initial_refinement_level = 1, - base_level = 1, med_level = 2, max_level = 3)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), + # Expected errors are exactly the same as with TreeMesh! + l2 = [1.1302812803902801e-5], + linf = [0.0007889950196294793], + coverage_override = ( + maxiters = 6, initial_refinement_level = 1, + base_level = 1, med_level = 2, max_level = 3, + ) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -93,13 +105,19 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl` besides minor # deviations from the expected error norms. @trixi_testset "elixir_advection_amr_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_unstructured_curved.jl"), - l2=[2.0535121347526814e-5], - linf=[0.0010586603797777504], - tspan=(0.0, 1.0), - coverage_override=(maxiters = 6, initial_refinement_level = 0, - base_level = 0, med_level = 1, max_level = 2)) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_unstructured_curved.jl" + ), + l2 = [2.0535121347526814e-5], + linf = [0.0010586603797777504], + tspan = (0.0, 1.0), + coverage_override = ( + maxiters = 6, initial_refinement_level = 0, + base_level = 0, med_level = 1, max_level = 2, + ) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -112,23 +130,27 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl`. @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonconforming_unstructured_curved.jl"), - l2=[ - 4.070355207909268e-5, - 4.4993257426833716e-5, - 5.10588457841744e-5, - 5.102840924036687e-5, - 0.00019986264001630542, - ], - linf=[ - 0.0016987332417202072, - 0.003622956808262634, - 0.002029576258317789, - 0.0024206977281964193, - 0.008526972236273522, - ], - tspan=(0.0, 0.01)) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonconforming_unstructured_curved.jl" + ), + l2 = [ + 4.070355207909268e-5, + 4.4993257426833716e-5, + 5.10588457841744e-5, + 5.102840924036687e-5, + 0.00019986264001630542, + ], + linf = [ + 0.0016987332417202072, + 0.003622956808262634, + 0.002029576258317789, + 0.0024206977281964193, + 0.008526972236273522, + ], + tspan = (0.0, 0.01) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -141,23 +163,27 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl`. @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 0.0015106060984283647, - 0.0014733349038567685, - 0.00147333490385685, - 0.001473334903856929, - 0.0028149479453087093, - ], - linf=[ - 0.008070806335238156, - 0.009007245083113125, - 0.009007245083121784, - 0.009007245083102688, - 0.01562861968368434, - ], - tspan=(0.0, 1.0)) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 0.0015106060984283647, + 0.0014733349038567685, + 0.00147333490385685, + 0.001473334903856929, + 0.0028149479453087093, + ], + linf = [ + 0.008070806335238156, + 0.009007245083113125, + 0.009007245083121784, + 0.009007245083102688, + 0.01562861968368434, + ], + tspan = (0.0, 1.0) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -170,22 +196,24 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl`. @trixi_testset "elixir_euler_free_stream.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), - l2=[ - 5.162664597942288e-15, - 1.941857343642486e-14, - 2.0232366394187278e-14, - 2.3381518645408552e-14, - 7.083114561232324e-14, - ], - linf=[ - 7.269740365245525e-13, - 3.289868377720495e-12, - 4.440087186807773e-12, - 3.8686831516088205e-12, - 9.412914891981927e-12, - ], - tspan=(0.0, 0.03)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + l2 = [ + 5.162664597942288e-15, + 1.941857343642486e-14, + 2.0232366394187278e-14, + 2.3381518645408552e-14, + 7.083114561232324e-14, + ], + linf = [ + 7.269740365245525e-13, + 3.289868377720495e-12, + 4.440087186807773e-12, + 3.8686831516088205e-12, + 9.412914891981927e-12, + ], + tspan = (0.0, 0.03) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -198,22 +226,24 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl`. @trixi_testset "elixir_euler_free_stream_extruded.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream_extruded.jl"), - l2=[ - 8.444868392439035e-16, - 4.889826056731442e-15, - 2.2921260987087585e-15, - 4.268460455702414e-15, - 1.1356712092620279e-14, - ], - linf=[ - 7.749356711883593e-14, - 2.8792246364872653e-13, - 1.1121659149182506e-13, - 3.3228975127030935e-13, - 9.592326932761353e-13, - ], - tspan=(0.0, 0.1), atol=5.0e-13,) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream_extruded.jl"), + l2 = [ + 8.444868392439035e-16, + 4.889826056731442e-15, + 2.2921260987087585e-15, + 4.268460455702414e-15, + 1.1356712092620279e-14, + ], + linf = [ + 7.749356711883593e-14, + 2.8792246364872653e-13, + 1.1121659149182506e-13, + 3.3228975127030935e-13, + 9.592326932761353e-13, + ], + tspan = (0.0, 0.1), atol = 5.0e-13, + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -226,23 +256,25 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl`. @trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.010380390326164493, - 0.006192950051354618, - 0.005970674274073704, - 0.005965831290564327, - 0.02628875593094754, - ], - linf=[ - 0.3326911600075694, - 0.2824952141320467, - 0.41401037398065543, - 0.45574161423218573, - 0.8099577682187109, - ], - tspan=(0.0, 0.2), - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.010380390326164493, + 0.006192950051354618, + 0.005970674274073704, + 0.005965831290564327, + 0.02628875593094754, + ], + linf = [ + 0.3326911600075694, + 0.2824952141320467, + 0.41401037398065543, + 0.45574161423218573, + 0.8099577682187109, + ], + tspan = (0.0, 0.2), + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -256,23 +288,25 @@ mkdir(outdir) # This test is identical to the one in `test_p4est_3d.jl` besides minor # deviations in the expected error norms. @trixi_testset "elixir_euler_sedov.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 7.82070951e-02, - 4.33260474e-02, - 4.33260474e-02, - 4.33260474e-02, - 3.75260911e-01, - ], - linf=[ - 7.45329845e-01, - 3.21754792e-01, - 3.21754792e-01, - 3.21754792e-01, - 4.76151527e+00, - ], - tspan=(0.0, 0.3), - coverage_override=(polydeg = 3,)) # Prevent long compile time in CI + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 7.82070951e-2, + 4.33260474e-2, + 4.33260474e-2, + 4.33260474e-2, + 3.75260911e-1, + ], + linf = [ + 7.45329845e-1, + 3.21754792e-1, + 3.21754792e-1, + 3.21754792e-1, + 4.76151527e+0, + ], + tspan = (0.0, 0.3), + coverage_override = (polydeg = 3,) + ) # Prevent long compile time in CI # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -284,29 +318,37 @@ mkdir(outdir) end @trixi_testset "elixir_euler_convergence_pure_fv.jl" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "tree_3d_dgsem"), - "elixir_euler_convergence_pure_fv.jl"), - l2=[ - 0.037182410351406, - 0.032062252638283974, - 0.032062252638283974, - 0.03206225263828395, - 0.12228177813586687, - ], - linf=[ - 0.0693648413632646, - 0.0622101894740843, - 0.06221018947408474, - 0.062210189474084965, - 0.24196451799555962, - ], - mesh=T8codeMesh((4, 4, 4), polydeg = 3, - coordinates_min = (0.0, 0.0, 0.0), - coordinates_max = (2.0, 2.0, 2.0)), - # Remove SaveSolution callback - callbacks=CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback)) + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "tree_3d_dgsem"), + "elixir_euler_convergence_pure_fv.jl" + ), + l2 = [ + 0.037182410351406, + 0.032062252638283974, + 0.032062252638283974, + 0.03206225263828395, + 0.12228177813586687, + ], + linf = [ + 0.0693648413632646, + 0.0622101894740843, + 0.06221018947408474, + 0.062210189474084965, + 0.24196451799555962, + ], + mesh = T8codeMesh( + (4, 4, 4), polydeg = 3, + coordinates_min = (0.0, 0.0, 0.0), + coordinates_max = (2.0, 2.0, 2.0) + ), + # Remove SaveSolution callback + callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback + ) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -318,23 +360,25 @@ mkdir(outdir) end @trixi_testset "elixir_euler_weak_blast_wave_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_weak_blast_wave_amr.jl"), - l2=[ - 0.010014531529951328, - 0.0176268986746271, - 0.01817514447099777, - 0.018271085903740675, - 0.15193033077438198, - ], - linf=[ - 0.2898958869606375, - 0.529717119064458, - 0.5567193302705906, - 0.570663236219957, - 3.5496520808512027, - ], - tspan=(0.0, 0.025), - coverage_override=(maxiters = 6,)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_weak_blast_wave_amr.jl"), + l2 = [ + 0.010014531529951328, + 0.0176268986746271, + 0.01817514447099777, + 0.018271085903740675, + 0.15193033077438198, + ], + linf = [ + 0.2898958869606375, + 0.529717119064458, + 0.5567193302705906, + 0.570663236219957, + 3.5496520808512027, + ], + tspan = (0.0, 0.025), + coverage_override = (maxiters = 6,) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -347,11 +391,11 @@ mkdir(outdir) state_integrals = Trixi.integrate(sol.u[2], semi) initial_state_integrals = analysis_callback.affect!.initial_state_integrals - @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1e-13) - @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1e-13) - @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1e-13) - @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1e-13) - @test isapprox(state_integrals[5], initial_state_integrals[5], atol = 1e-13) + @test isapprox(state_integrals[1], initial_state_integrals[1], atol = 1.0e-13) + @test isapprox(state_integrals[2], initial_state_integrals[2], atol = 1.0e-13) + @test isapprox(state_integrals[3], initial_state_integrals[3], atol = 1.0e-13) + @test isapprox(state_integrals[4], initial_state_integrals[4], atol = 1.0e-13) + @test isapprox(state_integrals[5], initial_state_integrals[5], atol = 1.0e-13) end end diff --git a/test/test_threaded.jl b/test/test_threaded.jl index 7fb64d61cb..9fa8b7d84b 100644 --- a/test/test_threaded.jl +++ b/test/test_threaded.jl @@ -11,464 +11,554 @@ Trixi.mpi_isroot() && isdir(outdir) && rm(outdir, recursive = true) Trixi.MPI.Barrier(Trixi.mpi_comm()) @testset "Threaded tests" begin -#! format: noindent - -@testset "TreeMesh" begin - @trixi_testset "elixir_advection_restart.jl" begin - elixir = joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_extended.jl") - Trixi.mpi_isroot() && println("═"^100) - Trixi.mpi_isroot() && println(elixir) - trixi_include(@__MODULE__, elixir, tspan = (0.0, 10.0)) - l2_expected, linf_expected = analysis_callback(sol) - - elixir = joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_restart.jl") - Trixi.mpi_isroot() && println("═"^100) - Trixi.mpi_isroot() && println(elixir) - # Errors are exactly the same as in the elixir_advection_extended.jl - trixi_include(@__MODULE__, elixir) - l2_actual, linf_actual = analysis_callback(sol) - - Trixi.mpi_isroot() && @test l2_actual == l2_expected - Trixi.mpi_isroot() && @test linf_actual == linf_expected - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + #! format: noindent + + @testset "TreeMesh" begin + @trixi_testset "elixir_advection_restart.jl" begin + elixir = joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_extended.jl" + ) + Trixi.mpi_isroot() && println("═"^100) + Trixi.mpi_isroot() && println(elixir) + trixi_include(@__MODULE__, elixir, tspan = (0.0, 10.0)) + l2_expected, linf_expected = analysis_callback(sol) + + elixir = joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_restart.jl" + ) + Trixi.mpi_isroot() && println("═"^100) + Trixi.mpi_isroot() && println(elixir) + # Errors are exactly the same as in the elixir_advection_extended.jl + trixi_include(@__MODULE__, elixir) + l2_actual, linf_actual = analysis_callback(sol) + + Trixi.mpi_isroot() && @test l2_actual == l2_expected + Trixi.mpi_isroot() && @test linf_actual == linf_expected + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_advection_restart.jl with threaded time integration" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_restart.jl"), - alg=CarpenterKennedy2N54(williamson_condition = false, - thread = OrdinaryDiffEq.True()), - # Expected errors are exactly the same as in the serial test! - l2=[8.005068880114254e-6], - linf=[6.39093577996519e-5]) - end + @trixi_testset "elixir_advection_restart.jl with threaded time integration" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_restart.jl" + ), + alg = CarpenterKennedy2N54( + williamson_condition = false, + thread = OrdinaryDiffEq.True() + ), + # Expected errors are exactly the same as in the serial test! + l2 = [8.005068880114254e-6], + linf = [6.39093577996519e-5] + ) + end - @trixi_testset "elixir_advection_amr_refine_twice.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_amr_refine_twice.jl"), - l2=[0.00020547512522578292], - linf=[0.007831753383083506]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "elixir_advection_amr_refine_twice.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_amr_refine_twice.jl" + ), + l2 = [0.00020547512522578292], + linf = [0.007831753383083506] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_advection_amr_coarsen_twice.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_amr_coarsen_twice.jl"), - l2=[0.0014321062757891826], - linf=[0.0253454486893413]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "elixir_advection_amr_coarsen_twice.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_amr_coarsen_twice.jl" + ), + l2 = [0.0014321062757891826], + linf = [0.0253454486893413] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 2.259440511766445e-6, - 2.318888155713922e-6, - 2.3188881557894307e-6, - 6.3327863238858925e-6, - ], - linf=[ - 1.498738264560373e-5, - 1.9182011928187137e-5, - 1.918201192685487e-5, - 6.0526717141407005e-5, - ], - rtol=0.001) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 2.259440511766445e-6, + 2.318888155713922e-6, + 2.3188881557894307e-6, + 6.3327863238858925e-6, + ], + linf = [ + 1.498738264560373e-5, + 1.9182011928187137e-5, + 1.918201192685487e-5, + 6.0526717141407005e-5, + ], + rtol = 0.001 + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_euler_ec.jl"), - l2=[ - 0.061751715597716854, - 0.05018223615408711, - 0.05018989446443463, - 0.225871559730513, - ], - linf=[ - 0.29347582879608825, - 0.31081249232844693, - 0.3107380389947736, - 1.0540358049885143, - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_euler_ec.jl" + ), + l2 = [ + 0.061751715597716854, + 0.05018223615408711, + 0.05018989446443463, + 0.225871559730513, + ], + linf = [ + 0.29347582879608825, + 0.31081249232844693, + 0.3107380389947736, + 1.0540358049885143, + ] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_advection_diffusion.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_diffusion.jl"), - initial_refinement_level=2, tspan=(0.0, 0.4), polydeg=5, - alg=RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()), - l2=[4.0915532997994255e-6], - linf=[2.3040850347877395e-5]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "elixir_advection_diffusion.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_diffusion.jl" + ), + initial_refinement_level = 2, tspan = (0.0, 0.4), polydeg = 5, + alg = RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()), + l2 = [4.0915532997994255e-6], + linf = [2.3040850347877395e-5] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "FDSBP, elixir_advection_extended.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_fdsbp", - "elixir_advection_extended.jl"), - l2=[2.898644263922225e-6], - linf=[8.491517930142578e-6], - rtol=1.0e-7) # These results change a little bit and depend on the CI system - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "FDSBP, elixir_advection_extended.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_fdsbp", + "elixir_advection_extended.jl" + ), + l2 = [2.898644263922225e-6], + linf = [8.491517930142578e-6], + rtol = 1.0e-7 + ) # These results change a little bit and depend on the CI system + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "FDSBP, elixir_euler_convergence.jl" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_fdsbp", - "elixir_euler_convergence.jl"), - l2=[ - 1.7088389997042244e-6, - 1.7437997855125774e-6, - 1.7437997855350776e-6, - 5.457223460127621e-6, - ], - linf=[ - 9.796504903736292e-6, - 9.614745892783105e-6, - 9.614745892783105e-6, - 4.026107182575345e-5, - ], - tspan=(0.0, 0.1)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "FDSBP, elixir_euler_convergence.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_fdsbp", + "elixir_euler_convergence.jl" + ), + l2 = [ + 1.7088389997042244e-6, + 1.7437997855125774e-6, + 1.7437997855350776e-6, + 5.457223460127621e-6, + ], + linf = [ + 9.796504903736292e-6, + 9.614745892783105e-6, + 9.614745892783105e-6, + 4.026107182575345e-5, + ], + tspan = (0.0, 0.1) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end end -end -@testset "StructuredMesh" begin - @trixi_testset "elixir_advection_restart.jl with waving flag mesh" begin - @test_trixi_include(joinpath(examples_dir(), "structured_2d_dgsem", - "elixir_advection_restart.jl"), - l2=[0.00016265538265929818], - linf=[0.0015194252169410394], - rtol=5.0e-5, # Higher tolerance to make tests pass in CI (in particular with macOS) - elixir_file="elixir_advection_waving_flag.jl", - restart_file="restart_000000021.h5", - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @testset "StructuredMesh" begin + @trixi_testset "elixir_advection_restart.jl with waving flag mesh" begin + @test_trixi_include( + joinpath( + examples_dir(), "structured_2d_dgsem", + "elixir_advection_restart.jl" + ), + l2 = [0.00016265538265929818], + linf = [0.0015194252169410394], + rtol = 5.0e-5, # Higher tolerance to make tests pass in CI (in particular with macOS) + elixir_file = "elixir_advection_waving_flag.jl", + restart_file = "restart_000000021.h5", + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_mhd_ec.jl" begin - @test_trixi_include(joinpath(examples_dir(), "structured_2d_dgsem", - "elixir_mhd_ec.jl"), - l2=[0.04937480811868297, 0.06117033019988596, - 0.060998028674664716, 0.03155145889799417, - 0.2319175391388658, 0.02476283192966346, - 0.024483244374818587, 0.035439957899127385, - 0.0016022148194667542], - linf=[0.24749024430983746, 0.2990608279625713, - 0.3966937932860247, 0.22265033744519683, - 0.9757376320946505, 0.12123736788315098, - 0.12837436699267113, 0.17793825293524734, - 0.03460761690059514], - tspan=(0.0, 0.3)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "elixir_mhd_ec.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "structured_2d_dgsem", + "elixir_mhd_ec.jl" + ), + l2 = [ + 0.04937480811868297, 0.06117033019988596, + 0.060998028674664716, 0.03155145889799417, + 0.2319175391388658, 0.02476283192966346, + 0.024483244374818587, 0.035439957899127385, + 0.0016022148194667542, + ], + linf = [ + 0.24749024430983746, 0.2990608279625713, + 0.3966937932860247, 0.22265033744519683, + 0.9757376320946505, 0.12123736788315098, + 0.12837436699267113, 0.17793825293524734, + 0.03460761690059514, + ], + tspan = (0.0, 0.3) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end end -end -@testset "UnstructuredMesh" begin - @trixi_testset "elixir_acoustics_gauss_wall.jl" begin - @test_trixi_include(joinpath(examples_dir(), "unstructured_2d_dgsem", - "elixir_acoustics_gauss_wall.jl"), - l2=[0.029330394861252995, 0.029345079728907965, - 0.03803795043486467, 0.0, - 7.175152371650832e-16, 1.4350304743301665e-15, - 1.4350304743301665e-15], - linf=[0.36236334472179443, 0.3690785638275256, - 0.8475748723784078, 0.0, - 8.881784197001252e-16, 1.7763568394002505e-15, - 1.7763568394002505e-15], - tspan=(0.0, 5.0)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @testset "UnstructuredMesh" begin + @trixi_testset "elixir_acoustics_gauss_wall.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "unstructured_2d_dgsem", + "elixir_acoustics_gauss_wall.jl" + ), + l2 = [ + 0.029330394861252995, 0.029345079728907965, + 0.03803795043486467, 0.0, + 7.175152371650832e-16, 1.4350304743301665e-15, + 1.4350304743301665e-15, + ], + linf = [ + 0.36236334472179443, 0.3690785638275256, + 0.8475748723784078, 0.0, + 8.881784197001252e-16, 1.7763568394002505e-15, + 1.7763568394002505e-15, + ], + tspan = (0.0, 5.0) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end end -end -@testset "P4estMesh" begin - @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), - l2=[ - 0.0034516244508588046, - 0.0023420334036925493, - 0.0024261923964557187, - 0.004731710454271893, - ], - linf=[ - 0.04155789011775046, - 0.024772109862748914, - 0.03759938693042297, - 0.08039824959535657, - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @testset "P4estMesh" begin + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" + ), + l2 = [ + 0.0034516244508588046, + 0.0023420334036925493, + 0.0024261923964557187, + 0.004731710454271893, + ], + linf = [ + 0.04155789011775046, + 0.024772109862748914, + 0.03759938693042297, + 0.08039824959535657, + ] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_eulergravity_convergence.jl" begin - @test_trixi_include(joinpath(examples_dir(), "p4est_2d_dgsem", - "elixir_eulergravity_convergence.jl"), - l2=[ - 0.00024871265138964204, - 0.0003370077102132591, - 0.0003370077102131964, - 0.0007231525513793697, - ], - linf=[ - 0.0015813032944647087, - 0.0020494288423820173, - 0.0020494288423824614, - 0.004793821195083758, - ], - tspan=(0.0, 0.1)) + @trixi_testset "elixir_eulergravity_convergence.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "p4est_2d_dgsem", + "elixir_eulergravity_convergence.jl" + ), + l2 = [ + 0.00024871265138964204, + 0.0003370077102132591, + 0.0003370077102131964, + 0.0007231525513793697, + ], + linf = [ + 0.0015813032944647087, + 0.0020494288423820173, + 0.0020494288423824614, + 0.004793821195083758, + ], + tspan = (0.0, 0.1) + ) + end end -end -@testset "T8codeMesh" begin - @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin - @test_trixi_include(joinpath(examples_dir(), "t8code_2d_dgsem", - "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), - l2=[ - 0.0034516244508588046, - 0.0023420334036925493, - 0.0024261923964557187, - 0.004731710454271893, - ], - linf=[ - 0.04155789011775046, - 0.024772109862748914, - 0.03759938693042297, - 0.08039824959535657, - ]) - end + @testset "T8codeMesh" begin + @trixi_testset "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "t8code_2d_dgsem", + "elixir_euler_source_terms_nonconforming_unstructured_flag.jl" + ), + l2 = [ + 0.0034516244508588046, + 0.0023420334036925493, + 0.0024261923964557187, + 0.004731710454271893, + ], + linf = [ + 0.04155789011775046, + 0.024772109862748914, + 0.03759938693042297, + 0.08039824959535657, + ] + ) + end - @trixi_testset "elixir_eulergravity_convergence.jl" begin - @test_trixi_include(joinpath(examples_dir(), "t8code_2d_dgsem", - "elixir_eulergravity_convergence.jl"), - l2=[ - 0.00024871265138964204, - 0.0003370077102132591, - 0.0003370077102131964, - 0.0007231525513793697, - ], - linf=[ - 0.0015813032944647087, - 0.0020494288423820173, - 0.0020494288423824614, - 0.004793821195083758, - ], - tspan=(0.0, 0.1)) + @trixi_testset "elixir_eulergravity_convergence.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "t8code_2d_dgsem", + "elixir_eulergravity_convergence.jl" + ), + l2 = [ + 0.00024871265138964204, + 0.0003370077102132591, + 0.0003370077102131964, + 0.0007231525513793697, + ], + linf = [ + 0.0015813032944647087, + 0.0020494288423820173, + 0.0020494288423824614, + 0.004793821195083758, + ], + tspan = (0.0, 0.1) + ) + end end -end -@testset "DGMulti" begin - @trixi_testset "elixir_euler_weakform.jl (SBP, EC)" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_euler_weakform.jl"), - cells_per_dimension=(4, 4), - volume_integral=VolumeIntegralFluxDifferencing(flux_ranocha), - surface_integral=SurfaceIntegralWeakForm(flux_ranocha), - approximation_type=SBP(), - l2=[ - 0.006400337855843578, - 0.005303799804137764, - 0.005303799804119745, - 0.013204169007030144, - ], - linf=[ - 0.03798302318566282, - 0.05321027922532284, - 0.05321027922605448, - 0.13392025411839015, - ],) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @testset "DGMulti" begin + @trixi_testset "elixir_euler_weakform.jl (SBP, EC)" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_euler_weakform.jl" + ), + cells_per_dimension = (4, 4), + volume_integral = VolumeIntegralFluxDifferencing(flux_ranocha), + surface_integral = SurfaceIntegralWeakForm(flux_ranocha), + approximation_type = SBP(), + l2 = [ + 0.006400337855843578, + 0.005303799804137764, + 0.005303799804119745, + 0.013204169007030144, + ], + linf = [ + 0.03798302318566282, + 0.05321027922532284, + 0.05321027922605448, + 0.13392025411839015, + ], + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_euler_curved.jl with threaded time integration" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_euler_curved.jl"), - alg=RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()), - l2=[ - 1.7204593127904542e-5, - 1.5921547179522804e-5, - 1.5921547180107928e-5, - 4.894071422525737e-5, - ], - linf=[ - 0.00010525416930584619, - 0.00010003778091061122, - 0.00010003778085621029, - 0.00036426282101720275, - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "elixir_euler_curved.jl with threaded time integration" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_euler_curved.jl" + ), + alg = RDPK3SpFSAL49(thread = OrdinaryDiffEq.True()), + l2 = [ + 1.7204593127904542e-5, + 1.5921547179522804e-5, + 1.5921547180107928e-5, + 4.894071422525737e-5, + ], + linf = [ + 0.00010525416930584619, + 0.00010003778091061122, + 0.00010003778085621029, + 0.00036426282101720275, + ] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_euler_triangulate_pkg_mesh.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_euler_triangulate_pkg_mesh.jl"), - l2=[ - 2.344076909832665e-6, - 1.8610002398709756e-6, - 2.4095132179484066e-6, - 6.37330249340445e-6, - ], - linf=[ - 2.509979394305084e-5, - 2.2683711321080935e-5, - 2.6180377720841363e-5, - 5.575278031910713e-5, - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "elixir_euler_triangulate_pkg_mesh.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_euler_triangulate_pkg_mesh.jl" + ), + l2 = [ + 2.344076909832665e-6, + 1.8610002398709756e-6, + 2.4095132179484066e-6, + 6.37330249340445e-6, + ], + linf = [ + 2.509979394305084e-5, + 2.2683711321080935e-5, + 2.6180377720841363e-5, + 5.575278031910713e-5, + ] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end - end - @trixi_testset "elixir_euler_fdsbp_periodic.jl" begin - @test_trixi_include(joinpath(examples_dir(), "dgmulti_2d", - "elixir_euler_fdsbp_periodic.jl"), - l2=[ - 1.3333320340010056e-6, - 2.044834627970641e-6, - 2.044834627855601e-6, - 5.282189803559564e-6, - ], - linf=[ - 2.7000151718858945e-6, - 3.988595028259212e-6, - 3.9885950273710336e-6, - 8.848583042286862e-6, - ]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + @trixi_testset "elixir_euler_fdsbp_periodic.jl" begin + @test_trixi_include( + joinpath( + examples_dir(), "dgmulti_2d", + "elixir_euler_fdsbp_periodic.jl" + ), + l2 = [ + 1.3333320340010056e-6, + 2.044834627970641e-6, + 2.044834627855601e-6, + 5.282189803559564e-6, + ], + linf = [ + 2.7000151718858945e-6, + 3.988595028259212e-6, + 3.9885950273710336e-6, + 8.848583042286862e-6, + ] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 5000 + end end end end -end # Clean up afterwards: delete Trixi.jl output directory Trixi.mpi_isroot() && isdir(outdir) && @test_nowarn rm(outdir, recursive = true) diff --git a/test/test_tree_1d.jl b/test/test_tree_1d.jl index 98d6ad11c7..e33ebfdb08 100644 --- a/test/test_tree_1d.jl +++ b/test/test_tree_1d.jl @@ -12,293 +12,325 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "TreeMesh1D" begin -#! format: noindent + #! format: noindent -# Run basic tests -@testset "Examples 1D" begin - # Linear scalar advection - include("test_tree_1d_advection.jl") + # Run basic tests + @testset "Examples 1D" begin + # Linear scalar advection + include("test_tree_1d_advection.jl") - # Burgers - include("test_tree_1d_burgers.jl") + # Burgers + include("test_tree_1d_burgers.jl") - # Hyperbolic diffusion - include("test_tree_1d_hypdiff.jl") + # Hyperbolic diffusion + include("test_tree_1d_hypdiff.jl") - # Compressible Euler - include("test_tree_1d_euler.jl") + # Compressible Euler + include("test_tree_1d_euler.jl") - # Compressible Euler Multicomponent - include("test_tree_1d_eulermulti.jl") + # Compressible Euler Multicomponent + include("test_tree_1d_eulermulti.jl") - # MHD - include("test_tree_1d_mhd.jl") + # MHD + include("test_tree_1d_mhd.jl") - # MHD Multicomponent - include("test_tree_1d_mhdmulti.jl") + # MHD Multicomponent + include("test_tree_1d_mhdmulti.jl") - # Compressible Euler with self-gravity - include("test_tree_1d_eulergravity.jl") + # Compressible Euler with self-gravity + include("test_tree_1d_eulergravity.jl") - # Shallow water - include("test_tree_1d_shallowwater.jl") + # Shallow water + include("test_tree_1d_shallowwater.jl") - # FDSBP methods on the TreeMesh - include("test_tree_1d_fdsbp.jl") + # FDSBP methods on the TreeMesh + include("test_tree_1d_fdsbp.jl") - # Traffic flow LWR - include("test_tree_1d_traffic_flow_lwr.jl") + # Traffic flow LWR + include("test_tree_1d_traffic_flow_lwr.jl") - # Linearized Euler - include("test_tree_1d_linearizedeuler.jl") + # Linearized Euler + include("test_tree_1d_linearizedeuler.jl") - # Maxwell - include("test_tree_1d_maxwell.jl") -end - -# Coverage test for all initial conditions -@testset "Tests for initial conditions" begin - # Linear scalar advection - @trixi_testset "elixir_advection_extended.jl with initial_condition_sin" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[0.00017373554109980247], - linf=[0.0006021275678165239], - maxiters=1, - initial_condition=Trixi.initial_condition_sin) + # Maxwell + include("test_tree_1d_maxwell.jl") end - @trixi_testset "elixir_advection_extended.jl with initial_condition_constant" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[2.441369287653687e-16], - linf=[4.440892098500626e-16], - maxiters=1, - initial_condition=initial_condition_constant) - end + # Coverage test for all initial conditions + @testset "Tests for initial conditions" begin + # Linear scalar advection + @trixi_testset "elixir_advection_extended.jl with initial_condition_sin" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [0.00017373554109980247], + linf = [0.0006021275678165239], + maxiters = 1, + initial_condition = Trixi.initial_condition_sin + ) + end - @trixi_testset "elixir_advection_extended.jl with initial_condition_linear_x" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[1.9882464973192864e-16], - linf=[1.4432899320127035e-15], - maxiters=1, - initial_condition=Trixi.initial_condition_linear_x, - boundary_conditions=Trixi.boundary_condition_linear_x, - periodicity=false) - end + @trixi_testset "elixir_advection_extended.jl with initial_condition_constant" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [2.441369287653687e-16], + linf = [4.440892098500626e-16], + maxiters = 1, + initial_condition = initial_condition_constant + ) + end + + @trixi_testset "elixir_advection_extended.jl with initial_condition_linear_x" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [1.9882464973192864e-16], + linf = [1.4432899320127035e-15], + maxiters = 1, + initial_condition = Trixi.initial_condition_linear_x, + boundary_conditions = Trixi.boundary_condition_linear_x, + periodicity = false + ) + end - @trixi_testset "elixir_advection_extended.jl with initial_condition_convergence_test" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[6.1803596620800215e-6], - linf=[2.4858560899509996e-5], - maxiters=1, - initial_condition=initial_condition_convergence_test, - boundary_conditions=BoundaryConditionDirichlet(initial_condition_convergence_test), - periodicity=false) + @trixi_testset "elixir_advection_extended.jl with initial_condition_convergence_test" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [6.1803596620800215e-6], + linf = [2.4858560899509996e-5], + maxiters = 1, + initial_condition = initial_condition_convergence_test, + boundary_conditions = BoundaryConditionDirichlet(initial_condition_convergence_test), + periodicity = false + ) + end end -end - -@testset "Displaying components 1D" begin - @test_nowarn include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl")) - - # test both short and long printing formats - @test_nowarn show(mesh) - println() - @test_nowarn println(mesh) - @test_nowarn display(mesh) - - @test_nowarn show(equations) - println() - @test_nowarn println(equations) - @test_nowarn display(equations) - - @test_nowarn show(solver) - println() - @test_nowarn println(solver) - @test_nowarn display(solver) - - @test_nowarn show(solver.basis) - println() - @test_nowarn println(solver.basis) - @test_nowarn display(solver.basis) - - @test_nowarn show(solver.mortar) - println() - @test_nowarn println(solver.mortar) - @test_nowarn display(solver.mortar) - - @test_nowarn show(solver.volume_integral) - println() - @test_nowarn println(solver.volume_integral) - @test_nowarn display(solver.volume_integral) - - @test_nowarn show(semi) - println() - @test_nowarn println(semi) - @test_nowarn display(semi) - - @test_nowarn show(summary_callback) - println() - @test_nowarn println(summary_callback) - @test_nowarn display(summary_callback) - - @test_nowarn show(amr_controller) - println() - @test_nowarn println(amr_controller) - @test_nowarn display(amr_controller) - - @test_nowarn show(amr_callback) - println() - @test_nowarn println(amr_callback) - @test_nowarn display(amr_callback) - - @test_nowarn show(stepsize_callback) - println() - @test_nowarn println(stepsize_callback) - @test_nowarn display(stepsize_callback) - - @test_nowarn show(save_solution) - println() - @test_nowarn println(save_solution) - @test_nowarn display(save_solution) - - @test_nowarn show(analysis_callback) - println() - @test_nowarn println(analysis_callback) - @test_nowarn display(analysis_callback) - - @test_nowarn show(alive_callback) - println() - @test_nowarn println(alive_callback) - @test_nowarn display(alive_callback) - - @test_nowarn println(callbacks) - - # Check whether all output is suppressed if the summary, analysis and alive - # callbacks are set to the TrivialCallback(). Modelled using `@test_nowarn` - # as basis. - let fname = tempname() - try - open(fname, "w") do f - redirect_stderr(f) do - trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_extended.jl"), - summary_callback = TrivialCallback(), - analysis_callback = TrivialCallback(), - alive_callback = TrivialCallback()) + + @testset "Displaying components 1D" begin + @test_nowarn include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl")) + + # test both short and long printing formats + @test_nowarn show(mesh) + println() + @test_nowarn println(mesh) + @test_nowarn display(mesh) + + @test_nowarn show(equations) + println() + @test_nowarn println(equations) + @test_nowarn display(equations) + + @test_nowarn show(solver) + println() + @test_nowarn println(solver) + @test_nowarn display(solver) + + @test_nowarn show(solver.basis) + println() + @test_nowarn println(solver.basis) + @test_nowarn display(solver.basis) + + @test_nowarn show(solver.mortar) + println() + @test_nowarn println(solver.mortar) + @test_nowarn display(solver.mortar) + + @test_nowarn show(solver.volume_integral) + println() + @test_nowarn println(solver.volume_integral) + @test_nowarn display(solver.volume_integral) + + @test_nowarn show(semi) + println() + @test_nowarn println(semi) + @test_nowarn display(semi) + + @test_nowarn show(summary_callback) + println() + @test_nowarn println(summary_callback) + @test_nowarn display(summary_callback) + + @test_nowarn show(amr_controller) + println() + @test_nowarn println(amr_controller) + @test_nowarn display(amr_controller) + + @test_nowarn show(amr_callback) + println() + @test_nowarn println(amr_callback) + @test_nowarn display(amr_callback) + + @test_nowarn show(stepsize_callback) + println() + @test_nowarn println(stepsize_callback) + @test_nowarn display(stepsize_callback) + + @test_nowarn show(save_solution) + println() + @test_nowarn println(save_solution) + @test_nowarn display(save_solution) + + @test_nowarn show(analysis_callback) + println() + @test_nowarn println(analysis_callback) + @test_nowarn display(analysis_callback) + + @test_nowarn show(alive_callback) + println() + @test_nowarn println(alive_callback) + @test_nowarn display(alive_callback) + + @test_nowarn println(callbacks) + + # Check whether all output is suppressed if the summary, analysis and alive + # callbacks are set to the TrivialCallback(). Modelled using `@test_nowarn` + # as basis. + let fname = tempname() + try + open(fname, "w") do f + redirect_stderr(f) do + trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_extended.jl" + ), + summary_callback = TrivialCallback(), + analysis_callback = TrivialCallback(), + alive_callback = TrivialCallback() + ) + end end + output = read(fname, String) + output = replace( + output, + "[ Info: You just called `trixi_include`. Julia may now compile the code, please be patient.\n" => "" + ) + @test isempty(output) + finally + rm(fname, force = true) end - output = read(fname, String) - output = replace(output, - "[ Info: You just called `trixi_include`. Julia may now compile the code, please be patient.\n" => "") - @test isempty(output) - finally - rm(fname, force = true) end end -end -@testset "Additional tests in 1D" begin - @testset "compressible Euler" begin - eqn = CompressibleEulerEquations1D(1.4) + @testset "Additional tests in 1D" begin + @testset "compressible Euler" begin + eqn = CompressibleEulerEquations1D(1.4) - @test isapprox(Trixi.entropy_thermodynamic([1.0, 2.0, 20.0], eqn), - 1.9740810260220094) - @test isapprox(Trixi.entropy_math([1.0, 2.0, 20.0], eqn), -4.935202565055024) - @test isapprox(Trixi.entropy([1.0, 2.0, 20.0], eqn), -4.935202565055024) + @test isapprox( + Trixi.entropy_thermodynamic([1.0, 2.0, 20.0], eqn), + 1.9740810260220094 + ) + @test isapprox(Trixi.entropy_math([1.0, 2.0, 20.0], eqn), -4.935202565055024) + @test isapprox(Trixi.entropy([1.0, 2.0, 20.0], eqn), -4.935202565055024) - @test isapprox(energy_total([1.0, 2.0, 20.0], eqn), 20.0) - @test isapprox(energy_kinetic([1.0, 2.0, 20.0], eqn), 2.0) - @test isapprox(energy_internal([1.0, 2.0, 20.0], eqn), 18.0) + @test isapprox(energy_total([1.0, 2.0, 20.0], eqn), 20.0) + @test isapprox(energy_kinetic([1.0, 2.0, 20.0], eqn), 2.0) + @test isapprox(energy_internal([1.0, 2.0, 20.0], eqn), 18.0) + end end -end -@trixi_testset "Nonconservative terms in 1D (linear advection)" begin - # Same setup as docs/src/adding_new_equations/nonconservative_advection.md + @trixi_testset "Nonconservative terms in 1D (linear advection)" begin + # Same setup as docs/src/adding_new_equations/nonconservative_advection.md - # Define new physics - using Trixi - using Trixi: AbstractEquations, get_node_vars + # Define new physics + using Trixi + using Trixi: AbstractEquations, get_node_vars - # Since there is no native support for variable coefficients, we use two - # variables: one for the basic unknown `u` and another one for the coefficient `a` - struct NonconservativeLinearAdvectionEquation <: AbstractEquations{1, #= spatial dimension =# - 2} #= two variables (u,a) =# - end + # Since there is no native support for variable coefficients, we use two + # variables: one for the basic unknown `u` and another one for the coefficient `a` + struct NonconservativeLinearAdvectionEquation <: AbstractEquations{ + 1, #= spatial dimension =# + 2, + } #= two variables (u,a) =# + end - Trixi.varnames(::typeof(cons2cons), ::NonconservativeLinearAdvectionEquation) = ("scalar", - "advection_velocity") + Trixi.varnames(::typeof(cons2cons), ::NonconservativeLinearAdvectionEquation) = ( + "scalar", + "advection_velocity", + ) - Trixi.default_analysis_integrals(::NonconservativeLinearAdvectionEquation) = () + Trixi.default_analysis_integrals(::NonconservativeLinearAdvectionEquation) = () - # The conservative part of the flux is zero - Trixi.flux(u, orientation, equation::NonconservativeLinearAdvectionEquation) = zero(u) + # The conservative part of the flux is zero + Trixi.flux(u, orientation, equation::NonconservativeLinearAdvectionEquation) = zero(u) - # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation - function Trixi.max_abs_speed_naive(u_ll, u_rr, orientation::Integer, - ::NonconservativeLinearAdvectionEquation) - _, advection_velocity_ll = u_ll - _, advection_velocity_rr = u_rr + # Calculate maximum wave speed for local Lax-Friedrichs-type dissipation + function Trixi.max_abs_speed_naive( + u_ll, u_rr, orientation::Integer, + ::NonconservativeLinearAdvectionEquation + ) + _, advection_velocity_ll = u_ll + _, advection_velocity_rr = u_rr - return max(abs(advection_velocity_ll), abs(advection_velocity_rr)) - end - - # We use nonconservative terms - Trixi.have_nonconservative_terms(::NonconservativeLinearAdvectionEquation) = Trixi.True() + return max(abs(advection_velocity_ll), abs(advection_velocity_rr)) + end - function flux_nonconservative(u_mine, u_other, orientation, - equations::NonconservativeLinearAdvectionEquation) - _, advection_velocity = u_mine - scalar, _ = u_other + # We use nonconservative terms + Trixi.have_nonconservative_terms(::NonconservativeLinearAdvectionEquation) = Trixi.True() - return SVector(advection_velocity * scalar, zero(scalar)) - end + function flux_nonconservative( + u_mine, u_other, orientation, + equations::NonconservativeLinearAdvectionEquation + ) + _, advection_velocity = u_mine + scalar, _ = u_other - # Create a simulation setup - using Trixi - using OrdinaryDiffEq + return SVector(advection_velocity * scalar, zero(scalar)) + end - equation = NonconservativeLinearAdvectionEquation() + # Create a simulation setup + using Trixi + using OrdinaryDiffEq + + equation = NonconservativeLinearAdvectionEquation() + + # You can derive the exact solution for this setup using the method of + # characteristics + function initial_condition_sine( + x, t, + equation::NonconservativeLinearAdvectionEquation + ) + x0 = -2 * atan(sqrt(3) * tan(sqrt(3) / 2 * t - atan(tan(x[1] / 2) / sqrt(3)))) + scalar = sin(x0) + advection_velocity = 2 + cos(x[1]) + SVector(scalar, advection_velocity) + end - # You can derive the exact solution for this setup using the method of - # characteristics - function initial_condition_sine(x, t, - equation::NonconservativeLinearAdvectionEquation) - x0 = -2 * atan(sqrt(3) * tan(sqrt(3) / 2 * t - atan(tan(x[1] / 2) / sqrt(3)))) - scalar = sin(x0) - advection_velocity = 2 + cos(x[1]) - SVector(scalar, advection_velocity) + # Create a uniform mesh in 1D in the interval [-π, π] with periodic boundaries + mesh = TreeMesh( + -Float64(π), Float64(π), # min/max coordinates + initial_refinement_level = 4, n_cells_max = 10^4 + ) + + # Create a DGSEM solver with polynomials of degree `polydeg` + volume_flux = (flux_central, flux_nonconservative) + surface_flux = (flux_lax_friedrichs, flux_nonconservative) + solver = DGSEM( + polydeg = 3, surface_flux = surface_flux, + volume_integral = VolumeIntegralFluxDifferencing(volume_flux) + ) + + # Setup the spatial semidiscretization containing all ingredients + semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver) + + # Create an ODE problem with given time span + tspan = (0.0, 1.0) + ode = semidiscretize(semi, tspan) + + summary_callback = SummaryCallback() + analysis_callback = AnalysisCallback(semi, interval = 50) + callbacks = CallbackSet(summary_callback, analysis_callback) + + # OrdinaryDiffEq's `solve` method evolves the solution in time and executes + # the passed callbacks + sol = solve( + ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6, + save_everystep = false, callback = callbacks + ) + + @test analysis_callback(sol).l2 ≈ [0.00029609575838969394, 5.5681704039507985e-6] end - # Create a uniform mesh in 1D in the interval [-π, π] with periodic boundaries - mesh = TreeMesh(-Float64(π), Float64(π), # min/max coordinates - initial_refinement_level = 4, n_cells_max = 10^4) - - # Create a DGSEM solver with polynomials of degree `polydeg` - volume_flux = (flux_central, flux_nonconservative) - surface_flux = (flux_lax_friedrichs, flux_nonconservative) - solver = DGSEM(polydeg = 3, surface_flux = surface_flux, - volume_integral = VolumeIntegralFluxDifferencing(volume_flux)) - - # Setup the spatial semidiscretization containing all ingredients - semi = SemidiscretizationHyperbolic(mesh, equation, initial_condition_sine, solver) - - # Create an ODE problem with given time span - tspan = (0.0, 1.0) - ode = semidiscretize(semi, tspan) - - summary_callback = SummaryCallback() - analysis_callback = AnalysisCallback(semi, interval = 50) - callbacks = CallbackSet(summary_callback, analysis_callback) - - # OrdinaryDiffEq's `solve` method evolves the solution in time and executes - # the passed callbacks - sol = solve(ode, Tsit5(), abstol = 1.0e-6, reltol = 1.0e-6, - save_everystep = false, callback = callbacks) - - @test analysis_callback(sol).l2 ≈ [0.00029609575838969394, 5.5681704039507985e-6] -end - -# Clean up afterwards: delete Trixi.jl output directory -@test_nowarn rm(outdir, recursive = true) + # Clean up afterwards: delete Trixi.jl output directory + @test_nowarn rm(outdir, recursive = true) end # TreeMesh1D end # module diff --git a/test/test_tree_1d_advection.jl b/test/test_tree_1d_advection.jl index 20586c4f3b..2bad3c6d20 100644 --- a/test/test_tree_1d_advection.jl +++ b/test/test_tree_1d_advection.jl @@ -8,113 +8,131 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Linear scalar advection" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - l2=[6.0388296447998465e-6], - linf=[3.217887726258972e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + l2 = [6.0388296447998465e-6], + linf = [3.217887726258972e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), - l2=[0.3540206249507417], - linf=[0.9999896603382347], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), + l2 = [0.3540206249507417], + linf = [0.9999896603382347], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr_nonperiodic.jl"), - l2=[4.283508859843524e-6], - linf=[3.235356127918171e-5], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_nonperiodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr_nonperiodic.jl"), + l2 = [4.283508859843524e-6], + linf = [3.235356127918171e-5], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_basic.jl (No errors)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - analysis_callback=AnalysisCallback(semi, interval = 42, - analysis_errors = Symbol[])) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_basic.jl (No errors)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + analysis_callback = AnalysisCallback( + semi, interval = 42, + analysis_errors = Symbol[] + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_finite_volume.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_finite_volume.jl"), - l2=[0.011662300515980219], - linf=[0.01647256923710194]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_finite_volume.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_finite_volume.jl"), + l2 = [0.011662300515980219], + linf = [0.01647256923710194] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_perk2.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_perk2.jl"), - l2=[0.011288030389423475], - linf=[0.01596735472556976]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 8000 + @trixi_testset "elixir_advection_perk2.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_perk2.jl"), + l2 = [0.011288030389423475], + linf = [0.01596735472556976] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 8000 + end end -end -# Testing the second-order paired explicit Runge-Kutta (PERK) method without stepsize callback -@trixi_testset "elixir_advection_perk2.jl(fixed time step)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_perk2.jl"), - dt=2.0e-3, - tspan=(0.0, 20.0), - save_solution=SaveSolutionCallback(dt = 0.1 + 1.0e-8), - callbacks=CallbackSet(summary_callback, save_solution, - analysis_callback, alive_callback), - l2=[9.886271430207691e-6], - linf=[3.729460413781638e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 8000 + # Testing the second-order paired explicit Runge-Kutta (PERK) method without stepsize callback + @trixi_testset "elixir_advection_perk2.jl(fixed time step)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_perk2.jl"), + dt = 2.0e-3, + tspan = (0.0, 20.0), + save_solution = SaveSolutionCallback(dt = 0.1 + 1.0e-8), + callbacks = CallbackSet( + summary_callback, save_solution, + analysis_callback, alive_callback + ), + l2 = [9.886271430207691e-6], + linf = [3.729460413781638e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 8000 + end end end -end end # module diff --git a/test/test_tree_1d_burgers.jl b/test/test_tree_1d_burgers.jl index 56e1ee749f..5d4dfd1300 100644 --- a/test/test_tree_1d_burgers.jl +++ b/test/test_tree_1d_burgers.jl @@ -8,63 +8,71 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Inviscid Burgers" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_burgers_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_burgers_basic.jl"), - l2=[2.967470209082194e-5], - linf=[0.00016152468882624227]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_burgers_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_burgers_basic.jl"), + l2 = [2.967470209082194e-5], + linf = [0.00016152468882624227] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_burgers_linear_stability.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_burgers_linear_stability.jl"), - l2=[0.5660569881106876], - linf=[1.9352238038313998]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_burgers_linear_stability.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_burgers_linear_stability.jl"), + l2 = [0.5660569881106876], + linf = [1.9352238038313998] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_burgers_shock.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_burgers_shock.jl"), - l2=[0.4422505602587537], - linf=[1.0000000000000009]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_burgers_shock.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_burgers_shock.jl"), + l2 = [0.4422505602587537], + linf = [1.0000000000000009] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_burgers_rarefaction.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_burgers_rarefaction.jl"), - l2=[0.4038224690923722], - linf=[1.0049201454652736]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_burgers_rarefaction.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_burgers_rarefaction.jl"), + l2 = [0.4038224690923722], + linf = [1.0049201454652736] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_1d_euler.jl b/test/test_tree_1d_euler.jl index dc523586f8..001cd234d1 100644 --- a/test/test_tree_1d_euler.jl +++ b/test/test_tree_1d_euler.jl @@ -8,488 +8,542 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Compressible Euler" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_euler_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), - l2=[ - 2.2527950196212703e-8, - 1.8187357193835156e-8, - 7.705669939973104e-8, - ], - linf=[ - 1.6205433861493646e-7, - 1.465427772462391e-7, - 5.372255111879554e-7, - ], - # With the default `maxiters = 1` in coverage tests, - # there would be no time series to check against. - coverage_override=(maxiters = 20,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), + l2 = [ + 2.2527950196212703e-8, + 1.8187357193835156e-8, + 7.705669939973104e-8, + ], + linf = [ + 1.6205433861493646e-7, + 1.465427772462391e-7, + 5.372255111879554e-7, + ], + # With the default `maxiters = 1` in coverage tests, + # there would be no time series to check against. + coverage_override = (maxiters = 20,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + # Extra test to make sure the "TimeSeriesCallback" made correct data. + # Extracts data at all points from the first step of the time series and compares it to the + # exact solution and an interpolated reference solution + point_data = [getindex(time_series.affect!.point_data[i], 1:3) for i in 1:3] + exact_data = [ + initial_condition_convergence_test( + time_series.affect!.point_coordinates[i], + time_series.affect!.time[1], + equations + ) for i in 1:3 + ] + ref_data = [ + [1.968279088772251, 1.9682791565395945, 3.874122958278797], + [2.0654816955822017, 2.0654817326611883, 4.26621471136323], + [2.0317209235018936, 2.0317209516429506, 4.127889808862571], + ] + @test point_data ≈ exact_data atol = 1.0e-6 + @test point_data ≈ ref_data end - # Extra test to make sure the "TimeSeriesCallback" made correct data. - # Extracts data at all points from the first step of the time series and compares it to the - # exact solution and an interpolated reference solution - point_data = [getindex(time_series.affect!.point_data[i], 1:3) for i in 1:3] - exact_data = [initial_condition_convergence_test(time_series.affect!.point_coordinates[i], - time_series.affect!.time[1], - equations) for i in 1:3] - ref_data = [[1.968279088772251, 1.9682791565395945, 3.874122958278797], - [2.0654816955822017, 2.0654817326611883, 4.26621471136323], - [2.0317209235018936, 2.0317209516429506, 4.127889808862571]] - @test point_data≈exact_data atol=1e-6 - @test point_data ≈ ref_data -end -@trixi_testset "elixir_euler_convergence_pure_fv.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_pure_fv.jl"), - l2=[ - 0.019355699748523896, - 0.022326984561234497, - 0.02523665947241734, - ], - linf=[ - 0.02895961127645519, - 0.03293442484199227, - 0.04246098278632804, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_convergence_pure_fv.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence_pure_fv.jl"), + l2 = [ + 0.019355699748523896, + 0.022326984561234497, + 0.02523665947241734, + ], + linf = [ + 0.02895961127645519, + 0.03293442484199227, + 0.04246098278632804, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_density_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), - l2=[ - 0.0011482554820217855, - 0.00011482554830323462, - 5.741277429325267e-6, - ], - linf=[ - 0.004090978306812376, - 0.0004090978313582294, - 2.045489210189544e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_density_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), + l2 = [ + 0.0011482554820217855, + 0.00011482554830323462, + 5.741277429325267e-6, + ], + linf = [ + 0.004090978306812376, + 0.0004090978313582294, + 2.045489210189544e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_density_wave.jl with initial_condition_constant" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), - l2=[ - 7.71293052584723e-16, - 1.9712947511091717e-14, - 7.50672833504266e-15, - ], - linf=[ - 3.774758283725532e-15, - 6.733502644351574e-14, - 2.4868995751603507e-14, - ], - initial_condition=initial_condition_constant) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_density_wave.jl with initial_condition_constant" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), + l2 = [ + 7.71293052584723e-16, + 1.9712947511091717e-14, + 7.50672833504266e-15, + ], + linf = [ + 3.774758283725532e-15, + 6.733502644351574e-14, + 2.4868995751603507e-14, + ], + initial_condition = initial_condition_constant + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 3.8099996914101204e-6, - 1.6745575717106341e-6, - 7.732189531480852e-6, - ], - linf=[ - 1.2971473393186272e-5, - 9.270328934274374e-6, - 3.092514399671842e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 3.8099996914101204e-6, + 1.6745575717106341e-6, + 7.732189531480852e-6, + ], + linf = [ + 1.2971473393186272e-5, + 9.270328934274374e-6, + 3.092514399671842e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.11821957357197649, - 0.15330089521538678, - 0.4417674632047301, - ], - linf=[ - 0.24280567569982958, - 0.29130548795961936, - 0.8847009003152442, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.11821957357197649, + 0.15330089521538678, + 0.4417674632047301, + ], + linf = [ + 0.24280567569982958, + 0.29130548795961936, + 0.8847009003152442, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with flux_kennedy_gruber" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.07803455838661963, - 0.10032577312032283, - 0.29228156303827935, - ], - linf=[ - 0.2549869853794955, - 0.3376472164661263, - 0.9650477546553962, - ], - maxiters=10, - surface_flux=flux_kennedy_gruber, - volume_flux=flux_kennedy_gruber) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl with flux_kennedy_gruber" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.07803455838661963, + 0.10032577312032283, + 0.29228156303827935, + ], + linf = [ + 0.2549869853794955, + 0.3376472164661263, + 0.9650477546553962, + ], + maxiters = 10, + surface_flux = flux_kennedy_gruber, + volume_flux = flux_kennedy_gruber + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with flux_shima_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.07800654460172655, - 0.10030365573277883, - 0.2921481199111959, - ], - linf=[ - 0.25408579350400395, - 0.3388657679031271, - 0.9776486386921928, - ], - maxiters=10, - surface_flux=flux_shima_etal, - volume_flux=flux_shima_etal) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl with flux_shima_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.07800654460172655, + 0.10030365573277883, + 0.2921481199111959, + ], + linf = [ + 0.25408579350400395, + 0.3388657679031271, + 0.9776486386921928, + ], + maxiters = 10, + surface_flux = flux_shima_etal, + volume_flux = flux_shima_etal + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with flux_chandrashekar" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.07801923089205756, - 0.10039557434912669, - 0.2922210399923278, - ], - linf=[ - 0.2576521982607225, - 0.3409717926625057, - 0.9772961936567048, - ], - maxiters=10, - surface_flux=flux_chandrashekar, - volume_flux=flux_chandrashekar) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl with flux_chandrashekar" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.07801923089205756, + 0.10039557434912669, + 0.2922210399923278, + ], + linf = [ + 0.2576521982607225, + 0.3409717926625057, + 0.9772961936567048, + ], + maxiters = 10, + surface_flux = flux_chandrashekar, + volume_flux = flux_chandrashekar + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with flux_hll" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[0.07855251823583848, 0.10213903748267686, 0.293985892532479], - linf=[ - 0.192621556068018, - 0.25184744005299536, - 0.7264977555504792, - ], - maxiters=10, - surface_flux=flux_hll, - volume_flux=flux_ranocha) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl with flux_hll" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [0.07855251823583848, 0.10213903748267686, 0.293985892532479], + linf = [ + 0.192621556068018, + 0.25184744005299536, + 0.7264977555504792, + ], + maxiters = 10, + surface_flux = flux_hll, + volume_flux = flux_ranocha + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing.jl"), - l2=[ - 0.11606096465319675, - 0.15028768943458806, - 0.4328230323046703, - ], - linf=[ - 0.18031710091067965, - 0.2351582421501841, - 0.6776805692092567, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing.jl"), + l2 = [ + 0.11606096465319675, + 0.15028768943458806, + 0.4328230323046703, + ], + linf = [ + 0.18031710091067965, + 0.2351582421501841, + 0.6776805692092567, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), - l2=[1.250005061244617, 0.06878411345533507, 0.9264328311018613], - linf=[ - 2.9766770877037168, - 0.16838100902295852, - 2.6655773445485798, - ], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), + l2 = [1.250005061244617, 0.06878411345533507, 0.9264328311018613], + linf = [ + 2.9766770877037168, + 0.16838100902295852, + 2.6655773445485798, + ], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave.jl (HLLE)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), - l2=[0.6442208390304879, 0.508817280068289, 0.9482809853033687], - linf=[3.007059066482486, 2.4678899558345506, 2.3952311739389787], - tspan=(0.0, 0.5), - surface_flux=flux_hlle) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave.jl (HLLE)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), + l2 = [0.6442208390304879, 0.508817280068289, 0.9482809853033687], + linf = [3.007059066482486, 2.4678899558345506, 2.3952311739389787], + tspan = (0.0, 0.5), + surface_flux = flux_hlle + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave_pure_fv.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_sedov_blast_wave_pure_fv.jl"), - l2=[1.0735456065491455, 0.07131078703089379, 0.9205739468590453], - linf=[ - 3.4296365168219216, - 0.17635583964559245, - 2.6574584326179505, - ], - # Let this test run longer to cover some lines in flux_hllc - coverage_override=(maxiters = 10^5, tspan = (0.0, 0.1))) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave_pure_fv.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_sedov_blast_wave_pure_fv.jl" + ), + l2 = [1.0735456065491455, 0.07131078703089379, 0.9205739468590453], + linf = [ + 3.4296365168219216, + 0.17635583964559245, + 2.6574584326179505, + ], + # Let this test run longer to cover some lines in flux_hllc + coverage_override = (maxiters = 10^5, tspan = (0.0, 0.1)) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave.jl with pressure" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), - l2=[1.297525985166995, 0.07964929522694145, 0.9269991156246368], - linf=[ - 3.1773015255764427, - 0.21331831536493773, - 2.6650170188241047, - ], - shock_indicator_variable=pressure, - cfl=0.2, - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave.jl with pressure" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), + l2 = [1.297525985166995, 0.07964929522694145, 0.9269991156246368], + linf = [ + 3.1773015255764427, + 0.21331831536493773, + 2.6650170188241047, + ], + shock_indicator_variable = pressure, + cfl = 0.2, + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave.jl with density" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), - l2=[1.2798798835860528, 0.07103461242058921, 0.9273792517187003], - linf=[ - 3.1087017048015824, - 0.17734706962928956, - 2.666689753470263, - ], - shock_indicator_variable=density, - cfl=0.2, - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave.jl with density" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), + l2 = [1.2798798835860528, 0.07103461242058921, 0.9273792517187003], + linf = [ + 3.1087017048015824, + 0.17734706962928956, + 2.666689753470263, + ], + shock_indicator_variable = density, + cfl = 0.2, + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_positivity.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_positivity.jl"), - l2=[1.6493820253458906, 0.19793887460986834, 0.9783506076125921], - linf=[4.71751203912051, 0.5272411022735763, 2.7426163947635844], - coverage_override=(maxiters = 3,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_positivity.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_positivity.jl"), + l2 = [1.6493820253458906, 0.19793887460986834, 0.9783506076125921], + linf = [4.71751203912051, 0.5272411022735763, 2.7426163947635844], + coverage_override = (maxiters = 3,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_blast_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave.jl"), - l2=[0.21934822867340323, 0.28131919126002686, 0.554361702716662], - linf=[ - 1.5180897390290355, - 1.3967085956620369, - 2.0663825294019595, - ], - maxiters=30) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_blast_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave.jl"), + l2 = [0.21934822867340323, 0.28131919126002686, 0.554361702716662], + linf = [ + 1.5180897390290355, + 1.3967085956620369, + 2.0663825294019595, + ], + maxiters = 30 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "test_quasi_1D_entropy" begin - a = 0.9 - u_1D = SVector(1.1, 0.2, 2.1) - u_quasi_1D = SVector(a * 1.1, a * 0.2, a * 2.1, a) - @test entropy(u_quasi_1D, CompressibleEulerEquationsQuasi1D(1.4)) ≈ - a * entropy(u_1D, CompressibleEulerEquations1D(1.4)) -end + @trixi_testset "test_quasi_1D_entropy" begin + a = 0.9 + u_1D = SVector(1.1, 0.2, 2.1) + u_quasi_1D = SVector(a * 1.1, a * 0.2, a * 2.1, a) + @test entropy(u_quasi_1D, CompressibleEulerEquationsQuasi1D(1.4)) ≈ + a * entropy(u_1D, CompressibleEulerEquations1D(1.4)) + end -@trixi_testset "elixir_euler_quasi_1d_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_quasi_1d_source_terms.jl"), - l2=[ - 3.876288369618363e-7, - 2.2247043122302947e-7, - 2.964004224572679e-7, - 5.2716983399807875e-8, - ], - linf=[ - 2.3925118561862746e-6, - 1.3603693522767912e-6, - 1.821888865105592e-6, - 1.1166012159335992e-7, - ]) + @trixi_testset "elixir_euler_quasi_1d_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_quasi_1d_source_terms.jl"), + l2 = [ + 3.876288369618363e-7, + 2.2247043122302947e-7, + 2.964004224572679e-7, + 5.2716983399807875e-8, + ], + linf = [ + 2.3925118561862746e-6, + 1.3603693522767912e-6, + 1.821888865105592e-6, + 1.1166012159335992e-7, + ] + ) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_quasi_1d_discontinuous.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_quasi_1d_discontinuous.jl"), - l2=[ - 0.045510421156346015, - 0.036750584788912195, - 0.2468985959132176, - 0.03684494180829024, - ], - linf=[ - 0.3313374853025697, - 0.11621933362158643, - 1.827403013568638, - 0.28045939999015723, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_quasi_1d_discontinuous.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_quasi_1d_discontinuous.jl" + ), + l2 = [ + 0.045510421156346015, + 0.036750584788912195, + 0.2468985959132176, + 0.03684494180829024, + ], + linf = [ + 0.3313374853025697, + 0.11621933362158643, + 1.827403013568638, + 0.28045939999015723, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_quasi_1d_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_quasi_1d_ec.jl"), - l2=[ - 0.08889113985713998, - 0.16199235348889673, - 0.40316524365054346, - 2.9602775074723667e-16, - ], - linf=[ - 0.28891355898284043, - 0.3752709888964313, - 0.84477102402413, - 8.881784197001252e-16, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_quasi_1d_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_quasi_1d_ec.jl"), + l2 = [ + 0.08889113985713998, + 0.16199235348889673, + 0.40316524365054346, + 2.9602775074723667e-16, + ], + linf = [ + 0.28891355898284043, + 0.3752709888964313, + 0.84477102402413, + 8.881784197001252e-16, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_1d_eulergravity.jl b/test/test_tree_1d_eulergravity.jl index 17bc0c71a7..728163c6af 100644 --- a/test/test_tree_1d_eulergravity.jl +++ b/test/test_tree_1d_eulergravity.jl @@ -8,27 +8,29 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Compressible Euler with self-gravity" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_eulergravity_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), - l2=[ - 0.00021708496949694728, 0.0002913795242132917, - 0.0006112500956552259, - ], - linf=[ - 0.0004977733237385706, 0.0013594226727522418, - 0.0020418739554664, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), + l2 = [ + 0.00021708496949694728, 0.0002913795242132917, + 0.0006112500956552259, + ], + linf = [ + 0.0004977733237385706, 0.0013594226727522418, + 0.0020418739554664, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_1d_eulermulti.jl b/test/test_tree_1d_eulermulti.jl index b6c79ce03d..a5c7410e35 100644 --- a/test/test_tree_1d_eulermulti.jl +++ b/test/test_tree_1d_eulermulti.jl @@ -11,15 +11,23 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Compressible Euler Multicomponent" begin @trixi_testset "Testing entropy2cons and cons2entropy" begin using ForwardDiff - gammas = (1.3272378792562836, 1.5269959187969864, 1.8362285750521512, - 1.0409061360276926, 1.4652015053812224, 1.3626493264184423) - gas_constants = (1.817636851910076, 6.760820475922636, 5.588953939749113, - 6.31574782981543, 3.362932038038397, 3.212779569399733) - equations = CompressibleEulerMulticomponentEquations1D(gammas = SVector{length(gammas)}(gammas...), - gas_constants = SVector{length(gas_constants)}(gas_constants...)) - u = [-1.4632513788889214, 0.9908786980927811, 0.2909066990257628, + gammas = ( + 1.3272378792562836, 1.5269959187969864, 1.8362285750521512, + 1.0409061360276926, 1.4652015053812224, 1.3626493264184423, + ) + gas_constants = ( + 1.817636851910076, 6.760820475922636, 5.588953939749113, + 6.31574782981543, 3.362932038038397, 3.212779569399733, + ) + equations = CompressibleEulerMulticomponentEquations1D( + gammas = SVector{length(gammas)}(gammas...), + gas_constants = SVector{length(gas_constants)}(gas_constants...) + ) + u = [ + -1.4632513788889214, 0.9908786980927811, 0.2909066990257628, 0.6256623915420473, 0.4905882754313441, 0.14481800501749112, - 1.0333532872771651, 0.6805599818745411] + 1.0333532872771651, 0.6805599818745411, + ] w = cons2entropy(u, equations) # test that the entropy variables match the gradients of the total entropy @test w ≈ ForwardDiff.gradient(u -> Trixi.total_entropy(u, equations), u) @@ -28,13 +36,19 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") end @trixi_testset "elixir_eulermulti_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_ec.jl"), - l2=[0.15330089521538684, 0.4417674632047301, - 0.016888510510282385, 0.03377702102056477, - 0.06755404204112954], - linf=[0.29130548795961864, 0.8847009003152357, - 0.034686525099975274, 0.06937305019995055, - 0.1387461003999011]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_ec.jl"), + l2 = [ + 0.15330089521538684, 0.4417674632047301, + 0.016888510510282385, 0.03377702102056477, + 0.06755404204112954, + ], + linf = [ + 0.29130548795961864, 0.8847009003152357, + 0.034686525099975274, 0.06937305019995055, + 0.1387461003999011, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -46,19 +60,21 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") end @trixi_testset "elixir_eulermulti_es.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_es.jl"), - l2=[ - 0.1522380497572071, - 0.43830846465313206, - 0.03907262116499431, - 0.07814524232998862, - ], - linf=[ - 0.24939193075537294, - 0.7139395740052739, - 0.06324208768391237, - 0.12648417536782475, - ]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_es.jl"), + l2 = [ + 0.1522380497572071, + 0.43830846465313206, + 0.03907262116499431, + 0.07814524232998862, + ], + linf = [ + 0.24939193075537294, + 0.7139395740052739, + 0.06324208768391237, + 0.12648417536782475, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -70,19 +86,21 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") end @trixi_testset "elixir_eulermulti_convergence_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_ec.jl"), - l2=[ - 8.575236038539227e-5, - 0.00016387804318585358, - 1.9412699303977585e-5, - 3.882539860795517e-5, - ], - linf=[ - 0.00030593277277124464, - 0.0006244803933350696, - 7.253121435135679e-5, - 0.00014506242870271358, - ]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_ec.jl"), + l2 = [ + 8.575236038539227e-5, + 0.00016387804318585358, + 1.9412699303977585e-5, + 3.882539860795517e-5, + ], + linf = [ + 0.00030593277277124464, + 0.0006244803933350696, + 7.253121435135679e-5, + 0.00014506242870271358, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -94,13 +112,19 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") end @trixi_testset "elixir_eulermulti_convergence_es.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_es.jl"), - l2=[1.8983933794407234e-5, 6.207744299844731e-5, - 1.5466205761868047e-6, 3.0932411523736094e-6, - 6.186482304747219e-6, 1.2372964609494437e-5], - linf=[0.00012014372605895218, 0.0003313207215800418, - 6.50836791016296e-6, 1.301673582032592e-5, - 2.603347164065184e-5, 5.206694328130368e-5]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_es.jl"), + l2 = [ + 1.8983933794407234e-5, 6.207744299844731e-5, + 1.5466205761868047e-6, 3.0932411523736094e-6, + 6.186482304747219e-6, 1.2372964609494437e-5, + ], + linf = [ + 0.00012014372605895218, 0.0003313207215800418, + 6.50836791016296e-6, 1.301673582032592e-5, + 2.603347164065184e-5, 5.206694328130368e-5, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -112,14 +136,20 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") end @trixi_testset "elixir_eulermulti_convergence_es.jl with flux_chandrashekar" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_es.jl"), - l2=[1.888450477353845e-5, 5.4910600482795386e-5, - 9.426737161533622e-7, 1.8853474323067245e-6, - 3.770694864613449e-6, 7.541389729226898e-6], - linf=[0.00011622351152063004, 0.0003079221967086099, - 3.2177423254231563e-6, 6.435484650846313e-6, - 1.2870969301692625e-5, 2.574193860338525e-5], - volume_flux=flux_chandrashekar) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_es.jl"), + l2 = [ + 1.888450477353845e-5, 5.4910600482795386e-5, + 9.426737161533622e-7, 1.8853474323067245e-6, + 3.770694864613449e-6, 7.541389729226898e-6, + ], + linf = [ + 0.00011622351152063004, 0.0003079221967086099, + 3.2177423254231563e-6, 6.435484650846313e-6, + 1.2870969301692625e-5, 2.574193860338525e-5, + ], + volume_flux = flux_chandrashekar + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -131,15 +161,23 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") end @trixi_testset "elixir_eulermulti_two_interacting_blast_waves.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulermulti_two_interacting_blast_waves.jl"), - l2=[1.288867611915533, 82.71335258388848, 0.00350680272313187, - 0.013698784353152794, - 0.019179518517518084], - linf=[29.6413044707026, 1322.5844802186496, 0.09191919374782143, - 0.31092970966717925, - 0.4417989757182038], - tspan=(0.0, 0.0001)) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulermulti_two_interacting_blast_waves.jl" + ), + l2 = [ + 1.288867611915533, 82.71335258388848, 0.00350680272313187, + 0.013698784353152794, + 0.019179518517518084, + ], + linf = [ + 29.6413044707026, 1322.5844802186496, 0.09191919374782143, + 0.31092970966717925, + 0.4417989757182038, + ], + tspan = (0.0, 0.0001) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let diff --git a/test/test_tree_1d_fdsbp.jl b/test/test_tree_1d_fdsbp.jl index 33d67e3366..f5c73dcd67 100644 --- a/test/test_tree_1d_fdsbp.jl +++ b/test/test_tree_1d_fdsbp.jl @@ -8,46 +8,52 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_fdsbp") @testset "Linear scalar advection" begin -#! format: noindent - -@trixi_testset "elixir_advection_upwind.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_upwind.jl"), - l2=[1.7735637157305526e-6], - linf=[1.0418854521951328e-5], - tspan=(0.0, 0.5)) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + #! format: noindent + + @trixi_testset "elixir_advection_upwind.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_upwind.jl"), + l2 = [1.7735637157305526e-6], + linf = [1.0418854521951328e-5], + tspan = (0.0, 0.5) + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_upwind_periodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_upwind_periodic.jl"), - l2=[1.1672962783692568e-5], - linf=[1.650514414558435e-5]) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_upwind_periodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_upwind_periodic.jl"), + l2 = [1.1672962783692568e-5], + linf = [1.650514414558435e-5] + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end @testset "Inviscid Burgers" begin @trixi_testset "elixir_burgers_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_burgers_basic.jl"), - l2=[8.316190308678742e-7], - linf=[7.1087263324720595e-6], - tspan=(0.0, 0.5)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_burgers_basic.jl"), + l2 = [8.316190308678742e-7], + linf = [7.1087263324720595e-6], + tspan = (0.0, 0.5) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -62,20 +68,26 @@ end # same tolerances as above since the methods should be identical (up to # machine precision) @trixi_testset "elixir_burgers_basic.jl with SurfaceIntegralStrongForm and FluxUpwind" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_burgers_basic.jl"), - l2=[8.316190308678742e-7], - linf=[7.1087263324720595e-6], - tspan=(0.0, 0.5), - solver=DG(D_upw, nothing, - SurfaceIntegralStrongForm(FluxUpwind(flux_splitting)), - VolumeIntegralUpwind(flux_splitting))) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_burgers_basic.jl"), + l2 = [8.316190308678742e-7], + linf = [7.1087263324720595e-6], + tspan = (0.0, 0.5), + solver = DG( + D_upw, nothing, + SurfaceIntegralStrongForm(FluxUpwind(flux_splitting)), + VolumeIntegralUpwind(flux_splitting) + ) + ) end @trixi_testset "elixir_burgers_linear_stability.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_burgers_linear_stability.jl"), - l2=[0.9999995642691271], - linf=[1.824702804788453], - tspan=(0.0, 0.25)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_burgers_linear_stability.jl"), + l2 = [0.9999995642691271], + linf = [1.824702804788453], + tspan = (0.0, 0.25) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -90,18 +102,20 @@ end @testset "Compressible Euler" begin @trixi_testset "elixir_euler_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 4.1370344463620254e-6, - 4.297052451817826e-6, - 9.857382045003056e-6, - ], - linf=[ - 1.675305070092392e-5, - 1.3448113863834266e-5, - 3.8185336878271414e-5, - ], - tspan=(0.0, 0.5)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 4.1370344463620254e-6, + 4.297052451817826e-6, + 9.857382045003056e-6, + ], + linf = [ + 1.675305070092392e-5, + 1.3448113863834266e-5, + 3.8185336878271414e-5, + ], + tspan = (0.0, 0.5) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -114,19 +128,21 @@ end end @trixi_testset "elixir_euler_convergence.jl with splitting_vanleer_haenel" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 3.413790589105506e-6, - 4.243957977156001e-6, - 8.667369423676437e-6, - ], - linf=[ - 1.4228079689537765e-5, - 1.3249887941046978e-5, - 3.201552933251861e-5, - ], - tspan=(0.0, 0.5), - flux_splitting=splitting_vanleer_haenel) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 3.413790589105506e-6, + 4.243957977156001e-6, + 8.667369423676437e-6, + ], + linf = [ + 1.4228079689537765e-5, + 1.3249887941046978e-5, + 3.201552933251861e-5, + ], + tspan = (0.0, 0.5), + flux_splitting = splitting_vanleer_haenel + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -139,20 +155,24 @@ end end @trixi_testset "elixir_euler_convergence.jl with VolumeIntegralStrongForm" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 8.6126767518378e-6, - 7.670897071480729e-6, - 1.4972772284191368e-5, - ], - linf=[ - 6.707982777909294e-5, - 3.487256699541419e-5, - 0.00010170331350556339, - ], - tspan=(0.0, 0.5), - solver=DG(D_upw.central, nothing, SurfaceIntegralStrongForm(), - VolumeIntegralStrongForm())) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 8.6126767518378e-6, + 7.670897071480729e-6, + 1.4972772284191368e-5, + ], + linf = [ + 6.707982777909294e-5, + 3.487256699541419e-5, + 0.00010170331350556339, + ], + tspan = (0.0, 0.5), + solver = DG( + D_upw.central, nothing, SurfaceIntegralStrongForm(), + VolumeIntegralStrongForm() + ) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -165,18 +185,20 @@ end end @trixi_testset "elixir_euler_density_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), - l2=[ - 1.5894925236031034e-5, - 9.428412101106044e-6, - 0.0008986477358789918, - ], - linf=[ - 4.969438024382544e-5, - 2.393091812063694e-5, - 0.003271817388146303, - ], - tspan=(0.0, 0.005), abstol=1.0e-9, reltol=1.0e-9) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), + l2 = [ + 1.5894925236031034e-5, + 9.428412101106044e-6, + 0.0008986477358789918, + ], + linf = [ + 4.969438024382544e-5, + 2.393091812063694e-5, + 0.003271817388146303, + ], + tspan = (0.0, 0.005), abstol = 1.0e-9, reltol = 1.0e-9 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) diff --git a/test/test_tree_1d_hypdiff.jl b/test/test_tree_1d_hypdiff.jl index 896a3d4c8d..b936b7d152 100644 --- a/test/test_tree_1d_hypdiff.jl +++ b/test/test_tree_1d_hypdiff.jl @@ -8,38 +8,44 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Hyperbolic diffusion" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_hypdiff_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), - l2=[1.3655114954641076e-7, 1.0200345025539218e-6], - linf=[7.173286515893551e-7, 4.507116363683394e-6], - atol=2.5e-13) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_hypdiff_nonperiodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), + l2 = [1.3655114954641076e-7, 1.0200345025539218e-6], + linf = [7.173286515893551e-7, 4.507116363683394e-6], + atol = 2.5e-13 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_hypdiff_harmonic_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_hypdiff_harmonic_nonperiodic.jl"), - l2=[3.0130941075207524e-12, 2.6240829677090014e-12], - linf=[4.054534485931072e-12, 3.8826719617190975e-12], - atol=2.5e-13) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 10000 + @trixi_testset "elixir_hypdiff_harmonic_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_hypdiff_harmonic_nonperiodic.jl" + ), + l2 = [3.0130941075207524e-12, 2.6240829677090014e-12], + linf = [4.054534485931072e-12, 3.8826719617190975e-12], + atol = 2.5e-13 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 10000 + end end end -end end # module diff --git a/test/test_tree_1d_linearizedeuler.jl b/test/test_tree_1d_linearizedeuler.jl index c7cffee3f6..7fadeeb82e 100644 --- a/test/test_tree_1d_linearizedeuler.jl +++ b/test/test_tree_1d_linearizedeuler.jl @@ -1,4 +1,3 @@ - using Test using Trixi @@ -7,45 +6,49 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Linearized Euler Equations 1D" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_linearizedeuler_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_convergence.jl"), - l2=[ - 0.00010894927270421941, - 0.00014295255695912358, - 0.00010894927270421941, - ], - linf=[ - 0.0005154647164193893, - 0.00048457837684242266, - 0.0005154647164193893, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_linearizedeuler_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_convergence.jl"), + l2 = [ + 0.00010894927270421941, + 0.00014295255695912358, + 0.00010894927270421941, + ], + linf = [ + 0.0005154647164193893, + 0.00048457837684242266, + 0.0005154647164193893, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_linearizedeuler_gauss_wall.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gauss_wall.jl"), - l2=[0.650082087850354, 0.2913911415488769, 0.650082087850354], - linf=[ - 1.9999505145390108, - 0.9999720404625275, - 1.9999505145390108, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_linearizedeuler_gauss_wall.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gauss_wall.jl"), + l2 = [0.650082087850354, 0.2913911415488769, 0.650082087850354], + linf = [ + 1.9999505145390108, + 0.9999720404625275, + 1.9999505145390108, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end diff --git a/test/test_tree_1d_maxwell.jl b/test/test_tree_1d_maxwell.jl index 0d936f703f..81f50ab196 100644 --- a/test/test_tree_1d_maxwell.jl +++ b/test/test_tree_1d_maxwell.jl @@ -8,37 +8,45 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Maxwell" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_maxwell_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_maxwell_convergence.jl"), - l2=[8933.196486422636, 2.979793603210305e-5], - linf=[21136.527033627033, 7.050386515528029e-5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_maxwell_convergence.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_maxwell_convergence.jl" + ), + l2 = [8933.196486422636, 2.979793603210305e-5], + linf = [21136.527033627033, 7.050386515528029e-5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_maxwell_E_excitation.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_maxwell_E_excitation.jl"), - l2=[1.8181768208894413e6, 0.09221738723979069], - linf=[2.5804473693440557e6, 0.1304024464192847]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_maxwell_E_excitation.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_maxwell_E_excitation.jl" + ), + l2 = [1.8181768208894413e6, 0.09221738723979069], + linf = [2.5804473693440557e6, 0.1304024464192847] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_1d_mhd.jl b/test/test_tree_1d_mhd.jl index 2150ddfd07..cfa303c29d 100644 --- a/test/test_tree_1d_mhd.jl +++ b/test/test_tree_1d_mhd.jl @@ -8,359 +8,383 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "MHD" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_mhd_alfven_wave.jl with initial_condition_constant" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 1.440611823425164e-15, - 1.1373567770134494e-14, - 3.024482376149653e-15, - 2.0553143516814395e-15, - 3.9938347410210535e-14, - 3.984545392098788e-16, - 2.4782402104201577e-15, - 1.551737464879987e-15, - ], - linf=[ - 1.9984014443252818e-15, - 1.3405943022348765e-14, - 3.3584246494910985e-15, - 3.164135620181696e-15, - 7.815970093361102e-14, - 8.881784197001252e-16, - 2.886579864025407e-15, - 2.942091015256665e-15, - ], - initial_condition=initial_condition_constant, - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl with initial_condition_constant" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 1.440611823425164e-15, + 1.1373567770134494e-14, + 3.024482376149653e-15, + 2.0553143516814395e-15, + 3.9938347410210535e-14, + 3.984545392098788e-16, + 2.4782402104201577e-15, + 1.551737464879987e-15, + ], + linf = [ + 1.9984014443252818e-15, + 1.3405943022348765e-14, + 3.3584246494910985e-15, + 3.164135620181696e-15, + 7.815970093361102e-14, + 8.881784197001252e-16, + 2.886579864025407e-15, + 2.942091015256665e-15, + ], + initial_condition = initial_condition_constant, + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 1.0375628983659061e-5, - 6.571144191446236e-7, - 3.5833569836289104e-5, - 3.583356983615859e-5, - 5.084863194951084e-6, - 1.1963224165731992e-16, - 3.598916927583752e-5, - 3.598916927594727e-5, - ], - linf=[ - 2.614095879338585e-5, - 9.577266731216823e-7, - 0.00012406198007461344, - 0.00012406198007509917, - 1.5066209528846741e-5, - 2.220446049250313e-16, - 0.00012658678753942054, - 0.00012658678753908748, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 1.0375628983659061e-5, + 6.571144191446236e-7, + 3.5833569836289104e-5, + 3.583356983615859e-5, + 5.084863194951084e-6, + 1.1963224165731992e-16, + 3.598916927583752e-5, + 3.598916927594727e-5, + ], + linf = [ + 2.614095879338585e-5, + 9.577266731216823e-7, + 0.00012406198007461344, + 0.00012406198007509917, + 1.5066209528846741e-5, + 2.220446049250313e-16, + 0.00012658678753942054, + 0.00012658678753908748, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl with flux_derigs_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 1.4396053943470756e-5, - 1.1211016739165248e-5, - 3.577870687983967e-5, - 3.577870687982181e-5, - 1.967962220860377e-6, - 1.1963224165731992e-16, - 3.583562899483433e-5, - 3.583562899486565e-5, - ], - linf=[ - 5.830577969345718e-5, - 3.280495696370357e-5, - 0.00012279619948236953, - 0.00012279619948227238, - 6.978806516122482e-6, - 2.220446049250313e-16, - 0.00012564003648959932, - 0.00012564003648994626, - ], - volume_flux=flux_derigs_etal) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl with flux_derigs_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 1.4396053943470756e-5, + 1.1211016739165248e-5, + 3.577870687983967e-5, + 3.577870687982181e-5, + 1.967962220860377e-6, + 1.1963224165731992e-16, + 3.583562899483433e-5, + 3.583562899486565e-5, + ], + linf = [ + 5.830577969345718e-5, + 3.280495696370357e-5, + 0.00012279619948236953, + 0.00012279619948227238, + 6.978806516122482e-6, + 2.220446049250313e-16, + 0.00012564003648959932, + 0.00012564003648994626, + ], + volume_flux = flux_derigs_etal + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl with flux_hllc" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 1.036850596986597e-5, 1.965192583650368e-6, - 3.5882124656715505e-5, 3.5882124656638764e-5, - 5.270975504780837e-6, 1.1963224165731992e-16, - 3.595811808912869e-5, 3.5958118089159453e-5, - ], - linf=[ - 2.887280521446378e-5, 7.310580790352001e-6, - 0.00012390046377899755, 0.00012390046377787345, - 1.5102711136583125e-5, 2.220446049250313e-16, - 0.0001261935452181312, 0.0001261935452182006, - ], - surface_flux=flux_hllc) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl with flux_hllc" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 1.036850596986597e-5, 1.965192583650368e-6, + 3.5882124656715505e-5, 3.5882124656638764e-5, + 5.270975504780837e-6, 1.1963224165731992e-16, + 3.595811808912869e-5, 3.5958118089159453e-5, + ], + linf = [ + 2.887280521446378e-5, 7.310580790352001e-6, + 0.00012390046377899755, 0.00012390046377787345, + 1.5102711136583125e-5, 2.220446049250313e-16, + 0.0001261935452181312, 0.0001261935452182006, + ], + surface_flux = flux_hllc + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), - l2=[ - 0.05815183849746399, - 0.08166807325621023, - 0.054659228513541165, - 0.054659228513541165, - 0.15578125987042743, - 4.130462730494e-17, - 0.05465258887150046, - 0.05465258887150046, - ], - linf=[ - 0.12165312668363826, - 0.1901920742264952, - 0.10059813883022554, - 0.10059813883022554, - 0.44079257431070706, - 1.1102230246251565e-16, - 0.10528911365809579, - 0.10528911365809579, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), + l2 = [ + 0.05815183849746399, + 0.08166807325621023, + 0.054659228513541165, + 0.054659228513541165, + 0.15578125987042743, + 4.130462730494e-17, + 0.05465258887150046, + 0.05465258887150046, + ], + linf = [ + 0.12165312668363826, + 0.1901920742264952, + 0.10059813883022554, + 0.10059813883022554, + 0.44079257431070706, + 1.1102230246251565e-16, + 0.10528911365809579, + 0.10528911365809579, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_briowu_shock_tube.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_briowu_shock_tube.jl"), - l2=[ - 0.17477712356961989, - 0.19489623595086944, - 0.3596546157640463, - 0.0, - 0.3723215736814466, - 1.2060075775846403e-15, - 0.36276754492568164, - 0.0, - ], - linf=[ - 0.5797109945880677, - 0.4372991899547103, - 1.0906536287185835, - 0.0, - 1.0526758874956808, - 5.995204332975845e-15, - 1.5122922036932964, - 0.0, - ], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_briowu_shock_tube.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_briowu_shock_tube.jl"), + l2 = [ + 0.17477712356961989, + 0.19489623595086944, + 0.3596546157640463, + 0.0, + 0.3723215736814466, + 1.2060075775846403e-15, + 0.36276754492568164, + 0.0, + ], + linf = [ + 0.5797109945880677, + 0.4372991899547103, + 1.0906536287185835, + 0.0, + 1.0526758874956808, + 5.995204332975845e-15, + 1.5122922036932964, + 0.0, + ], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_torrilhon_shock_tube.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_torrilhon_shock_tube.jl"), - l2=[ - 0.45700904847931145, - 0.4792535936512035, - 0.340651203521865, - 0.4478034694296928, - 0.9204708961093411, - 1.3216517820475193e-16, - 0.28897419402047725, - 0.25521206483145126, - ], - linf=[ - 1.2185238171352286, - 0.8913202384963431, - 0.8488793580488431, - 0.973083603686, - 1.660723397705417, - 2.220446049250313e-16, - 0.6874726847741993, - 0.65536978110274, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_torrilhon_shock_tube.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_torrilhon_shock_tube.jl"), + l2 = [ + 0.45700904847931145, + 0.4792535936512035, + 0.340651203521865, + 0.4478034694296928, + 0.9204708961093411, + 1.3216517820475193e-16, + 0.28897419402047725, + 0.25521206483145126, + ], + linf = [ + 1.2185238171352286, + 0.8913202384963431, + 0.8488793580488431, + 0.973083603686, + 1.660723397705417, + 2.220446049250313e-16, + 0.6874726847741993, + 0.65536978110274, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_torrilhon_shock_tube.jl (HLLC)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_torrilhon_shock_tube.jl"), - surface_flux=flux_hllc, - l2=[ - 0.4573799618744708, 0.4792633358230866, 0.34064852506872795, - 0.4479668434955162, 0.9203891782415092, - 1.3216517820475193e-16, 0.28887826520860815, - 0.255281629265771, - ], - linf=[ - 1.2382842201671505, 0.8929169308132259, 0.871298623806198, - 0.9822415614542821, 1.6726170732132717, - 2.220446049250313e-16, 0.7016155888023747, - 0.6556091522071984, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_torrilhon_shock_tube.jl (HLLC)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_torrilhon_shock_tube.jl"), + surface_flux = flux_hllc, + l2 = [ + 0.4573799618744708, 0.4792633358230866, 0.34064852506872795, + 0.4479668434955162, 0.9203891782415092, + 1.3216517820475193e-16, 0.28887826520860815, + 0.255281629265771, + ], + linf = [ + 1.2382842201671505, 0.8929169308132259, 0.871298623806198, + 0.9822415614542821, 1.6726170732132717, + 2.220446049250313e-16, 0.7016155888023747, + 0.6556091522071984, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_ryujones_shock_tube.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ryujones_shock_tube.jl"), - l2=[ - 0.23469781891518154, - 0.3916675299696121, - 0.08245195301016353, - 0.1745346945706147, - 0.9606363432904367, - 6.608258910237605e-17, - 0.21542929107153735, - 0.10705457908737925, - ], - linf=[ - 0.6447951791685409, - 0.9461857095377463, - 0.35074627554617605, - 0.8515177411529542, - 2.0770652030507053, - 1.1102230246251565e-16, - 0.49670855513788204, - 0.24830199967863564, - ], - tspan=(0.0, 0.1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ryujones_shock_tube.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ryujones_shock_tube.jl"), + l2 = [ + 0.23469781891518154, + 0.3916675299696121, + 0.08245195301016353, + 0.1745346945706147, + 0.9606363432904367, + 6.608258910237605e-17, + 0.21542929107153735, + 0.10705457908737925, + ], + linf = [ + 0.6447951791685409, + 0.9461857095377463, + 0.35074627554617605, + 0.8515177411529542, + 2.0770652030507053, + 1.1102230246251565e-16, + 0.49670855513788204, + 0.24830199967863564, + ], + tspan = (0.0, 0.1) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_shu_osher_shock_tube.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_shu_osher_shock_tube.jl"), - l2=[ - 1.01126210e+00, - 8.27157902e+00, - 1.30882545e+00, - 0.00000000e+00, - 5.21930435e+01, - 6.56538824e-16, - 1.01022340e+00, - 0.00000000e+00, - ], - linf=[ - 2.87172004e+00, - 2.26438057e+01, - 4.16672442e+00, - 0.00000000e+00, - 1.35152372e+02, - 3.44169138e-15, - 2.83556069e+00, - 0.00000000e+00, - ], - tspan=(0.0, 0.2), - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_shu_osher_shock_tube.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_shu_osher_shock_tube.jl"), + l2 = [ + 1.0112621e+0, + 8.27157902e+0, + 1.30882545e+0, + 0.0e+0, + 5.21930435e+1, + 6.56538824e-16, + 1.0102234e+0, + 0.0e+0, + ], + linf = [ + 2.87172004e+0, + 2.26438057e+1, + 4.16672442e+0, + 0.0e+0, + 1.35152372e+2, + 3.44169138e-15, + 2.83556069e+0, + 0.0e+0, + ], + tspan = (0.0, 0.2), + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_shu_osher_shock_tube.jl with flipped shock direction" begin - # Include this elixir to make `initial_condition_shu_osher_shock_tube_flipped` available, which is used below - trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_shu_osher_shock_tube.jl"), - tspan = (0.0, 0.0)) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_shu_osher_shock_tube.jl"), - l2=[ - 1.01539817e+00, - 8.29625810e+00, - 1.29548008e+00, - 0.00000000e+00, - 5.23565514e+01, - 3.18641825e-16, - 1.00485291e+00, - 0.00000000e+00, - ], - linf=[ - 2.92876280e+00, - 2.28341581e+01, - 4.11643561e+00, - 0.00000000e+00, - 1.36966213e+02, - 1.55431223e-15, - 2.80548864e+00, - 0.00000000e+00, - ], - initial_condition=initial_condition_shu_osher_shock_tube_flipped, - boundary_conditions=BoundaryConditionDirichlet(initial_condition_shu_osher_shock_tube_flipped), - tspan=(0.0, 0.2), - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_shu_osher_shock_tube.jl with flipped shock direction" begin + # Include this elixir to make `initial_condition_shu_osher_shock_tube_flipped` available, which is used below + trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_shu_osher_shock_tube.jl"), + tspan = (0.0, 0.0) + ) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_shu_osher_shock_tube.jl"), + l2 = [ + 1.01539817e+0, + 8.2962581e+0, + 1.29548008e+0, + 0.0e+0, + 5.23565514e+1, + 3.18641825e-16, + 1.00485291e+0, + 0.0e+0, + ], + linf = [ + 2.9287628e+0, + 2.28341581e+1, + 4.11643561e+0, + 0.0e+0, + 1.36966213e+2, + 1.55431223e-15, + 2.80548864e+0, + 0.0e+0, + ], + initial_condition = initial_condition_shu_osher_shock_tube_flipped, + boundary_conditions = BoundaryConditionDirichlet(initial_condition_shu_osher_shock_tube_flipped), + tspan = (0.0, 0.2), + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_1d_mhdmulti.jl b/test/test_tree_1d_mhdmulti.jl index 9bf3463488..2604745881 100644 --- a/test/test_tree_1d_mhdmulti.jl +++ b/test/test_tree_1d_mhdmulti.jl @@ -8,119 +8,149 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "MHD Multicomponent" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_mhdmulti_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_ec.jl"), - l2=[0.08166807325620999, 0.054659228513541616, - 0.054659228513541616, 0.15578125987042812, - 4.130462730494e-17, 0.054652588871500665, - 0.054652588871500665, 0.008307405499637766, - 0.01661481099927553, 0.03322962199855106], - linf=[0.19019207422649645, 0.10059813883022888, - 0.10059813883022888, 0.4407925743107146, - 1.1102230246251565e-16, 0.10528911365809623, - 0.10528911365809623, 0.01737901809766182, - 0.03475803619532364, 0.06951607239064728]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_ec.jl"), + l2 = [ + 0.08166807325620999, 0.054659228513541616, + 0.054659228513541616, 0.15578125987042812, + 4.130462730494e-17, 0.054652588871500665, + 0.054652588871500665, 0.008307405499637766, + 0.01661481099927553, 0.03322962199855106, + ], + linf = [ + 0.19019207422649645, 0.10059813883022888, + 0.10059813883022888, 0.4407925743107146, + 1.1102230246251565e-16, 0.10528911365809623, + 0.10528911365809623, 0.01737901809766182, + 0.03475803619532364, 0.06951607239064728, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhdmulti_ec.jl with flux_derigs_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_ec.jl"), - l2=[0.08151404166186461, 0.054640238302693274, - 0.054640238302693274, 0.15536125426328573, - 4.130462730494e-17, 0.054665489963920275, - 0.054665489963920275, 0.008308349501359825, - 0.01661669900271965, 0.0332333980054393], - linf=[0.1824424257860952, 0.09734687137001484, - 0.09734687137001484, 0.4243089502087325, - 1.1102230246251565e-16, 0.09558639591092555, - 0.09558639591092555, 0.017364773041550624, - 0.03472954608310125, 0.0694590921662025], - volume_flux=flux_derigs_etal) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_ec.jl with flux_derigs_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_ec.jl"), + l2 = [ + 0.08151404166186461, 0.054640238302693274, + 0.054640238302693274, 0.15536125426328573, + 4.130462730494e-17, 0.054665489963920275, + 0.054665489963920275, 0.008308349501359825, + 0.01661669900271965, 0.0332333980054393, + ], + linf = [ + 0.1824424257860952, 0.09734687137001484, + 0.09734687137001484, 0.4243089502087325, + 1.1102230246251565e-16, 0.09558639591092555, + 0.09558639591092555, 0.017364773041550624, + 0.03472954608310125, 0.0694590921662025, + ], + volume_flux = flux_derigs_etal + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhdmulti_es.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_es.jl"), - l2=[0.07994082660130175, 0.053940174914031976, - 0.053940174914031976, 0.15165513559250643, - 4.130462730494e-17, 0.05363207135290325, - 0.05363207135290325, 0.008258265884659555, - 0.01651653176931911, 0.03303306353863822], - linf=[0.14101014428198477, 0.07762441749521025, - 0.07762441749521025, 0.3381334453289866, - 1.1102230246251565e-16, 0.07003646400675223, - 0.07003646400675223, 0.014962483760600165, - 0.02992496752120033, 0.05984993504240066]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_es.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_es.jl"), + l2 = [ + 0.07994082660130175, 0.053940174914031976, + 0.053940174914031976, 0.15165513559250643, + 4.130462730494e-17, 0.05363207135290325, + 0.05363207135290325, 0.008258265884659555, + 0.01651653176931911, 0.03303306353863822, + ], + linf = [ + 0.14101014428198477, 0.07762441749521025, + 0.07762441749521025, 0.3381334453289866, + 1.1102230246251565e-16, 0.07003646400675223, + 0.07003646400675223, 0.014962483760600165, + 0.02992496752120033, 0.05984993504240066, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhdmulti_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_convergence.jl"), - l2=[1.7337265267786785e-5, 0.00032976971029271364, - 0.0003297697102926479, 6.194071694759044e-5, - 4.130462730494001e-17, 0.00032596825025664136, - 0.0003259682502567132, 2.5467510126885455e-5, - 5.093502025377091e-5, 0.00010187004050754182], - linf=[3.877554303711845e-5, 0.0012437848638874956, - 0.0012437848638876898, 0.00016431262020277781, - 1.1102230246251565e-16, 0.0012443734922607112, - 0.001244373492260704, 5.691007974162332e-5, - 0.00011382015948324664, 0.00022764031896649328]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_convergence.jl"), + l2 = [ + 1.7337265267786785e-5, 0.00032976971029271364, + 0.0003297697102926479, 6.194071694759044e-5, + 4.130462730494001e-17, 0.00032596825025664136, + 0.0003259682502567132, 2.5467510126885455e-5, + 5.093502025377091e-5, 0.00010187004050754182, + ], + linf = [ + 3.877554303711845e-5, 0.0012437848638874956, + 0.0012437848638876898, 0.00016431262020277781, + 1.1102230246251565e-16, 0.0012443734922607112, + 0.001244373492260704, 5.691007974162332e-5, + 0.00011382015948324664, 0.00022764031896649328, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhdmulti_briowu_shock_tube.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_briowu_shock_tube.jl"), - l2=[0.1877830835572639, 0.3455841730726793, 0.0, - 0.35413123388836687, - 8.745556626531982e-16, 0.3629920109231055, 0.0, - 0.05329005553971236, - 0.10658011107942472], - linf=[0.4288187627971754, 1.0386547815614993, 0.0, - 0.9541678878162702, - 5.773159728050814e-15, 1.4595119339458051, 0.0, - 0.18201910908829552, - 0.36403821817659104], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_briowu_shock_tube.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_briowu_shock_tube.jl"), + l2 = [ + 0.1877830835572639, 0.3455841730726793, 0.0, + 0.35413123388836687, + 8.745556626531982e-16, 0.3629920109231055, 0.0, + 0.05329005553971236, + 0.10658011107942472, + ], + linf = [ + 0.4288187627971754, 1.0386547815614993, 0.0, + 0.9541678878162702, + 5.773159728050814e-15, 1.4595119339458051, 0.0, + 0.18201910908829552, + 0.36403821817659104, + ], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_1d_shallowwater.jl b/test/test_tree_1d_shallowwater.jl index 42a91e578e..b97e214b0c 100644 --- a/test/test_tree_1d_shallowwater.jl +++ b/test/test_tree_1d_shallowwater.jl @@ -8,390 +8,456 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Shallow Water" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_shallowwater_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), - l2=[ - 0.24476140682560343, - 0.8587309324660326, - 0.07330427577586297, - ], - linf=[ - 2.1636963952308372, - 3.8737770522883115, - 1.7711213427919539, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), + l2 = [ + 0.24476140682560343, + 0.8587309324660326, + 0.07330427577586297, + ], + linf = [ + 2.1636963952308372, + 3.8737770522883115, + 1.7711213427919539, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_ec.jl with initial_condition_weak_blast_wave" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), - l2=[ - 0.39472828074570576, - 2.0390687947320076, - 4.1623084150546725e-10, - ], - linf=[ - 0.7793741954662221, - 3.2411927977882096, - 7.419800190922032e-10, - ], - initial_condition=initial_condition_weak_blast_wave, - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_ec.jl with initial_condition_weak_blast_wave" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), + l2 = [ + 0.39472828074570576, + 2.0390687947320076, + 4.1623084150546725e-10, + ], + linf = [ + 0.7793741954662221, + 3.2411927977882096, + 7.419800190922032e-10, + ], + initial_condition = initial_condition_weak_blast_wave, + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 0.10416666834254829, - 1.4352935256803184e-14, - 0.10416666834254838, - ], - linf=[1.9999999999999996, 3.248036646353028e-14, 2.0], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 0.10416666834254829, + 1.4352935256803184e-14, + 0.10416666834254838, + ], + linf = [1.9999999999999996, 3.248036646353028e-14, 2.0], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl with FluxHydrostaticReconstruction" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 0.10416666834254835, - 1.1891029971551825e-14, - 0.10416666834254838, - ], - linf=[2.0000000000000018, 2.4019608337954543e-14, 2.0], - surface_flux=(FluxHydrostaticReconstruction(flux_lax_friedrichs, - hydrostatic_reconstruction_audusse_etal), - flux_nonconservative_audusse_etal), - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl with FluxHydrostaticReconstruction" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 0.10416666834254835, + 1.1891029971551825e-14, + 0.10416666834254838, + ], + linf = [2.0000000000000018, 2.4019608337954543e-14, 2.0], + surface_flux = ( + FluxHydrostaticReconstruction( + flux_lax_friedrichs, + hydrostatic_reconstruction_audusse_etal + ), + flux_nonconservative_audusse_etal, + ), + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl with flux_nonconservative_wintermeyer_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 0.10416666834254838, - 1.6657566141935285e-14, - 0.10416666834254838, - ], - linf=[2.0000000000000004, 3.0610625110157164e-14, 2.0], - surface_flux=(flux_wintermeyer_etal, - flux_nonconservative_wintermeyer_etal), - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl with flux_nonconservative_wintermeyer_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 0.10416666834254838, + 1.6657566141935285e-14, + 0.10416666834254838, + ], + linf = [2.0000000000000004, 3.0610625110157164e-14, 2.0], + surface_flux = ( + flux_wintermeyer_etal, + flux_nonconservative_wintermeyer_etal, + ), + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.0022363707373868713, - 0.01576799981934617, - 4.436491725585346e-5, - ], - linf=[ - 0.00893601803417754, - 0.05939797350246456, - 9.098379777405796e-5, - ], - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.0022363707373868713, + 0.01576799981934617, + 4.436491725585346e-5, + ], + linf = [ + 0.00893601803417754, + 0.05939797350246456, + 9.098379777405796e-5, + ], + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl with flux_hll" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.002275023323848826, - 0.015861093821754046, - 4.436491725585346e-5, - ], - linf=[ - 0.008461451098266792, - 0.05722331401673486, - 9.098379777405796e-5, - ], - tspan=(0.0, 0.025), - surface_flux=(flux_hll, - flux_nonconservative_fjordholm_etal)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl with flux_hll" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.002275023323848826, + 0.015861093821754046, + 4.436491725585346e-5, + ], + linf = [ + 0.008461451098266792, + 0.05722331401673486, + 9.098379777405796e-5, + ], + tspan = (0.0, 0.025), + surface_flux = ( + flux_hll, + flux_nonconservative_fjordholm_etal, + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl with flux_nonconservative_wintermeyer_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.005774284062933275, - 0.017408601639513584, - 4.43649172561843e-5, - ], - linf=[ - 0.01639116193303547, - 0.05102877460799604, - 9.098379777450205e-5, - ], - surface_flux=(flux_wintermeyer_etal, - flux_nonconservative_wintermeyer_etal), - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl with flux_nonconservative_wintermeyer_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.005774284062933275, + 0.017408601639513584, + 4.43649172561843e-5, + ], + linf = [ + 0.01639116193303547, + 0.05102877460799604, + 9.098379777450205e-5, + ], + surface_flux = ( + flux_wintermeyer_etal, + flux_nonconservative_wintermeyer_etal, + ), + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms_dirichlet.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_source_terms_dirichlet.jl"), - l2=[ - 0.0022667320585353927, - 0.01571629729279524, - 4.4364917255842716e-5, - ], - linf=[ - 0.008945234652224965, - 0.059403165802872415, - 9.098379777405796e-5, - ], - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms_dirichlet.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_source_terms_dirichlet.jl" + ), + l2 = [ + 0.0022667320585353927, + 0.01571629729279524, + 4.4364917255842716e-5, + ], + linf = [ + 0.008945234652224965, + 0.059403165802872415, + 9.098379777405796e-5, + ], + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms_dirichlet.jl with FluxHydrostaticReconstruction" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_source_terms_dirichlet.jl"), - l2=[ - 0.0022774071143995952, - 0.01566214422689219, - 4.4364917255842716e-5, - ], - linf=[ - 0.008451721489057373, - 0.05720939055279217, - 9.098379777405796e-5, - ], - surface_flux=(FluxHydrostaticReconstruction(FluxHLL(min_max_speed_naive), - hydrostatic_reconstruction_audusse_etal), - flux_nonconservative_audusse_etal), - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms_dirichlet.jl with FluxHydrostaticReconstruction" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_source_terms_dirichlet.jl" + ), + l2 = [ + 0.0022774071143995952, + 0.01566214422689219, + 4.4364917255842716e-5, + ], + linf = [ + 0.008451721489057373, + 0.05720939055279217, + 9.098379777405796e-5, + ], + surface_flux = ( + FluxHydrostaticReconstruction( + FluxHLL(min_max_speed_naive), + hydrostatic_reconstruction_audusse_etal + ), + flux_nonconservative_audusse_etal, + ), + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced_nonperiodic.jl with Dirichlet boundary" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_well_balanced_nonperiodic.jl"), - l2=[ - 1.725964362045055e-8, - 5.0427180314307505e-16, - 1.7259643530442137e-8, - ], - linf=[ - 3.844551077492042e-8, - 3.469453422316143e-15, - 3.844551077492042e-8, - ], - tspan=(0.0, 0.25), - surface_flux=(FluxHLL(min_max_speed_naive), - flux_nonconservative_fjordholm_etal),) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced_nonperiodic.jl with Dirichlet boundary" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_well_balanced_nonperiodic.jl" + ), + l2 = [ + 1.725964362045055e-8, + 5.0427180314307505e-16, + 1.7259643530442137e-8, + ], + linf = [ + 3.844551077492042e-8, + 3.469453422316143e-15, + 3.844551077492042e-8, + ], + tspan = (0.0, 0.25), + surface_flux = ( + FluxHLL(min_max_speed_naive), + flux_nonconservative_fjordholm_etal, + ), + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced_nonperiodic.jl with wall boundary" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_well_balanced_nonperiodic.jl"), - l2=[ - 1.7259643614361866e-8, - 3.5519018243195145e-16, - 1.7259643530442137e-8, - ], - linf=[ - 3.844551010878661e-8, - 9.846474508971374e-16, - 3.844551077492042e-8, - ], - tspan=(0.0, 0.25), - surface_flux=(FluxHLL(min_max_speed_naive), - flux_nonconservative_fjordholm_etal), - boundary_condition=boundary_condition_slip_wall) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced_nonperiodic.jl with wall boundary" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_well_balanced_nonperiodic.jl" + ), + l2 = [ + 1.7259643614361866e-8, + 3.5519018243195145e-16, + 1.7259643530442137e-8, + ], + linf = [ + 3.844551010878661e-8, + 9.846474508971374e-16, + 3.844551077492042e-8, + ], + tspan = (0.0, 0.25), + surface_flux = ( + FluxHLL(min_max_speed_naive), + flux_nonconservative_fjordholm_etal, + ), + boundary_condition = boundary_condition_slip_wall + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_shock_capturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_shock_capturing.jl"), - l2=[0.07424140641160326, 0.2148642632748155, 0.0372579849000542], - linf=[ - 1.1209754279344226, - 1.3230788645853582, - 0.8646939843534251, - ], - tspan=(0.0, 0.05)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_shock_capturing.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_shock_capturing.jl" + ), + l2 = [0.07424140641160326, 0.2148642632748155, 0.0372579849000542], + linf = [ + 1.1209754279344226, + 1.3230788645853582, + 0.8646939843534251, + ], + tspan = (0.0, 0.05) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallow_water_quasi_1d_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallow_water_quasi_1d_source_terms.jl"), - l2=[ - 6.37048760275098e-5, - 0.0002745658116815704, - 4.436491725647962e-6, - 8.872983451152218e-6, - ], - linf=[ - 0.00026747526881631956, - 0.0012106730729152249, - 9.098379777500165e-6, - 1.8196759554278685e-5, - ], - tspan=(0.0, 0.05)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallow_water_quasi_1d_source_terms.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallow_water_quasi_1d_source_terms.jl" + ), + l2 = [ + 6.37048760275098e-5, + 0.0002745658116815704, + 4.436491725647962e-6, + 8.872983451152218e-6, + ], + linf = [ + 0.00026747526881631956, + 0.0012106730729152249, + 9.098379777500165e-6, + 1.8196759554278685e-5, + ], + tspan = (0.0, 0.05) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_quasi_1d_well_balanced.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_quasi_1d_well_balanced.jl"), - l2=[ - 1.4250229186905198e-14, - 2.495109919406496e-12, - 7.408599286788738e-17, - 2.7205812409138776e-16, - ], - linf=[ - 5.284661597215745e-14, - 2.74056233065078e-12, - 2.220446049250313e-16, - 8.881784197001252e-16, - ], - tspan=(0.0, 100.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_quasi_1d_well_balanced.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_quasi_1d_well_balanced.jl" + ), + l2 = [ + 1.4250229186905198e-14, + 2.495109919406496e-12, + 7.408599286788738e-17, + 2.7205812409138776e-16, + ], + linf = [ + 5.284661597215745e-14, + 2.74056233065078e-12, + 2.220446049250313e-16, + 8.881784197001252e-16, + ], + tspan = (0.0, 100.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_quasi_1d_discontinuous.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_quasi_1d_discontinuous.jl"), - l2=[ - 0.02843233740533314, - 0.14083324483705398, - 0.0054554472558998, - 0.005455447255899814, - ], - linf=[ - 0.26095842440037487, - 0.45919004549253795, - 0.09999999999999983, - 0.10000000000000009, - ],) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_quasi_1d_discontinuous.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_quasi_1d_discontinuous.jl" + ), + l2 = [ + 0.02843233740533314, + 0.14083324483705398, + 0.0054554472558998, + 0.005455447255899814, + ], + linf = [ + 0.26095842440037487, + 0.45919004549253795, + 0.09999999999999983, + 0.10000000000000009, + ], + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_1d_traffic_flow_lwr.jl b/test/test_tree_1d_traffic_flow_lwr.jl index 54412e314b..3c6a7e5564 100644 --- a/test/test_tree_1d_traffic_flow_lwr.jl +++ b/test/test_tree_1d_traffic_flow_lwr.jl @@ -8,35 +8,41 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_1d_dgsem") @testset "Traffic-flow LWR" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_traffic_flow_lwr_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_traffic_flow_lwr_convergence.jl"), - l2=[0.0008455067389588569], - linf=[0.004591951086623913]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_traffic_flow_lwr_convergence.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_traffic_flow_lwr_convergence.jl" + ), + l2 = [0.0008455067389588569], + linf = [0.004591951086623913] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_traffic_flow_lwr_trafficjam.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_traffic_flow_lwr_trafficjam.jl"), - l2=[0.1761758135539748], linf=[0.5]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_traffic_flow_lwr_trafficjam.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_traffic_flow_lwr_trafficjam.jl"), + l2 = [0.1761758135539748], linf = [0.5] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_2d_acoustics.jl b/test/test_tree_2d_acoustics.jl index 89bccbf8ca..b5b327e92d 100644 --- a/test/test_tree_2d_acoustics.jl +++ b/test/test_tree_2d_acoustics.jl @@ -8,137 +8,155 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "Acoustic Perturbation" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_acoustics_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_acoustics_convergence.jl"), - l2=[ - 0.0019921138796370834, - 0.002090394698052287, - 0.0006091925854593805, - 0.0, - 0.0, - 0.0, - 0.0, - ], - linf=[ - 0.00769282588065634, - 0.008276649669227254, - 0.004196479023954813, - 0.0, - 0.0, - 0.0, - 0.0, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_acoustics_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_acoustics_convergence.jl"), + l2 = [ + 0.0019921138796370834, + 0.002090394698052287, + 0.0006091925854593805, + 0.0, + 0.0, + 0.0, + 0.0, + ], + linf = [ + 0.00769282588065634, + 0.008276649669227254, + 0.004196479023954813, + 0.0, + 0.0, + 0.0, + 0.0, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_acoustics_gauss.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_acoustics_gauss.jl"), - l2=[ - 0.08005276517890283, - 0.08005276517890268, - 0.4187202920734123, - 0.0, - 0.0, - 0.0, - 0.0, - ], - linf=[ - 0.17261097190220992, - 0.17261097190220973, - 1.13601894068238, - 0.0, - 0.0, - 0.0, - 0.0, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_acoustics_gauss.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_acoustics_gauss.jl"), + l2 = [ + 0.08005276517890283, + 0.08005276517890268, + 0.4187202920734123, + 0.0, + 0.0, + 0.0, + 0.0, + ], + linf = [ + 0.17261097190220992, + 0.17261097190220973, + 1.13601894068238, + 0.0, + 0.0, + 0.0, + 0.0, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_acoustics_gaussian_source.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_acoustics_gaussian_source.jl"), - l2=[ - 0.004296394903650806, - 0.004241280404758938, - 0.006269684906035964, - 0.0, - 0.0, - 0.0, - 0.0, - ], - linf=[ - 0.03970270697049378, - 0.04151096349298151, - 0.0640019829058819, - 0.0, - 0.0, - 0.0, - 0.0, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_acoustics_gaussian_source.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_acoustics_gaussian_source.jl"), + l2 = [ + 0.004296394903650806, + 0.004241280404758938, + 0.006269684906035964, + 0.0, + 0.0, + 0.0, + 0.0, + ], + linf = [ + 0.03970270697049378, + 0.04151096349298151, + 0.0640019829058819, + 0.0, + 0.0, + 0.0, + 0.0, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_acoustics_gauss_wall.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_acoustics_gauss_wall.jl"), - l2=[0.019419398248465843, 0.019510701017551826, - 0.04818246051887614, - 7.382060834820337e-17, 0.0, 1.4764121669640674e-16, - 1.4764121669640674e-16], - linf=[0.18193631937316496, 0.1877464607867628, - 1.0355388011792845, - 2.220446049250313e-16, 0.0, 4.440892098500626e-16, - 4.440892098500626e-16]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_acoustics_gauss_wall.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_acoustics_gauss_wall.jl"), + l2 = [ + 0.019419398248465843, 0.019510701017551826, + 0.04818246051887614, + 7.382060834820337e-17, 0.0, 1.4764121669640674e-16, + 1.4764121669640674e-16, + ], + linf = [ + 0.18193631937316496, 0.1877464607867628, + 1.0355388011792845, + 2.220446049250313e-16, 0.0, 4.440892098500626e-16, + 4.440892098500626e-16, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_acoustics_monopole.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_acoustics_monopole.jl"), - l2=[0.006816790293009947, 0.0065068948357351625, - 0.008724512056168938, - 0.0009894398191644543, 0.0, 7.144325530679576e-17, - 7.144325530679576e-17], - linf=[1.000633375007386, 0.5599788929862504, 0.5738432957070382, - 0.015590137026938428, 0.0, 2.220446049250313e-16, - 2.220446049250313e-16], - maxiters=50) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_acoustics_monopole.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_acoustics_monopole.jl"), + l2 = [ + 0.006816790293009947, 0.0065068948357351625, + 0.008724512056168938, + 0.0009894398191644543, 0.0, 7.144325530679576e-17, + 7.144325530679576e-17, + ], + linf = [ + 1.000633375007386, 0.5599788929862504, 0.5738432957070382, + 0.015590137026938428, 0.0, 2.220446049250313e-16, + 2.220446049250313e-16, + ], + maxiters = 50 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_2d_advection.jl b/test/test_tree_2d_advection.jl index b111651aa6..619b900ead 100644 --- a/test/test_tree_2d_advection.jl +++ b/test/test_tree_2d_advection.jl @@ -8,172 +8,196 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "Linear scalar advection" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - # Expected errors are exactly the same as in the parallel test! - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5], - # Let the small basic test run to the end - coverage_override=(maxiters = 10^5,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + # Expected errors are exactly the same as in the parallel test! + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5], + # Let the small basic test run to the end + coverage_override = (maxiters = 10^5,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_extended.jl with polydeg=1" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[0.02134571266411136], - linf=[0.04347734797775926], - polydeg=1) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_extended.jl with polydeg=1" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [0.02134571266411136], + linf = [0.04347734797775926], + polydeg = 1 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_restart.jl" begin - using OrdinaryDiffEq: SSPRK43 - println("═"^100) - println(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl")) - trixi_include(@__MODULE__, joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - alg = SSPRK43(), tspan = (0.0, 10.0)) - l2_expected, linf_expected = analysis_callback(sol) + @trixi_testset "elixir_advection_restart.jl" begin + using OrdinaryDiffEq: SSPRK43 + println("═"^100) + println(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl")) + trixi_include( + @__MODULE__, joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + alg = SSPRK43(), tspan = (0.0, 10.0) + ) + l2_expected, linf_expected = analysis_callback(sol) - println("═"^100) - println(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl")) - # Errors are exactly the same as in the elixir_advection_extended.jl - trixi_include(@__MODULE__, joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - alg = SSPRK43()) - l2_actual, linf_actual = analysis_callback(sol) + println("═"^100) + println(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl")) + # Errors are exactly the same as in the elixir_advection_extended.jl + trixi_include( + @__MODULE__, joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + alg = SSPRK43() + ) + l2_actual, linf_actual = analysis_callback(sol) - @test l2_actual == l2_expected - @test linf_actual == linf_expected -end + @test l2_actual == l2_expected + @test linf_actual == linf_expected + end -@trixi_testset "elixir_advection_mortar.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_mortar.jl"), - # Expected errors are exactly the same as in the parallel test! - l2=[0.0015188466707237375], - linf=[0.008446655719187679]) + @trixi_testset "elixir_advection_mortar.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_mortar.jl"), + # Expected errors are exactly the same as in the parallel test! + l2 = [0.0015188466707237375], + linf = [0.008446655719187679] + ) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), - # Expected errors are exactly the same as in the parallel test! - l2=[4.913300828257469e-5], - linf=[0.00045263895394385967], - # Let this test run to the end to cover some AMR code - coverage_override=(maxiters = 10^5,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), + # Expected errors are exactly the same as in the parallel test! + l2 = [4.913300828257469e-5], + linf = [0.00045263895394385967], + # Let this test run to the end to cover some AMR code + coverage_override = (maxiters = 10^5,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr_nonperiodic.jl"), - # Expected errors are exactly the same as in the parallel test! - l2=[3.2207388565869075e-5], - linf=[0.0007508059772436404], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_nonperiodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr_nonperiodic.jl"), + # Expected errors are exactly the same as in the parallel test! + l2 = [3.2207388565869075e-5], + linf = [0.0007508059772436404], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr_solution_independent.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_advection_amr_solution_independent.jl"), - l2=[4.949660644033807e-5], - linf=[0.0004867846262313763], - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_amr_solution_independent.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_advection_amr_solution_independent.jl" + ), + l2 = [4.949660644033807e-5], + linf = [0.0004867846262313763], + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr_visualization.jl" begin - # To make CI tests work, disable showing a plot window with the GR backend of the Plots package - # Xref: https://github.com/jheinen/GR.jl/issues/278 - # Xref: https://github.com/JuliaPlots/Plots.jl/blob/8cc6d9d48755ba452a2835f9b89d3880e9945377/test/runtests.jl#L103 - if !isinteractive() - restore = get(ENV, "GKSwstype", nothing) - ENV["GKSwstype"] = "100" - end + @trixi_testset "elixir_advection_amr_visualization.jl" begin + # To make CI tests work, disable showing a plot window with the GR backend of the Plots package + # Xref: https://github.com/jheinen/GR.jl/issues/278 + # Xref: https://github.com/JuliaPlots/Plots.jl/blob/8cc6d9d48755ba452a2835f9b89d3880e9945377/test/runtests.jl#L103 + if !isinteractive() + restore = get(ENV, "GKSwstype", nothing) + ENV["GKSwstype"] = "100" + end - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr_visualization.jl"), - l2=[0.0007225529919720868], - linf=[0.005954447875428925], - coverage_override=(maxiters = 6,)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr_visualization.jl"), + l2 = [0.0007225529919720868], + linf = [0.005954447875428925], + coverage_override = (maxiters = 6,) + ) - # Restore GKSwstype to previous value (if it was set) - if !isinteractive() - if isnothing(restore) - delete!(ENV, "GKSwstype") - else - ENV["GKSwstype"] = restore + # Restore GKSwstype to previous value (if it was set) + if !isinteractive() + if isnothing(restore) + delete!(ENV, "GKSwstype") + else + ENV["GKSwstype"] = restore + end end end -end -@trixi_testset "elixir_advection_timeintegration.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), - l2=[2.4976030518356626e-5], - linf=[0.0005531580316338533], - # Let this test terminate by time instead of maxiters to cover some lines - # in time_integration/methods_2N.jl - coverage_override=(maxiters = 10^5, tspan = (0.0, 0.1))) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_advection_timeintegration.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), + l2 = [2.4976030518356626e-5], + linf = [0.0005531580316338533], + # Let this test terminate by time instead of maxiters to cover some lines + # in time_integration/methods_2N.jl + coverage_override = (maxiters = 10^5, tspan = (0.0, 0.1)) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end end -end @trixi_testset "elixir_advection_timeintegration.jl with carpenter_kennedy_erk43" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), - l2=[2.5314747030031457e-5], - linf=[0.0005437136621948904], - ode_algorithm=Trixi.CarpenterKennedy2N43(), - cfl=1.0) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), + l2 = [2.5314747030031457e-5], + linf = [0.0005437136621948904], + ode_algorithm = Trixi.CarpenterKennedy2N43(), + cfl = 1.0 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -185,12 +209,14 @@ end end @trixi_testset "elixir_advection_timeintegration.jl with carpenter_kennedy_erk43 with maxiters=1" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), - l2=[1.2135350502911197e-5], - linf=[9.999985420537649e-5], - ode_algorithm=Trixi.CarpenterKennedy2N43(), - cfl=1.0, - maxiters=1) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), + l2 = [1.2135350502911197e-5], + linf = [9.999985420537649e-5], + ode_algorithm = Trixi.CarpenterKennedy2N43(), + cfl = 1.0, + maxiters = 1 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -202,10 +228,12 @@ end end @trixi_testset "elixir_advection_timeintegration.jl with parsani_ketcheson_deconinck_erk94" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), - l2=[2.4976673477385313e-5], - linf=[0.0005534166916640881], - ode_algorithm=Trixi.ParsaniKetchesonDeconinck3Sstar94()) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), + l2 = [2.4976673477385313e-5], + linf = [0.0005534166916640881], + ode_algorithm = Trixi.ParsaniKetchesonDeconinck3Sstar94() + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -217,11 +245,13 @@ end end @trixi_testset "elixir_advection_timeintegration.jl with parsani_ketcheson_deconinck_erk32" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), - l2=[3.667894656471403e-5], - linf=[0.0005799465470165757], - ode_algorithm=Trixi.ParsaniKetchesonDeconinck3Sstar32(), - cfl=1.0) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), + l2 = [3.667894656471403e-5], + linf = [0.0005799465470165757], + ode_algorithm = Trixi.ParsaniKetchesonDeconinck3Sstar32(), + cfl = 1.0 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -233,12 +263,14 @@ end end @trixi_testset "elixir_advection_timeintegration.jl with parsani_ketcheson_deconinck_erk32 with maxiters=1" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), - l2=[1.2198725469737875e-5], - linf=[9.977247740793407e-5], - ode_algorithm=Trixi.ParsaniKetchesonDeconinck3Sstar32(), - cfl=1.0, - maxiters=1) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_timeintegration.jl"), + l2 = [1.2198725469737875e-5], + linf = [9.977247740793407e-5], + ode_algorithm = Trixi.ParsaniKetchesonDeconinck3Sstar32(), + cfl = 1.0, + maxiters = 1 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -250,9 +282,11 @@ end end @trixi_testset "elixir_advection_callbacks.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_callbacks.jl"), - l2=[8.311947673061856e-6], - linf=[6.627000273229378e-5]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_callbacks.jl"), + l2 = [8.311947673061856e-6], + linf = [6.627000273229378e-5] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -267,11 +301,13 @@ end @testset "Linear scalar advection: Tests for initial conditions" begin # Linear scalar advection @trixi_testset "elixir_advection_extended.jl with initial_condition_sin_sin" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[0.0001420618061089383], - linf=[0.0007140570281718439], - maxiters=1, - initial_condition=Trixi.initial_condition_sin_sin) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [0.0001420618061089383], + linf = [0.0007140570281718439], + maxiters = 1, + initial_condition = Trixi.initial_condition_sin_sin + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -283,11 +319,13 @@ end end @trixi_testset "elixir_advection_extended.jl with initial_condition_constant" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[3.8302867746057483e-16], - linf=[1.3322676295501878e-15], - maxiters=1, - initial_condition=initial_condition_constant) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [3.8302867746057483e-16], + linf = [1.3322676295501878e-15], + maxiters = 1, + initial_condition = initial_condition_constant + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -299,13 +337,15 @@ end end @trixi_testset "elixir_advection_extended.jl with initial_condition_linear_x_y" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[2.7276160570381226e-16], - linf=[5.10702591327572e-15], - maxiters=1, - initial_condition=Trixi.initial_condition_linear_x_y, - boundary_conditions=Trixi.boundary_condition_linear_x_y, - periodicity=false) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [2.7276160570381226e-16], + linf = [5.10702591327572e-15], + maxiters = 1, + initial_condition = Trixi.initial_condition_linear_x_y, + boundary_conditions = Trixi.boundary_condition_linear_x_y, + periodicity = false + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -317,13 +357,15 @@ end end @trixi_testset "elixir_advection_extended.jl with initial_condition_linear_x" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[1.5121648229368207e-16], - linf=[1.3322676295501878e-15], - maxiters=1, - initial_condition=Trixi.initial_condition_linear_x, - boundary_conditions=Trixi.boundary_condition_linear_x, - periodicity=false) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [1.5121648229368207e-16], + linf = [1.3322676295501878e-15], + maxiters = 1, + initial_condition = Trixi.initial_condition_linear_x, + boundary_conditions = Trixi.boundary_condition_linear_x, + periodicity = false + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -335,13 +377,15 @@ end end @trixi_testset "elixir_advection_extended.jl with initial_condition_linear_y" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[1.714292614252588e-16], - linf=[2.220446049250313e-15], - maxiters=1, - initial_condition=Trixi.initial_condition_linear_y, - boundary_conditions=Trixi.boundary_condition_linear_y, - periodicity=false) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [1.714292614252588e-16], + linf = [2.220446049250313e-15], + maxiters = 1, + initial_condition = Trixi.initial_condition_linear_y, + boundary_conditions = Trixi.boundary_condition_linear_y, + periodicity = false + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let diff --git a/test/test_tree_2d_euler.jl b/test/test_tree_2d_euler.jl index a004d1452b..6c01b249a3 100644 --- a/test/test_tree_2d_euler.jl +++ b/test/test_tree_2d_euler.jl @@ -8,955 +8,993 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "Compressible Euler" begin -#! format: noindent - -@trixi_testset "elixir_euler_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), - l2=[ - 9.321181253186009e-7, - 1.4181210743438511e-6, - 1.4181210743487851e-6, - 4.824553091276693e-6, - ], - linf=[ - 9.577246529612893e-6, - 1.1707525976012434e-5, - 1.1707525976456523e-5, - 4.8869615580926506e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end - -@trixi_testset "elixir_euler_convergence_pure_fv.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_pure_fv.jl"), - l2=[ - 0.026440292358506527, - 0.013245905852168414, - 0.013245905852168479, - 0.03912520302609374, - ], - linf=[ - 0.042130817806361964, - 0.022685499230187034, - 0.022685499230187922, - 0.06999771202145322, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 - end -end + #! format: noindent -@trixi_testset "elixir_euler_density_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), - l2=[ - 0.0010600778457964775, - 0.00010600778457634275, - 0.00021201556915872665, - 2.650194614399671e-5, - ], - linf=[ - 0.006614198043413566, - 0.0006614198043973507, - 0.001322839608837334, - 0.000165354951256802, - ], - tspan=(0.0, 0.5)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), + l2 = [ + 9.321181253186009e-7, + 1.4181210743438511e-6, + 1.4181210743487851e-6, + 4.824553091276693e-6, + ], + linf = [ + 9.577246529612893e-6, + 1.1707525976012434e-5, + 1.1707525976456523e-5, + 4.8869615580926506e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_source_terms_nonperiodic.jl"), - l2=[ - 2.259440511766445e-6, - 2.318888155713922e-6, - 2.3188881557894307e-6, - 6.3327863238858925e-6, - ], - linf=[ - 1.498738264560373e-5, - 1.9182011928187137e-5, - 1.918201192685487e-5, - 6.0526717141407005e-5, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_convergence_pure_fv.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence_pure_fv.jl"), + l2 = [ + 0.026440292358506527, + 0.013245905852168414, + 0.013245905852168479, + 0.03912520302609374, + ], + linf = [ + 0.042130817806361964, + 0.022685499230187034, + 0.022685499230187922, + 0.06999771202145322, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.061751715597716854, - 0.05018223615408711, - 0.05018989446443463, - 0.225871559730513, - ], - linf=[ - 0.29347582879608825, - 0.31081249232844693, - 0.3107380389947736, - 1.0540358049885143, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_density_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_density_wave.jl"), + l2 = [ + 0.0010600778457964775, + 0.00010600778457634275, + 0.00021201556915872665, + 2.650194614399671e-5, + ], + linf = [ + 0.006614198043413566, + 0.0006614198043973507, + 0.001322839608837334, + 0.000165354951256802, + ], + tspan = (0.0, 0.5) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with flux_kennedy_gruber" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.03481471610306124, - 0.027694280613944234, - 0.027697905866996532, - 0.12932052501462554, - ], - linf=[ - 0.31052098400669004, - 0.3481295959664616, - 0.34807152194137336, - 1.1044947556170719, - ], - maxiters=10, - surface_flux=flux_kennedy_gruber, - volume_flux=flux_kennedy_gruber) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_source_terms_nonperiodic.jl" + ), + l2 = [ + 2.259440511766445e-6, + 2.318888155713922e-6, + 2.3188881557894307e-6, + 6.3327863238858925e-6, + ], + linf = [ + 1.498738264560373e-5, + 1.9182011928187137e-5, + 1.918201192685487e-5, + 6.0526717141407005e-5, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with flux_chandrashekar" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.03481122603050542, - 0.027662840593087695, - 0.027665658732350273, - 0.12927455860656786, - ], - linf=[ - 0.3110089578739834, - 0.34888111987218107, - 0.3488278669826813, - 1.1056349046774305, - ], - maxiters=10, - surface_flux=flux_chandrashekar, - volume_flux=flux_chandrashekar) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.061751715597716854, + 0.05018223615408711, + 0.05018989446443463, + 0.225871559730513, + ], + linf = [ + 0.29347582879608825, + 0.31081249232844693, + 0.3107380389947736, + 1.0540358049885143, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing.jl"), - l2=[ - 0.05380629130119074, - 0.04696798008325309, - 0.04697067787841479, - 0.19687382235494968, - ], - linf=[ - 0.18527440131928286, - 0.2404798030563736, - 0.23269573860381076, - 0.6874012187446894, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl with flux_kennedy_gruber" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.03481471610306124, + 0.027694280613944234, + 0.027697905866996532, + 0.12932052501462554, + ], + linf = [ + 0.31052098400669004, + 0.3481295959664616, + 0.34807152194137336, + 1.1044947556170719, + ], + maxiters = 10, + surface_flux = flux_kennedy_gruber, + volume_flux = flux_kennedy_gruber + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing_subcell.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_shockcapturing_subcell.jl"), - l2=[ - 0.08508152653623638, - 0.04510301725066843, - 0.04510304668512745, - 0.6930705064715306, - ], - linf=[ - 0.31136518019691406, - 0.5617651935473419, - 0.5621200790240503, - 2.8866869108596056, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_euler_ec.jl with flux_chandrashekar" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.03481122603050542, + 0.027662840593087695, + 0.027665658732350273, + 0.12927455860656786, + ], + linf = [ + 0.3110089578739834, + 0.34888111987218107, + 0.3488278669826813, + 1.1056349046774305, + ], + maxiters = 10, + surface_flux = flux_chandrashekar, + volume_flux = flux_chandrashekar + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing_subcell.jl (fixed time step)" begin - # Testing local SSP method without stepsize callback - # Additionally, tests combination with SaveSolutionCallback using time interval - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_shockcapturing_subcell.jl"), - dt=2.0e-3, - tspan=(0.0, 0.25), - save_solution=SaveSolutionCallback(dt = 0.1 + 1.0e-8), - callbacks=CallbackSet(summary_callback, save_solution, - analysis_callback, alive_callback), - l2=[ - 0.05624855363458103, - 0.06931288786158463, - 0.06931283188960778, - 0.6200535829842072, - ], - linf=[ - 0.29029967648805566, - 0.6494728865862608, - 0.6494729363533714, - 3.0949621505674787, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_euler_shockcapturing.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing.jl"), + l2 = [ + 0.05380629130119074, + 0.04696798008325309, + 0.04697067787841479, + 0.19687382235494968, + ], + linf = [ + 0.18527440131928286, + 0.2404798030563736, + 0.23269573860381076, + 0.6874012187446894, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_blast_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave.jl"), - l2=[ - 0.14170569763947993, - 0.11647068900798814, - 0.11647072556898294, - 0.3391989213659599, - ], - linf=[ - 1.6544204510794196, - 1.35194638484646, - 1.3519463848472744, - 1.831228461662809, - ], - maxiters=30) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing_subcell.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_shockcapturing_subcell.jl" + ), + l2 = [ + 0.08508152653623638, + 0.04510301725066843, + 0.04510304668512745, + 0.6930705064715306, + ], + linf = [ + 0.31136518019691406, + 0.5617651935473419, + 0.5621200790240503, + 2.8866869108596056, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end -end -@trixi_testset "elixir_euler_blast_wave_pure_fv.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_pure_fv.jl"), - l2=[ - 0.39957047631960346, - 0.21006912294983154, - 0.21006903549932, - 0.6280328163981136, - ], - linf=[ - 2.20417889887697, - 1.5487238480003327, - 1.5486788679247812, - 2.4656795949035857, - ], - tspan=(0.0, 0.5), - # Let this test run longer to cover some lines in flux_hllc - coverage_override=(maxiters = 10^5, tspan = (0.0, 0.1))) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing_subcell.jl (fixed time step)" begin + # Testing local SSP method without stepsize callback + # Additionally, tests combination with SaveSolutionCallback using time interval + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_shockcapturing_subcell.jl" + ), + dt = 2.0e-3, + tspan = (0.0, 0.25), + save_solution = SaveSolutionCallback(dt = 0.1 + 1.0e-8), + callbacks = CallbackSet( + summary_callback, save_solution, + analysis_callback, alive_callback + ), + l2 = [ + 0.05624855363458103, + 0.06931288786158463, + 0.06931283188960778, + 0.6200535829842072, + ], + linf = [ + 0.29029967648805566, + 0.6494728865862608, + 0.6494729363533714, + 3.0949621505674787, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end -end -@trixi_testset "elixir_euler_blast_wave_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr.jl"), - l2=[ - 0.6835576416907511, - 0.2839963955262972, - 0.28399565983676, - 0.7229447806293277, - ], - linf=[ - 3.0969614882801393, - 1.7967947300740248, - 1.7967508302506658, - 3.040149575567518, - ], - tspan=(0.0, 1.0), - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_blast_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave.jl"), + l2 = [ + 0.14170569763947993, + 0.11647068900798814, + 0.11647072556898294, + 0.3391989213659599, + ], + linf = [ + 1.6544204510794196, + 1.35194638484646, + 1.3519463848472744, + 1.831228461662809, + ], + maxiters = 30 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_blast_wave_sc_subcell_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_blast_wave_sc_subcell_nonperiodic.jl"), - l2=[ - 0.3221177942225801, - 0.1798478357478982, - 0.1798364616438908, - 0.6136884131056267, - ], - linf=[ - 1.343766644801395, - 1.1749593109683463, - 1.1747613085307178, - 2.4216006041018785, - ], - tspan=(0.0, 0.5), - initial_refinement_level=4, - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_euler_blast_wave_pure_fv.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_pure_fv.jl"), + l2 = [ + 0.39957047631960346, + 0.21006912294983154, + 0.21006903549932, + 0.6280328163981136, + ], + linf = [ + 2.20417889887697, + 1.5487238480003327, + 1.5486788679247812, + 2.4656795949035857, + ], + tspan = (0.0, 0.5), + # Let this test run longer to cover some lines in flux_hllc + coverage_override = (maxiters = 10^5, tspan = (0.0, 0.1)) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), - l2=[ - 0.4866953770742574, - 0.1673477470091984, - 0.16734774700934, - 0.6184367248923149, - ], - linf=[ - 2.6724832723962053, - 1.2916089288910635, - 1.2916089289001427, - 6.474699399394252, - ], - tspan=(0.0, 1.0), - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_blast_wave_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_blast_wave_amr.jl"), + l2 = [ + 0.6835576416907511, + 0.2839963955262972, + 0.28399565983676, + 0.7229447806293277, + ], + linf = [ + 3.0969614882801393, + 1.7967947300740248, + 1.7967508302506658, + 3.040149575567518, + ], + tspan = (0.0, 1.0), + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave_sc_subcell.jl" begin - rm(joinpath("out", "deviations.txt"), force = true) - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_sedov_blast_wave_sc_subcell.jl"), - l2=[ - 0.41444427153173785, - 0.1460669409661223, - 0.14606693069201596, - 0.6168046457461059, - ], - linf=[ - 1.5720584643579567, - 0.7946656826861964, - 0.7946656525739751, - 6.455520291414711, - ], - tspan=(0.0, 1.0), - initial_refinement_level=4, - coverage_override=(maxiters = 6,), - save_errors=true) - lines = readlines(joinpath("out", "deviations.txt")) - @test lines[1] == "# iter, simu_time, rho_min, rho_max, entropy_guermond_etal_min" - cmd = string(Base.julia_cmd()) - coverage = occursin("--code-coverage", cmd) && - !occursin("--code-coverage=none", cmd) - if coverage - # Run with coverage takes 6 time steps. - @test startswith(lines[end], "6") - else - # Run without coverage takes 89 time steps. - @test startswith(lines[end], "89") - end - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_euler_blast_wave_sc_subcell_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_blast_wave_sc_subcell_nonperiodic.jl" + ), + l2 = [ + 0.3221177942225801, + 0.1798478357478982, + 0.1798364616438908, + 0.6136884131056267, + ], + linf = [ + 1.343766644801395, + 1.1749593109683463, + 1.1747613085307178, + 2.4216006041018785, + ], + tspan = (0.0, 0.5), + initial_refinement_level = 4, + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave.jl (HLLE)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), - l2=[ - 0.352405949321075, - 0.17207721487429464, - 0.17207721487433883, - 0.6263024434020885, - ], - linf=[ - 2.760997358628186, - 1.8279186132509326, - 1.8279186132502805, - 6.251573757093399, - ], - tspan=(0.0, 0.5), - callbacks=CallbackSet(summary_callback, - analysis_callback, alive_callback, - stepsize_callback), - surface_flux=flux_hlle), - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), + l2 = [ + 0.4866953770742574, + 0.1673477470091984, + 0.16734774700934, + 0.6184367248923149, + ], + linf = [ + 2.6724832723962053, + 1.2916089288910635, + 1.2916089289001427, + 6.474699399394252, + ], + tspan = (0.0, 1.0), + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_positivity.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_positivity.jl"), - l2=[ - 0.48862067511841695, - 0.16787541578869494, - 0.16787541578869422, - 0.6184319933114926, - ], - linf=[ - 2.6766520821013002, - 1.2910938760258996, - 1.2910938760258899, - 6.473385481404865, - ], - tspan=(0.0, 1.0), - coverage_override=(maxiters = 3,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave_sc_subcell.jl" begin + rm(joinpath("out", "deviations.txt"), force = true) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_sedov_blast_wave_sc_subcell.jl" + ), + l2 = [ + 0.41444427153173785, + 0.1460669409661223, + 0.14606693069201596, + 0.6168046457461059, + ], + linf = [ + 1.5720584643579567, + 0.7946656826861964, + 0.7946656525739751, + 6.455520291414711, + ], + tspan = (0.0, 1.0), + initial_refinement_level = 4, + coverage_override = (maxiters = 6,), + save_errors = true + ) + lines = readlines(joinpath("out", "deviations.txt")) + @test lines[1] == "# iter, simu_time, rho_min, rho_max, entropy_guermond_etal_min" + cmd = string(Base.julia_cmd()) + coverage = occursin("--code-coverage", cmd) && + !occursin("--code-coverage=none", cmd) + if coverage + # Run with coverage takes 6 time steps. + @test startswith(lines[end], "6") + else + # Run without coverage takes 89 time steps. + @test startswith(lines[end], "89") + end + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end -end -@trixi_testset "elixir_euler_blob_mortar.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blob_mortar.jl"), - l2=[ - 0.22271619518391986, - 0.6284824759323494, - 0.24864213447943648, - 2.9591811489995474, - ], - linf=[ - 9.15245400430106, - 24.96562810334389, - 10.388109127032374, - 101.20581544156934, - ], - tspan=(0.0, 0.5)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave.jl (HLLE)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), + l2 = [ + 0.352405949321075, + 0.17207721487429464, + 0.17207721487433883, + 0.6263024434020885, + ], + linf = [ + 2.760997358628186, + 1.8279186132509326, + 1.8279186132502805, + 6.251573757093399, + ], + tspan = (0.0, 0.5), + callbacks = CallbackSet( + summary_callback, + analysis_callback, alive_callback, + stepsize_callback + ), + surface_flux = flux_hlle + ), + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_blob_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blob_amr.jl"), - l2=[ - 0.2086261501910662, - 1.2118352377894666, - 0.10255333189606497, - 5.296238138639236, - ], - linf=[ - 14.829071984498198, - 74.12967742435727, - 6.863554388300223, - 303.58813147491134, - ], - tspan=(0.0, 0.12), - # Let this test run longer to cover the ControllerThreeLevelCombined lines - coverage_override=(maxiters = 10^5,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_positivity.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_positivity.jl"), + l2 = [ + 0.48862067511841695, + 0.16787541578869494, + 0.16787541578869422, + 0.6184319933114926, + ], + linf = [ + 2.6766520821013002, + 1.2910938760258996, + 1.2910938760258899, + 6.473385481404865, + ], + tspan = (0.0, 1.0), + coverage_override = (maxiters = 3,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability_fjordholm_etal.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability_fjordholm_etal.jl"), - l2=[ - 0.1057230211245312, - 0.10621112311257341, - 0.07260957505339989, - 0.11178239111065721, - ], - linf=[ - 2.998719417992662, - 2.1400285015556166, - 1.1569648700415078, - 1.8922492268110913, - ], - tspan=(0.0, 0.1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_blob_mortar.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_blob_mortar.jl"), + l2 = [ + 0.22271619518391986, + 0.6284824759323494, + 0.24864213447943648, + 2.9591811489995474, + ], + linf = [ + 9.15245400430106, + 24.96562810334389, + 10.388109127032374, + 101.20581544156934, + ], + tspan = (0.0, 0.5) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability.jl"), - l2=[ - 0.055691508271624536, - 0.032986009333751655, - 0.05224390923711999, - 0.08009536362771563, - ], - linf=[ - 0.24043622527087494, - 0.1660878796929941, - 0.12355946691711608, - 0.2694290787257758, - ], - tspan=(0.0, 0.2)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_blob_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_blob_amr.jl"), + l2 = [ + 0.2086261501910662, + 1.2118352377894666, + 0.10255333189606497, + 5.296238138639236, + ], + linf = [ + 14.829071984498198, + 74.12967742435727, + 6.863554388300223, + 303.58813147491134, + ], + tspan = (0.0, 0.12), + # Let this test run longer to cover the ControllerThreeLevelCombined lines + coverage_override = (maxiters = 10^5,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability_amr.jl"), - l2=[ - 0.05569452733654995, - 0.033107109983417926, - 0.05223609622852158, - 0.08007777597488817, - ], - linf=[ - 0.2535807803900303, - 0.17397028249895308, - 0.12321616095649354, - 0.269046666668995, - ], - tspan=(0.0, 0.2), - coverage_override=(maxiters = 2,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_kelvin_helmholtz_instability_fjordholm_etal.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability_fjordholm_etal.jl" + ), + l2 = [ + 0.1057230211245312, + 0.10621112311257341, + 0.07260957505339989, + 0.11178239111065721, + ], + linf = [ + 2.998719417992662, + 2.1400285015556166, + 1.1569648700415078, + 1.8922492268110913, + ], + tspan = (0.0, 0.1) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl" begin - rm(joinpath("out", "deviations.txt"), force = true) - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl"), - l2=[ - 0.42185634563805724, - 0.1686471269704017, - 0.18240674916968103, - 0.17858250604280654, - ], - linf=[ - 1.7012978064377158, - 0.7149714986746726, - 0.5822547982757897, - 0.7300051017382696, - ], - tspan=(0.0, 2.0), - coverage_override=(maxiters = 7,), - save_errors=true) - lines = readlines(joinpath("out", "deviations.txt")) - @test lines[1] == "# iter, simu_time, rho_min, pressure_min" - # Run without (with) coverage takes 745 (7) time steps - @test startswith(lines[end], "7") - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability.jl" + ), + l2 = [ + 0.055691508271624536, + 0.032986009333751655, + 0.05224390923711999, + 0.08009536362771563, + ], + linf = [ + 0.24043622527087494, + 0.1660878796929941, + 0.12355946691711608, + 0.2694290787257758, + ], + tspan = (0.0, 0.2) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_colliding_flow.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_colliding_flow.jl"), - l2=[ - 0.007237139090503349, - 0.044887582765386916, - 1.0453570959003603e-6, - 0.6627307840935432, - ], - linf=[ - 0.19437260992446315, - 0.5554343646648533, - 5.943891455255412e-5, - 15.188919846360125, - ], - tspan=(0.0, 0.1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_kelvin_helmholtz_instability_amr.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability_amr.jl" + ), + l2 = [ + 0.05569452733654995, + 0.033107109983417926, + 0.05223609622852158, + 0.08007777597488817, + ], + linf = [ + 0.2535807803900303, + 0.17397028249895308, + 0.12321616095649354, + 0.269046666668995, + ], + tspan = (0.0, 0.2), + coverage_override = (maxiters = 2,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_colliding_flow_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_colliding_flow_amr.jl"), - l2=[ - 0.006768801432802192, - 0.032184992228603666, - 6.923887797276484e-7, - 0.6784222932398366, - ], - linf=[ - 0.2508663007713608, - 0.4097017076529792, - 0.0003528986458217968, - 22.435474993016918, - ], - tspan=(0.0, 0.1), - coverage_override=(maxiters = 2,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl" begin + rm(joinpath("out", "deviations.txt"), force = true) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability_sc_subcell.jl" + ), + l2 = [ + 0.42185634563805724, + 0.1686471269704017, + 0.18240674916968103, + 0.17858250604280654, + ], + linf = [ + 1.7012978064377158, + 0.7149714986746726, + 0.5822547982757897, + 0.7300051017382696, + ], + tspan = (0.0, 2.0), + coverage_override = (maxiters = 7,), + save_errors = true + ) + lines = readlines(joinpath("out", "deviations.txt")) + @test lines[1] == "# iter, simu_time, rho_min, pressure_min" + # Run without (with) coverage takes 745 (7) time steps + @test startswith(lines[end], "7") + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end -end -@trixi_testset "elixir_euler_astro_jet_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_astro_jet_amr.jl"), - l2=[ - 0.011338365293662804, - 10.09743543555765, - 0.00392429463200361, - 4031.7811487690506, - ], - linf=[ - 3.3178633141984193, - 2993.6445033486402, - 8.031723414357423, - 1.1918867260293828e6, - ], - tspan=(0.0, 1.0e-7), - coverage_override=(maxiters = 6,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_colliding_flow.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_colliding_flow.jl"), + l2 = [ + 0.007237139090503349, + 0.044887582765386916, + 1.0453570959003603e-6, + 0.6627307840935432, + ], + linf = [ + 0.19437260992446315, + 0.5554343646648533, + 5.943891455255412e-5, + 15.188919846360125, + ], + tspan = (0.0, 0.1) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_vortex.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex.jl"), - l2=[ - 0.00013492249515826863, - 0.006615696236378061, - 0.006782108219800376, - 0.016393831451740604, - ], - linf=[ - 0.0020782600954247776, - 0.08150078921935999, - 0.08663621974991986, - 0.2829930622010579, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_colliding_flow_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_colliding_flow_amr.jl"), + l2 = [ + 0.006768801432802192, + 0.032184992228603666, + 6.923887797276484e-7, + 0.6784222932398366, + ], + linf = [ + 0.2508663007713608, + 0.4097017076529792, + 0.0003528986458217968, + 22.435474993016918, + ], + tspan = (0.0, 0.1), + coverage_override = (maxiters = 2,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_vortex_mortar.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex_mortar.jl"), - # Expected errors are exactly the same as in the parallel test! - l2=[ - 0.0017208369388227673, - 0.09628684992237334, - 0.09620157717330868, - 0.1758809552387432, - ], - linf=[ - 0.021869936355319086, - 0.9956698009442038, - 1.0002507727219028, - 2.223249697515648, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_astro_jet_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_astro_jet_amr.jl"), + l2 = [ + 0.011338365293662804, + 10.09743543555765, + 0.00392429463200361, + 4031.7811487690506, + ], + linf = [ + 3.3178633141984193, + 2993.6445033486402, + 8.031723414357423, + 1.1918867260293828e6, + ], + tspan = (0.0, 1.0e-7), + coverage_override = (maxiters = 6,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_vortex_mortar_split.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex_mortar_split.jl"), - l2=[ - 0.0017203323613648241, - 0.09628962878682261, - 0.09621241164155782, - 0.17585995600340926, - ], - linf=[ - 0.021740570456931674, - 0.9938841665880938, - 1.004140123355135, - 2.224108857746245, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_vortex.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex.jl"), + l2 = [ + 0.00013492249515826863, + 0.006615696236378061, + 0.006782108219800376, + 0.016393831451740604, + ], + linf = [ + 0.0020782600954247776, + 0.08150078921935999, + 0.08663621974991986, + 0.2829930622010579, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_vortex_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex_shockcapturing.jl"), - l2=[ - 0.0017158367642679273, - 0.09619888722871434, - 0.09616432767924141, - 0.17553381166255197, - ], - linf=[ - 0.021853862449723982, - 0.9878047229255944, - 0.9880191167111795, - 2.2154030488035588, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_vortex_mortar.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex_mortar.jl"), + # Expected errors are exactly the same as in the parallel test! + l2 = [ + 0.0017208369388227673, + 0.09628684992237334, + 0.09620157717330868, + 0.1758809552387432, + ], + linf = [ + 0.021869936355319086, + 0.9956698009442038, + 1.0002507727219028, + 2.223249697515648, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_vortex_mortar_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_vortex_mortar_shockcapturing.jl"), - l2=[ - 0.0017203324051381415, - 0.09628962899999398, - 0.0962124115572114, - 0.1758599596626405, - ], - linf=[ - 0.021740568112562086, - 0.9938841624655501, - 1.0041401179009877, - 2.2241087041100798, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_vortex_mortar_split.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex_mortar_split.jl"), + l2 = [ + 0.0017203323613648241, + 0.09628962878682261, + 0.09621241164155782, + 0.17585995600340926, + ], + linf = [ + 0.021740570456931674, + 0.9938841665880938, + 1.004140123355135, + 2.224108857746245, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_vortex_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex_amr.jl"), - # Expected errors are exactly the same as in the parallel test! - l2=[ - 5.051719943432265e-5, - 0.0022574259317084747, - 0.0021755998463189713, - 0.004346492398617521, - ], - linf=[ - 0.0012880114865917447, - 0.03857193149447702, - 0.031090457959835893, - 0.12125130332971423, - ], - # Let this test run longer to cover some lines in the AMR indicator - coverage_override=(maxiters = 10^5, tspan = (0.0, 10.5))) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_vortex_shockcapturing.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex_shockcapturing.jl"), + l2 = [ + 0.0017158367642679273, + 0.09619888722871434, + 0.09616432767924141, + 0.17553381166255197, + ], + linf = [ + 0.021853862449723982, + 0.9878047229255944, + 0.9880191167111795, + 2.2154030488035588, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with boundary_condition_slip_wall" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.03341239373099515, - 0.026673245711492915, - 0.026678871434568822, - 0.12397486476145089, - ], - linf=[ - 0.3290981764688339, - 0.3812055782309788, - 0.3812041851225023, - 1.168251216556933, - ], - periodicity=false, - boundary_conditions=boundary_condition_slip_wall, - cfl=0.3, tspan=(0.0, 0.1)) # this test is sensitive to the CFL factor - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_vortex_mortar_shockcapturing.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_vortex_mortar_shockcapturing.jl" + ), + l2 = [ + 0.0017203324051381415, + 0.09628962899999398, + 0.0962124115572114, + 0.1758599596626405, + ], + linf = [ + 0.021740568112562086, + 0.9938841624655501, + 1.0041401179009877, + 2.2241087041100798, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_warm_bubble.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_warm_bubble.jl"), - l2=[ - 0.0001379946769624388, - 0.02078779689715382, - 0.033237241571263176, - 31.36068872331705, - ], - linf=[ - 0.0016286690573188434, - 0.15623770697198225, - 0.3341371832270615, - 334.5373488726036, - ], - tspan=(0.0, 10.0), - initial_refinement_level=4) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 100 + @trixi_testset "elixir_euler_vortex_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex_amr.jl"), + # Expected errors are exactly the same as in the parallel test! + l2 = [ + 5.051719943432265e-5, + 0.0022574259317084747, + 0.0021755998463189713, + 0.004346492398617521, + ], + linf = [ + 0.0012880114865917447, + 0.03857193149447702, + 0.031090457959835893, + 0.12125130332971423, + ], + # Let this test run longer to cover some lines in the AMR indicator + coverage_override = (maxiters = 10^5, tspan = (0.0, 10.5)) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -# Coverage test for all initial conditions -@testset "Compressible Euler: Tests for initial conditions" begin - @trixi_testset "elixir_euler_vortex.jl one step with initial_condition_constant" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex.jl"), - l2=[ - 1.1790213022362371e-16, - 8.580657423476384e-17, - 1.3082387431804115e-16, - 1.6182739965672862e-15, - ], - linf=[ - 3.3306690738754696e-16, - 2.220446049250313e-16, - 5.273559366969494e-16, - 3.552713678800501e-15, - ], - maxiters=1, - initial_condition=initial_condition_constant) + @trixi_testset "elixir_euler_ec.jl with boundary_condition_slip_wall" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.03341239373099515, + 0.026673245711492915, + 0.026678871434568822, + 0.12397486476145089, + ], + linf = [ + 0.3290981764688339, + 0.3812055782309788, + 0.3812041851225023, + 1.168251216556933, + ], + periodicity = false, + boundary_conditions = boundary_condition_slip_wall, + cfl = 0.3, tspan = (0.0, 0.1) + ) # this test is sensitive to the CFL factor # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -967,32 +1005,92 @@ end end end - @trixi_testset "elixir_euler_sedov_blast_wave.jl one step" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), - l2=[ - 0.0021196114178949396, - 0.010703549234544042, - 0.01070354923454404, - 0.10719124037195142, - ], - linf=[ - 0.11987270645890724, - 0.7468615461136827, - 0.7468615461136827, - 3.910689155287799, - ], - maxiters=1) - + @trixi_testset "elixir_euler_warm_bubble.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_warm_bubble.jl"), + l2 = [ + 0.0001379946769624388, + 0.02078779689715382, + 0.033237241571263176, + 31.36068872331705, + ], + linf = [ + 0.0016286690573188434, + 0.15623770697198225, + 0.3341371832270615, + 334.5373488726036, + ], + tspan = (0.0, 10.0), + initial_refinement_level = 4 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let t = sol.t[end] u_ode = sol.u[end] du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 100 + end + end + + # Coverage test for all initial conditions + @testset "Compressible Euler: Tests for initial conditions" begin + @trixi_testset "elixir_euler_vortex.jl one step with initial_condition_constant" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex.jl"), + l2 = [ + 1.1790213022362371e-16, + 8.580657423476384e-17, + 1.3082387431804115e-16, + 1.6182739965672862e-15, + ], + linf = [ + 3.3306690738754696e-16, + 2.220446049250313e-16, + 5.273559366969494e-16, + 3.552713678800501e-15, + ], + maxiters = 1, + initial_condition = initial_condition_constant + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + end + + @trixi_testset "elixir_euler_sedov_blast_wave.jl one step" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), + l2 = [ + 0.0021196114178949396, + 0.010703549234544042, + 0.01070354923454404, + 0.10719124037195142, + ], + linf = [ + 0.11987270645890724, + 0.7468615461136827, + 0.7468615461136827, + 3.910689155287799, + ], + maxiters = 1 + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end end # module diff --git a/test/test_tree_2d_euleracoustics.jl b/test/test_tree_2d_euleracoustics.jl index e3a4d65f39..91991149b9 100644 --- a/test/test_tree_2d_euleracoustics.jl +++ b/test/test_tree_2d_euleracoustics.jl @@ -8,40 +8,44 @@ include("test_trixi.jl") EXAMPLES_DIR = joinpath(examples_dir(), "tree_2d_dgsem") @testset "Acoustic perturbation coupled with compressible Euler" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_euleracoustics_co-rotating_vortex_pair.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euleracoustics_co-rotating_vortex_pair.jl"), - initial_refinement_level=5, - tspan1=(0.0, 1.0), tspan_averaging=(0.0, 1.0), tspan=(0.0, 1.0), - l2=[ - 0.00013268029905807722, - 0.0001335062197031223, - 0.00021776333678401362, - 13.000001753042364, - 26.00000080243847, - 38.00000884725549, - 51.000000003859995, - ], - linf=[ - 0.22312716933051027, - 0.1579924424942319, - 0.25194831158255576, - 13.468872744263273, - 26.54666679978679, - 38.139032147739684, - 51.378134660241294, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euleracoustics_co-rotating_vortex_pair.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euleracoustics_co-rotating_vortex_pair.jl" + ), + initial_refinement_level = 5, + tspan1 = (0.0, 1.0), tspan_averaging = (0.0, 1.0), tspan = (0.0, 1.0), + l2 = [ + 0.00013268029905807722, + 0.0001335062197031223, + 0.00021776333678401362, + 13.000001753042364, + 26.00000080243847, + 38.00000884725549, + 51.000000003859995, + ], + linf = [ + 0.22312716933051027, + 0.1579924424942319, + 0.25194831158255576, + 13.468872744263273, + 26.54666679978679, + 38.139032147739684, + 51.378134660241294, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_2d_eulermulti.jl b/test/test_tree_2d_eulermulti.jl index 5b98461168..c2bee1693c 100644 --- a/test/test_tree_2d_eulermulti.jl +++ b/test/test_tree_2d_eulermulti.jl @@ -10,18 +10,26 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "Compressible Euler Multicomponent" begin @trixi_testset "Testing entropy2cons and cons2entropy" begin using ForwardDiff - gammas = (1.1546412974182538, 1.1171560258914812, 1.097107661471476, - 1.0587601652669245, 1.6209889683979308, 1.6732209755396386, - 1.2954303574165822) - gas_constants = (5.969461071171914, 3.6660802003290183, 6.639008614675539, - 8.116604827140456, 6.190706056680031, 1.6795013743693712, - 2.197737590916966) - equations = CompressibleEulerMulticomponentEquations2D(gammas = SVector{length(gammas)}(gammas...), - gas_constants = SVector{length(gas_constants)}(gas_constants...)) - u = [-1.7433292819144075, 0.8844413258376495, 0.6050737175812364, + gammas = ( + 1.1546412974182538, 1.1171560258914812, 1.097107661471476, + 1.0587601652669245, 1.6209889683979308, 1.6732209755396386, + 1.2954303574165822, + ) + gas_constants = ( + 5.969461071171914, 3.6660802003290183, 6.639008614675539, + 8.116604827140456, 6.190706056680031, 1.6795013743693712, + 2.197737590916966, + ) + equations = CompressibleEulerMulticomponentEquations2D( + gammas = SVector{length(gammas)}(gammas...), + gas_constants = SVector{length(gas_constants)}(gas_constants...) + ) + u = [ + -1.7433292819144075, 0.8844413258376495, 0.6050737175812364, 0.8261998359817043, 1.0801186290896465, 0.505654488367698, 0.6364415555805734, 0.851669392285058, 0.31219606420306223, - 1.0930477805612038] + 1.0930477805612038, + ] w = cons2entropy(u, equations) # test that the entropy variables match the gradients of the total entropy @test w ≈ ForwardDiff.gradient(u -> Trixi.total_entropy(u, equations), u) @@ -34,22 +42,24 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") # units is 101325 Pa, i.e., pressure has values of O(10^5) @trixi_testset "elixir_eulermulti_shock_bubble.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_shock_bubble.jl"), - l2=[ - 73.78467629094177, - 0.9174752929795251, - 57942.83587826468, - 0.1828847253029943, - 0.011127037850925347, - ], - linf=[ - 196.81051991521073, - 7.8456811648529605, - 158891.88930113698, - 0.811379581519794, - 0.08011973559187913, - ], - tspan=(0.0, 0.001)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_shock_bubble.jl"), + l2 = [ + 73.78467629094177, + 0.9174752929795251, + 57942.83587826468, + 0.1828847253029943, + 0.011127037850925347, + ], + linf = [ + 196.81051991521073, + 7.8456811648529605, + 158891.88930113698, + 0.811379581519794, + 0.08011973559187913, + ], + tspan = (0.0, 0.001) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -62,25 +72,29 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @trixi_testset "elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl" begin rm(joinpath("out", "deviations.txt"), force = true) - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl"), - l2=[ - 81.52845664909304, - 2.5455678559421346, - 63229.190712645846, - 0.19929478404550321, - 0.011068604228443425, - ], - linf=[ - 249.21708417382013, - 40.33299887640794, - 174205.0118831558, - 0.6881458768113586, - 0.11274401158173972, - ], - initial_refinement_level=3, - tspan=(0.0, 0.001), - save_errors=true) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulermulti_shock_bubble_shockcapturing_subcell_positivity.jl" + ), + l2 = [ + 81.52845664909304, + 2.5455678559421346, + 63229.190712645846, + 0.19929478404550321, + 0.011068604228443425, + ], + linf = [ + 249.21708417382013, + 40.33299887640794, + 174205.0118831558, + 0.6881458768113586, + 0.11274401158173972, + ], + initial_refinement_level = 3, + tspan = (0.0, 0.001), + save_errors = true + ) lines = readlines(joinpath("out", "deviations.txt")) @test lines[1] == "# iter, simu_time, rho1_min, rho2_min" # Runs with and without coverage take 1 and 15 time steps. @@ -96,24 +110,28 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") end @trixi_testset "elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl"), - l2=[ - 73.10860950390489, - 1.4599090197303102, - 57176.23978426408, - 0.17812910616624406, - 0.010123079422717837, - ], - linf=[ - 214.50568817511956, - 25.40392579616452, - 152862.41011222568, - 0.564195553101797, - 0.0956331651771212, - ], - initial_refinement_level=3, - tspan=(0.0, 0.001)) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulermulti_shock_bubble_shockcapturing_subcell_minmax.jl" + ), + l2 = [ + 73.10860950390489, + 1.4599090197303102, + 57176.23978426408, + 0.17812910616624406, + 0.010123079422717837, + ], + linf = [ + 214.50568817511956, + 25.40392579616452, + 152862.41011222568, + 0.564195553101797, + 0.0956331651771212, + ], + initial_refinement_level = 3, + tspan = (0.0, 0.001) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -125,19 +143,21 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") end @trixi_testset "elixir_eulermulti_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_ec.jl"), - l2=[ - 0.050182236154087095, - 0.050189894464434635, - 0.2258715597305131, - 0.06175171559771687, - ], - linf=[ - 0.3108124923284472, - 0.3107380389947733, - 1.054035804988521, - 0.29347582879608936, - ]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_ec.jl"), + l2 = [ + 0.050182236154087095, + 0.050189894464434635, + 0.2258715597305131, + 0.06175171559771687, + ], + linf = [ + 0.3108124923284472, + 0.3107380389947733, + 1.054035804988521, + 0.29347582879608936, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -149,25 +169,27 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") end @trixi_testset "elixir_eulermulti_es.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_es.jl"), - l2=[ - 0.0496546258404055, - 0.04965550099933263, - 0.22425206549856372, - 0.004087155041747821, - 0.008174310083495642, - 0.016348620166991283, - 0.032697240333982566, - ], - linf=[ - 0.2488251110766228, - 0.24832493304479406, - 0.9310354690058298, - 0.017452870465607374, - 0.03490574093121475, - 0.0698114818624295, - 0.139622963724859, - ]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_es.jl"), + l2 = [ + 0.0496546258404055, + 0.04965550099933263, + 0.22425206549856372, + 0.004087155041747821, + 0.008174310083495642, + 0.016348620166991283, + 0.032697240333982566, + ], + linf = [ + 0.2488251110766228, + 0.24832493304479406, + 0.9310354690058298, + 0.017452870465607374, + 0.03490574093121475, + 0.0698114818624295, + 0.139622963724859, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -179,21 +201,23 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") end @trixi_testset "elixir_eulermulti_convergence_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_ec.jl"), - l2=[ - 0.00012290225488326508, - 0.00012290225488321876, - 0.00018867397906337653, - 4.8542321753649044e-5, - 9.708464350729809e-5, - ], - linf=[ - 0.0006722819239133315, - 0.0006722819239128874, - 0.0012662292789555885, - 0.0002843844182700561, - 0.0005687688365401122, - ]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_ec.jl"), + l2 = [ + 0.00012290225488326508, + 0.00012290225488321876, + 0.00018867397906337653, + 4.8542321753649044e-5, + 9.708464350729809e-5, + ], + linf = [ + 0.0006722819239133315, + 0.0006722819239128874, + 0.0012662292789555885, + 0.0002843844182700561, + 0.0005687688365401122, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -205,21 +229,23 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") end @trixi_testset "elixir_eulermulti_convergence_es.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_es.jl"), - l2=[ - 2.2661773867001696e-6, - 2.266177386666318e-6, - 6.593514692980009e-6, - 8.836308667348217e-7, - 1.7672617334696433e-6, - ], - linf=[ - 1.4713170997993075e-5, - 1.4713170997104896e-5, - 5.115618808515521e-5, - 5.3639516094383666e-6, - 1.0727903218876733e-5, - ]) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_es.jl"), + l2 = [ + 2.2661773867001696e-6, + 2.266177386666318e-6, + 6.593514692980009e-6, + 8.836308667348217e-7, + 1.7672617334696433e-6, + ], + linf = [ + 1.4713170997993075e-5, + 1.4713170997104896e-5, + 5.115618808515521e-5, + 5.3639516094383666e-6, + 1.0727903218876733e-5, + ] + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let @@ -231,22 +257,24 @@ EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") end @trixi_testset "elixir_eulermulti_convergence_es.jl with flux_chandrashekar" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_es.jl"), - l2=[ - 1.8621737639352465e-6, - 1.862173764098385e-6, - 5.942585713809631e-6, - 6.216263279534722e-7, - 1.2432526559069443e-6, - ], - linf=[ - 1.6235495582606063e-5, - 1.6235495576388814e-5, - 5.854523678827661e-5, - 5.790274858807898e-6, - 1.1580549717615796e-5, - ], - volume_flux=flux_chandrashekar) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulermulti_convergence_es.jl"), + l2 = [ + 1.8621737639352465e-6, + 1.862173764098385e-6, + 5.942585713809631e-6, + 6.216263279534722e-7, + 1.2432526559069443e-6, + ], + linf = [ + 1.6235495582606063e-5, + 1.6235495576388814e-5, + 5.854523678827661e-5, + 5.790274858807898e-6, + 1.1580549717615796e-5, + ], + volume_flux = flux_chandrashekar + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) let diff --git a/test/test_tree_2d_eulerpolytropic.jl b/test/test_tree_2d_eulerpolytropic.jl index 545cf7274f..2b99a035e9 100644 --- a/test/test_tree_2d_eulerpolytropic.jl +++ b/test/test_tree_2d_eulerpolytropic.jl @@ -8,28 +8,32 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "Polytropic Euler" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_eulerpolytropic_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_eulerpolytropic_convergence.jl"), - l2=[ - 0.0016689832177626373, 0.0025920263793094526, - 0.003281074494626679, - ], - linf=[ - 0.010994883201896677, 0.013309526619350365, - 0.02008032661117376, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulerpolytropic_convergence.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_eulerpolytropic_convergence.jl" + ), + l2 = [ + 0.0016689832177626373, 0.0025920263793094526, + 0.003281074494626679, + ], + linf = [ + 0.010994883201896677, 0.013309526619350365, + 0.02008032661117376, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_2d_fdsbp.jl b/test/test_tree_2d_fdsbp.jl index d477cab056..dd7641499f 100644 --- a/test/test_tree_2d_fdsbp.jl +++ b/test/test_tree_2d_fdsbp.jl @@ -8,63 +8,71 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_fdsbp") @testset "Linear scalar advection" begin -#! format: noindent - -@trixi_testset "elixir_advection_extended.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[2.898644263922225e-6], - linf=[8.491517930142578e-6], - rtol=1.0e-7) # These results change a little bit and depend on the CI system - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + #! format: noindent + + @trixi_testset "elixir_advection_extended.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [2.898644263922225e-6], + linf = [8.491517930142578e-6], + rtol = 1.0e-7 + ) # These results change a little bit and depend on the CI system + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_extended.jl with periodic operators" begin - global D = SummationByPartsOperators.periodic_derivative_operator(derivative_order = 1, - accuracy_order = 4, - xmin = 0.0, - xmax = 1.0, - N = 40) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[1.1239649404463432e-5], - linf=[1.5895264629195438e-5], - D_SBP=D, - initial_refinement_level=0) - - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_extended.jl with periodic operators" begin + global D = SummationByPartsOperators.periodic_derivative_operator( + derivative_order = 1, + accuracy_order = 4, + xmin = 0.0, + xmax = 1.0, + N = 40 + ) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [1.1239649404463432e-5], + linf = [1.5895264629195438e-5], + D_SBP = D, + initial_refinement_level = 0 + ) + + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end @testset "Compressible Euler" begin @trixi_testset "elixir_euler_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 1.7088389997042244e-6, - 1.7437997855125774e-6, - 1.7437997855350776e-6, - 5.457223460127621e-6, - ], - linf=[ - 9.796504903736292e-6, - 9.614745892783105e-6, - 9.614745892783105e-6, - 4.026107182575345e-5, - ], - tspan=(0.0, 0.1)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 1.7088389997042244e-6, + 1.7437997855125774e-6, + 1.7437997855350776e-6, + 5.457223460127621e-6, + ], + linf = [ + 9.796504903736292e-6, + 9.614745892783105e-6, + 9.614745892783105e-6, + 4.026107182575345e-5, + ], + tspan = (0.0, 0.1) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -77,20 +85,22 @@ end end @trixi_testset "elixir_euler_convergence.jl with Lax-Friedrichs splitting" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 2.1149087345799973e-6, - 1.9391438806845798e-6, - 1.9391438806759794e-6, - 5.842833764682604e-6, - ], - linf=[ - 1.3679037540903494e-5, - 1.1770587849069258e-5, - 1.1770587848403125e-5, - 4.68952678644996e-5, - ], - tspan=(0.0, 0.1), flux_splitting=splitting_lax_friedrichs) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 2.1149087345799973e-6, + 1.9391438806845798e-6, + 1.9391438806759794e-6, + 5.842833764682604e-6, + ], + linf = [ + 1.3679037540903494e-5, + 1.1770587849069258e-5, + 1.1770587848403125e-5, + 4.68952678644996e-5, + ], + tspan = (0.0, 0.1), flux_splitting = splitting_lax_friedrichs + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -103,20 +113,22 @@ end end @trixi_testset "elixir_euler_convergence.jl with Drikakis-Tsangaris splitting" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 1.708838999643608e-6, - 1.7437997854485807e-6, - 1.7437997854741082e-6, - 5.457223460116349e-6, - ], - linf=[ - 9.796504911285808e-6, - 9.614745899888533e-6, - 9.614745899444443e-6, - 4.02610718399643e-5, - ], - tspan=(0.0, 0.1), flux_splitting=splitting_drikakis_tsangaris) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 1.708838999643608e-6, + 1.7437997854485807e-6, + 1.7437997854741082e-6, + 5.457223460116349e-6, + ], + linf = [ + 9.796504911285808e-6, + 9.614745899888533e-6, + 9.614745899444443e-6, + 4.02610718399643e-5, + ], + tspan = (0.0, 0.1), flux_splitting = splitting_drikakis_tsangaris + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -129,21 +141,25 @@ end end @trixi_testset "elixir_euler_kelvin_helmholtz_instability.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_euler_kelvin_helmholtz_instability.jl"), - l2=[ - 0.02607850081951497, - 0.020357717558016252, - 0.028510191844948945, - 0.02951535039734857, - ], - linf=[ - 0.12185328623662173, - 0.1065055387595834, - 0.06257122956937419, - 0.11992349951978643, - ], - tspan=(0.0, 0.1)) + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_euler_kelvin_helmholtz_instability.jl" + ), + l2 = [ + 0.02607850081951497, + 0.020357717558016252, + 0.028510191844948945, + 0.02951535039734857, + ], + linf = [ + 0.12185328623662173, + 0.1065055387595834, + 0.06257122956937419, + 0.11992349951978643, + ], + tspan = (0.0, 0.1) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -156,20 +172,22 @@ end end @trixi_testset "elixir_euler_vortex.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_vortex.jl"), - l2=[ - 0.0005330228930711585, - 0.028475888529345014, - 0.02847513865894387, - 0.056259951995581196, - ], - linf=[ - 0.007206088611304784, - 0.31690373882847234, - 0.31685665067192326, - 0.7938167296134893, - ], - tspan=(0.0, 0.25)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_vortex.jl"), + l2 = [ + 0.0005330228930711585, + 0.028475888529345014, + 0.02847513865894387, + 0.056259951995581196, + ], + linf = [ + 0.007206088611304784, + 0.31690373882847234, + 0.31685665067192326, + 0.7938167296134893, + ], + tspan = (0.0, 0.25) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) diff --git a/test/test_tree_2d_hypdiff.jl b/test/test_tree_2d_hypdiff.jl index 8c5973cbf0..b3acbe66ef 100644 --- a/test/test_tree_2d_hypdiff.jl +++ b/test/test_tree_2d_hypdiff.jl @@ -8,97 +8,107 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "Hyperbolic diffusion" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_hypdiff_lax_friedrichs.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_lax_friedrichs.jl"), - l2=[ - 0.00015687751817403066, - 0.001025986772216324, - 0.0010259867722164071, - ], - linf=[ - 0.001198695637957381, - 0.006423873515531753, - 0.006423873515533529, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_hypdiff_lax_friedrichs.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_lax_friedrichs.jl"), + l2 = [ + 0.00015687751817403066, + 0.001025986772216324, + 0.0010259867722164071, + ], + linf = [ + 0.001198695637957381, + 0.006423873515531753, + 0.006423873515533529, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end -end -@trixi_testset "elixir_hypdiff_harmonic_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_hypdiff_harmonic_nonperiodic.jl"), - l2=[ - 8.618132355121019e-8, - 5.619399844384306e-7, - 5.619399844844044e-7, - ], - linf=[ - 1.1248618588430072e-6, - 8.622436487026874e-6, - 8.622436487915053e-6, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_hypdiff_harmonic_nonperiodic.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_hypdiff_harmonic_nonperiodic.jl" + ), + l2 = [ + 8.618132355121019e-8, + 5.619399844384306e-7, + 5.619399844844044e-7, + ], + linf = [ + 1.1248618588430072e-6, + 8.622436487026874e-6, + 8.622436487915053e-6, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_hypdiff_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), - l2=[ - 8.523077653954864e-6, - 2.8779323653020624e-5, - 5.454942769125663e-5, - ], - linf=[ - 5.522740952468297e-5, - 0.00014544895978971679, - 0.00032396328684924924, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_hypdiff_nonperiodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), + l2 = [ + 8.523077653954864e-6, + 2.8779323653020624e-5, + 5.454942769125663e-5, + ], + linf = [ + 5.522740952468297e-5, + 0.00014544895978971679, + 0.00032396328684924924, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_hypdiff_godunov.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_godunov.jl"), - l2=[ - 5.868147556427088e-6, - 3.80517927324465e-5, - 3.805179273249344e-5, - ], - linf=[ - 3.701965498725812e-5, - 0.0002122422943138247, - 0.00021224229431116015, - ], - atol=2.0e-12) #= required for CI on macOS =# - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_hypdiff_godunov.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_godunov.jl"), + l2 = [ + 5.868147556427088e-6, + 3.80517927324465e-5, + 3.805179273249344e-5, + ], + linf = [ + 3.701965498725812e-5, + 0.0002122422943138247, + 0.00021224229431116015, + ], + atol = 2.0e-12 + ) #= required for CI on macOS =# + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_2d_kpp.jl b/test/test_tree_2d_kpp.jl index c9af68c6cc..8aefed668d 100644 --- a/test/test_tree_2d_kpp.jl +++ b/test/test_tree_2d_kpp.jl @@ -8,28 +8,30 @@ include("test_trixi.jl") EXAMPLES_DIR = joinpath(examples_dir(), "tree_2d_dgsem") @testset "KPP" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_kpp.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_kpp.jl"), - l2=[0.36563290910786106], - linf=[9.116732052340398], - max_refinement_level=6, - tspan=(0.0, 0.01), - atol=1e-6, - rtol=1e-6, - skip_coverage=true) - if @isdefined sol # Skipped in coverage run - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_kpp.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_kpp.jl"), + l2 = [0.36563290910786106], + linf = [9.116732052340398], + max_refinement_level = 6, + tspan = (0.0, 0.01), + atol = 1.0e-6, + rtol = 1.0e-6, + skip_coverage = true + ) + if @isdefined sol # Skipped in coverage run + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end end -end end # module diff --git a/test/test_tree_2d_lbm.jl b/test/test_tree_2d_lbm.jl index 4705c9d0d0..d00f212de5 100644 --- a/test/test_tree_2d_lbm.jl +++ b/test/test_tree_2d_lbm.jl @@ -8,154 +8,186 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "Lattice-Boltzmann" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_lbm_constant.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_lbm_constant.jl"), - l2=[4.888991832247047e-15, 4.8856380534982224e-15, - 5.140829677785587e-16, - 7.340293204570167e-16, 2.0559494114924474e-15, - 6.125746684189216e-16, - 1.6545443003155128e-16, 6.001333022242579e-16, - 9.450994018139234e-15], - linf=[5.551115123125783e-15, 5.662137425588298e-15, - 1.2212453270876722e-15, - 1.27675647831893e-15, 2.4980018054066022e-15, - 7.494005416219807e-16, - 4.3021142204224816e-16, 8.881784197001252e-16, - 1.0436096431476471e-14]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_lbm_constant.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_lbm_constant.jl"), + l2 = [ + 4.888991832247047e-15, 4.8856380534982224e-15, + 5.140829677785587e-16, + 7.340293204570167e-16, 2.0559494114924474e-15, + 6.125746684189216e-16, + 1.6545443003155128e-16, 6.001333022242579e-16, + 9.450994018139234e-15, + ], + linf = [ + 5.551115123125783e-15, 5.662137425588298e-15, + 1.2212453270876722e-15, + 1.27675647831893e-15, 2.4980018054066022e-15, + 7.494005416219807e-16, + 4.3021142204224816e-16, 8.881784197001252e-16, + 1.0436096431476471e-14, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_lbm_couette.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_lbm_couette.jl"), - l2=[0.0007899749117603378, 7.0995283148275575e-6, - 0.0007454191223764233, - 1.6482025869100257e-5, 0.00012684365365448903, - 0.0001198942846383015, - 0.00028436349827736705, 0.0003005161103138576, - 4.496683876631818e-5], - linf=[0.005596384769998769, 4.771160474496827e-5, - 0.005270322068908595, - 0.00011747787108790098, 0.00084326349695725, - 0.000795551892211168, - 0.001956482118303543, 0.0020739599893902436, - 0.00032606270109525326], - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_lbm_couette.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_lbm_couette.jl"), + l2 = [ + 0.0007899749117603378, 7.0995283148275575e-6, + 0.0007454191223764233, + 1.6482025869100257e-5, 0.00012684365365448903, + 0.0001198942846383015, + 0.00028436349827736705, 0.0003005161103138576, + 4.496683876631818e-5, + ], + linf = [ + 0.005596384769998769, 4.771160474496827e-5, + 0.005270322068908595, + 0.00011747787108790098, 0.00084326349695725, + 0.000795551892211168, + 0.001956482118303543, 0.0020739599893902436, + 0.00032606270109525326, + ], + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_lbm_lid_driven_cavity.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_lbm_lid_driven_cavity.jl"), - l2=[0.0013628495945172754, 0.00021475256243322154, - 0.0012579141312268184, - 0.00036542734715110765, 0.00024127756258120715, - 0.00022899415795341014, - 0.0004225564518328741, 0.0004593854895507851, - 0.00044244398903669927], - linf=[0.025886626070758242, 0.00573859077176217, - 0.027568805277855102, 0.00946724671122974, - 0.004031686575556803, 0.0038728927083346437, - 0.020038695575169005, - 0.02061789496737146, 0.05568236920459335], - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_lbm_lid_driven_cavity.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_lbm_lid_driven_cavity.jl"), + l2 = [ + 0.0013628495945172754, 0.00021475256243322154, + 0.0012579141312268184, + 0.00036542734715110765, 0.00024127756258120715, + 0.00022899415795341014, + 0.0004225564518328741, 0.0004593854895507851, + 0.00044244398903669927, + ], + linf = [ + 0.025886626070758242, 0.00573859077176217, + 0.027568805277855102, 0.00946724671122974, + 0.004031686575556803, 0.0038728927083346437, + 0.020038695575169005, + 0.02061789496737146, 0.05568236920459335, + ], + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_lbm_couette.jl with initial_condition_couette_steady" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_lbm_couette.jl"), - l2=[9.321369073400123e-16, 1.6498793963435488e-6, - 5.211495843124065e-16, - 1.6520893954826173e-6, 1.0406056181388841e-5, - 8.801606429417205e-6, - 8.801710065560555e-6, 1.040614383799995e-5, - 2.6135657178357052e-15], - linf=[1.4432899320127035e-15, 2.1821189867266e-6, - 8.881784197001252e-16, - 2.2481261510165496e-6, 1.0692966335143494e-5, - 9.606391697600247e-6, - 9.62138334279633e-6, 1.0725969916147021e-5, - 3.3861802251067274e-15], - initial_condition=function initial_condition_couette_steady(x, - t, - equations::LatticeBoltzmannEquations2D) - # Initial state for a *steady* Couette flow setup. To be used in combination with - # [`boundary_condition_couette`](@ref) and [`boundary_condition_noslip_wall`](@ref). - @unpack L, u0, rho0 = equations + @trixi_testset "elixir_lbm_couette.jl with initial_condition_couette_steady" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_lbm_couette.jl"), + l2 = [ + 9.321369073400123e-16, 1.6498793963435488e-6, + 5.211495843124065e-16, + 1.6520893954826173e-6, 1.0406056181388841e-5, + 8.801606429417205e-6, + 8.801710065560555e-6, 1.040614383799995e-5, + 2.6135657178357052e-15, + ], + linf = [ + 1.4432899320127035e-15, 2.1821189867266e-6, + 8.881784197001252e-16, + 2.2481261510165496e-6, 1.0692966335143494e-5, + 9.606391697600247e-6, + 9.62138334279633e-6, 1.0725969916147021e-5, + 3.3861802251067274e-15, + ], + initial_condition = function initial_condition_couette_steady( + x, + t, + equations::LatticeBoltzmannEquations2D + ) + # Initial state for a *steady* Couette flow setup. To be used in combination with + # [`boundary_condition_couette`](@ref) and [`boundary_condition_noslip_wall`](@ref). + @unpack L, u0, rho0 = equations - rho = rho0 - v1 = u0 * x[2] / L - v2 = 0 + rho = rho0 + v1 = u0 * x[2] / L + v2 = 0 - return equilibrium_distribution(rho, v1, v2, equations) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < - 1000 - end - end, - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + return equilibrium_distribution(rho, v1, v2, equations) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < + 1000 + end + end, + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_lbm_lid_driven_cavity.jl with stationary walls" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_lbm_lid_driven_cavity.jl"), - l2=[1.7198203373689985e-16, 1.685644347036533e-16, - 2.1604974801394525e-16, - 2.1527076266915764e-16, 4.2170298143732604e-17, - 5.160156233016299e-17, - 6.167794865198169e-17, 5.24166554417795e-17, - 6.694740573885739e-16], - linf=[5.967448757360216e-16, 6.522560269672795e-16, - 6.522560269672795e-16, - 6.245004513516506e-16, 2.1163626406917047e-16, - 2.185751579730777e-16, - 2.185751579730777e-16, 2.393918396847994e-16, - 1.887379141862766e-15], - boundary_conditions=boundary_condition_noslip_wall, - tspan=(0, 0.1)) + @trixi_testset "elixir_lbm_lid_driven_cavity.jl with stationary walls" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_lbm_lid_driven_cavity.jl"), + l2 = [ + 1.7198203373689985e-16, 1.685644347036533e-16, + 2.1604974801394525e-16, + 2.1527076266915764e-16, 4.2170298143732604e-17, + 5.160156233016299e-17, + 6.167794865198169e-17, 5.24166554417795e-17, + 6.694740573885739e-16, + ], + linf = [ + 5.967448757360216e-16, 6.522560269672795e-16, + 6.522560269672795e-16, + 6.245004513516506e-16, 2.1163626406917047e-16, + 2.185751579730777e-16, + 2.185751579730777e-16, 2.393918396847994e-16, + 1.887379141862766e-15, + ], + boundary_conditions = boundary_condition_noslip_wall, + tspan = (0, 0.1) + ) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_2d_linearizedeuler.jl b/test/test_tree_2d_linearizedeuler.jl index 7bdb83e328..08962c690f 100644 --- a/test/test_tree_2d_linearizedeuler.jl +++ b/test/test_tree_2d_linearizedeuler.jl @@ -1,4 +1,3 @@ - using Test using Trixi @@ -7,54 +6,58 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "Linearized Euler Equations 2D" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_linearizedeuler_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_convergence.jl"), - l2=[ - 0.00020601485381444888, - 0.00013380483421751216, - 0.0001338048342174503, - 0.00020601485381444888, - ], - linf=[ - 0.0011006084408365924, - 0.0005788678074691855, - 0.0005788678074701847, - 0.0011006084408365924, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_linearizedeuler_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_convergence.jl"), + l2 = [ + 0.00020601485381444888, + 0.00013380483421751216, + 0.0001338048342174503, + 0.00020601485381444888, + ], + linf = [ + 0.0011006084408365924, + 0.0005788678074691855, + 0.0005788678074701847, + 0.0011006084408365924, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_linearizedeuler_gauss_wall.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gauss_wall.jl"), - l2=[ - 0.048185623945503485, - 0.01941899333212175, - 0.019510224816991825, - 0.048185623945503485, - ], - linf=[ - 1.0392165942153189, - 0.18188777290819994, - 0.1877028372108587, - 1.0392165942153189, - ]) + @trixi_testset "elixir_linearizedeuler_gauss_wall.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gauss_wall.jl"), + l2 = [ + 0.048185623945503485, + 0.01941899333212175, + 0.019510224816991825, + 0.048185623945503485, + ], + linf = [ + 1.0392165942153189, + 0.18188777290819994, + 0.1877028372108587, + 1.0392165942153189, + ] + ) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end diff --git a/test/test_tree_2d_mhd.jl b/test/test_tree_2d_mhd.jl index 66b47138a4..5b23df174c 100644 --- a/test/test_tree_2d_mhd.jl +++ b/test/test_tree_2d_mhd.jl @@ -8,362 +8,384 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "MHD" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_mhd_alfven_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 0.00011149543672225127, - 5.888242524520296e-6, - 5.888242524510072e-6, - 8.476931432519067e-6, - 1.3160738644036652e-6, - 1.2542675002588144e-6, - 1.2542675002747718e-6, - 1.8705223407238346e-6, - 4.651717010670585e-7, - ], - linf=[ - 0.00026806333988971254, - 1.6278838272418272e-5, - 1.627883827305665e-5, - 2.7551183488072617e-5, - 5.457878055614707e-6, - 8.130129322880819e-6, - 8.130129322769797e-6, - 1.2406302192291552e-5, - 2.373765544951732e-6, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 0.00011149543672225127, + 5.888242524520296e-6, + 5.888242524510072e-6, + 8.476931432519067e-6, + 1.3160738644036652e-6, + 1.2542675002588144e-6, + 1.2542675002747718e-6, + 1.8705223407238346e-6, + 4.651717010670585e-7, + ], + linf = [ + 0.00026806333988971254, + 1.6278838272418272e-5, + 1.627883827305665e-5, + 2.7551183488072617e-5, + 5.457878055614707e-6, + 8.130129322880819e-6, + 8.130129322769797e-6, + 1.2406302192291552e-5, + 2.373765544951732e-6, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl with flux_derigs_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 1.7201098719531215e-6, - 8.692057393373005e-7, - 8.69205739320643e-7, - 1.2726508184718958e-6, - 1.040607127595208e-6, - 1.07029565814218e-6, - 1.0702956581404748e-6, - 1.3291748105236525e-6, - 4.6172239295786824e-7, - ], - linf=[ - 9.865325754310206e-6, - 7.352074675170961e-6, - 7.352074674185638e-6, - 1.0675656902672803e-5, - 5.112498347226158e-6, - 7.789533065905019e-6, - 7.789533065905019e-6, - 1.0933531593274037e-5, - 2.340244047768378e-6, - ], - volume_flux=(flux_derigs_etal, flux_nonconservative_powell)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl with flux_derigs_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 1.7201098719531215e-6, + 8.692057393373005e-7, + 8.69205739320643e-7, + 1.2726508184718958e-6, + 1.040607127595208e-6, + 1.07029565814218e-6, + 1.0702956581404748e-6, + 1.3291748105236525e-6, + 4.6172239295786824e-7, + ], + linf = [ + 9.865325754310206e-6, + 7.352074675170961e-6, + 7.352074674185638e-6, + 1.0675656902672803e-5, + 5.112498347226158e-6, + 7.789533065905019e-6, + 7.789533065905019e-6, + 1.0933531593274037e-5, + 2.340244047768378e-6, + ], + volume_flux = (flux_derigs_etal, flux_nonconservative_powell) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave_mortar.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave_mortar.jl"), - l2=[ - 3.7762324533854616e-6, - 1.5534623833573546e-6, - 1.4577234868196855e-6, - 1.7647724628707057e-6, - 1.4831911814574333e-6, - 1.456369119716533e-6, - 1.4115666913995062e-6, - 1.804758237422838e-6, - 8.320469738087189e-7, - ], - linf=[ - 3.670661330201774e-5, - 1.530289442645827e-5, - 1.3592183785327006e-5, - 1.5173897443654383e-5, - 9.43771379136038e-6, - 1.0906323046233624e-5, - 1.0603954940346938e-5, - 1.5900499596113726e-5, - 5.978772247650426e-6, - ], - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave_mortar.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave_mortar.jl"), + l2 = [ + 3.7762324533854616e-6, + 1.5534623833573546e-6, + 1.4577234868196855e-6, + 1.7647724628707057e-6, + 1.4831911814574333e-6, + 1.456369119716533e-6, + 1.4115666913995062e-6, + 1.804758237422838e-6, + 8.320469738087189e-7, + ], + linf = [ + 3.670661330201774e-5, + 1.530289442645827e-5, + 1.3592183785327006e-5, + 1.5173897443654383e-5, + 9.43771379136038e-6, + 1.0906323046233624e-5, + 1.0603954940346938e-5, + 1.5900499596113726e-5, + 5.978772247650426e-6, + ], + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), - l2=[ - 0.03637302248881514, - 0.043002991956758996, - 0.042987505670836056, - 0.02574718055258975, - 0.1621856170457943, - 0.01745369341302589, - 0.017454552320664566, - 0.026873190440613117, - 5.336243933079389e-16, - ], - linf=[ - 0.23623816236321427, - 0.3137152204179957, - 0.30378397831730597, - 0.21500228807094865, - 0.9042495730546518, - 0.09398098096581875, - 0.09470282020962917, - 0.15277253978297378, - 4.307694418935709e-15, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), + l2 = [ + 0.03637302248881514, + 0.043002991956758996, + 0.042987505670836056, + 0.02574718055258975, + 0.1621856170457943, + 0.01745369341302589, + 0.017454552320664566, + 0.026873190440613117, + 5.336243933079389e-16, + ], + linf = [ + 0.23623816236321427, + 0.3137152204179957, + 0.30378397831730597, + 0.21500228807094865, + 0.9042495730546518, + 0.09398098096581875, + 0.09470282020962917, + 0.15277253978297378, + 4.307694418935709e-15, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_orszag_tang.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_orszag_tang.jl"), - l2=[ - 0.21967600768935716, - 0.2643126515795721, - 0.31488287201980875, - 0.0, - 0.5160141621186931, - 0.23028914748088603, - 0.34413527376463915, - 0.0, - 0.003178793090381426, - ], - linf=[ - 1.2749969218080568, - 0.6737013368774057, - 0.8604154399895696, - 0.0, - 2.799342099887639, - 0.6473347557712643, - 0.9691773375490476, - 0.0, - 0.05729832038724348, - ], - tspan=(0.0, 0.09)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_orszag_tang.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_orszag_tang.jl"), + l2 = [ + 0.21967600768935716, + 0.2643126515795721, + 0.31488287201980875, + 0.0, + 0.5160141621186931, + 0.23028914748088603, + 0.34413527376463915, + 0.0, + 0.003178793090381426, + ], + linf = [ + 1.2749969218080568, + 0.6737013368774057, + 0.8604154399895696, + 0.0, + 2.799342099887639, + 0.6473347557712643, + 0.9691773375490476, + 0.0, + 0.05729832038724348, + ], + tspan = (0.0, 0.09) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_orszag_tang.jl with flux_hlle" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_orszag_tang.jl"), - l2=[ - 0.10806619664693064, - 0.20199136742199922, - 0.22984589847526207, - 0.0, - 0.29950152196422647, - 0.15688413207147794, - 0.24293641543490646, - 0.0, - 0.003246181006326598, - ], - linf=[ - 0.560316034595759, - 0.5095520363866776, - 0.6536748458764621, - 0.0, - 0.9627447086204038, - 0.3981375420906146, - 0.673472146198816, - 0.0, - 0.04879208429337193, - ], - tspan=(0.0, 0.06), - surface_flux=(flux_hlle, - flux_nonconservative_powell)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_orszag_tang.jl with flux_hlle" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_orszag_tang.jl"), + l2 = [ + 0.10806619664693064, + 0.20199136742199922, + 0.22984589847526207, + 0.0, + 0.29950152196422647, + 0.15688413207147794, + 0.24293641543490646, + 0.0, + 0.003246181006326598, + ], + linf = [ + 0.560316034595759, + 0.5095520363866776, + 0.6536748458764621, + 0.0, + 0.9627447086204038, + 0.3981375420906146, + 0.673472146198816, + 0.0, + 0.04879208429337193, + ], + tspan = (0.0, 0.06), + surface_flux = ( + flux_hlle, + flux_nonconservative_powell, + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl one step with initial_condition_constant" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 7.144325530681224e-17, - 2.123397983547417e-16, - 5.061138912500049e-16, - 3.6588423152083e-17, - 8.449816179702522e-15, - 3.9171737639099993e-16, - 2.445565690318772e-16, - 3.6588423152083e-17, - 9.971153407737885e-17, - ], - linf=[ - 2.220446049250313e-16, - 8.465450562766819e-16, - 1.8318679906315083e-15, - 1.1102230246251565e-16, - 1.4210854715202004e-14, - 8.881784197001252e-16, - 4.440892098500626e-16, - 1.1102230246251565e-16, - 4.779017148551244e-16, - ], - maxiters=1, - initial_condition=initial_condition_constant, - atol=2.0e-13) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl one step with initial_condition_constant" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 7.144325530681224e-17, + 2.123397983547417e-16, + 5.061138912500049e-16, + 3.6588423152083e-17, + 8.449816179702522e-15, + 3.9171737639099993e-16, + 2.445565690318772e-16, + 3.6588423152083e-17, + 9.971153407737885e-17, + ], + linf = [ + 2.220446049250313e-16, + 8.465450562766819e-16, + 1.8318679906315083e-15, + 1.1102230246251565e-16, + 1.4210854715202004e-14, + 8.881784197001252e-16, + 4.440892098500626e-16, + 1.1102230246251565e-16, + 4.779017148551244e-16, + ], + maxiters = 1, + initial_condition = initial_condition_constant, + atol = 2.0e-13 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_rotor.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_rotor.jl"), - l2=[ - 1.2623319195262743, - 1.8273050553090515, - 1.7004151198284634, - 0.0, - 2.2978570581460818, - 0.2147235065899803, - 0.23558337696054493, - 0.0, - 0.0032515115395693483, - ], - linf=[ - 11.003677581472843, - 14.70614192714736, - 15.687648666952708, - 0.0, - 17.098104835553823, - 1.3283750501377847, - 1.4365828094434892, - 0.0, - 0.07886241196068537, - ], - tspan=(0.0, 0.05)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_rotor.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_rotor.jl"), + l2 = [ + 1.2623319195262743, + 1.8273050553090515, + 1.7004151198284634, + 0.0, + 2.2978570581460818, + 0.2147235065899803, + 0.23558337696054493, + 0.0, + 0.0032515115395693483, + ], + linf = [ + 11.003677581472843, + 14.70614192714736, + 15.687648666952708, + 0.0, + 17.098104835553823, + 1.3283750501377847, + 1.4365828094434892, + 0.0, + 0.07886241196068537, + ], + tspan = (0.0, 0.05) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_blast_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_blast_wave.jl"), - l2=[ - 0.17646728395490927, - 3.866230215339417, - 2.4867304651291255, - 0.0, - 355.4562971958441, - 2.359493623565687, - 1.4030741420730297, - 0.0, - 0.029613599942667133, - ], - linf=[ - 1.581630420824181, - 44.15725488910748, - 13.056964982196554, - 0.0, - 2244.875490238186, - 13.07679044647926, - 9.14612176426092, - 0.0, - 0.5154756722488522, - ], - tspan=(0.0, 0.003), - # Calling the AnalysisCallback before iteration 9 causes the interpolation - # of this IC to have negative density/pressure values, crashing the simulation. - coverage_override=(maxiters = 9,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_blast_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_blast_wave.jl"), + l2 = [ + 0.17646728395490927, + 3.866230215339417, + 2.4867304651291255, + 0.0, + 355.4562971958441, + 2.359493623565687, + 1.4030741420730297, + 0.0, + 0.029613599942667133, + ], + linf = [ + 1.581630420824181, + 44.15725488910748, + 13.056964982196554, + 0.0, + 2244.875490238186, + 13.07679044647926, + 9.14612176426092, + 0.0, + 0.5154756722488522, + ], + tspan = (0.0, 0.003), + # Calling the AnalysisCallback before iteration 9 causes the interpolation + # of this IC to have negative density/pressure values, crashing the simulation. + coverage_override = (maxiters = 9,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_shockcapturing_subcell.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_shockcapturing_subcell.jl"), - l2=[ - 3.2064026219236076e-02, - 7.2461094392606618e-02, - 7.2380202888062711e-02, - 0.0000000000000000e+00, - 8.6293936673145932e-01, - 8.4091669534557805e-03, - 5.2156364913231732e-03, - 0.0000000000000000e+00, - 2.0786952301129021e-04, - ], - linf=[ - 3.8778760255775635e-01, - 9.4666683953698927e-01, - 9.4618924645661928e-01, - 0.0000000000000000e+00, - 1.0980297261521951e+01, - 1.0264404591009069e-01, - 1.0655686942176350e-01, - 0.0000000000000000e+00, - 6.1013422157115546e-03, - ], - tspan=(0.0, 0.003)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_mhd_shockcapturing_subcell.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_shockcapturing_subcell.jl"), + l2 = [ + 3.2064026219236076e-2, + 7.2461094392606618e-2, + 7.2380202888062711e-2, + 0.0e+0, + 8.6293936673145932e-1, + 8.4091669534557805e-3, + 5.2156364913231732e-3, + 0.0e+0, + 2.0786952301129021e-4, + ], + linf = [ + 3.8778760255775635e-1, + 9.4666683953698927e-1, + 9.4618924645661928e-1, + 0.0e+0, + 1.0980297261521951e+1, + 1.0264404591009069e-1, + 1.065568694217635e-1, + 0.0e+0, + 6.1013422157115546e-3, + ], + tspan = (0.0, 0.003) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end end -end end # module diff --git a/test/test_tree_2d_mhdmulti.jl b/test/test_tree_2d_mhdmulti.jl index d36554a667..2dfeaf5fee 100644 --- a/test/test_tree_2d_mhdmulti.jl +++ b/test/test_tree_2d_mhdmulti.jl @@ -8,126 +8,156 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_2d_dgsem") @testset "MHD Multicomponent" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_mhdmulti_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_ec.jl"), - l2=[0.04300299195675897, 0.042987505670835945, - 0.025747180552589767, 0.1621856170457937, - 0.017453693413025828, 0.0174545523206645, - 0.026873190440613162, 1.364647699274761e-15, - 0.012124340829605002, 0.024248681659210004], - linf=[0.31371522041799105, 0.3037839783173047, - 0.21500228807094351, 0.904249573054642, - 0.0939809809658183, 0.09470282020962761, 0.1527725397829759, - 8.245701827530042e-15, - 0.0787460541210726, 0.1574921082421452]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_ec.jl"), + l2 = [ + 0.04300299195675897, 0.042987505670835945, + 0.025747180552589767, 0.1621856170457937, + 0.017453693413025828, 0.0174545523206645, + 0.026873190440613162, 1.364647699274761e-15, + 0.012124340829605002, 0.024248681659210004, + ], + linf = [ + 0.31371522041799105, 0.3037839783173047, + 0.21500228807094351, 0.904249573054642, + 0.0939809809658183, 0.09470282020962761, 0.1527725397829759, + 8.245701827530042e-15, + 0.0787460541210726, 0.1574921082421452, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhdmulti_ec.jl with flux_derigs_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_ec.jl"), - l2=[0.04301155595653799, 0.04299735787276207, - 0.025745530869947714, - 0.16206102676791553, 0.017454384272339165, - 0.01745523378100091, - 0.026879482381500154, 0.0002038008756963954, - 0.012094208262809778, - 0.024188416525619556], - linf=[0.3156206778985397, 0.30941696929809526, - 0.21167563519254176, - 0.9688251298546122, 0.09076254289155083, - 0.09160589769498295, - 0.15698032974768705, 0.006131914796912965, - 0.07839287555951036, - 0.1567857511190207], - volume_flux=(flux_derigs_etal, flux_nonconservative_powell), - surface_flux=(flux_derigs_etal, flux_nonconservative_powell)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_ec.jl with flux_derigs_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_ec.jl"), + l2 = [ + 0.04301155595653799, 0.04299735787276207, + 0.025745530869947714, + 0.16206102676791553, 0.017454384272339165, + 0.01745523378100091, + 0.026879482381500154, 0.0002038008756963954, + 0.012094208262809778, + 0.024188416525619556, + ], + linf = [ + 0.3156206778985397, 0.30941696929809526, + 0.21167563519254176, + 0.9688251298546122, 0.09076254289155083, + 0.09160589769498295, + 0.15698032974768705, 0.006131914796912965, + 0.07839287555951036, + 0.1567857511190207, + ], + volume_flux = (flux_derigs_etal, flux_nonconservative_powell), + surface_flux = (flux_derigs_etal, flux_nonconservative_powell) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhdmulti_es.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_es.jl"), - l2=[0.042511527162267, 0.04250603277530184, 0.02385422747993974, - 0.11555081362726903, - 0.016366641053738043, 0.01636681584592762, - 0.02581748418797907, 0.00023394429554818215, - 0.010834603551662698, 0.021669207103325396], - linf=[0.23454607703107877, 0.23464789247380322, - 0.11898832084115452, 0.5331209602648022, - 0.061744814466827336, 0.061767127585091286, - 0.09595041452184983, 0.004421037168524759, - 0.06186597801911198, 0.12373195603822396]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_es.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_es.jl"), + l2 = [ + 0.042511527162267, 0.04250603277530184, 0.02385422747993974, + 0.11555081362726903, + 0.016366641053738043, 0.01636681584592762, + 0.02581748418797907, 0.00023394429554818215, + 0.010834603551662698, 0.021669207103325396, + ], + linf = [ + 0.23454607703107877, 0.23464789247380322, + 0.11898832084115452, 0.5331209602648022, + 0.061744814466827336, 0.061767127585091286, + 0.09595041452184983, 0.004421037168524759, + 0.06186597801911198, 0.12373195603822396, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhdmulti_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_convergence.jl"), - l2=[0.0003808877028249613, 0.0003808877028249593, - 0.0005155994511260122, 0.000570394227652563, - 0.000439568811048544, 0.0004395688110485541, - 0.0005074093477702055, 0.0003859005258180428, - 7.4611207452221e-5, 0.000149222414904442, - 0.000298444829808884], - linf=[0.0013324014301672943, 0.0013324014301669181, - 0.002684449324758791, 0.0016236816790307085, - 0.0019172373117153363, 0.0019172373117148922, - 0.002664932274107224, 0.0011872396664042962, - 0.0002855492944235094, 0.0005710985888470188, - 0.0011421971776940376]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_convergence.jl"), + l2 = [ + 0.0003808877028249613, 0.0003808877028249593, + 0.0005155994511260122, 0.000570394227652563, + 0.000439568811048544, 0.0004395688110485541, + 0.0005074093477702055, 0.0003859005258180428, + 7.4611207452221e-5, 0.000149222414904442, + 0.000298444829808884, + ], + linf = [ + 0.0013324014301672943, 0.0013324014301669181, + 0.002684449324758791, 0.0016236816790307085, + 0.0019172373117153363, 0.0019172373117148922, + 0.002664932274107224, 0.0011872396664042962, + 0.0002855492944235094, 0.0005710985888470188, + 0.0011421971776940376, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhdmulti_rotor.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhdmulti_rotor.jl"), - l2=[0.6574605535168556, 0.6623234319361953, 0.0, - 0.689806698245354, - 0.04883686128677976, 0.08382459729494686, 0.0, - 0.0021114516459281177, - 0.15909290019096098, 0.07954645009548049], - linf=[9.362339085941425, 9.169838118652539, 0.0, - 10.600957847359556, - 0.6628317732399827, 1.4185626901435056, 0.0, - 0.06914316292003836, - 3.328770801731456, 1.664385400865728], - tspan=(0.0, 0.01)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhdmulti_rotor.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhdmulti_rotor.jl"), + l2 = [ + 0.6574605535168556, 0.6623234319361953, 0.0, + 0.689806698245354, + 0.04883686128677976, 0.08382459729494686, 0.0, + 0.0021114516459281177, + 0.15909290019096098, 0.07954645009548049, + ], + linf = [ + 9.362339085941425, 9.169838118652539, 0.0, + 10.600957847359556, + 0.6628317732399827, 1.4185626901435056, 0.0, + 0.06914316292003836, + 3.328770801731456, 1.664385400865728, + ], + tspan = (0.0, 0.01) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_2d_part1.jl b/test/test_tree_2d_part1.jl index 2af1f29fcb..ef7a0c5ed2 100644 --- a/test/test_tree_2d_part1.jl +++ b/test/test_tree_2d_part1.jl @@ -12,91 +12,91 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "TreeMesh2D Part 1" begin -#! format: noindent - -# Run basic tests -@testset "Examples 2D" begin - # Linear advection - include("test_tree_2d_advection.jl") - - # Hyperbolic diffusion - include("test_tree_2d_hypdiff.jl") -end - -@testset "Displaying components 2D" begin - @test_nowarn include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl")) - - # test both short and long printing formats - @test_nowarn show(mesh) - println() - @test_nowarn println(mesh) - @test_nowarn display(mesh) - - @test_nowarn show(equations) - println() - @test_nowarn println(equations) - @test_nowarn display(equations) - - @test_nowarn show(solver) - println() - @test_nowarn println(solver) - @test_nowarn display(solver) - - @test_nowarn show(solver.basis) - println() - @test_nowarn println(solver.basis) - @test_nowarn display(solver.basis) - - @test_nowarn show(solver.mortar) - println() - @test_nowarn println(solver.mortar) - @test_nowarn display(solver.mortar) - - @test_nowarn show(semi) - println() - @test_nowarn println(semi) - @test_nowarn display(semi) - - @test_nowarn show(summary_callback) - println() - @test_nowarn println(summary_callback) - @test_nowarn display(summary_callback) - - @test_nowarn show(amr_controller) - println() - @test_nowarn println(amr_controller) - @test_nowarn display(amr_controller) - - @test_nowarn show(amr_callback) - println() - @test_nowarn println(amr_callback) - @test_nowarn display(amr_callback) - - @test_nowarn show(stepsize_callback) - println() - @test_nowarn println(stepsize_callback) - @test_nowarn display(stepsize_callback) - - @test_nowarn show(save_solution) - println() - @test_nowarn println(save_solution) - @test_nowarn display(save_solution) - - @test_nowarn show(analysis_callback) - println() - @test_nowarn println(analysis_callback) - @test_nowarn display(analysis_callback) - - @test_nowarn show(alive_callback) - println() - @test_nowarn println(alive_callback) - @test_nowarn display(alive_callback) - - @test_nowarn println(callbacks) -end - -# Clean up afterwards: delete Trixi.jl output directory -@test_nowarn rm(outdir, recursive = true) + #! format: noindent + + # Run basic tests + @testset "Examples 2D" begin + # Linear advection + include("test_tree_2d_advection.jl") + + # Hyperbolic diffusion + include("test_tree_2d_hypdiff.jl") + end + + @testset "Displaying components 2D" begin + @test_nowarn include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl")) + + # test both short and long printing formats + @test_nowarn show(mesh) + println() + @test_nowarn println(mesh) + @test_nowarn display(mesh) + + @test_nowarn show(equations) + println() + @test_nowarn println(equations) + @test_nowarn display(equations) + + @test_nowarn show(solver) + println() + @test_nowarn println(solver) + @test_nowarn display(solver) + + @test_nowarn show(solver.basis) + println() + @test_nowarn println(solver.basis) + @test_nowarn display(solver.basis) + + @test_nowarn show(solver.mortar) + println() + @test_nowarn println(solver.mortar) + @test_nowarn display(solver.mortar) + + @test_nowarn show(semi) + println() + @test_nowarn println(semi) + @test_nowarn display(semi) + + @test_nowarn show(summary_callback) + println() + @test_nowarn println(summary_callback) + @test_nowarn display(summary_callback) + + @test_nowarn show(amr_controller) + println() + @test_nowarn println(amr_controller) + @test_nowarn display(amr_controller) + + @test_nowarn show(amr_callback) + println() + @test_nowarn println(amr_callback) + @test_nowarn display(amr_callback) + + @test_nowarn show(stepsize_callback) + println() + @test_nowarn println(stepsize_callback) + @test_nowarn display(stepsize_callback) + + @test_nowarn show(save_solution) + println() + @test_nowarn println(save_solution) + @test_nowarn display(save_solution) + + @test_nowarn show(analysis_callback) + println() + @test_nowarn println(analysis_callback) + @test_nowarn display(analysis_callback) + + @test_nowarn show(alive_callback) + println() + @test_nowarn println(alive_callback) + @test_nowarn display(alive_callback) + + @test_nowarn println(callbacks) + end + + # Clean up afterwards: delete Trixi.jl output directory + @test_nowarn rm(outdir, recursive = true) end # TreeMesh2D Part 1 end #module diff --git a/test/test_tree_2d_part2.jl b/test/test_tree_2d_part2.jl index 622f12109f..26aa917915 100644 --- a/test/test_tree_2d_part2.jl +++ b/test/test_tree_2d_part2.jl @@ -10,34 +10,34 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "TreeMesh2D Part 2" begin -#! format: noindent + #! format: noindent -# Run basic tests -@testset "Examples 2D" begin - # Acoustic perturbation - include("test_tree_2d_acoustics.jl") + # Run basic tests + @testset "Examples 2D" begin + # Acoustic perturbation + include("test_tree_2d_acoustics.jl") - # Linearized Euler - include("test_tree_2d_linearizedeuler.jl") + # Linearized Euler + include("test_tree_2d_linearizedeuler.jl") - # Compressible Euler - include("test_tree_2d_euler.jl") + # Compressible Euler + include("test_tree_2d_euler.jl") - # Compressible Euler Multicomponent - include("test_tree_2d_eulermulti.jl") + # Compressible Euler Multicomponent + include("test_tree_2d_eulermulti.jl") - # Compressible Polytropic Euler - include("test_tree_2d_eulerpolytropic.jl") + # Compressible Polytropic Euler + include("test_tree_2d_eulerpolytropic.jl") - # Compressible Euler coupled with acoustic perturbation equations - include("test_tree_2d_euleracoustics.jl") + # Compressible Euler coupled with acoustic perturbation equations + include("test_tree_2d_euleracoustics.jl") - # KPP problem - include("test_tree_2d_kpp.jl") -end + # KPP problem + include("test_tree_2d_kpp.jl") + end -# Clean up afterwards: delete Trixi.jl output directory -@test_nowarn rm(outdir, recursive = true) + # Clean up afterwards: delete Trixi.jl output directory + @test_nowarn rm(outdir, recursive = true) end # TreeMesh2D Part 2 end #module diff --git a/test/test_tree_2d_part3.jl b/test/test_tree_2d_part3.jl index 0eff564132..d24f7bcc2f 100644 --- a/test/test_tree_2d_part3.jl +++ b/test/test_tree_2d_part3.jl @@ -10,28 +10,28 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "TreeMesh2D Part 3" begin -#! format: noindent + #! format: noindent -# Run basic tests -@testset "Examples 2D" begin - # MHD - include("test_tree_2d_mhd.jl") + # Run basic tests + @testset "Examples 2D" begin + # MHD + include("test_tree_2d_mhd.jl") - # MHD Multicomponent - include("test_tree_2d_mhdmulti.jl") + # MHD Multicomponent + include("test_tree_2d_mhdmulti.jl") - # Lattice-Boltzmann - include("test_tree_2d_lbm.jl") + # Lattice-Boltzmann + include("test_tree_2d_lbm.jl") - # Shallow water - include("test_tree_2d_shallowwater.jl") + # Shallow water + include("test_tree_2d_shallowwater.jl") - # FDSBP methods on the TreeMesh - include("test_tree_2d_fdsbp.jl") -end + # FDSBP methods on the TreeMesh + include("test_tree_2d_fdsbp.jl") + end -# Clean up afterwards: delete Trixi.jl output directory -@test_nowarn rm(outdir, recursive = true) + # Clean up afterwards: delete Trixi.jl output directory + @test_nowarn rm(outdir, recursive = true) end # TreeMesh2D Part 3 end #module diff --git a/test/test_tree_2d_shallowwater.jl b/test/test_tree_2d_shallowwater.jl index bcad663008..a3a3882020 100644 --- a/test/test_tree_2d_shallowwater.jl +++ b/test/test_tree_2d_shallowwater.jl @@ -8,295 +8,333 @@ include("test_trixi.jl") EXAMPLES_DIR = joinpath(examples_dir(), "tree_2d_dgsem") @testset "Shallow Water" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_shallowwater_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), - l2=[ - 0.9911802019934329, - 0.7340106828033273, - 0.7446338002084801, - 0.5875351036989047, - ], - linf=[ - 2.0120253138457564, - 2.991158989293406, - 2.6557412817714035, - 3.0, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), + l2 = [ + 0.9911802019934329, + 0.7340106828033273, + 0.7446338002084801, + 0.5875351036989047, + ], + linf = [ + 2.0120253138457564, + 2.991158989293406, + 2.6557412817714035, + 3.0, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 0.9130579602987144, - 1.0602847041965408e-14, - 1.082225645390032e-14, - 0.9130579602987147, - ], - linf=[ - 2.113062037615659, - 4.6613606802974e-14, - 5.4225772771633196e-14, - 2.1130620376156584, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 0.9130579602987144, + 1.0602847041965408e-14, + 1.082225645390032e-14, + 0.9130579602987147, + ], + linf = [ + 2.113062037615659, + 4.6613606802974e-14, + 5.4225772771633196e-14, + 2.1130620376156584, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced_wall.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_well_balanced_wall.jl"), - l2=[ - 0.9130579602987144, - 1.0602847041965408e-14, - 1.082225645390032e-14, - 0.9130579602987147, - ], - linf=[ - 2.113062037615659, - 4.6613606802974e-14, - 5.4225772771633196e-14, - 2.1130620376156584, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced_wall.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_well_balanced_wall.jl" + ), + l2 = [ + 0.9130579602987144, + 1.0602847041965408e-14, + 1.082225645390032e-14, + 0.9130579602987147, + ], + linf = [ + 2.113062037615659, + 4.6613606802974e-14, + 5.4225772771633196e-14, + 2.1130620376156584, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl with FluxHydrostaticReconstruction" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 0.9130579602987147, - 9.68729463970494e-15, - 9.694538537436981e-15, - 0.9130579602987147, - ], - linf=[ - 2.1130620376156584, - 2.3875905654916432e-14, - 2.2492839032269154e-14, - 2.1130620376156584, - ], - surface_flux=(FluxHydrostaticReconstruction(flux_lax_friedrichs, - hydrostatic_reconstruction_audusse_etal), - flux_nonconservative_audusse_etal), - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl with FluxHydrostaticReconstruction" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 0.9130579602987147, + 9.68729463970494e-15, + 9.694538537436981e-15, + 0.9130579602987147, + ], + linf = [ + 2.1130620376156584, + 2.3875905654916432e-14, + 2.2492839032269154e-14, + 2.1130620376156584, + ], + surface_flux = ( + FluxHydrostaticReconstruction( + flux_lax_friedrichs, + hydrostatic_reconstruction_audusse_etal + ), + flux_nonconservative_audusse_etal, + ), + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl with flux_nonconservative_wintermeyer_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 0.9130579602987146, - 1.0323158914614244e-14, - 1.0276096319430528e-14, - 0.9130579602987147, - ], - linf=[ - 2.11306203761566, - 4.063916419044386e-14, - 3.694484044448245e-14, - 2.1130620376156584, - ], - surface_flux=(flux_wintermeyer_etal, - flux_nonconservative_wintermeyer_etal), - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl with flux_nonconservative_wintermeyer_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 0.9130579602987146, + 1.0323158914614244e-14, + 1.0276096319430528e-14, + 0.9130579602987147, + ], + linf = [ + 2.11306203761566, + 4.063916419044386e-14, + 3.694484044448245e-14, + 2.1130620376156584, + ], + surface_flux = ( + flux_wintermeyer_etal, + flux_nonconservative_wintermeyer_etal, + ), + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.001868474306068482, - 0.01731687445878443, - 0.017649083171490863, - 6.274146767717023e-5, - ], - linf=[ - 0.016962486402209986, - 0.08768628853889782, - 0.09038488750767648, - 0.0001819675955490041, - ], - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.001868474306068482, + 0.01731687445878443, + 0.017649083171490863, + 6.274146767717023e-5, + ], + linf = [ + 0.016962486402209986, + 0.08768628853889782, + 0.09038488750767648, + 0.0001819675955490041, + ], + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms_dirichlet.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_source_terms_dirichlet.jl"), - l2=[ - 0.0018596727473552813, - 0.017306217777629147, - 0.016367646997420396, - 6.274146767723934e-5, - ], - linf=[ - 0.016548007102923368, - 0.08726160568822783, - 0.09043621622245013, - 0.0001819675955490041, - ], - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms_dirichlet.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_source_terms_dirichlet.jl" + ), + l2 = [ + 0.0018596727473552813, + 0.017306217777629147, + 0.016367646997420396, + 6.274146767723934e-5, + ], + linf = [ + 0.016548007102923368, + 0.08726160568822783, + 0.09043621622245013, + 0.0001819675955490041, + ], + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl with flux_hll" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.0018952610547425214, - 0.016943425162728183, - 0.017556784292859465, - 6.274146767717414e-5, - ], - linf=[ - 0.0151635341334182, - 0.07967467926956129, - 0.08400050790965174, - 0.0001819675955490041, - ], - tspan=(0.0, 0.025), - surface_flux=(flux_hll, - flux_nonconservative_fjordholm_etal)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl with flux_hll" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.0018952610547425214, + 0.016943425162728183, + 0.017556784292859465, + 6.274146767717414e-5, + ], + linf = [ + 0.0151635341334182, + 0.07967467926956129, + 0.08400050790965174, + 0.0001819675955490041, + ], + tspan = (0.0, 0.025), + surface_flux = ( + flux_hll, + flux_nonconservative_fjordholm_etal, + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl with FluxHLL(min_max_speed_naive)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.0018957692481057034, - 0.016943229710439864, - 0.01755623297390675, - 6.274146767717414e-5, - ], - linf=[ - 0.015156105797771602, - 0.07964811135780492, - 0.0839787097210376, - 0.0001819675955490041, - ], - tspan=(0.0, 0.025), - surface_flux=(FluxHLL(min_max_speed_naive), - flux_nonconservative_fjordholm_etal)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl with FluxHLL(min_max_speed_naive)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.0018957692481057034, + 0.016943229710439864, + 0.01755623297390675, + 6.274146767717414e-5, + ], + linf = [ + 0.015156105797771602, + 0.07964811135780492, + 0.0839787097210376, + 0.0001819675955490041, + ], + tspan = (0.0, 0.025), + surface_flux = ( + FluxHLL(min_max_speed_naive), + flux_nonconservative_fjordholm_etal, + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl with flux_nonconservative_wintermeyer_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.002471853426064005, - 0.05619168608950033, - 0.11844727575152562, - 6.274146767730281e-5, - ], - linf=[ - 0.014332922987500218, - 0.2141204806174546, - 0.5392313755637872, - 0.0001819675955490041, - ], - surface_flux=(flux_wintermeyer_etal, - flux_nonconservative_wintermeyer_etal), - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl with flux_nonconservative_wintermeyer_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.002471853426064005, + 0.05619168608950033, + 0.11844727575152562, + 6.274146767730281e-5, + ], + linf = [ + 0.014332922987500218, + 0.2141204806174546, + 0.5392313755637872, + 0.0001819675955490041, + ], + surface_flux = ( + flux_wintermeyer_etal, + flux_nonconservative_wintermeyer_etal, + ), + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_wall.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_wall.jl"), - l2=[ - 0.1351723240085936, - 0.20010881416550014, - 0.2001088141654999, - 2.719538414346464e-7, - ], - linf=[ - 0.5303608302490757, - 0.5080987791967457, - 0.5080987791967506, - 1.1301675764130437e-6, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_wall.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_wall.jl"), + l2 = [ + 0.1351723240085936, + 0.20010881416550014, + 0.2001088141654999, + 2.719538414346464e-7, + ], + linf = [ + 0.5303608302490757, + 0.5080987791967457, + 0.5080987791967506, + 1.1301675764130437e-6, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_3d_advection.jl b/test/test_tree_3d_advection.jl index ae53a2df52..9d57a9f8d8 100644 --- a/test/test_tree_3d_advection.jl +++ b/test/test_tree_3d_advection.jl @@ -8,117 +8,133 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_dgsem") @testset "Linear scalar advection" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - l2=[0.00016263963870641478], - linf=[0.0014537194925779984]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + l2 = [0.00016263963870641478], + linf = [0.0014537194925779984] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_restart.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), - l2=[0.00016017848135651983], - linf=[0.0014175368788298393], - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_restart.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_restart.jl"), + l2 = [0.00016017848135651983], + linf = [0.0014175368788298393], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_extended.jl with initial_condition_sin" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[0.002647730309275237], - linf=[0.02114324070353557], - initial_condition=Trixi.initial_condition_sin) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_extended.jl with initial_condition_sin" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [0.002647730309275237], + linf = [0.02114324070353557], + initial_condition = Trixi.initial_condition_sin + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_extended.jl with initial_condition_constant" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[7.728011630010656e-16], - linf=[3.9968028886505635e-15], - initial_condition=initial_condition_constant) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_extended.jl with initial_condition_constant" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [7.728011630010656e-16], + linf = [3.9968028886505635e-15], + initial_condition = initial_condition_constant + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_extended.jl with initial_condition_linear_z and periodicity=false" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[3.007995700405795e-16], - linf=[2.886579864025407e-15], - initial_condition=Trixi.initial_condition_linear_z, - boundary_conditions=Trixi.boundary_condition_linear_z, - periodicity=false) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_extended.jl with initial_condition_linear_z and periodicity=false" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [3.007995700405795e-16], + linf = [2.886579864025407e-15], + initial_condition = Trixi.initial_condition_linear_z, + boundary_conditions = Trixi.boundary_condition_linear_z, + periodicity = false + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_mortar.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_mortar.jl"), - l2=[0.001810141301577316], - linf=[0.017848192256602058]) + @trixi_testset "elixir_advection_mortar.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_mortar.jl"), + l2 = [0.001810141301577316], + linf = [0.017848192256602058] + ) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), - l2=[9.773852895157622e-6], - linf=[0.0005853874124926162], - coverage_override=(maxiters = 6, initial_refinement_level = 1, - base_level = 1, med_level = 1, max_level = 3)) + @trixi_testset "elixir_advection_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_amr.jl"), + l2 = [9.773852895157622e-6], + linf = [0.0005853874124926162], + coverage_override = ( + maxiters = 6, initial_refinement_level = 1, + base_level = 1, med_level = 1, max_level = 3, + ) + ) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_3d_euler.jl b/test/test_tree_3d_euler.jl index 47669dce2f..823e314bc2 100644 --- a/test/test_tree_3d_euler.jl +++ b/test/test_tree_3d_euler.jl @@ -8,531 +8,578 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_dgsem") @testset "Compressible Euler" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_euler_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), - l2=[ - 0.010385936842224346, - 0.009776048833895767, - 0.00977604883389591, - 0.009776048833895733, - 0.01506687097416608, - ], - linf=[ - 0.03285848350791731, - 0.0321792316408982, - 0.032179231640894645, - 0.032179231640895534, - 0.0655408023333299, - ], - # With the default `maxiters = 1` in coverage tests, - # there would be no time series to check against. - coverage_override=(maxiters = 20,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), + l2 = [ + 0.010385936842224346, + 0.009776048833895767, + 0.00977604883389591, + 0.009776048833895733, + 0.01506687097416608, + ], + linf = [ + 0.03285848350791731, + 0.0321792316408982, + 0.032179231640894645, + 0.032179231640895534, + 0.0655408023333299, + ], + # With the default `maxiters = 1` in coverage tests, + # there would be no time series to check against. + coverage_override = (maxiters = 20,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end + # Extra test to make sure the "TimeSeriesCallback" made correct data. + # Extracts data at all points from the first step of the time series and compares it to the + # exact solution and an interpolated reference solution + point_data = [getindex(time_series.affect!.point_data[i], 1:5) for i in 1:3] + exact_data = [ + initial_condition_convergence_test( + time_series.affect!.point_coordinates[ + :, + i, + ], + time_series.affect!.time[1], + equations + ) for i in 1:3 + ] + ref_data = [ + [ + 1.951156832316166, + 1.952073047561595, + 1.9520730475615966, + 1.9520730475615953, + 3.814390510967551, + ], + [ + 2.0506452262144363, + 2.050727319703708, + 2.0507273197037073, + 2.0507273197037077, + 4.203653999433724, + ], + [ + 2.046982357537558, + 2.0463728824399654, + 2.0463728824399654, + 2.0463728824399645, + 4.190033459318115, + ], + ] + @test point_data ≈ exact_data atol = 1.0e-1 + @test point_data ≈ ref_data end - # Extra test to make sure the "TimeSeriesCallback" made correct data. - # Extracts data at all points from the first step of the time series and compares it to the - # exact solution and an interpolated reference solution - point_data = [getindex(time_series.affect!.point_data[i], 1:5) for i in 1:3] - exact_data = [initial_condition_convergence_test(time_series.affect!.point_coordinates[:, - i], - time_series.affect!.time[1], - equations) for i in 1:3] - ref_data = [ - [ - 1.951156832316166, - 1.952073047561595, - 1.9520730475615966, - 1.9520730475615953, - 3.814390510967551, - ], - [ - 2.0506452262144363, - 2.050727319703708, - 2.0507273197037073, - 2.0507273197037077, - 4.203653999433724, - ], - [ - 2.046982357537558, - 2.0463728824399654, - 2.0463728824399654, - 2.0463728824399645, - 4.190033459318115, - ]] - @test point_data≈exact_data atol=1e-1 - @test point_data ≈ ref_data -end -@trixi_testset "elixir_euler_convergence_pure_fv.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence_pure_fv.jl"), - l2=[ - 0.037182410351406, - 0.032062252638283974, - 0.032062252638283974, - 0.03206225263828395, - 0.12228177813586687, - ], - linf=[ - 0.0693648413632646, - 0.0622101894740843, - 0.06221018947408474, - 0.062210189474084965, - 0.24196451799555962, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_convergence_pure_fv.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence_pure_fv.jl"), + l2 = [ + 0.037182410351406, + 0.032062252638283974, + 0.032062252638283974, + 0.03206225263828395, + 0.12228177813586687, + ], + linf = [ + 0.0693648413632646, + 0.0622101894740843, + 0.06221018947408474, + 0.062210189474084965, + 0.24196451799555962, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_source_terms.jl with split_form" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), - l2=[ - 0.010385936842223388, - 0.009776048833894784, - 0.009776048833894784, - 0.009776048833894765, - 0.015066870974164096, - ], - linf=[ - 0.03285848350791687, - 0.032179231640897754, - 0.0321792316408942, - 0.0321792316408982, - 0.06554080233333615, - ], - volume_integral=VolumeIntegralFluxDifferencing(flux_central)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_source_terms.jl with split_form" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_source_terms.jl"), + l2 = [ + 0.010385936842223388, + 0.009776048833894784, + 0.009776048833894784, + 0.009776048833894765, + 0.015066870974164096, + ], + linf = [ + 0.03285848350791687, + 0.032179231640897754, + 0.0321792316408942, + 0.0321792316408982, + 0.06554080233333615, + ], + volume_integral = VolumeIntegralFluxDifferencing(flux_central) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 0.0003637241020254673, 0.00039555708663848046, - 0.00039555708663832644, 0.0003955570866385083, - 0.0007811613481643962, - ], - linf=[ - 0.0024000660244567484, 0.002963541002521053, - 0.0029635410025201647, 0.002963541002522385, - 0.007191437359379549, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 0.0003637241020254673, 0.00039555708663848046, + 0.00039555708663832644, 0.0003955570866385083, + 0.0007811613481643962, + ], + linf = [ + 0.0024000660244567484, 0.002963541002521053, + 0.0029635410025201647, 0.002963541002522385, + 0.007191437359379549, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_mortar.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_mortar.jl"), - l2=[ - 0.0019428114665068841, - 0.0018659907926698422, - 0.0018659907926698589, - 0.0018659907926698747, - 0.0034549095578444056, - ], - linf=[ - 0.011355360771142298, - 0.011526889155693887, - 0.011526889155689002, - 0.011526889155701436, - 0.02299726519821288, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_mortar.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_mortar.jl"), + l2 = [ + 0.0019428114665068841, + 0.0018659907926698422, + 0.0018659907926698589, + 0.0018659907926698747, + 0.0034549095578444056, + ], + linf = [ + 0.011355360771142298, + 0.011526889155693887, + 0.011526889155689002, + 0.011526889155701436, + 0.02299726519821288, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_amr.jl"), - l2=[ - 0.0038281920613404716, - 0.003828192061340465, - 0.0038281920613404694, - 0.0038281920613404672, - 0.005742288092010652, - ], - linf=[ - 0.07390396464027349, - 0.07390396464027305, - 0.07390396464027305, - 0.07390396464027305, - 0.11085594696041134, - ], - tspan=(0.0, 0.1), - coverage_override=(maxiters = 6, initial_refinement_level = 0, - base_level = 0, med_level = 0, max_level = 1)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_amr.jl"), + l2 = [ + 0.0038281920613404716, + 0.003828192061340465, + 0.0038281920613404694, + 0.0038281920613404672, + 0.005742288092010652, + ], + linf = [ + 0.07390396464027349, + 0.07390396464027305, + 0.07390396464027305, + 0.07390396464027305, + 0.11085594696041134, + ], + tspan = (0.0, 0.1), + coverage_override = ( + maxiters = 6, initial_refinement_level = 0, + base_level = 0, med_level = 0, max_level = 1, + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_taylor_green_vortex.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_taylor_green_vortex.jl"), - l2=[ - 0.00034949871748737876, - 0.03133384111621587, - 0.03133384111621582, - 0.04378599329988925, - 0.015796137903453026, - ], - linf=[ - 0.0013935237751798724, - 0.0724080091006194, - 0.07240800910061806, - 0.12795921224174792, - 0.07677156293692633, - ], - tspan=(0.0, 0.5)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_taylor_green_vortex.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_taylor_green_vortex.jl"), + l2 = [ + 0.00034949871748737876, + 0.03133384111621587, + 0.03133384111621582, + 0.04378599329988925, + 0.015796137903453026, + ], + linf = [ + 0.0013935237751798724, + 0.0724080091006194, + 0.07240800910061806, + 0.12795921224174792, + 0.07677156293692633, + ], + tspan = (0.0, 0.5) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing.jl"), - l2=[ - 0.02570137197844877, - 0.016179934130642552, - 0.01617993413064253, - 0.016172648598753545, - 0.09261669328795467, - ], - linf=[ - 0.3954458125573179, - 0.26876916180359345, - 0.26876916180359345, - 0.26933123042178553, - 1.3724137121660251, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing.jl"), + l2 = [ + 0.02570137197844877, + 0.016179934130642552, + 0.01617993413064253, + 0.016172648598753545, + 0.09261669328795467, + ], + linf = [ + 0.3954458125573179, + 0.26876916180359345, + 0.26876916180359345, + 0.26933123042178553, + 1.3724137121660251, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_shockcapturing_amr.jl" begin - # OBS! This setup does not make much practical sense. It is only added to exercise the - # `sedov_self_gravity` AMR indicator, which in its original configuration is too expensive for - # CI testing - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_amr.jl"), - l2=[ - 0.02217299067704248, - 0.012771561294571411, - 0.01277156129457143, - 0.012770635779336643, - 0.08091898488262424, - ], - linf=[ - 0.4047819603427084, - 0.27493532130155474, - 0.2749353213015551, - 0.2749304638368023, - 1.4053942765487641, - ], - maxiters=10, - coverage_override=(maxiters = 2,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_shockcapturing_amr.jl" begin + # OBS! This setup does not make much practical sense. It is only added to exercise the + # `sedov_self_gravity` AMR indicator, which in its original configuration is too expensive for + # CI testing + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_shockcapturing_amr.jl"), + l2 = [ + 0.02217299067704248, + 0.012771561294571411, + 0.01277156129457143, + 0.012770635779336643, + 0.08091898488262424, + ], + linf = [ + 0.4047819603427084, + 0.27493532130155474, + 0.2749353213015551, + 0.2749304638368023, + 1.4053942765487641, + ], + maxiters = 10, + coverage_override = (maxiters = 2,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_density_pulse.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_density_pulse.jl"), - l2=[ - 0.057196526814004715, - 0.057196526814004715, - 0.05719652681400473, - 0.057196526814004736, - 0.08579479022100575, - ], - linf=[ - 0.27415246703018203, - 0.2741524670301829, - 0.2741524670301827, - 0.27415246703018226, - 0.41122870054527816, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_density_pulse.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_density_pulse.jl"), + l2 = [ + 0.057196526814004715, + 0.057196526814004715, + 0.05719652681400473, + 0.057196526814004736, + 0.08579479022100575, + ], + linf = [ + 0.27415246703018203, + 0.2741524670301829, + 0.2741524670301827, + 0.27415246703018226, + 0.41122870054527816, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.02526341317987378, - 0.016632068583699623, - 0.016632068583699623, - 0.01662548715216875, - 0.0913477018048886, - ], - linf=[ - 0.4372549540810414, - 0.28613118232798984, - 0.28613118232799006, - 0.28796686065271876, - 1.5072828647309124, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.02526341317987378, + 0.016632068583699623, + 0.016632068583699623, + 0.01662548715216875, + 0.0913477018048886, + ], + linf = [ + 0.4372549540810414, + 0.28613118232798984, + 0.28613118232799006, + 0.28796686065271876, + 1.5072828647309124, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with initial_condition=initial_condition_constant" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 4.183721551616214e-16, - 6.059779958716338e-16, - 4.916596221090319e-16, - 9.739943366304456e-16, - 3.7485908743251566e-15, - ], - linf=[ - 2.4424906541753444e-15, - 3.733124920302089e-15, - 4.440892098500626e-15, - 5.329070518200751e-15, - 2.4868995751603507e-14, - ], - initial_condition=initial_condition_constant) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl with initial_condition=initial_condition_constant" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 4.183721551616214e-16, + 6.059779958716338e-16, + 4.916596221090319e-16, + 9.739943366304456e-16, + 3.7485908743251566e-15, + ], + linf = [ + 2.4424906541753444e-15, + 3.733124920302089e-15, + 4.440892098500626e-15, + 5.329070518200751e-15, + 2.4868995751603507e-14, + ], + initial_condition = initial_condition_constant + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with flux_chandrashekar" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.025265721172813106, - 0.016649800693500427, - 0.01664980069350042, - 0.01664379306708522, - 0.09137248646784184, - ], - linf=[ - 0.4373399329742198, - 0.28434487167605427, - 0.28434487167605427, - 0.28522678968890774, - 1.532471676033761, - ], - surface_flux=flux_chandrashekar, volume_flux=flux_chandrashekar) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl with flux_chandrashekar" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.025265721172813106, + 0.016649800693500427, + 0.01664980069350042, + 0.01664379306708522, + 0.09137248646784184, + ], + linf = [ + 0.4373399329742198, + 0.28434487167605427, + 0.28434487167605427, + 0.28522678968890774, + 1.532471676033761, + ], + surface_flux = flux_chandrashekar, volume_flux = flux_chandrashekar + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with flux_kennedy_gruber" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.025280033869871984, - 0.016675487948639846, - 0.016675487948639853, - 0.016668992714991282, - 0.091455613470441, - ], - linf=[ - 0.43348628145015766, - 0.28853549062014217, - 0.28853549062014217, - 0.2903943042772536, - 1.5236557526482426, - ], - surface_flux=flux_kennedy_gruber, - volume_flux=flux_kennedy_gruber) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl with flux_kennedy_gruber" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.025280033869871984, + 0.016675487948639846, + 0.016675487948639853, + 0.016668992714991282, + 0.091455613470441, + ], + linf = [ + 0.43348628145015766, + 0.28853549062014217, + 0.28853549062014217, + 0.2903943042772536, + 1.5236557526482426, + ], + surface_flux = flux_kennedy_gruber, + volume_flux = flux_kennedy_gruber + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl with flux_shima_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.025261716925811403, - 0.016637655557848952, - 0.01663765555784895, - 0.01663105921013437, - 0.09136239054024566, - ], - linf=[ - 0.43692416928732536, - 0.28622033209064734, - 0.28622033209064746, - 0.2881197143457632, - 1.506534270303663, - ], - surface_flux=flux_shima_etal, volume_flux=flux_shima_etal) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl with flux_shima_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.025261716925811403, + 0.016637655557848952, + 0.01663765555784895, + 0.01663105921013437, + 0.09136239054024566, + ], + linf = [ + 0.43692416928732536, + 0.28622033209064734, + 0.28622033209064746, + 0.2881197143457632, + 1.506534270303663, + ], + surface_flux = flux_shima_etal, volume_flux = flux_shima_etal + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_blob_amr.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_blob_amr.jl"), - l2=[ - 0.04867856452253151, - 0.2640486962336911, - 0.0354927658652858, - 0.03549276586528571, - 1.0777274757408568, - ], - linf=[ - 9.558543313792217, - 49.4518309553356, - 10.319859082570309, - 10.319859082570487, - 195.1066220797401, - ], - tspan=(0.0, 0.2), - # Let this test run longer to cover some lines in the positivity preserving limiter - # and some AMR lines - coverage_override=(maxiters = 10^5,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_blob_amr.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_blob_amr.jl"), + l2 = [ + 0.04867856452253151, + 0.2640486962336911, + 0.0354927658652858, + 0.03549276586528571, + 1.0777274757408568, + ], + linf = [ + 9.558543313792217, + 49.4518309553356, + 10.319859082570309, + 10.319859082570487, + 195.1066220797401, + ], + tspan = (0.0, 0.2), + # Let this test run longer to cover some lines in the positivity preserving limiter + # and some AMR lines + coverage_override = (maxiters = 10^5,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), - l2=[ - 0.0007127163978031706, - 0.0023166296394624025, - 0.002316629639462401, - 0.0023166296394624038, - 0.010200581509653256, - ], - linf=[ - 0.06344190883105805, - 0.6292607955969378, - 0.6292607955969377, - 0.6292607955969377, - 2.397746252817731, - ], - maxiters=5, max_level=6, - surface_flux=FluxHLL(min_max_speed_naive), - coverage_override=(maxiters = 2, initial_refinement_level = 1, - base_level = 1, max_level = 3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), + l2 = [ + 0.0007127163978031706, + 0.0023166296394624025, + 0.002316629639462401, + 0.0023166296394624038, + 0.010200581509653256, + ], + linf = [ + 0.06344190883105805, + 0.6292607955969378, + 0.6292607955969377, + 0.6292607955969377, + 2.397746252817731, + ], + maxiters = 5, max_level = 6, + surface_flux = FluxHLL(min_max_speed_naive), + coverage_override = ( + maxiters = 2, initial_refinement_level = 1, + base_level = 1, max_level = 3, + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov_blast_wave.jl (HLLE)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), - l2=[ - 0.0007871241159752619, - 0.0037168004033428146, - 0.0037168004033428094, - 0.0037168004033428514, - 0.011119869089205635, - ], - linf=[ - 0.13982864363612468, - 0.786004687738243, - 0.786004687738243, - 0.7860046877382431, - 1.7082524045150382, - ], - tspan=(0.0, 0.01), - surface_flux=flux_hlle) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov_blast_wave.jl (HLLE)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov_blast_wave.jl"), + l2 = [ + 0.0007871241159752619, + 0.0037168004033428146, + 0.0037168004033428094, + 0.0037168004033428514, + 0.011119869089205635, + ], + linf = [ + 0.13982864363612468, + 0.786004687738243, + 0.786004687738243, + 0.7860046877382431, + 1.7082524045150382, + ], + tspan = (0.0, 0.01), + surface_flux = flux_hlle + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_3d_eulergravity.jl b/test/test_tree_3d_eulergravity.jl index 1b5e715f77..027452548e 100644 --- a/test/test_tree_3d_eulergravity.jl +++ b/test/test_tree_3d_eulergravity.jl @@ -8,34 +8,36 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_dgsem") @testset "Compressible Euler with self-gravity" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_eulergravity_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), - l2=[ - 0.0004276779201667428, - 0.00047204222332596204, - 0.00047204222332608705, - 0.0004720422233259819, - 0.0010987026250960728, - ], - linf=[ - 0.003496616916238704, - 0.003764418290373106, - 0.003764418290377103, - 0.0037644182903766588, - 0.008370424899251105, - ], - resid_tol=1.0e-4, tspan=(0.0, 0.2)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_eulergravity_convergence.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_eulergravity_convergence.jl"), + l2 = [ + 0.0004276779201667428, + 0.00047204222332596204, + 0.00047204222332608705, + 0.0004720422233259819, + 0.0010987026250960728, + ], + linf = [ + 0.003496616916238704, + 0.003764418290373106, + 0.003764418290377103, + 0.0037644182903766588, + 0.008370424899251105, + ], + resid_tol = 1.0e-4, tspan = (0.0, 0.2) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_3d_fdsbp.jl b/test/test_tree_3d_fdsbp.jl index e0e2bfe4b8..4ea913cce3 100644 --- a/test/test_tree_3d_fdsbp.jl +++ b/test/test_tree_3d_fdsbp.jl @@ -8,65 +8,73 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_fdsbp") @testset "Linear scalar advection" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_advection_extended.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[0.005355755365412444], - linf=[0.01856044696350767]) + @trixi_testset "elixir_advection_extended.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [0.005355755365412444], + linf = [0.01856044696350767] + ) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_extended.jl with periodic operators" begin - global D = SummationByPartsOperators.periodic_derivative_operator(derivative_order = 1, - accuracy_order = 4, - xmin = 0.0, - xmax = 1.0, - N = 10) - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2=[5.228248923012878e-9], - linf=[9.24430243465224e-9], - D_SBP=D, - initial_refinement_level=0, - tspan=(0.0, 5.0)) + @trixi_testset "elixir_advection_extended.jl with periodic operators" begin + global D = SummationByPartsOperators.periodic_derivative_operator( + derivative_order = 1, + accuracy_order = 4, + xmin = 0.0, + xmax = 1.0, + N = 10 + ) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), + l2 = [5.228248923012878e-9], + linf = [9.24430243465224e-9], + D_SBP = D, + initial_refinement_level = 0, + tspan = (0.0, 5.0) + ) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end @testset "Compressible Euler" begin @trixi_testset "elixir_euler_convergence.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 2.247522803543667e-5, - 2.2499169224681058e-5, - 2.24991692246826e-5, - 2.2499169224684707e-5, - 5.814121361417382e-5, - ], - linf=[ - 9.579357410749445e-5, - 9.544871933409027e-5, - 9.54487193367548e-5, - 9.544871933453436e-5, - 0.0004192294529472562, - ], - tspan=(0.0, 0.2)) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 2.247522803543667e-5, + 2.2499169224681058e-5, + 2.24991692246826e-5, + 2.2499169224684707e-5, + 5.814121361417382e-5, + ], + linf = [ + 9.579357410749445e-5, + 9.544871933409027e-5, + 9.54487193367548e-5, + 9.544871933453436e-5, + 0.0004192294529472562, + ], + tspan = (0.0, 0.2) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -79,24 +87,28 @@ end end @trixi_testset "elixir_euler_convergence.jl with VolumeIntegralStrongForm" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), - l2=[ - 4.084919840272202e-5, - 4.1320630860402814e-5, - 4.132063086040211e-5, - 4.132063086039092e-5, - 8.502518355874354e-5, - ], - linf=[ - 0.0001963934848161486, - 0.00020239883896255861, - 0.0002023988389729947, - 0.00020239883896766564, - 0.00052605624510349, - ], - tspan=(0.0, 0.2), - solver=DG(D_upw.central, nothing, SurfaceIntegralStrongForm(), - VolumeIntegralStrongForm())) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_convergence.jl"), + l2 = [ + 4.084919840272202e-5, + 4.1320630860402814e-5, + 4.132063086040211e-5, + 4.132063086039092e-5, + 8.502518355874354e-5, + ], + linf = [ + 0.0001963934848161486, + 0.00020239883896255861, + 0.0002023988389729947, + 0.00020239883896766564, + 0.00052605624510349, + ], + tspan = (0.0, 0.2), + solver = DG( + D_upw.central, nothing, SurfaceIntegralStrongForm(), + VolumeIntegralStrongForm() + ) + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) @@ -109,22 +121,24 @@ end end @trixi_testset "elixir_euler_taylor_green_vortex.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_taylor_green_vortex.jl"), - l2=[ - 3.529693407280806e-6, - 0.0004691301922633193, - 0.00046913019226332234, - 0.0006630180220973541, - 0.0015732759680929076, - ], - linf=[ - 3.4253965106145756e-5, - 0.0010033197685090707, - 0.0010033197685091054, - 0.0018655642702542635, - 0.008479800046757191, - ], - tspan=(0.0, 0.0075), abstol=1.0e-9, reltol=1.0e-9) + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_taylor_green_vortex.jl"), + l2 = [ + 3.529693407280806e-6, + 0.0004691301922633193, + 0.00046913019226332234, + 0.0006630180220973541, + 0.0015732759680929076, + ], + linf = [ + 3.4253965106145756e-5, + 0.0010033197685090707, + 0.0010033197685091054, + 0.0018655642702542635, + 0.008479800046757191, + ], + tspan = (0.0, 0.0075), abstol = 1.0e-9, reltol = 1.0e-9 + ) # Ensure that we do not have excessive memory allocations # (e.g., from type instabilities) diff --git a/test/test_tree_3d_hypdiff.jl b/test/test_tree_3d_hypdiff.jl index 5c9dacbd87..94ba75d7f4 100644 --- a/test/test_tree_3d_hypdiff.jl +++ b/test/test_tree_3d_hypdiff.jl @@ -8,81 +8,87 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_dgsem") @testset "Hyperbolic diffusion" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_hypdiff_lax_friedrichs.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_lax_friedrichs.jl"), - l2=[ - 0.001530331609036682, - 0.011314177033289238, - 0.011314177033289402, - 0.011314177033289631, - ], - linf=[ - 0.02263459033909354, - 0.10139777904683545, - 0.10139777904683545, - 0.10139777904683545, - ], - initial_refinement_level=2) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_hypdiff_lax_friedrichs.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_lax_friedrichs.jl"), + l2 = [ + 0.001530331609036682, + 0.011314177033289238, + 0.011314177033289402, + 0.011314177033289631, + ], + linf = [ + 0.02263459033909354, + 0.10139777904683545, + 0.10139777904683545, + 0.10139777904683545, + ], + initial_refinement_level = 2 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end -end -@trixi_testset "elixir_hypdiff_lax_friedrichs.jl with surface_flux=flux_godunov)" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_lax_friedrichs.jl"), - l2=[ - 0.0015377731806850128, - 0.01137685274151801, - 0.011376852741518175, - 0.011376852741518494, - ], - linf=[ - 0.022715420630041172, - 0.10183745338964201, - 0.10183745338964201, - 0.1018374533896429, - ], - initial_refinement_level=2, surface_flux=flux_godunov) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_hypdiff_lax_friedrichs.jl with surface_flux=flux_godunov)" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_lax_friedrichs.jl"), + l2 = [ + 0.0015377731806850128, + 0.01137685274151801, + 0.011376852741518175, + 0.011376852741518494, + ], + linf = [ + 0.022715420630041172, + 0.10183745338964201, + 0.10183745338964201, + 0.1018374533896429, + ], + initial_refinement_level = 2, surface_flux = flux_godunov + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end -end -@trixi_testset "elixir_hypdiff_nonperiodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), - l2=[ - 0.00022868320512754316, - 0.0007974309948540525, - 0.0015035143230654987, - 0.0015035143230655293, - ], - linf=[ - 0.0016405001653623241, - 0.0029870057159104594, - 0.009410031618285686, - 0.009410031618287462, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + @trixi_testset "elixir_hypdiff_nonperiodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_hypdiff_nonperiodic.jl"), + l2 = [ + 0.00022868320512754316, + 0.0007974309948540525, + 0.0015035143230654987, + 0.0015035143230655293, + ], + linf = [ + 0.0016405001653623241, + 0.0029870057159104594, + 0.009410031618285686, + 0.009410031618287462, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 15000 + end end end -end end # module diff --git a/test/test_tree_3d_lbm.jl b/test/test_tree_3d_lbm.jl index dc7e770dfa..27ae98c387 100644 --- a/test/test_tree_3d_lbm.jl +++ b/test/test_tree_3d_lbm.jl @@ -8,104 +8,116 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_dgsem") @testset "Lattice-Boltzmann" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_lbm_constant.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_lbm_constant.jl"), - l2=[5.861930511199053e-16, 6.282772442363201e-16, - 5.47591540767842e-16, - 6.379244339335046e-16, 5.81421258408584e-16, - 6.634626069779352e-16, - 2.9188639102691596e-16, 2.1539168764807097e-16, - 3.0131714783573674e-16, - 2.2126555191449657e-16, 2.622901122013102e-16, - 2.2115776381362187e-16, - 6.32031843208421e-17, 6.103875364141341e-17, - 6.821138567646266e-17, - 6.48022057541854e-17, 5.013642264182462e-17, - 7.169498358338181e-17, - 1.3879946832660896e-16, 5.649850447603551e-17, - 3.4686869828797276e-17, - 3.8719518141614167e-17, 3.5852179230919525e-17, - 4.292415147083455e-17, - 3.608945206319316e-17, 4.187850903422495e-17, - 8.254587760492495e-16], - linf=[1.1657341758564144e-15, 1.5543122344752192e-15, - 1.1657341758564144e-15, - 1.4710455076283324e-15, 1.0547118733938987e-15, - 1.4988010832439613e-15, - 5.273559366969494e-16, 5.065392549852277e-16, - 5.967448757360216e-16, - 5.342948306008566e-16, 5.412337245047638e-16, - 5.967448757360216e-16, - 3.608224830031759e-16, 3.469446951953614e-16, - 2.42861286636753e-16, - 2.498001805406602e-16, 2.2898349882893854e-16, - 4.3021142204224816e-16, - 2.1510571102112408e-16, 1.43982048506075e-16, - 1.214306433183765e-16, - 1.4224732503009818e-16, 1.214306433183765e-16, - 1.3877787807814457e-16, - 8.673617379884035e-17, 9.71445146547012e-17, - 2.7755575615628914e-15], - tspan=(0.0, 0.5)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_lbm_constant.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_lbm_constant.jl"), + l2 = [ + 5.861930511199053e-16, 6.282772442363201e-16, + 5.47591540767842e-16, + 6.379244339335046e-16, 5.81421258408584e-16, + 6.634626069779352e-16, + 2.9188639102691596e-16, 2.1539168764807097e-16, + 3.0131714783573674e-16, + 2.2126555191449657e-16, 2.622901122013102e-16, + 2.2115776381362187e-16, + 6.32031843208421e-17, 6.103875364141341e-17, + 6.821138567646266e-17, + 6.48022057541854e-17, 5.013642264182462e-17, + 7.169498358338181e-17, + 1.3879946832660896e-16, 5.649850447603551e-17, + 3.4686869828797276e-17, + 3.8719518141614167e-17, 3.5852179230919525e-17, + 4.292415147083455e-17, + 3.608945206319316e-17, 4.187850903422495e-17, + 8.254587760492495e-16, + ], + linf = [ + 1.1657341758564144e-15, 1.5543122344752192e-15, + 1.1657341758564144e-15, + 1.4710455076283324e-15, 1.0547118733938987e-15, + 1.4988010832439613e-15, + 5.273559366969494e-16, 5.065392549852277e-16, + 5.967448757360216e-16, + 5.342948306008566e-16, 5.412337245047638e-16, + 5.967448757360216e-16, + 3.608224830031759e-16, 3.469446951953614e-16, + 2.42861286636753e-16, + 2.498001805406602e-16, 2.2898349882893854e-16, + 4.3021142204224816e-16, + 2.1510571102112408e-16, 1.43982048506075e-16, + 1.214306433183765e-16, + 1.4224732503009818e-16, 1.214306433183765e-16, + 1.3877787807814457e-16, + 8.673617379884035e-17, 9.71445146547012e-17, + 2.7755575615628914e-15, + ], + tspan = (0.0, 0.5) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_lbm_taylor_green_vortex.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_lbm_taylor_green_vortex.jl"), - l2=[7.516128821554829e-5, 7.516128821554695e-5, - 7.516128821554932e-5, - 7.516128821554856e-5, 7.022624942862394e-6, - 7.022624942862171e-6, - 2.961794427142361e-6, 2.961794427142168e-6, - 2.6527195181287848e-5, - 2.6527195181287404e-5, 2.652719518128811e-5, - 2.6527195181287916e-5, - 2.9617944271423104e-6, 2.961794427142108e-6, - 2.652719518128758e-5, - 2.6527195181287513e-5, 2.6527195181287916e-5, - 2.6527195181287872e-5, - 6.697526775466e-6, 6.697526775466029e-6, - 6.697526775465903e-6, 6.697526775465986e-6, - 6.697526775466051e-6, 6.697526775465938e-6, - 6.697526775465983e-6, - 6.697526775466005e-6, 2.8805380887802028e-6], - linf=[0.00021570074723312183, 0.0002157007472331357, - 0.00021570074723314958, - 0.00021570074723316346, 1.9675688146578163e-5, - 1.967568814660592e-5, - 9.53539471547013e-6, 9.53539471547013e-6, - 5.7339968249785905e-5, - 5.7339968249778966e-5, 5.7339968249792844e-5, - 5.7339968249778966e-5, - 9.535394715480539e-6, 9.53539471546666e-6, - 5.73399682497755e-5, 5.7339968249785905e-5, - 5.7339968249782436e-5, 5.7339968249782436e-5, - 1.3570400471223966e-5, - 1.3570400471222231e-5, 1.3570400471220496e-5, - 1.3570400471224833e-5, - 1.3570400471223966e-5, 1.3570400471221364e-5, - 1.3570400471224833e-5, - 1.3570400471224833e-5, 1.4249297322244114e-5], - tspan=(0.0, 0.1), - initial_refinement_level=3) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_lbm_taylor_green_vortex.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_lbm_taylor_green_vortex.jl"), + l2 = [ + 7.516128821554829e-5, 7.516128821554695e-5, + 7.516128821554932e-5, + 7.516128821554856e-5, 7.022624942862394e-6, + 7.022624942862171e-6, + 2.961794427142361e-6, 2.961794427142168e-6, + 2.6527195181287848e-5, + 2.6527195181287404e-5, 2.652719518128811e-5, + 2.6527195181287916e-5, + 2.9617944271423104e-6, 2.961794427142108e-6, + 2.652719518128758e-5, + 2.6527195181287513e-5, 2.6527195181287916e-5, + 2.6527195181287872e-5, + 6.697526775466e-6, 6.697526775466029e-6, + 6.697526775465903e-6, 6.697526775465986e-6, + 6.697526775466051e-6, 6.697526775465938e-6, + 6.697526775465983e-6, + 6.697526775466005e-6, 2.8805380887802028e-6, + ], + linf = [ + 0.00021570074723312183, 0.0002157007472331357, + 0.00021570074723314958, + 0.00021570074723316346, 1.9675688146578163e-5, + 1.967568814660592e-5, + 9.53539471547013e-6, 9.53539471547013e-6, + 5.7339968249785905e-5, + 5.7339968249778966e-5, 5.7339968249792844e-5, + 5.7339968249778966e-5, + 9.535394715480539e-6, 9.53539471546666e-6, + 5.73399682497755e-5, 5.7339968249785905e-5, + 5.7339968249782436e-5, 5.7339968249782436e-5, + 1.3570400471223966e-5, + 1.3570400471222231e-5, 1.3570400471220496e-5, + 1.3570400471224833e-5, + 1.3570400471223966e-5, 1.3570400471221364e-5, + 1.3570400471224833e-5, + 1.3570400471224833e-5, 1.4249297322244114e-5, + ], + tspan = (0.0, 0.1), + initial_refinement_level = 3 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_3d_linearizedeuler.jl b/test/test_tree_3d_linearizedeuler.jl index 00f8d62dad..a153b4674b 100644 --- a/test/test_tree_3d_linearizedeuler.jl +++ b/test/test_tree_3d_linearizedeuler.jl @@ -1,4 +1,3 @@ - using Test using Trixi @@ -7,29 +6,31 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_dgsem") @testset "Linearized Euler Equations 3D" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_linearizedeuler_gauss_wall.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gauss_wall.jl"), - l2=[ - 0.020380328336745232, 0.027122442311921492, - 0.02712244231192152, 8.273108096127844e-17, - 0.020380328336745232, - ], - linf=[ - 0.2916021983572774, 0.32763703462270843, - 0.32763703462270855, 1.641012595221666e-15, - 0.2916021983572774, - ], - tspan=(0.0, 1.0)) + @trixi_testset "elixir_linearizedeuler_gauss_wall.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_linearizedeuler_gauss_wall.jl"), + l2 = [ + 0.020380328336745232, 0.027122442311921492, + 0.02712244231192152, 8.273108096127844e-17, + 0.020380328336745232, + ], + linf = [ + 0.2916021983572774, 0.32763703462270843, + 0.32763703462270855, 1.641012595221666e-15, + 0.2916021983572774, + ], + tspan = (0.0, 1.0) + ) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end diff --git a/test/test_tree_3d_mhd.jl b/test/test_tree_3d_mhd.jl index 74107d462d..39ee96319a 100644 --- a/test/test_tree_3d_mhd.jl +++ b/test/test_tree_3d_mhd.jl @@ -8,289 +8,311 @@ include("test_trixi.jl") EXAMPLES_DIR = pkgdir(Trixi, "examples", "tree_3d_dgsem") @testset "MHD" begin -#! format: noindent + #! format: noindent -@trixi_testset "elixir_mhd_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), - l2=[ - 0.017590099293094203, - 0.017695875823827714, - 0.017695875823827686, - 0.017698038279620777, - 0.07495006099352074, - 0.010391801950005755, - 0.010391801950005759, - 0.010393502246627087, - 2.524766553484067e-16, - ], - linf=[ - 0.28173002819718196, - 0.3297583616136297, - 0.32975836161363004, - 0.356862935505337, - 1.2893514981209626, - 0.10950981489747313, - 0.10950981489747136, - 0.11517234329681891, - 2.0816911067714202e-15, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), + l2 = [ + 0.017590099293094203, + 0.017695875823827714, + 0.017695875823827686, + 0.017698038279620777, + 0.07495006099352074, + 0.010391801950005755, + 0.010391801950005759, + 0.010393502246627087, + 2.524766553484067e-16, + ], + linf = [ + 0.28173002819718196, + 0.3297583616136297, + 0.32975836161363004, + 0.356862935505337, + 1.2893514981209626, + 0.10950981489747313, + 0.10950981489747136, + 0.11517234329681891, + 2.0816911067714202e-15, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_ec.jl with initial_condition=initial_condition_constant" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), - l2=[ - 4.270231310667203e-16, - 2.4381208042014784e-15, - 5.345107673575357e-15, - 3.00313882171883e-15, - 1.7772703118758417e-14, - 1.0340110783830874e-15, - 1.1779095371939702e-15, - 9.961878521814573e-16, - 8.1201730630719145e-16, - ], - linf=[ - 2.4424906541753444e-15, - 2.881028748902281e-14, - 2.4646951146678475e-14, - 2.3092638912203256e-14, - 2.3447910280083306e-13, - 1.7763568394002505e-14, - 1.0436096431476471e-14, - 2.042810365310288e-14, - 7.057203733035201e-15, - ], - atol=1000 * eps(), - initial_condition=initial_condition_constant) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ec.jl with initial_condition=initial_condition_constant" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), + l2 = [ + 4.270231310667203e-16, + 2.4381208042014784e-15, + 5.345107673575357e-15, + 3.00313882171883e-15, + 1.7772703118758417e-14, + 1.0340110783830874e-15, + 1.1779095371939702e-15, + 9.961878521814573e-16, + 8.1201730630719145e-16, + ], + linf = [ + 2.4424906541753444e-15, + 2.881028748902281e-14, + 2.4646951146678475e-14, + 2.3092638912203256e-14, + 2.3447910280083306e-13, + 1.7763568394002505e-14, + 1.0436096431476471e-14, + 2.042810365310288e-14, + 7.057203733035201e-15, + ], + atol = 1000 * eps(), + initial_condition = initial_condition_constant + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 0.0032217291057246157, - 0.009511644936958913, - 0.004217358459420256, - 0.011591709179125335, - 0.009456218722393708, - 0.00916500047763897, - 0.005069863732625444, - 0.011503011541926135, - 0.003988175543749985, - ], - linf=[ - 0.01188593784273051, - 0.03638015998373141, - 0.01568200398945724, - 0.04666974730787579, - 0.031235294705421968, - 0.03316343064943483, - 0.011539436992528018, - 0.04896687646520839, - 0.018714054039927555, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 0.0032217291057246157, + 0.009511644936958913, + 0.004217358459420256, + 0.011591709179125335, + 0.009456218722393708, + 0.00916500047763897, + 0.005069863732625444, + 0.011503011541926135, + 0.003988175543749985, + ], + linf = [ + 0.01188593784273051, + 0.03638015998373141, + 0.01568200398945724, + 0.04666974730787579, + 0.031235294705421968, + 0.03316343064943483, + 0.011539436992528018, + 0.04896687646520839, + 0.018714054039927555, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl with flux_derigs_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 0.003755235939722358, - 0.009062519246840721, - 0.004096299856228109, - 0.011429935838448906, - 0.006897420817511043, - 0.00900886245212482, - 0.004926537542780259, - 0.01153285554590683, - 0.0037842060148666886, - ], - linf=[ - 0.012982853115883541, - 0.0320228076558316, - 0.011575276754611022, - 0.04425778643430531, - 0.02478109022285846, - 0.03198699034954189, - 0.009761077061886558, - 0.04433669321441455, - 0.01618905441148782, - ], - volume_flux=(flux_derigs_etal, flux_nonconservative_powell)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl with flux_derigs_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 0.003755235939722358, + 0.009062519246840721, + 0.004096299856228109, + 0.011429935838448906, + 0.006897420817511043, + 0.00900886245212482, + 0.004926537542780259, + 0.01153285554590683, + 0.0037842060148666886, + ], + linf = [ + 0.012982853115883541, + 0.0320228076558316, + 0.011575276754611022, + 0.04425778643430531, + 0.02478109022285846, + 0.03198699034954189, + 0.009761077061886558, + 0.04433669321441455, + 0.01618905441148782, + ], + volume_flux = (flux_derigs_etal, flux_nonconservative_powell) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave_mortar.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave_mortar.jl"), - l2=[ - 0.001879021634926363, - 0.007032724521848316, - 0.0032793932234187325, - 0.009056594733320348, - 0.007514150120617965, - 0.007328739509868727, - 0.00309794018112387, - 0.009026356949274878, - 0.0035732583778049776, - ], - linf=[ - 0.013734346970999622, - 0.06173467158736011, - 0.02183946452704291, - 0.06258216169457917, - 0.03672304497348122, - 0.055120532123884625, - 0.018202716205672487, - 0.06133688282205586, - 0.019888161885935608, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave_mortar.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave_mortar.jl"), + l2 = [ + 0.001879021634926363, + 0.007032724521848316, + 0.0032793932234187325, + 0.009056594733320348, + 0.007514150120617965, + 0.007328739509868727, + 0.00309794018112387, + 0.009026356949274878, + 0.0035732583778049776, + ], + linf = [ + 0.013734346970999622, + 0.06173467158736011, + 0.02183946452704291, + 0.06258216169457917, + 0.03672304497348122, + 0.055120532123884625, + 0.018202716205672487, + 0.06133688282205586, + 0.019888161885935608, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl with Orszag-Tang setup + flux_hlle" begin - # OBS! This setup does not make much sense and is only used to exercise all components of the - # flux_hlle implementation - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[ - 0.004391143689111404, - 0.04144737547475548, - 0.041501307637678286, - 0.04150353006408862, - 0.03693135855995625, - 0.021125605214031118, - 0.03295607553556973, - 0.03296235755245784, - 7.16035229384135e-6, - ], - linf=[ - 0.017894703320895378, - 0.08486850681397005, - 0.0891044523165206, - 0.08492024792056754, - 0.10448301878352373, - 0.05381260695579509, - 0.0884774018719996, - 0.07784546966765199, - 7.71609149516089e-5, - ], - initial_condition=function initial_condition_orszag_tang(x, t, - equations::IdealGlmMhdEquations3D) - # The classical Orszag-Tang vortex test case adapted to 3D. Setup is taken from - # Table 4 of the paper - # - M. Bohm, A. R. Winters, G. J. Gassner, D. Derigs, F. Hindenlang, & J. Saur (2020) - # An entropy stable nodal discontinuous Galerkin method for the resistive MHD - # equations. Part I: Theory and numerical verification - # [doi: 10.1016/j.jcp.2018.06.027](https://doi.org/10.1016/j.jcp.2018.06.027) - # Domain must be [0, 1]^3 , γ = 5/3 - rho = 25.0 / (36.0 * pi) - v1 = -sin(2.0 * pi * x[3]) - v2 = sin(2.0 * pi * x[1]) - v3 = sin(2.0 * pi * x[2]) - p = 5.0 / (12.0 * pi) - B1 = -sin(2.0 * pi * x[3]) / (4.0 * pi) - B2 = sin(4.0 * pi * x[1]) / (4.0 * pi) - B3 = sin(4.0 * pi * x[2]) / (4.0 * pi) - psi = 0.0 - return prim2cons(SVector(rho, v1, v2, v3, p, B1, B2, B3, - psi), equations) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < - 1000 - end - end, - surface_flux=(flux_hlle, - flux_nonconservative_powell), - volume_flux=(flux_central, flux_nonconservative_powell), - coordinates_min=(0.0, 0.0, 0.0), - coordinates_max=(1.0, 1.0, 1.0), - initial_refinement_level=3, - cfl=1.1, - tspan=(0.0, 0.06)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl with Orszag-Tang setup + flux_hlle" begin + # OBS! This setup does not make much sense and is only used to exercise all components of the + # flux_hlle implementation + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 0.004391143689111404, + 0.04144737547475548, + 0.041501307637678286, + 0.04150353006408862, + 0.03693135855995625, + 0.021125605214031118, + 0.03295607553556973, + 0.03296235755245784, + 7.16035229384135e-6, + ], + linf = [ + 0.017894703320895378, + 0.08486850681397005, + 0.0891044523165206, + 0.08492024792056754, + 0.10448301878352373, + 0.05381260695579509, + 0.0884774018719996, + 0.07784546966765199, + 7.71609149516089e-5, + ], + initial_condition = function initial_condition_orszag_tang( + x, t, + equations::IdealGlmMhdEquations3D + ) + # The classical Orszag-Tang vortex test case adapted to 3D. Setup is taken from + # Table 4 of the paper + # - M. Bohm, A. R. Winters, G. J. Gassner, D. Derigs, F. Hindenlang, & J. Saur (2020) + # An entropy stable nodal discontinuous Galerkin method for the resistive MHD + # equations. Part I: Theory and numerical verification + # [doi: 10.1016/j.jcp.2018.06.027](https://doi.org/10.1016/j.jcp.2018.06.027) + # Domain must be [0, 1]^3 , γ = 5/3 + rho = 25.0 / (36.0 * pi) + v1 = -sin(2.0 * pi * x[3]) + v2 = sin(2.0 * pi * x[1]) + v3 = sin(2.0 * pi * x[2]) + p = 5.0 / (12.0 * pi) + B1 = -sin(2.0 * pi * x[3]) / (4.0 * pi) + B2 = sin(4.0 * pi * x[1]) / (4.0 * pi) + B3 = sin(4.0 * pi * x[2]) / (4.0 * pi) + psi = 0.0 + return prim2cons( + SVector( + rho, v1, v2, v3, p, B1, B2, B3, + psi + ), equations + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < + 1000 + end + end, + surface_flux = ( + flux_hlle, + flux_nonconservative_powell, + ), + volume_flux = (flux_central, flux_nonconservative_powell), + coordinates_min = (0.0, 0.0, 0.0), + coordinates_max = (1.0, 1.0, 1.0), + initial_refinement_level = 3, + cfl = 1.1, + tspan = (0.0, 0.06) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_ec_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec_shockcapturing.jl"), - l2=[ - 0.0186712969755079, - 0.01620736832264799, - 0.01620736832264803, - 0.016207474382769683, - 0.07306422729650594, - 0.007355137041002365, - 0.0073551370410023425, - 0.00735520932001833, - 0.000506140942330923, - ], - linf=[ - 0.28040713666979633, - 0.27212885844703694, - 0.2721288584470349, - 0.2837380205051839, - 0.7915852408267114, - 0.08770240288089526, - 0.08770240288089792, - 0.08773409387876674, - 0.050221095224119834, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ec_shockcapturing.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec_shockcapturing.jl"), + l2 = [ + 0.0186712969755079, + 0.01620736832264799, + 0.01620736832264803, + 0.016207474382769683, + 0.07306422729650594, + 0.007355137041002365, + 0.0073551370410023425, + 0.00735520932001833, + 0.000506140942330923, + ], + linf = [ + 0.28040713666979633, + 0.27212885844703694, + 0.2721288584470349, + 0.2837380205051839, + 0.7915852408267114, + 0.08770240288089526, + 0.08770240288089792, + 0.08773409387876674, + 0.050221095224119834, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end end # module diff --git a/test/test_tree_3d_part1.jl b/test/test_tree_3d_part1.jl index 3fdddc7723..8e0d138cef 100644 --- a/test/test_tree_3d_part1.jl +++ b/test/test_tree_3d_part1.jl @@ -10,16 +10,16 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "TreeMesh3D Part 1" begin -#! format: noindent + #! format: noindent -# Run basic tests -@testset "Examples 3D" begin - # Compressible Euler - include("test_tree_3d_euler.jl") -end + # Run basic tests + @testset "Examples 3D" begin + # Compressible Euler + include("test_tree_3d_euler.jl") + end -# Clean up afterwards: delete Trixi.jl output directory -@test_nowarn rm(outdir, recursive = true) + # Clean up afterwards: delete Trixi.jl output directory + @test_nowarn rm(outdir, recursive = true) end # TreeMesh3D Part 1 end #module diff --git a/test/test_tree_3d_part2.jl b/test/test_tree_3d_part2.jl index 4b9da039f9..30c70fda8e 100644 --- a/test/test_tree_3d_part2.jl +++ b/test/test_tree_3d_part2.jl @@ -10,113 +10,117 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "TreeMesh3D Part 2" begin -#! format: noindent + #! format: noindent -# Run basic tests -@testset "Examples 3D" begin - # Linear scalar advection - include("test_tree_3d_advection.jl") + # Run basic tests + @testset "Examples 3D" begin + # Linear scalar advection + include("test_tree_3d_advection.jl") - # Hyperbolic diffusion - include("test_tree_3d_hypdiff.jl") + # Hyperbolic diffusion + include("test_tree_3d_hypdiff.jl") - # Compressible Euler with self-gravity - include("test_tree_3d_eulergravity.jl") + # Compressible Euler with self-gravity + include("test_tree_3d_eulergravity.jl") - # Linearized Euler - include("test_tree_3d_linearizedeuler.jl") -end + # Linearized Euler + include("test_tree_3d_linearizedeuler.jl") + end + + @trixi_testset "Additional tests in 3D" begin + @trixi_testset "compressible Euler" begin + eqn = CompressibleEulerEquations3D(1.4) -@trixi_testset "Additional tests in 3D" begin - @trixi_testset "compressible Euler" begin - eqn = CompressibleEulerEquations3D(1.4) + @test isapprox(energy_total([1.0, 2.0, 3.0, 4.0, 20.0], eqn), 20.0) + @test isapprox(energy_kinetic([1.0, 2.0, 3.0, 4.0, 20], eqn), 14.5) + @test isapprox(energy_internal([1.0, 2.0, 3.0, 4.0, 20], eqn), 5.5) + end - @test isapprox(energy_total([1.0, 2.0, 3.0, 4.0, 20.0], eqn), 20.0) - @test isapprox(energy_kinetic([1.0, 2.0, 3.0, 4.0, 20], eqn), 14.5) - @test isapprox(energy_internal([1.0, 2.0, 3.0, 4.0, 20], eqn), 5.5) + @trixi_testset "hyperbolic diffusion" begin + @test_nowarn HyperbolicDiffusionEquations3D(nu = 1.0) + eqn = HyperbolicDiffusionEquations3D(nu = 1.0) + end end - @trixi_testset "hyperbolic diffusion" begin - @test_nowarn HyperbolicDiffusionEquations3D(nu = 1.0) - eqn = HyperbolicDiffusionEquations3D(nu = 1.0) + @trixi_testset "Displaying components 3D" begin + @test_nowarn include( + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_advection_amr.jl" + ) + ) + + # test both short and long printing formats + @test_nowarn show(mesh) + println() + @test_nowarn println(mesh) + @test_nowarn display(mesh) + + @test_nowarn show(equations) + println() + @test_nowarn println(equations) + @test_nowarn display(equations) + + @test_nowarn show(solver) + println() + @test_nowarn println(solver) + @test_nowarn display(solver) + + @test_nowarn show(solver.basis) + println() + @test_nowarn println(solver.basis) + @test_nowarn display(solver.basis) + + @test_nowarn show(solver.mortar) + println() + @test_nowarn println(solver.mortar) + @test_nowarn display(solver.mortar) + + @test_nowarn show(semi) + println() + @test_nowarn println(semi) + @test_nowarn display(semi) + + @test_nowarn show(summary_callback) + println() + @test_nowarn println(summary_callback) + @test_nowarn display(summary_callback) + + @test_nowarn show(amr_controller) + println() + @test_nowarn println(amr_controller) + @test_nowarn display(amr_controller) + + @test_nowarn show(amr_callback) + println() + @test_nowarn println(amr_callback) + @test_nowarn display(amr_callback) + + @test_nowarn show(stepsize_callback) + println() + @test_nowarn println(stepsize_callback) + @test_nowarn display(stepsize_callback) + + @test_nowarn show(save_solution) + println() + @test_nowarn println(save_solution) + @test_nowarn display(save_solution) + + @test_nowarn show(analysis_callback) + println() + @test_nowarn println(analysis_callback) + @test_nowarn display(analysis_callback) + + @test_nowarn show(alive_callback) + println() + @test_nowarn println(alive_callback) + @test_nowarn display(alive_callback) + + @test_nowarn println(callbacks) end -end - -@trixi_testset "Displaying components 3D" begin - @test_nowarn include(joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_advection_amr.jl")) - - # test both short and long printing formats - @test_nowarn show(mesh) - println() - @test_nowarn println(mesh) - @test_nowarn display(mesh) - - @test_nowarn show(equations) - println() - @test_nowarn println(equations) - @test_nowarn display(equations) - - @test_nowarn show(solver) - println() - @test_nowarn println(solver) - @test_nowarn display(solver) - - @test_nowarn show(solver.basis) - println() - @test_nowarn println(solver.basis) - @test_nowarn display(solver.basis) - - @test_nowarn show(solver.mortar) - println() - @test_nowarn println(solver.mortar) - @test_nowarn display(solver.mortar) - - @test_nowarn show(semi) - println() - @test_nowarn println(semi) - @test_nowarn display(semi) - - @test_nowarn show(summary_callback) - println() - @test_nowarn println(summary_callback) - @test_nowarn display(summary_callback) - - @test_nowarn show(amr_controller) - println() - @test_nowarn println(amr_controller) - @test_nowarn display(amr_controller) - - @test_nowarn show(amr_callback) - println() - @test_nowarn println(amr_callback) - @test_nowarn display(amr_callback) - - @test_nowarn show(stepsize_callback) - println() - @test_nowarn println(stepsize_callback) - @test_nowarn display(stepsize_callback) - - @test_nowarn show(save_solution) - println() - @test_nowarn println(save_solution) - @test_nowarn display(save_solution) - - @test_nowarn show(analysis_callback) - println() - @test_nowarn println(analysis_callback) - @test_nowarn display(analysis_callback) - - @test_nowarn show(alive_callback) - println() - @test_nowarn println(alive_callback) - @test_nowarn display(alive_callback) - - @test_nowarn println(callbacks) -end - -# Clean up afterwards: delete Trixi.jl output directory -@test_nowarn rm(outdir, recursive = true) + + # Clean up afterwards: delete Trixi.jl output directory + @test_nowarn rm(outdir, recursive = true) end # TreeMesh3D Part 2 end #module diff --git a/test/test_tree_3d_part3.jl b/test/test_tree_3d_part3.jl index 9e1e8bc4dc..467899a5fa 100644 --- a/test/test_tree_3d_part3.jl +++ b/test/test_tree_3d_part3.jl @@ -10,44 +10,44 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "TreeMesh3D Part 2" begin -#! format: noindent + #! format: noindent -# Run basic tests -@testset "Examples 3D" begin - # MHD - include("test_tree_3d_mhd.jl") + # Run basic tests + @testset "Examples 3D" begin + # MHD + include("test_tree_3d_mhd.jl") - # Lattice-Boltzmann - include("test_tree_3d_lbm.jl") + # Lattice-Boltzmann + include("test_tree_3d_lbm.jl") - # FDSBP methods on the TreeMesh - include("test_tree_3d_fdsbp.jl") -end + # FDSBP methods on the TreeMesh + include("test_tree_3d_fdsbp.jl") + end -@trixi_testset "Additional tests in 3D" begin - @testset "ideal GLM MHD" begin - eqn = IdealGlmMhdEquations3D(1.4) - u = [1.0, 2.0, 3.0, 4.0, 20.0, 0.1, 0.2, 0.3, 1.5] + @trixi_testset "Additional tests in 3D" begin + @testset "ideal GLM MHD" begin + eqn = IdealGlmMhdEquations3D(1.4) + u = [1.0, 2.0, 3.0, 4.0, 20.0, 0.1, 0.2, 0.3, 1.5] - @test isapprox(density(u, eqn), 1.0) - @test isapprox(pressure(u, eqn), 1.7219999999999995) - @test isapprox(density_pressure(u, eqn), 1.7219999999999995) + @test isapprox(density(u, eqn), 1.0) + @test isapprox(pressure(u, eqn), 1.7219999999999995) + @test isapprox(density_pressure(u, eqn), 1.7219999999999995) - @test isapprox(Trixi.entropy_thermodynamic(u, eqn), 0.5434864060055388) - @test isapprox(Trixi.entropy_math(u, eqn), -1.3587160150138473) - @test isapprox(Trixi.entropy(u, eqn), -1.3587160150138473) + @test isapprox(Trixi.entropy_thermodynamic(u, eqn), 0.5434864060055388) + @test isapprox(Trixi.entropy_math(u, eqn), -1.3587160150138473) + @test isapprox(Trixi.entropy(u, eqn), -1.3587160150138473) - @test isapprox(energy_total(u, eqn), 20.0) - @test isapprox(energy_kinetic(u, eqn), 14.5) - @test isapprox(energy_magnetic(u, eqn), 0.07) - @test isapprox(energy_internal(u, eqn), 4.305) + @test isapprox(energy_total(u, eqn), 20.0) + @test isapprox(energy_kinetic(u, eqn), 14.5) + @test isapprox(energy_magnetic(u, eqn), 0.07) + @test isapprox(energy_internal(u, eqn), 4.305) - @test isapprox(cross_helicity(u, eqn), 2.0) + @test isapprox(cross_helicity(u, eqn), 2.0) + end end -end -# Clean up afterwards: delete Trixi.jl output directory -@test_nowarn rm(outdir, recursive = true) + # Clean up afterwards: delete Trixi.jl output directory + @test_nowarn rm(outdir, recursive = true) end # TreeMesh3D Part 2 end #module diff --git a/test/test_trixi.jl b/test/test_trixi.jl index be3521ed16..4d2bcaccb1 100644 --- a/test/test_trixi.jl +++ b/test/test_trixi.jl @@ -36,14 +36,20 @@ macro test_trixi_include(elixir, args...) local cmd = string(Base.julia_cmd()) local coverage = occursin("--code-coverage", cmd) && - !occursin("--code-coverage=none", cmd) + !occursin("--code-coverage=none", cmd) local kwargs = Pair{Symbol, Any}[] for arg in args - if (arg.head == :(=) && - !(arg.args[1] in (:l2, :linf, :RealT, :atol, :rtol, :coverage_override, - :skip_coverage)) - && !(coverage && arg.args[1] in keys(coverage_override))) + if ( + arg.head == :(=) && + !( + arg.args[1] in ( + :l2, :linf, :RealT, :atol, :rtol, :coverage_override, + :skip_coverage, + ) + ) + && !(coverage && arg.args[1] in keys(coverage_override)) + ) push!(kwargs, Pair(arg.args...)) end end @@ -74,7 +80,8 @@ macro test_trixi_include(elixir, args...) if any(==(:maxiters) ∘ first, $kwargs) additional_ignore_content = [ r"┌ Warning: Interrupted\. Larger maxiters is needed\..*\n└ @ SciMLBase .+\n", - r"┌ Warning: Interrupted\. Larger maxiters is needed\..*\n└ @ Trixi .+\n"] + r"┌ Warning: Interrupted\. Larger maxiters is needed\..*\n└ @ Trixi .+\n", + ] else additional_ignore_content = [] end @@ -151,23 +158,24 @@ macro test_nowarn_mod(expr, additional_ignore_content = String[]) # passed as arguments can also be regular expressions, so we just use the # type `Any` for `ignore_content`. ignore_content = Any[ - # We need to ignore steady state information reported by our callbacks - r"┌ Info: Steady state tolerance reached\n│ steady_state_callback .+\n└ t = .+\n", - # We also ignore our own compilation messages - "[ Info: You just called `trixi_include`. Julia may now compile the code, please be patient.\n", - # TODO: Upstream (PlotUtils). This should be removed again once the - # deprecated stuff is fixed upstream. - "WARNING: importing deprecated binding Colors.RGB1 into Plots.\n", - "WARNING: importing deprecated binding Colors.RGB4 into Plots.\n", - r"┌ Warning: Keyword argument letter not supported with Plots.+\n└ @ Plots.+\n", - r"┌ Warning: `parse\(::Type, ::Coloarant\)` is deprecated.+\n│.+\n│.+\n└ @ Plots.+\n", - # TODO: Silence warning introduced by Flux v0.13.13. Should be properly fixed. - r"┌ Warning: Layer with Float32 parameters got Float64 input.+\n│.+\n│.+\n│.+\n└ @ Flux.+\n", - # NOTE: These warnings arose from Julia 1.10 onwards - r"WARNING: Method definition .* in module .* at .* overwritten .*.\n", - # Warnings from third party packages - r"┌ Warning: Problem status ALMOST_INFEASIBLE; solution may be inaccurate.\n└ @ Convex ~/.julia/packages/Convex/.*\n", - r"┌ Warning: Problem status ALMOST_OPTIMAL; solution may be inaccurate.\n└ @ Convex ~/.julia/packages/Convex/.*\n"] + # We need to ignore steady state information reported by our callbacks + r"┌ Info: Steady state tolerance reached\n│ steady_state_callback .+\n└ t = .+\n", + # We also ignore our own compilation messages + "[ Info: You just called `trixi_include`. Julia may now compile the code, please be patient.\n", + # TODO: Upstream (PlotUtils). This should be removed again once the + # deprecated stuff is fixed upstream. + "WARNING: importing deprecated binding Colors.RGB1 into Plots.\n", + "WARNING: importing deprecated binding Colors.RGB4 into Plots.\n", + r"┌ Warning: Keyword argument letter not supported with Plots.+\n└ @ Plots.+\n", + r"┌ Warning: `parse\(::Type, ::Coloarant\)` is deprecated.+\n│.+\n│.+\n└ @ Plots.+\n", + # TODO: Silence warning introduced by Flux v0.13.13. Should be properly fixed. + r"┌ Warning: Layer with Float32 parameters got Float64 input.+\n│.+\n│.+\n│.+\n└ @ Flux.+\n", + # NOTE: These warnings arose from Julia 1.10 onwards + r"WARNING: Method definition .* in module .* at .* overwritten .*.\n", + # Warnings from third party packages + r"┌ Warning: Problem status ALMOST_INFEASIBLE; solution may be inaccurate.\n└ @ Convex ~/.julia/packages/Convex/.*\n", + r"┌ Warning: Problem status ALMOST_OPTIMAL; solution may be inaccurate.\n└ @ Convex ~/.julia/packages/Convex/.*\n", + ] append!(ignore_content, $additional_ignore_content) for pattern in ignore_content stderr_content = replace(stderr_content, pattern => "") @@ -199,8 +207,10 @@ macro timed_testset(name, expr) local time_stop = time_ns() if Trixi.mpi_isroot() flush(stdout) - @info("Testset "*$name*" finished in " - *string(1.0e-9 * (time_stop - time_start))*" seconds.\n") + @info( + "Testset " * $name * " finished in " + * string(1.0e-9 * (time_stop - time_start)) * " seconds.\n" + ) flush(stdout) end end @@ -244,8 +254,10 @@ macro trixi_testset(name, expr) local time_stop = time_ns() if Trixi.mpi_isroot() flush(stdout) - @info("Testset "*$name*" finished in " - *string(1.0e-9 * (time_stop - time_start))*" seconds.\n") + @info( + "Testset " * $name * " finished in " + * string(1.0e-9 * (time_stop - time_start)) * " seconds.\n" + ) end nothing end diff --git a/test/test_type.jl b/test/test_type.jl index d507cded95..f0a002cdbe 100644 --- a/test/test_type.jl +++ b/test/test_type.jl @@ -18,9 +18,13 @@ isdir(outdir) && rm(outdir, recursive = true) @test typeof(@inferred Trixi.inv_ln_mean(RealT1(1), RealT2(2))) == RealT for RealT3 in (Float32, Float64) RealT = promote_type(RealT1, RealT2, RealT3) - @test typeof(@inferred Trixi.stolarsky_mean(RealT1(1), RealT2(2), - RealT3(3))) == - RealT + @test typeof( + @inferred Trixi.stolarsky_mean( + RealT1(1), RealT2(2), + RealT3(3) + ) + ) == + RealT end end end @@ -30,16 +34,20 @@ isdir(outdir) && rm(outdir, recursive = true) v_mean_global = (zero(RealT), zero(RealT)) c_mean_global = one(RealT) rho_mean_global = one(RealT) - equations = @inferred AcousticPerturbationEquations2D(v_mean_global, - c_mean_global, - rho_mean_global) + equations = @inferred AcousticPerturbationEquations2D( + v_mean_global, + c_mean_global, + rho_mean_global + ) x = SVector(zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = u_inner = SVector(one(RealT), one(RealT), one(RealT), - one(RealT), - one(RealT), - one(RealT), one(RealT)) + u = u_ll = u_rr = u_inner = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT), + one(RealT), + one(RealT), one(RealT) + ) orientations = [1, 2] directions = [1, 2, 3, 4] normal_direction = SVector(one(RealT), zero(RealT)) @@ -49,42 +57,58 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_gauss(x, t, equations)) == RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT for orientation in orientations for direction in directions - @test eltype(@inferred boundary_condition_wall(u_inner, orientation, - direction, x, t, - surface_flux_function, - equations)) == RealT + @test eltype( + @inferred boundary_condition_wall( + u_inner, orientation, + direction, x, t, + surface_flux_function, + equations + ) + ) == RealT end end - @test eltype(@inferred boundary_condition_slip_wall(u_inner, - normal_direction, x, t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred boundary_condition_slip_wall( + u_inner, + normal_direction, x, t, + surface_flux_function, + equations + ) + ) == + RealT @test eltype(@inferred flux(u, normal_direction, equations)) == RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == - RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT @test eltype(@inferred dissipation(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT @test eltype(@inferred dissipation(u_ll, u_rr, orientation, equations)) == - RealT + RealT end @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @@ -109,57 +133,91 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_density_wave(x, t, equations)) == RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT - @test eltype(@inferred initial_condition_eoc_test_coupled_euler_gravity(x, t, - equations)) == - RealT + RealT + @test eltype( + @inferred initial_condition_eoc_test_coupled_euler_gravity( + x, t, + equations + ) + ) == + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT for direction in directions - @test eltype(@inferred boundary_condition_slip_wall(u_inner, orientation, - direction, - x, t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred boundary_condition_slip_wall( + u_inner, orientation, + direction, + x, t, + surface_flux_function, + equations + ) + ) == + RealT end @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_shima_etal(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred flux_kennedy_gruber(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred flux_hllc(u_ll, u_rr, orientation, equations)) == RealT - @test eltype(@inferred flux_chandrashekar(u_ll, u_rr, orientation, - equations)) == - RealT + @test eltype( + @inferred flux_chandrashekar( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT @test eltype(@inferred flux_ranocha(u_ll, u_rr, orientation, equations)) == - RealT - - @test eltype(eltype(@inferred splitting_steger_warming(u, orientation, - equations))) == - RealT - @test eltype(eltype(@inferred splitting_vanleer_haenel(u, orientation, - equations))) == - RealT - @test eltype(eltype(@inferred splitting_coirier_vanleer(u, orientation, - equations))) == - RealT + RealT + + @test eltype( + eltype( + @inferred splitting_steger_warming( + u, orientation, + equations + ) + ) + ) == + RealT + @test eltype( + eltype( + @inferred splitting_vanleer_haenel( + u, orientation, + equations + ) + ) + ) == + RealT + @test eltype( + eltype( + @inferred splitting_coirier_vanleer( + u, orientation, + equations + ) + ) + ) == + RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, orientation, - equations)) == - RealT + RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @@ -181,8 +239,10 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = u_inner = cons = SVector(one(RealT), one(RealT), one(RealT), - one(RealT)) + u = u_ll = u_rr = u_inner = cons = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT) + ) orientations = [1, 2] directions = [1, 2, 3, 4] normal_direction = SVector(one(RealT), zero(RealT)) @@ -192,112 +252,218 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_density_wave(x, t, equations)) == RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT - @test eltype(@inferred initial_condition_eoc_test_coupled_euler_gravity(x, t, - equations)) == - RealT + RealT + @test eltype( + @inferred initial_condition_eoc_test_coupled_euler_gravity( + x, t, + equations + ) + ) == + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT - @test eltype(@inferred source_terms_eoc_test_coupled_euler_gravity(u, x, t, - equations)) == - RealT + RealT + @test eltype( + @inferred source_terms_eoc_test_coupled_euler_gravity( + u, x, t, + equations + ) + ) == + RealT @test eltype(@inferred source_terms_eoc_test_euler(u, x, t, equations)) == - RealT + RealT for orientation in orientations for direction in directions - @test eltype(@inferred boundary_condition_slip_wall(u_inner, - orientation, - direction, x, t, - surface_flux_function, - equations)) == RealT + @test eltype( + @inferred boundary_condition_slip_wall( + u_inner, + orientation, + direction, x, t, + surface_flux_function, + equations + ) + ) == RealT end end @test eltype(@inferred flux(u, normal_direction, equations)) == RealT @test eltype(@inferred flux_shima_etal(u_ll, u_rr, normal_direction, equations)) == - RealT - @test eltype(@inferred flux_kennedy_gruber(u_ll, u_rr, normal_direction, - equations)) == - RealT + RealT + @test eltype( + @inferred flux_kennedy_gruber( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT @test eltype(@inferred flux_lmars(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT @test eltype(@inferred flux_hllc(u_ll, u_rr, normal_direction, equations)) == - RealT - @test eltype(@inferred flux_chandrashekar(u_ll, u_rr, normal_direction, - equations)) == - RealT - @test eltype(@inferred flux_ranocha(u_ll, u_rr, normal_direction, - equations)) == RealT - - @test eltype(eltype(@inferred splitting_drikakis_tsangaris(u, normal_direction, - equations))) == RealT - @test eltype(eltype(@inferred splitting_vanleer_haenel(u, normal_direction, - equations))) == - RealT - @test eltype(eltype(@inferred splitting_lax_friedrichs(u, normal_direction, - equations))) == - RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == - RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, normal_direction, - equations)) == - RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, normal_direction, - equations)) == - RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, normal_direction, - equations)) == - RealT + RealT + @test eltype( + @inferred flux_chandrashekar( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_ranocha( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + + @test eltype( + eltype( + @inferred splitting_drikakis_tsangaris( + u, normal_direction, + equations + ) + ) + ) == RealT + @test eltype( + eltype( + @inferred splitting_vanleer_haenel( + u, normal_direction, + equations + ) + ) + ) == + RealT + @test eltype( + eltype( + @inferred splitting_lax_friedrichs( + u, normal_direction, + equations + ) + ) + ) == + RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_shima_etal(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred flux_kennedy_gruber(u_ll, u_rr, orientation, - equations)) == - RealT + RealT + @test eltype( + @inferred flux_kennedy_gruber( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT @test eltype(@inferred flux_lmars(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred flux_hllc(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred flux_chandrashekar(u_ll, u_rr, orientation, - equations)) == - RealT + RealT + @test eltype( + @inferred flux_chandrashekar( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT @test eltype(@inferred flux_ranocha(u_ll, u_rr, orientation, equations)) == - RealT - - @test eltype(eltype(@inferred splitting_steger_warming(u, orientation, - equations))) == - RealT - @test eltype(eltype(@inferred splitting_drikakis_tsangaris(u, orientation, - equations))) == - RealT - @test eltype(eltype(@inferred splitting_vanleer_haenel(u, orientation, - equations))) == - RealT - @test eltype(eltype(@inferred splitting_lax_friedrichs(u, orientation, - equations))) == - RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, - equations)) == - RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, orientation, - equations)) == - RealT + RealT + + @test eltype( + eltype( + @inferred splitting_steger_warming( + u, orientation, + equations + ) + ) + ) == + RealT + @test eltype( + eltype( + @inferred splitting_drikakis_tsangaris( + u, orientation, + equations + ) + ) + ) == + RealT + @test eltype( + eltype( + @inferred splitting_vanleer_haenel( + u, orientation, + equations + ) + ) + ) == + RealT + @test eltype( + eltype( + @inferred splitting_lax_friedrichs( + u, orientation, + equations + ) + ) + ) == + RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT end @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @@ -316,12 +482,20 @@ isdir(outdir) && rm(outdir, recursive = true) @test typeof(@inferred energy_internal(cons, equations)) == RealT @test eltype(@inferred Trixi.gradient_conservative(pressure, u, equations)) == - RealT - @test eltype(@inferred Trixi.gradient_conservative(Trixi.entropy_math, u, - equations)) == RealT - @test eltype(@inferred Trixi.gradient_conservative(Trixi.entropy_guermond_etal, - u, - equations)) == RealT + RealT + @test eltype( + @inferred Trixi.gradient_conservative( + Trixi.entropy_math, u, + equations + ) + ) == RealT + @test eltype( + @inferred Trixi.gradient_conservative( + Trixi.entropy_guermond_etal, + u, + equations + ) + ) == RealT end end @@ -332,8 +506,10 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT), zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = u_inner = cons = SVector(one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT)) + u = u_ll = u_rr = u_inner = cons = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT) + ) orientations = [1, 2, 3] directions = [1, 2, 3, 4, 5, 6] normal_direction = SVector(one(RealT), zero(RealT), zero(RealT)) @@ -343,82 +519,152 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT - @test eltype(@inferred initial_condition_eoc_test_coupled_euler_gravity(x, t, - equations)) == - RealT + RealT + @test eltype( + @inferred initial_condition_eoc_test_coupled_euler_gravity( + x, t, + equations + ) + ) == + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT - @test eltype(@inferred source_terms_eoc_test_coupled_euler_gravity(u, x, t, - equations)) == - RealT + RealT + @test eltype( + @inferred source_terms_eoc_test_coupled_euler_gravity( + u, x, t, + equations + ) + ) == + RealT @test eltype(@inferred source_terms_eoc_test_euler(u, x, t, equations)) == RealT for orientation in orientations for direction in directions - @test eltype(@inferred boundary_condition_slip_wall(u_inner, - orientation, - direction, x, t, - surface_flux_function, - equations)) == RealT + @test eltype( + @inferred boundary_condition_slip_wall( + u_inner, + orientation, + direction, x, t, + surface_flux_function, + equations + ) + ) == RealT end end @test eltype(@inferred flux(u, normal_direction, equations)) == RealT @test eltype(@inferred flux_shima_etal(u_ll, u_rr, normal_direction, equations)) == - RealT - @test eltype(@inferred flux_kennedy_gruber(u_ll, u_rr, normal_direction, - equations)) == RealT + RealT + @test eltype( + @inferred flux_kennedy_gruber( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT @test eltype(@inferred flux_lmars(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT @test eltype(@inferred flux_hllc(u_ll, u_rr, normal_direction, equations)) == - RealT - @test eltype(@inferred flux_chandrashekar(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred flux_ranocha(u_ll, u_rr, normal_direction, - equations)) == RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == - RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, normal_direction, - equations)) == RealT + RealT + @test eltype( + @inferred flux_chandrashekar( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred flux_ranocha( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_shima_etal(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred flux_kennedy_gruber(u_ll, u_rr, orientation, - equations)) == - RealT + RealT + @test eltype( + @inferred flux_kennedy_gruber( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT @test eltype(@inferred flux_lmars(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred flux_hllc(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred flux_chandrashekar(u_ll, u_rr, orientation, - equations)) == RealT + RealT + @test eltype( + @inferred flux_chandrashekar( + u_ll, u_rr, orientation, + equations + ) + ) == RealT @test eltype(@inferred flux_ranocha(u_ll, u_rr, orientation, equations)) == - RealT - - @test eltype(eltype(@inferred splitting_steger_warming(u, orientation, - equations))) == - RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, orientation, - equations)) == RealT + RealT + + @test eltype( + eltype( + @inferred splitting_steger_warming( + u, orientation, + equations + ) + ) + ) == + RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, orientation, + equations + ) + ) == RealT end @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @@ -440,8 +686,10 @@ isdir(outdir) && rm(outdir, recursive = true) for RealT in (Float32, Float64) gammas = (RealT(1.4), RealT(1.4)) gas_constants = (RealT(0.4), RealT(0.4)) - equations = @inferred CompressibleEulerMulticomponentEquations1D(gammas = gammas, - gas_constants = gas_constants) + equations = @inferred CompressibleEulerMulticomponentEquations1D( + gammas = gammas, + gas_constants = gas_constants + ) x = SVector(zero(RealT)) t = zero(RealT) @@ -449,21 +697,25 @@ isdir(outdir) && rm(outdir, recursive = true) orientation = 1 @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_chandrashekar(u_ll, u_rr, orientation, - equations)) == RealT + @test eltype( + @inferred flux_chandrashekar( + u_ll, u_rr, orientation, + equations + ) + ) == RealT @test eltype(@inferred flux_ranocha(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred prim2cons(u, equations)) == RealT @@ -481,38 +733,54 @@ isdir(outdir) && rm(outdir, recursive = true) for RealT in (Float32, Float64) gammas = (RealT(1.4), RealT(1.4)) gas_constants = (RealT(0.4), RealT(0.4)) - equations = @inferred CompressibleEulerMulticomponentEquations2D(gammas = gammas, - gas_constants = gas_constants) + equations = @inferred CompressibleEulerMulticomponentEquations2D( + gammas = gammas, + gas_constants = gas_constants + ) x = SVector(zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = SVector(one(RealT), one(RealT), one(RealT), one(RealT), - one(RealT)) + u = u_ll = u_rr = SVector( + one(RealT), one(RealT), one(RealT), one(RealT), + one(RealT) + ) orientations = [1, 2] normal_direction = SVector(one(RealT), zero(RealT)) @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, normal_direction, equations)) == RealT - @test eltype(@inferred flux_ranocha(u_ll, u_rr, normal_direction, - equations)) == RealT + @test eltype( + @inferred flux_ranocha( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_chandrashekar(u_ll, u_rr, orientation, - equations)) == RealT + @test eltype( + @inferred flux_chandrashekar( + u_ll, u_rr, orientation, + equations + ) + ) == RealT @test eltype(@inferred flux_ranocha(u_ll, u_rr, orientation, equations)) == - RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT + RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT end @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @@ -539,25 +807,37 @@ isdir(outdir) && rm(outdir, recursive = true) normal_direction = normal_ll = normal_rr = SVector(one(RealT)) @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_nonconservative_chan_etal(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred flux_nonconservative_chan_etal(u_ll, u_rr, - normal_direction, - equations)) == - RealT - @test eltype(@inferred flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, - normal_rr, equations)) == - RealT + @test eltype( + @inferred flux_nonconservative_chan_etal( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_nonconservative_chan_etal( + u_ll, u_rr, + normal_direction, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_nonconservative_chan_etal( + u_ll, u_rr, normal_ll, + normal_rr, equations + ) + ) == + RealT @test eltype(@inferred flux_chan_etal(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred prim2cons(u, equations)) == RealT @@ -574,19 +854,25 @@ isdir(outdir) && rm(outdir, recursive = true) equations = @inferred CompressibleEulerEquations1D(RealT(1.4)) prandtl_number = RealT(0.72) mu = RealT(0.01) - equations_parabolic_primitive = @inferred CompressibleNavierStokesDiffusion1D(equations, - mu = mu, - Prandtl = prandtl_number, - gradient_variables = GradientVariablesPrimitive()) - equations_parabolic_entropy = @inferred CompressibleNavierStokesDiffusion1D(equations, - mu = mu, - Prandtl = prandtl_number, - gradient_variables = GradientVariablesEntropy()) + equations_parabolic_primitive = @inferred CompressibleNavierStokesDiffusion1D( + equations, + mu = mu, + Prandtl = prandtl_number, + gradient_variables = GradientVariablesPrimitive() + ) + equations_parabolic_entropy = @inferred CompressibleNavierStokesDiffusion1D( + equations, + mu = mu, + Prandtl = prandtl_number, + gradient_variables = GradientVariablesEntropy() + ) x = SVector(zero(RealT)) t = zero(RealT) - u = u_inner = u_transformed = flux_inner = SVector(one(RealT), zero(RealT), - zero(RealT)) + u = u_inner = u_transformed = flux_inner = SVector( + one(RealT), zero(RealT), + zero(RealT) + ) orientation = 1 directions = [1, 2] gradients = SVector(RealT(0.1), RealT(0.1), RealT(0.1)) @@ -610,10 +896,12 @@ isdir(outdir) && rm(outdir, recursive = true) return prim2cons(SVector(rho, v1, p), equations) end - for equations_parabolic in (equations_parabolic_primitive, - equations_parabolic_entropy) + for equations_parabolic in ( + equations_parabolic_primitive, + equations_parabolic_entropy, + ) @test eltype(@inferred flux(u, gradients, orientation, equations_parabolic)) == - RealT + RealT @test eltype(@inferred cons2prim(u, equations_parabolic)) == RealT @test eltype(@inferred prim2cons(u, equations_parabolic)) == RealT @@ -621,53 +909,91 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred entropy2cons(u, equations_parabolic)) == RealT @test typeof(@inferred Trixi.temperature(u, equations_parabolic)) == RealT - @test eltype(@inferred Trixi.convert_transformed_to_primitive(u_transformed, - equations_parabolic)) == - RealT - @test eltype(@inferred Trixi.convert_derivative_to_primitive(u, gradients, - equations_parabolic)) == - RealT + @test eltype( + @inferred Trixi.convert_transformed_to_primitive( + u_transformed, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred Trixi.convert_derivative_to_primitive( + u, gradients, + equations_parabolic + ) + ) == + RealT # For BC tests - velocity_bc_left_right = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2]) - heat_bc_left = Isothermal((x, t, equations) -> Trixi.temperature(initial_condition_navier_stokes_convergence_test(x, - t, - equations), - equations_parabolic)) + velocity_bc_left_right = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2] + ) + heat_bc_left = Isothermal( + (x, t, equations) -> Trixi.temperature( + initial_condition_navier_stokes_convergence_test( + x, + t, + equations + ), + equations_parabolic + ) + ) heat_bc_right = Adiabatic((x, t, equations) -> oftype(t, 0)) - boundary_condition_left = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_left) - boundary_condition_right = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_right) + boundary_condition_left = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_left + ) + boundary_condition_right = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_right + ) # BC tests for direction in directions - @test eltype(@inferred boundary_condition_right(flux_inner, u_inner, - orientation, direction, - x, - t, operator_gradient, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_right(flux_inner, u_inner, - orientation, direction, - x, - t, operator_divergence, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_left(flux_inner, u_inner, - orientation, direction, - x, - t, operator_gradient, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_left(flux_inner, u_inner, - orientation, direction, - x, - t, operator_divergence, - equations_parabolic)) == - RealT + @test eltype( + @inferred boundary_condition_right( + flux_inner, u_inner, + orientation, direction, + x, + t, operator_gradient, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_right( + flux_inner, u_inner, + orientation, direction, + x, + t, operator_divergence, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_left( + flux_inner, u_inner, + orientation, direction, + x, + t, operator_gradient, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_left( + flux_inner, u_inner, + orientation, direction, + x, + t, operator_divergence, + equations_parabolic + ) + ) == + RealT end end end @@ -678,21 +1004,27 @@ isdir(outdir) && rm(outdir, recursive = true) equations = @inferred CompressibleEulerEquations2D(RealT(1.4)) prandtl_number = RealT(0.72) mu = RealT(0.01) - equations_parabolic_primitive = @inferred CompressibleNavierStokesDiffusion2D(equations, - mu = mu, - Prandtl = prandtl_number, - gradient_variables = GradientVariablesPrimitive()) - equations_parabolic_entropy = @inferred CompressibleNavierStokesDiffusion2D(equations, - mu = mu, - Prandtl = prandtl_number, - gradient_variables = GradientVariablesEntropy()) + equations_parabolic_primitive = @inferred CompressibleNavierStokesDiffusion2D( + equations, + mu = mu, + Prandtl = prandtl_number, + gradient_variables = GradientVariablesPrimitive() + ) + equations_parabolic_entropy = @inferred CompressibleNavierStokesDiffusion2D( + equations, + mu = mu, + Prandtl = prandtl_number, + gradient_variables = GradientVariablesEntropy() + ) x = SVector(zero(RealT), zero(RealT)) t = zero(RealT) - u = w_inner = u_transformed = flux_inner = normal = SVector(one(RealT), - zero(RealT), - zero(RealT), - zero(RealT)) + u = w_inner = u_transformed = flux_inner = normal = SVector( + one(RealT), + zero(RealT), + zero(RealT), + zero(RealT) + ) orientations = [1, 2] gradient = SVector(RealT(0.1), RealT(0.1), RealT(0.1), RealT(0.1)) gradients = SVector(gradient, gradient) @@ -718,11 +1050,17 @@ isdir(outdir) && rm(outdir, recursive = true) return prim2cons(SVector(rho, v1, v2, p), equations) end - for equations_parabolic in (equations_parabolic_primitive, - equations_parabolic_entropy) + for equations_parabolic in ( + equations_parabolic_primitive, + equations_parabolic_entropy, + ) for orientation in orientations - @test eltype(@inferred flux(u, gradients, orientation, - equations_parabolic)) == RealT + @test eltype( + @inferred flux( + u, gradients, orientation, + equations_parabolic + ) + ) == RealT end @test eltype(@inferred cons2prim(u, equations_parabolic)) == RealT @@ -731,59 +1069,97 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred entropy2cons(u, equations_parabolic)) == RealT @test typeof(@inferred Trixi.temperature(u, equations_parabolic)) == RealT @test typeof(@inferred Trixi.enstrophy(u, gradients, equations_parabolic)) == - RealT + RealT @test typeof(@inferred Trixi.vorticity(u, gradients, equations_parabolic)) == - RealT - - @test eltype(@inferred Trixi.convert_transformed_to_primitive(u_transformed, - equations_parabolic)) == - RealT - @test eltype(@inferred Trixi.convert_derivative_to_primitive(u, gradient, - equations_parabolic)) == - RealT + RealT + + @test eltype( + @inferred Trixi.convert_transformed_to_primitive( + u_transformed, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred Trixi.convert_derivative_to_primitive( + u, gradient, + equations_parabolic + ) + ) == + RealT # For BC tests - velocity_bc_left_right = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2:3]) - heat_bc_left = Isothermal((x, t, equations) -> Trixi.temperature(initial_condition_navier_stokes_convergence_test(x, - t, - equations), - equations_parabolic)) + velocity_bc_left_right = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2:3] + ) + heat_bc_left = Isothermal( + (x, t, equations) -> Trixi.temperature( + initial_condition_navier_stokes_convergence_test( + x, + t, + equations + ), + equations_parabolic + ) + ) heat_bc_right = Adiabatic((x, t, equations) -> oftype(t, 0)) - boundary_condition_left = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_left) - boundary_condition_right = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_right) + boundary_condition_left = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_left + ) + boundary_condition_right = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_right + ) # BC tests - @test eltype(@inferred boundary_condition_right(flux_inner, w_inner, - normal, - x, - t, - operator_gradient, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_right(flux_inner, w_inner, - normal, - x, - t, - operator_divergence, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_left(flux_inner, w_inner, - normal, - x, - t, operator_gradient, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_left(flux_inner, w_inner, - normal, - x, - t, operator_divergence, - equations_parabolic)) == - RealT + @test eltype( + @inferred boundary_condition_right( + flux_inner, w_inner, + normal, + x, + t, + operator_gradient, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_right( + flux_inner, w_inner, + normal, + x, + t, + operator_divergence, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_left( + flux_inner, w_inner, + normal, + x, + t, operator_gradient, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_left( + flux_inner, w_inner, + normal, + x, + t, operator_divergence, + equations_parabolic + ) + ) == + RealT end end end @@ -793,22 +1169,28 @@ isdir(outdir) && rm(outdir, recursive = true) equations = @inferred CompressibleEulerEquations3D(RealT(1.4)) prandtl_number = RealT(0.72) mu = RealT(0.01) - equations_parabolic_primitive = @inferred CompressibleNavierStokesDiffusion3D(equations, - mu = mu, - Prandtl = prandtl_number, - gradient_variables = GradientVariablesPrimitive()) - equations_parabolic_entropy = @inferred CompressibleNavierStokesDiffusion3D(equations, - mu = mu, - Prandtl = prandtl_number, - gradient_variables = GradientVariablesEntropy()) + equations_parabolic_primitive = @inferred CompressibleNavierStokesDiffusion3D( + equations, + mu = mu, + Prandtl = prandtl_number, + gradient_variables = GradientVariablesPrimitive() + ) + equations_parabolic_entropy = @inferred CompressibleNavierStokesDiffusion3D( + equations, + mu = mu, + Prandtl = prandtl_number, + gradient_variables = GradientVariablesEntropy() + ) x = SVector(zero(RealT), zero(RealT), zero(RealT)) t = zero(RealT) - u = w_inner = u_transformed = flux_inner = normal = SVector(one(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT)) + u = w_inner = u_transformed = flux_inner = normal = SVector( + one(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT) + ) orientations = [1, 2, 3] gradient = SVector(RealT(0.1), RealT(0.1), RealT(0.1), RealT(0.1), RealT(0.1)) gradients = SVector(gradient, gradient, gradient) @@ -831,7 +1213,7 @@ isdir(outdir) && rm(outdir, recursive = true) rho = c + A1 * sin(pi_x) * cos(pi_y) * sin(pi_z) * cos(pi_t) v1 = A2 * sin(pi_x) * log(x[2] + 2) * (1 - exp(-A3 * (x[2] - 1))) * - sin(pi_z) * cos(pi_t) + sin(pi_z) * cos(pi_t) v2 = v1 v3 = v1 p = rho^2 @@ -839,11 +1221,17 @@ isdir(outdir) && rm(outdir, recursive = true) return prim2cons(SVector(rho, v1, v2, v3, p), equations) end - for equations_parabolic in (equations_parabolic_primitive, - equations_parabolic_entropy) + for equations_parabolic in ( + equations_parabolic_primitive, + equations_parabolic_entropy, + ) for orientation in orientations - @test eltype(@inferred flux(u, gradients, orientation, - equations_parabolic)) == RealT + @test eltype( + @inferred flux( + u, gradients, orientation, + equations_parabolic + ) + ) == RealT end @test eltype(@inferred cons2prim(u, equations_parabolic)) == RealT @@ -852,59 +1240,97 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred entropy2cons(u, equations_parabolic)) == RealT @test typeof(@inferred Trixi.temperature(u, equations_parabolic)) == RealT @test typeof(@inferred Trixi.enstrophy(u, gradients, equations_parabolic)) == - RealT + RealT @test eltype(@inferred Trixi.vorticity(u, gradients, equations_parabolic)) == - RealT - - @test eltype(@inferred Trixi.convert_transformed_to_primitive(u_transformed, - equations_parabolic)) == - RealT - @test eltype(@inferred Trixi.convert_derivative_to_primitive(u, gradient, - equations_parabolic)) == - RealT + RealT + + @test eltype( + @inferred Trixi.convert_transformed_to_primitive( + u_transformed, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred Trixi.convert_derivative_to_primitive( + u, gradient, + equations_parabolic + ) + ) == + RealT # For BC tests - velocity_bc_left_right = NoSlip((x, t, equations) -> initial_condition_navier_stokes_convergence_test(x, - t, - equations)[2:4]) - heat_bc_left = Isothermal((x, t, equations) -> Trixi.temperature(initial_condition_navier_stokes_convergence_test(x, - t, - equations), - equations_parabolic)) + velocity_bc_left_right = NoSlip( + (x, t, equations) -> initial_condition_navier_stokes_convergence_test( + x, + t, + equations + )[2:4] + ) + heat_bc_left = Isothermal( + (x, t, equations) -> Trixi.temperature( + initial_condition_navier_stokes_convergence_test( + x, + t, + equations + ), + equations_parabolic + ) + ) heat_bc_right = Adiabatic((x, t, equations) -> oftype(t, 0)) - boundary_condition_left = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_left) - boundary_condition_right = BoundaryConditionNavierStokesWall(velocity_bc_left_right, - heat_bc_right) + boundary_condition_left = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_left + ) + boundary_condition_right = BoundaryConditionNavierStokesWall( + velocity_bc_left_right, + heat_bc_right + ) # BC tests - @test eltype(@inferred boundary_condition_right(flux_inner, w_inner, - normal, - x, - t, - operator_gradient, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_right(flux_inner, w_inner, - normal, - x, - t, - operator_divergence, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_left(flux_inner, w_inner, - normal, - x, - t, operator_gradient, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_left(flux_inner, w_inner, - normal, - x, - t, operator_divergence, - equations_parabolic)) == - RealT + @test eltype( + @inferred boundary_condition_right( + flux_inner, w_inner, + normal, + x, + t, + operator_gradient, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_right( + flux_inner, w_inner, + normal, + x, + t, + operator_divergence, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_left( + flux_inner, w_inner, + normal, + x, + t, operator_gradient, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_left( + flux_inner, w_inner, + normal, + x, + t, operator_divergence, + equations_parabolic + ) + ) == + RealT end end end @@ -925,29 +1351,41 @@ isdir(outdir) && rm(outdir, recursive = true) @test typeof(@inferred Trixi.residual_steady_state(du, equations)) == RealT @test eltype(@inferred initial_condition_poisson_nonperiodic(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_poisson_nonperiodic(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_harmonic(u, x, t, equations)) == RealT - @test eltype(@inferred Trixi.initial_condition_eoc_test_coupled_euler_gravity(x, - t, - equations)) == - RealT + @test eltype( + @inferred Trixi.initial_condition_eoc_test_coupled_euler_gravity( + x, + t, + equations + ) + ) == + RealT for direction in directions - @test eltype(@inferred boundary_condition_poisson_nonperiodic(u_inner, - orientation, - direction, - x, t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred boundary_condition_poisson_nonperiodic( + u_inner, + orientation, + direction, + x, t, + surface_flux_function, + equations + ) + ) == + RealT end @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT @test eltype(@inferred Trixi.max_abs_speeds(equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT @@ -973,41 +1411,61 @@ isdir(outdir) && rm(outdir, recursive = true) @test typeof(@inferred Trixi.residual_steady_state(du, equations)) == RealT @test eltype(@inferred initial_condition_poisson_nonperiodic(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_poisson_nonperiodic(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_harmonic(u, x, t, equations)) == RealT - @test eltype(@inferred Trixi.initial_condition_eoc_test_coupled_euler_gravity(x, - t, - equations)) == - RealT + @test eltype( + @inferred Trixi.initial_condition_eoc_test_coupled_euler_gravity( + x, + t, + equations + ) + ) == + RealT for orientation in orientations for direction in directions - @test eltype(@inferred boundary_condition_poisson_nonperiodic(u_inner, - orientation, - direction, - x, t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred boundary_condition_poisson_nonperiodic( + u_inner, + orientation, + direction, + x, t, + surface_flux_function, + equations + ) + ) == + RealT end end @test eltype(@inferred flux(u, normal_direction, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_godunov(u_ll, u_rr, orientation, - equations)) == RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT + @test eltype( + @inferred flux_godunov( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT end @test eltype(@inferred Trixi.max_abs_speeds(equations)) == RealT @@ -1026,8 +1484,10 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT), zero(RealT), zero(RealT)) t = zero(RealT) - u = du = u_ll = u_rr = u_inner = SVector(one(RealT), one(RealT), one(RealT), - one(RealT)) + u = du = u_ll = u_rr = u_inner = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT) + ) orientations = [1, 2, 3] directions = [1, 2, 3, 4, 5, 6] @@ -1035,34 +1495,50 @@ isdir(outdir) && rm(outdir, recursive = true) @test typeof(@inferred Trixi.residual_steady_state(du, equations)) == RealT @test eltype(@inferred initial_condition_poisson_nonperiodic(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_poisson_nonperiodic(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_harmonic(u, x, t, equations)) == RealT - @test eltype(@inferred Trixi.initial_condition_eoc_test_coupled_euler_gravity(x, - t, - equations)) == - RealT + @test eltype( + @inferred Trixi.initial_condition_eoc_test_coupled_euler_gravity( + x, + t, + equations + ) + ) == + RealT for orientation in orientations for direction in directions - @test eltype(@inferred boundary_condition_poisson_nonperiodic(u_inner, - orientation, - direction, - x, t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred boundary_condition_poisson_nonperiodic( + u_inner, + orientation, + direction, + x, t, + surface_flux_function, + equations + ) + ) == + RealT end end for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_godunov(u_ll, u_rr, orientation, - equations)) == RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT + @test eltype( + @inferred flux_godunov( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT end @test eltype(@inferred Trixi.max_abs_speeds(equations)) == RealT @@ -1079,42 +1555,52 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = cons = SVector(one(RealT), zero(RealT), zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT)) + u = u_ll = u_rr = cons = SVector( + one(RealT), zero(RealT), zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT) + ) orientation = 1 directions = [1, 2] @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_hllc(u_ll, u_rr, orientation, equations)) == RealT @test eltype(@inferred flux_derigs_etal(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred flux_hindenlang_gassner(u_ll, u_rr, - orientation, - equations)) == RealT + RealT + @test eltype( + @inferred flux_hindenlang_gassner( + u_ll, u_rr, + orientation, + equations + ) + ) == RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, orientation, - equations)) == - RealT + RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT @test typeof(@inferred Trixi.max_abs_speeds(u, equations)) == - RealT + RealT @test eltype(@inferred cons2prim(u, equations)) == - RealT + RealT @test eltype(@inferred prim2cons(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT @test typeof(@inferred density(u, equations)) == RealT @@ -1123,8 +1609,12 @@ isdir(outdir) && rm(outdir, recursive = true) for direction in directions @test typeof(Trixi.calc_fast_wavespeed(cons, direction, equations)) == RealT - @test eltype(Trixi.calc_fast_wavespeed_roe(u_ll, u_rr, direction, - equations)) == RealT + @test eltype( + Trixi.calc_fast_wavespeed_roe( + u_ll, u_rr, direction, + equations + ) + ) == RealT end @test typeof(@inferred Trixi.entropy_thermodynamic(cons, equations)) == RealT @@ -1144,81 +1634,149 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = cons = SVector(one(RealT), zero(RealT), zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT)) + u = u_ll = u_rr = cons = SVector( + one(RealT), zero(RealT), zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT) + ) orientations = [1, 2] directions = [1, 2, 3, 4] - normal_direction = normal_direction_ll = normal_direction_average = SVector(one(RealT), - zero(RealT)) + normal_direction = normal_direction_ll = normal_direction_average = SVector( + one(RealT), + zero(RealT) + ) nonconservative_type_local = Trixi.NonConservativeLocal() nonconservative_type_symmetric = Trixi.NonConservativeSymmetric() nonconservative_terms = [1, 2] @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, normal_direction, equations)) == RealT - @test eltype(@inferred flux_nonconservative_powell(u_ll, u_rr, - normal_direction_ll, - normal_direction_average, - equations)) == RealT - @test eltype(@inferred flux_hindenlang_gassner(u_ll, u_rr, normal_direction, - equations)) == RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, normal_direction, - equations)) == RealT + @test eltype( + @inferred flux_nonconservative_powell( + u_ll, u_rr, + normal_direction_ll, + normal_direction_average, + equations + ) + ) == RealT + @test eltype( + @inferred flux_hindenlang_gassner( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_nonconservative_powell(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred flux_nonconservative_powell_local_symmetric(u_ll, - u_rr, - orientation, - equations)) == - RealT - @test eltype(@inferred flux_derigs_etal(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred flux_hindenlang_gassner(u_ll, u_rr, orientation, - equations)) == RealT + @test eltype( + @inferred flux_nonconservative_powell( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_nonconservative_powell_local_symmetric( + u_ll, + u_rr, + orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_derigs_etal( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_hindenlang_gassner( + u_ll, u_rr, orientation, + equations + ) + ) == RealT for nonconservative_term in nonconservative_terms - @test eltype(@inferred flux_nonconservative_powell_local_symmetric(u_ll, - orientation, - equations, - nonconservative_type_local, - nonconservative_term)) == - RealT - @test eltype(@inferred flux_nonconservative_powell_local_symmetric(u_ll, - u_rr, - orientation, - equations, - nonconservative_type_symmetric, - nonconservative_term)) == - RealT + @test eltype( + @inferred flux_nonconservative_powell_local_symmetric( + u_ll, + orientation, + equations, + nonconservative_type_local, + nonconservative_term + ) + ) == + RealT + @test eltype( + @inferred flux_nonconservative_powell_local_symmetric( + u_ll, + u_rr, + orientation, + equations, + nonconservative_type_symmetric, + nonconservative_term + ) + ) == + RealT end - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, orientation, - equations)) == RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, orientation, + equations + ) + ) == RealT end @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @@ -1229,21 +1787,37 @@ isdir(outdir) && rm(outdir, recursive = true) @test typeof(@inferred density(u, equations)) == RealT @test typeof(@inferred pressure(u, equations)) == RealT @test eltype(@inferred Trixi.gradient_conservative(pressure, u, equations)) == - RealT + RealT @test typeof(@inferred density_pressure(u, equations)) == RealT - @test typeof(@inferred Trixi.calc_fast_wavespeed(cons, normal_direction, - equations)) == RealT - @test eltype(@inferred Trixi.calc_fast_wavespeed_roe(u_ll, u_rr, - normal_direction, - equations)) == RealT + @test typeof( + @inferred Trixi.calc_fast_wavespeed( + cons, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred Trixi.calc_fast_wavespeed_roe( + u_ll, u_rr, + normal_direction, + equations + ) + ) == RealT for orientation in orientations - @test typeof(@inferred Trixi.calc_fast_wavespeed(cons, orientation, - equations)) == - RealT - @test eltype(@inferred Trixi.calc_fast_wavespeed_roe(u_ll, u_rr, - orientation, - equations)) == RealT + @test typeof( + @inferred Trixi.calc_fast_wavespeed( + cons, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred Trixi.calc_fast_wavespeed_roe( + u_ll, u_rr, + orientation, + equations + ) + ) == RealT end @test typeof(@inferred Trixi.entropy_thermodynamic(cons, equations)) == RealT @@ -1263,60 +1837,116 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT), zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = cons = SVector(one(RealT), zero(RealT), zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT), - zero(RealT)) + u = u_ll = u_rr = cons = SVector( + one(RealT), zero(RealT), zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT), + zero(RealT) + ) orientations = [1, 2, 3] directions = [1, 2, 3, 4, 5, 6] - normal_direction = normal_direction_ll = normal_direction_average = SVector(one(RealT), - zero(RealT), - zero(RealT)) + normal_direction = normal_direction_ll = normal_direction_average = SVector( + one(RealT), + zero(RealT), + zero(RealT) + ) @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, normal_direction, equations)) == RealT - @test eltype(@inferred flux_nonconservative_powell(u_ll, u_rr, - normal_direction_ll, - normal_direction_average, - equations)) == RealT - @test eltype(@inferred flux_hindenlang_gassner(u_ll, u_rr, normal_direction, - equations)) == RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, normal_direction, - equations)) == RealT + @test eltype( + @inferred flux_nonconservative_powell( + u_ll, u_rr, + normal_direction_ll, + normal_direction_average, + equations + ) + ) == RealT + @test eltype( + @inferred flux_hindenlang_gassner( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_nonconservative_powell(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred flux_derigs_etal(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred flux_hindenlang_gassner(u_ll, u_rr, orientation, - equations)) == RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, orientation, - equations)) == RealT + @test eltype( + @inferred flux_nonconservative_powell( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_derigs_etal( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_hindenlang_gassner( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, orientation, + equations + ) + ) == RealT end @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @@ -1327,18 +1957,34 @@ isdir(outdir) && rm(outdir, recursive = true) @test typeof(@inferred pressure(u, equations)) == RealT @test typeof(@inferred density_pressure(u, equations)) == RealT - @test typeof(@inferred Trixi.calc_fast_wavespeed(cons, normal_direction, - equations)) == RealT - @test eltype(@inferred Trixi.calc_fast_wavespeed_roe(u_ll, u_rr, - normal_direction, - equations)) == RealT + @test typeof( + @inferred Trixi.calc_fast_wavespeed( + cons, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred Trixi.calc_fast_wavespeed_roe( + u_ll, u_rr, + normal_direction, + equations + ) + ) == RealT for orientation in orientations - @test typeof(@inferred Trixi.calc_fast_wavespeed(cons, orientation, - equations)) == - RealT - @test eltype(@inferred Trixi.calc_fast_wavespeed_roe(u_ll, u_rr, - orientation, - equations)) == RealT + @test typeof( + @inferred Trixi.calc_fast_wavespeed( + cons, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred Trixi.calc_fast_wavespeed_roe( + u_ll, u_rr, + orientation, + equations + ) + ) == RealT end @test typeof(@inferred Trixi.entropy_thermodynamic(cons, equations)) == RealT @@ -1356,36 +2002,44 @@ isdir(outdir) && rm(outdir, recursive = true) for RealT in (Float32, Float64) gammas = (RealT(2), RealT(2)) gas_constants = (RealT(2), RealT(2)) - equations = @inferred IdealGlmMhdMulticomponentEquations1D(gammas = gammas, - gas_constants = gas_constants) + equations = @inferred IdealGlmMhdMulticomponentEquations1D( + gammas = gammas, + gas_constants = gas_constants + ) x = SVector(zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = cons = SVector(one(RealT), one(RealT), one(RealT), - one(RealT), - one(RealT), - one(RealT), - one(RealT), - one(RealT), - one(RealT)) + u = u_ll = u_rr = cons = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT), + one(RealT), + one(RealT), + one(RealT), + one(RealT), + one(RealT) + ) orientation = 1 directions = [1, 2] @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_derigs_etal(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred flux_hindenlang_gassner(u_ll, u_rr, orientation, - equations)) == RealT + RealT + @test eltype( + @inferred flux_hindenlang_gassner( + u_ll, u_rr, orientation, + equations + ) + ) == RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == - RealT + RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred prim2cons(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT @@ -1403,41 +2057,61 @@ isdir(outdir) && rm(outdir, recursive = true) for RealT in (Float32, Float64) gammas = (RealT(2), RealT(2)) gas_constants = (RealT(2), RealT(2)) - equations = @inferred IdealGlmMhdMulticomponentEquations2D(gammas = gammas, - gas_constants = gas_constants) + equations = @inferred IdealGlmMhdMulticomponentEquations2D( + gammas = gammas, + gas_constants = gas_constants + ) x = SVector(zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = cons = SVector(one(RealT), one(RealT), one(RealT), - one(RealT), - one(RealT), - one(RealT), - one(RealT), - one(RealT), - one(RealT), - one(RealT)) + u = u_ll = u_rr = cons = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT), + one(RealT), + one(RealT), + one(RealT), + one(RealT), + one(RealT), + one(RealT) + ) orientations = [1, 2] directions = [1, 2, 3, 4] @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT + RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_nonconservative_powell(u_ll, u_rr, orientation, - equations)) == - RealT - @test eltype(@inferred flux_derigs_etal(u_ll, u_rr, orientation, - equations)) == - RealT - @test eltype(@inferred flux_hindenlang_gassner(u_ll, u_rr, orientation, - equations)) == RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT + @test eltype( + @inferred flux_nonconservative_powell( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_derigs_etal( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_hindenlang_gassner( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT end @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @@ -1465,29 +2139,39 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_ec(u_ll, u_rr, orientation, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred Trixi.flux_engquist_osher(u_ll, u_rr, orientation, - equations)) == - RealT - - @test eltype(eltype(@inferred splitting_lax_friedrichs(u, orientation, - equations))) == - RealT + RealT + @test eltype( + @inferred Trixi.flux_engquist_osher( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + + @test eltype( + eltype( + @inferred splitting_lax_friedrichs( + u, orientation, + equations + ) + ) + ) == + RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == - RealT + RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT @test eltype(@inferred entropy2cons(u, equations)) == RealT @@ -1510,11 +2194,13 @@ isdir(outdir) && rm(outdir, recursive = true) operator_divergence = Trixi.Divergence() @test eltype(@inferred flux(u, gradients, orientation, equations_parabolic)) == - RealT + RealT # For BC tests - function initial_condition_convergence_test(x, t, - equation::LaplaceDiffusion1D) + function initial_condition_convergence_test( + x, t, + equation::LaplaceDiffusion1D + ) RealT_local = eltype(x) x_trans = x[1] - equation.diffusivity * t @@ -1528,28 +2214,48 @@ isdir(outdir) && rm(outdir, recursive = true) end boundary_condition_dirichlet = BoundaryConditionDirichlet(initial_condition_convergence_test) - boundary_condition_neumann = BoundaryConditionNeumann((x, t, equations) -> oftype(t, - 0)) + boundary_condition_neumann = BoundaryConditionNeumann( + (x, t, equations) -> oftype( + t, + 0 + ) + ) # BC tests - @test eltype(@inferred boundary_condition_dirichlet(flux_inner, u_inner, normal, - x, t, - operator_gradient, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_dirichlet(flux_inner, u_inner, normal, - x, t, - operator_divergence, - equations_parabolic)) == - RealT - @test eltype(@inferred boundary_condition_neumann(flux_inner, u_inner, normal, - x, t, - operator_gradient, - equations_parabolic)) == RealT - @test eltype(@inferred boundary_condition_neumann(flux_inner, u_inner, normal, - x, t, - operator_divergence, - equations_parabolic)) == RealT + @test eltype( + @inferred boundary_condition_dirichlet( + flux_inner, u_inner, normal, + x, t, + operator_gradient, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_dirichlet( + flux_inner, u_inner, normal, + x, t, + operator_divergence, + equations_parabolic + ) + ) == + RealT + @test eltype( + @inferred boundary_condition_neumann( + flux_inner, u_inner, normal, + x, t, + operator_gradient, + equations_parabolic + ) + ) == RealT + @test eltype( + @inferred boundary_condition_neumann( + flux_inner, u_inner, normal, + x, t, + operator_divergence, + equations_parabolic + ) + ) == RealT end end @@ -1565,13 +2271,17 @@ isdir(outdir) && rm(outdir, recursive = true) for orientation in orientations @test eltype(@inferred flux(u, gradients, orientation, equations_parabolic)) == - RealT + RealT end parabolic_solver = ViscousFormulationLocalDG(RealT(0.1)) - @test eltype(@inferred Trixi.penalty(u_outer, u_inner, inv_h, - equations_parabolic, parabolic_solver)) == - RealT + @test eltype( + @inferred Trixi.penalty( + u_outer, u_inner, inv_h, + equations_parabolic, parabolic_solver + ) + ) == + RealT end end @@ -1582,19 +2292,25 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT), zero(RealT), zero(RealT)) t = zero(RealT) - u = u_inner = u_outer = inv_h = gradients = SVector(one(RealT), one(RealT), - one(RealT)) + u = u_inner = u_outer = inv_h = gradients = SVector( + one(RealT), one(RealT), + one(RealT) + ) orientations = [1, 2, 3] for orientation in orientations @test eltype(@inferred flux(u, gradients, orientation, equations_parabolic)) == - RealT + RealT end parabolic_solver = ViscousFormulationLocalDG(RealT(0.1)) - @test eltype(@inferred Trixi.penalty(u_outer, u_inner, inv_h, - equations_parabolic, parabolic_solver)) == - RealT + @test eltype( + @inferred Trixi.penalty( + u_outer, u_inner, inv_h, + equations_parabolic, parabolic_solver + ) + ) == + RealT end end @@ -1604,9 +2320,11 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = u_inner = SVector(one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT)) + u = u_ll = u_rr = u_inner = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT) + ) orientations = [1, 2] directions = [1, 2, 3, 4] p = rho = dt = one(RealT) @@ -1617,17 +2335,21 @@ isdir(outdir) && rm(outdir, recursive = true) for orientation in orientations for direction in directions - @test eltype(@inferred boundary_condition_noslip_wall(u_inner, - orientation, - direction, x, t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred boundary_condition_noslip_wall( + u_inner, + orientation, + direction, x, t, + surface_flux_function, + equations + ) + ) == + RealT end @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test typeof(@inferred velocity(u, orientation, equations)) == RealT end @@ -1652,15 +2374,17 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT), zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = SVector(one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT), one(RealT)) + u = u_ll = u_rr = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT), one(RealT) + ) orientations = [1, 2, 3] p = rho = dt = one(RealT) @@ -1669,7 +2393,7 @@ isdir(outdir) && rm(outdir, recursive = true) for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test typeof(@inferred velocity(u, orientation, equations)) == RealT end @@ -1687,7 +2411,7 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred cons2entropy(u, equations)) == RealT @test typeof(@inferred energy_kinetic(u, equations)) == RealT @test typeof(@inferred Trixi.energy_kinetic_nondimensional(u, equations)) == - RealT + RealT end end @@ -1705,34 +2429,52 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_gauss(x, t, equations)) == RealT @test eltype(@inferred Trixi.initial_condition_sin(x, t, equations)) == RealT @test eltype(@inferred Trixi.initial_condition_linear_x(x, t, equations)) == - RealT + RealT for direction in directions - @test eltype(@inferred Trixi.boundary_condition_linear_x(u_inner, - orientation, - direction, x, - t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred Trixi.boundary_condition_linear_x( + u_inner, + orientation, + direction, x, + t, + surface_flux_function, + equations + ) + ) == + RealT end @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred Trixi.flux_engquist_osher(u_ll, u_rr, orientation, - equations)) == RealT - - @test eltype(eltype(@inferred splitting_lax_friedrichs(u, orientation, - equations))) == - RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT + RealT + @test eltype( + @inferred Trixi.flux_engquist_osher( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + + @test eltype( + eltype( + @inferred splitting_lax_friedrichs( + u, orientation, + equations + ) + ) + ) == + RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT @test eltype(@inferred Trixi.max_abs_speeds(equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT @@ -1758,62 +2500,82 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_gauss(x, t, equations)) == RealT @test eltype(@inferred Trixi.initial_condition_sin_sin(x, t, equations)) == - RealT + RealT @test eltype(@inferred Trixi.initial_condition_linear_x_y(x, t, equations)) == - RealT + RealT @test eltype(@inferred Trixi.initial_condition_linear_x(x, t, equations)) == - RealT + RealT @test eltype(@inferred Trixi.initial_condition_linear_y(x, t, equations)) == - RealT + RealT for orientation in orientations for direction in directions - @test eltype(@inferred Trixi.boundary_condition_linear_x_y(u_inner, - orientation, - direction, - x, - t, - surface_flux_function, - equations)) == - RealT - @test eltype(@inferred Trixi.boundary_condition_linear_x(u_inner, - orientation, - direction, - x, - t, - surface_flux_function, - equations)) == - RealT - @test eltype(@inferred Trixi.boundary_condition_linear_y(u_inner, - orientation, - direction, - x, - t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred Trixi.boundary_condition_linear_x_y( + u_inner, + orientation, + direction, + x, + t, + surface_flux_function, + equations + ) + ) == + RealT + @test eltype( + @inferred Trixi.boundary_condition_linear_x( + u_inner, + orientation, + direction, + x, + t, + surface_flux_function, + equations + ) + ) == + RealT + @test eltype( + @inferred Trixi.boundary_condition_linear_y( + u_inner, + orientation, + direction, + x, + t, + surface_flux_function, + equations + ) + ) == + RealT end end @test eltype(@inferred flux(u, normal_direction, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT - @test eltype(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == - RealT + @test eltype( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, orientation, equations)) == - RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT + RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT end @test eltype(@inferred Trixi.max_abs_speeds(equations)) == RealT @@ -1826,8 +2588,10 @@ isdir(outdir) && rm(outdir, recursive = true) @timed_testset "Linear Scalar Advection 3D" begin for RealT in (Float32, Float64) - equations = @inferred LinearScalarAdvectionEquation3D(RealT(1), RealT(1), - RealT(1)) + equations = @inferred LinearScalarAdvectionEquation3D( + RealT(1), RealT(1), + RealT(1) + ) x = SVector(zero(RealT), zero(RealT), zero(RealT)) t = zero(RealT) @@ -1840,39 +2604,51 @@ isdir(outdir) && rm(outdir, recursive = true) @test eltype(@inferred initial_condition_constant(x, t, equations)) == RealT @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_gauss(x, t, equations)) == RealT @test eltype(@inferred Trixi.initial_condition_sin(x, t, equations)) == RealT @test eltype(@inferred Trixi.initial_condition_linear_z(x, t, equations)) == - RealT + RealT for orientation in orientations for direction in directions - @test eltype(@inferred Trixi.boundary_condition_linear_z(u_inner, - orientation, - direction, - x, - t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred Trixi.boundary_condition_linear_z( + u_inner, + orientation, + direction, + x, + t, + surface_flux_function, + equations + ) + ) == + RealT end end @test eltype(@inferred flux(u, normal_direction, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT - @test eltype(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT + @test eltype( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, orientation, equations)) == - RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT + RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT end @test eltype(@inferred Trixi.max_abs_speeds(equations)) == RealT @@ -1894,18 +2670,18 @@ isdir(outdir) && rm(outdir, recursive = true) orientation = 1 @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, orientation, equations)) == RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred Trixi.max_abs_speeds(equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT end @@ -1913,9 +2689,11 @@ isdir(outdir) && rm(outdir, recursive = true) @timed_testset "Linearized Euler 1D" begin for RealT in (Float32, Float64) - equations = @inferred LinearizedEulerEquations1D(v_mean_global = RealT(0), - c_mean_global = RealT(1), - rho_mean_global = RealT(1)) + equations = @inferred LinearizedEulerEquations1D( + v_mean_global = RealT(0), + c_mean_global = RealT(1), + rho_mean_global = RealT(1) + ) x = SVector(zero(RealT)) t = zero(RealT) @@ -1926,25 +2704,29 @@ isdir(outdir) && rm(outdir, recursive = true) surface_flux_function = flux_hll @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT for direction in directions - @test eltype(@inferred boundary_condition_wall(u_inner, orientation, - direction, x, t, - surface_flux_function, - equations)) == RealT + @test eltype( + @inferred boundary_condition_wall( + u_inner, orientation, + direction, x, t, + surface_flux_function, + equations + ) + ) == RealT end @test eltype(@inferred flux(u, orientation, equations)) == RealT @test typeof(@inferred Trixi.max_abs_speeds(equations)) == - RealT + RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT @@ -1953,15 +2735,21 @@ isdir(outdir) && rm(outdir, recursive = true) @timed_testset "Linearized Euler 2D" begin for RealT in (Float32, Float64) - equations = @inferred LinearizedEulerEquations2D(v_mean_global = (RealT(0), - RealT(0)), - c_mean_global = RealT(1), - rho_mean_global = RealT(1)) + equations = @inferred LinearizedEulerEquations2D( + v_mean_global = ( + RealT(0), + RealT(0), + ), + c_mean_global = RealT(1), + rho_mean_global = RealT(1) + ) x = SVector(zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = u_inner = SVector(one(RealT), one(RealT), one(RealT), - one(RealT)) + u = u_ll = u_rr = u_inner = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT) + ) orientations = [1, 2] directions = [1, 2, 3, 4] normal_direction = SVector(one(RealT), zero(RealT)) @@ -1969,42 +2757,70 @@ isdir(outdir) && rm(outdir, recursive = true) surface_flux_function = flux_hll @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT for orientation in orientations for direction in directions - @test eltype(@inferred boundary_condition_wall(u_inner, orientation, - direction, x, t, - surface_flux_function, - equations)) == RealT + @test eltype( + @inferred boundary_condition_wall( + u_inner, orientation, + direction, x, t, + surface_flux_function, + equations + ) + ) == RealT end @test eltype(@inferred flux(u, orientation, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, orientation, equations)) == - RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, - equations)) == - RealT + RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT end @test eltype(@inferred flux(u, normal_direction, equations)) == RealT @test eltype(@inferred flux_godunov(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT @test eltype(@inferred Trixi.max_abs_speeds(equations)) == RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, normal_direction, - equations)) == RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT @@ -2013,16 +2829,22 @@ isdir(outdir) && rm(outdir, recursive = true) @timed_testset "Linearized Euler 3D" begin for RealT in (Float32, Float64) - equations = @inferred LinearizedEulerEquations3D(v_mean_global = (RealT(0), - RealT(0), - RealT(0)), - c_mean_global = RealT(1), - rho_mean_global = RealT(1)) + equations = @inferred LinearizedEulerEquations3D( + v_mean_global = ( + RealT(0), + RealT(0), + RealT(0), + ), + c_mean_global = RealT(1), + rho_mean_global = RealT(1) + ) x = SVector(zero(RealT), zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = u_inner = SVector(one(RealT), one(RealT), one(RealT), - one(RealT), one(RealT)) + u = u_ll = u_rr = u_inner = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT), one(RealT) + ) orientations = [1, 2, 3] directions = [1, 2, 3, 4, 5, 6] normal_direction = SVector(one(RealT), zero(RealT), zero(RealT)) @@ -2030,35 +2852,63 @@ isdir(outdir) && rm(outdir, recursive = true) surface_flux_function = flux_hll @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT for orientation in orientations for direction in directions - @test eltype(@inferred boundary_condition_wall(u_inner, orientation, - direction, x, t, - surface_flux_function, - equations)) == RealT + @test eltype( + @inferred boundary_condition_wall( + u_inner, orientation, + direction, x, t, + surface_flux_function, + equations + ) + ) == RealT end @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, - equations)) == RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, orientation, + equations + ) + ) == RealT end @test eltype(@inferred flux(u, normal_direction, equations)) == RealT @test eltype(@inferred Trixi.max_abs_speeds(equations)) == RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, normal_direction, - equations)) == RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT @@ -2067,8 +2917,10 @@ isdir(outdir) && rm(outdir, recursive = true) @timed_testset "Polytropic Euler 2D" begin for RealT in (Float32, Float64) - equations1 = @inferred PolytropicEulerEquations2D(RealT(1), - RealT(1)) # equations.gamma == 1 + equations1 = @inferred PolytropicEulerEquations2D( + RealT(1), + RealT(1) + ) # equations.gamma == 1 equations2 = @inferred PolytropicEulerEquations2D(RealT(1.4), RealT(0.5)) # equations.gamma > 1 for equations in (equations1, equations2) @@ -2080,37 +2932,65 @@ isdir(outdir) && rm(outdir, recursive = true) normal_direction = SVector(one(RealT), zero(RealT)) @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, normal_direction, equations)) == RealT - @test eltype(@inferred flux_winters_etal(u_ll, u_rr, normal_direction, - equations)) == - RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, normal_direction, - equations)) == - RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, normal_direction, - equations)) == - RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == - RealT + @test eltype( + @inferred flux_winters_etal( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT for orientation in orientations @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_winters_etal(u_ll, u_rr, orientation, - equations)) == - RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, - equations)) == - RealT - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT + @test eltype( + @inferred flux_winters_etal( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT end @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @@ -2139,59 +3019,97 @@ isdir(outdir) && rm(outdir, recursive = true) numflux = FluxHLL() @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT for direction in directions - @test eltype(@inferred boundary_condition_slip_wall(u_inner, - orientation, - direction, - x, t, - surface_flux_function, - equations)) == RealT - @test eltype(@inferred Trixi.calc_wavespeed_roe(u_ll, u_rr, direction, - equations)) == - RealT + @test eltype( + @inferred boundary_condition_slip_wall( + u_inner, + orientation, + direction, + x, t, + surface_flux_function, + equations + ) + ) == RealT + @test eltype( + @inferred Trixi.calc_wavespeed_roe( + u_ll, u_rr, direction, + equations + ) + ) == + RealT end @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_nonconservative_wintermeyer_etal(u_ll, u_rr, - orientation, - equations)) == - RealT - @test eltype(@inferred flux_nonconservative_fjordholm_etal(u_ll, u_rr, - orientation, - equations)) == RealT - @test eltype(@inferred flux_nonconservative_audusse_etal(u_ll, u_rr, - orientation, - equations)) == RealT - @test eltype(@inferred flux_fjordholm_etal(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred flux_wintermeyer_etal(u_ll, u_rr, orientation, - equations)) == RealT - - @test eltype(eltype(@inferred hydrostatic_reconstruction_audusse_etal(u_ll, - u_rr, - equations))) == - RealT + @test eltype( + @inferred flux_nonconservative_wintermeyer_etal( + u_ll, u_rr, + orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_nonconservative_fjordholm_etal( + u_ll, u_rr, + orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_nonconservative_audusse_etal( + u_ll, u_rr, + orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_fjordholm_etal( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_wintermeyer_etal( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + + @test eltype( + eltype( + @inferred hydrostatic_reconstruction_audusse_etal( + u_ll, + u_rr, + equations + ) + ) + ) == + RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred dissipation(u_ll, u_rr, orientation, equations)) == RealT @test eltype(@inferred dissipation(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT @test eltype(@inferred numflux(u_ll, u_rr, orientation, equations)) == RealT # no matching method # @test eltype(@inferred numflux(u_ll, u_rr, normal_direction, equations)) == RealT @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, equations)) == - RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, orientation, - equations)) == RealT + RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, orientation, + equations + ) + ) == RealT @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test typeof(@inferred velocity(u, equations)) == RealT @@ -2217,112 +3135,210 @@ isdir(outdir) && rm(outdir, recursive = true) x = SVector(zero(RealT), zero(RealT)) t = zero(RealT) - u = u_ll = u_rr = u_inner = cons = SVector(one(RealT), one(RealT), one(RealT), - one(RealT)) + u = u_ll = u_rr = u_inner = cons = SVector( + one(RealT), one(RealT), one(RealT), + one(RealT) + ) orientations = [1, 2] directions = [1, 2, 3, 4] - normal_direction = normal_direction_ll = normal_direction_average = SVector(one(RealT), - zero(RealT)) + normal_direction = normal_direction_ll = normal_direction_average = SVector( + one(RealT), + zero(RealT) + ) surface_flux_function = flux_lax_friedrichs dissipation = DissipationLocalLaxFriedrichs() numflux = FluxHLL() @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred initial_condition_weak_blast_wave(x, t, equations)) == - RealT - @test eltype(@inferred boundary_condition_slip_wall(u_inner, - normal_direction, - x, t, - surface_flux_function, - equations)) == RealT + RealT + @test eltype( + @inferred boundary_condition_slip_wall( + u_inner, + normal_direction, + x, t, + surface_flux_function, + equations + ) + ) == RealT @test eltype(@inferred flux(u, normal_direction, equations)) == RealT - @test eltype(@inferred flux_nonconservative_wintermeyer_etal(u_ll, u_rr, - normal_direction_ll, - normal_direction_average, - equations)) == - RealT - @test eltype(@inferred flux_nonconservative_fjordholm_etal(u_ll, u_rr, - normal_direction_ll, - normal_direction_average, - equations)) == RealT - @test eltype(@inferred flux_nonconservative_audusse_etal(u_ll, u_rr, - normal_direction_ll, - normal_direction_average, - equations)) == RealT - @test eltype(@inferred flux_fjordholm_etal(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred flux_wintermeyer_etal(u_ll, u_rr, normal_direction, - equations)) == RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, normal_direction, - equations)) == - RealT + @test eltype( + @inferred flux_nonconservative_wintermeyer_etal( + u_ll, u_rr, + normal_direction_ll, + normal_direction_average, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_nonconservative_fjordholm_etal( + u_ll, u_rr, + normal_direction_ll, + normal_direction_average, + equations + ) + ) == RealT + @test eltype( + @inferred flux_nonconservative_audusse_etal( + u_ll, u_rr, + normal_direction_ll, + normal_direction_average, + equations + ) + ) == RealT + @test eltype( + @inferred flux_fjordholm_etal( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred flux_wintermeyer_etal( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == + RealT @test eltype(@inferred dissipation(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT @test eltype(@inferred numflux(u_ll, u_rr, normal_direction, equations)) == - RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, normal_direction, - equations)) == RealT - @test eltype(@inferred Trixi.calc_wavespeed_roe(u_ll, u_rr, normal_direction, - equations)) == RealT + RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT + @test eltype( + @inferred Trixi.calc_wavespeed_roe( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT for orientation in orientations for direction in directions - @test eltype(@inferred boundary_condition_slip_wall(u_inner, - orientation, - direction, x, t, - surface_flux_function, - equations)) == - RealT + @test eltype( + @inferred boundary_condition_slip_wall( + u_inner, + orientation, + direction, x, t, + surface_flux_function, + equations + ) + ) == + RealT end @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_nonconservative_wintermeyer_etal(u_ll, u_rr, - orientation, - equations)) == - RealT - @test eltype(@inferred flux_nonconservative_fjordholm_etal(u_ll, u_rr, - orientation, - equations)) == - RealT - @test eltype(@inferred flux_nonconservative_audusse_etal(u_ll, u_rr, - orientation, - equations)) == - RealT - @test eltype(@inferred flux_fjordholm_etal(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred flux_wintermeyer_etal(u_ll, u_rr, orientation, - equations)) == RealT - - @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, - equations)) == - RealT + @test eltype( + @inferred flux_nonconservative_wintermeyer_etal( + u_ll, u_rr, + orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_nonconservative_fjordholm_etal( + u_ll, u_rr, + orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_nonconservative_audusse_etal( + u_ll, u_rr, + orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_fjordholm_etal( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_wintermeyer_etal( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + + @test typeof( + @inferred max_abs_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == + RealT @test eltype(@inferred dissipation(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred numflux(u_ll, u_rr, orientation, equations)) == RealT - @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred min_max_speed_einfeldt(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred Trixi.calc_wavespeed_roe(u_ll, u_rr, orientation, - equations)) == RealT + @test eltype( + @inferred min_max_speed_naive( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_davis( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred min_max_speed_einfeldt( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred Trixi.calc_wavespeed_roe( + u_ll, u_rr, orientation, + equations + ) + ) == RealT end - @test eltype(eltype(@inferred hydrostatic_reconstruction_audusse_etal(u_ll, - u_rr, - equations))) == - RealT + @test eltype( + eltype( + @inferred hydrostatic_reconstruction_audusse_etal( + u_ll, + u_rr, + equations + ) + ) + ) == + RealT @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test eltype(@inferred velocity(u, equations)) == RealT @@ -2355,32 +3371,52 @@ isdir(outdir) && rm(outdir, recursive = true) dissipation = DissipationLocalLaxFriedrichs() @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, orientation, equations)) == RealT - @test eltype(@inferred flux_nonconservative_chan_etal(u_ll, u_rr, - orientation, - equations)) == - RealT - @test eltype(@inferred flux_nonconservative_chan_etal(u_ll, u_rr, - normal_direction, - equations)) == - RealT - @test eltype(@inferred flux_nonconservative_chan_etal(u_ll, u_rr, normal_ll, - normal_rr, - equations)) == RealT - @test eltype(@inferred flux_chan_etal(u_ll, u_rr, orientation, - equations)) == RealT - @test eltype(@inferred flux_chan_etal(u_ll, u_rr, normal_direction, - equations)) == RealT + @test eltype( + @inferred flux_nonconservative_chan_etal( + u_ll, u_rr, + orientation, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_nonconservative_chan_etal( + u_ll, u_rr, + normal_direction, + equations + ) + ) == + RealT + @test eltype( + @inferred flux_nonconservative_chan_etal( + u_ll, u_rr, normal_ll, + normal_rr, + equations + ) + ) == RealT + @test eltype( + @inferred flux_chan_etal( + u_ll, u_rr, orientation, + equations + ) + ) == RealT + @test eltype( + @inferred flux_chan_etal( + u_ll, u_rr, normal_direction, + equations + ) + ) == RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred dissipation(u_ll, u_rr, orientation, equations)) == RealT @test eltype(@inferred dissipation(u_ll, u_rr, normal_direction, equations)) == - RealT + RealT @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test typeof(@inferred velocity(u, equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @@ -2405,18 +3441,18 @@ isdir(outdir) && rm(outdir, recursive = true) c = one(RealT) @test eltype(@inferred initial_condition_convergence_test(x, t, equations)) == - RealT + RealT @test eltype(@inferred source_terms_convergence_test(u, x, t, equations)) == - RealT + RealT @test eltype(@inferred flux(u, orientation, equations)) == RealT @test typeof(@inferred max_abs_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_naive(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred min_max_speed_davis(u_ll, u_rr, orientation, equations)) == - RealT + RealT @test eltype(@inferred Trixi.max_abs_speeds(u, equations)) == RealT @test eltype(@inferred cons2prim(u, equations)) == RealT @test eltype(@inferred cons2entropy(u, equations)) == RealT diff --git a/test/test_unit.jl b/test/test_unit.jl index aa66ce2556..1528c8477e 100644 --- a/test/test_unit.jl +++ b/test/test_unit.jl @@ -18,1725 +18,1975 @@ isdir(outdir) && rm(outdir, recursive = true) # Run various unit (= non-elixir-triggered) tests @testset "Unit tests" begin -#! format: noindent + #! format: noindent -@timed_testset "SerialTree" begin - @testset "constructors" begin - @test_nowarn Trixi.SerialTree(Val(1), 10, 0.0, 1.0) - end + @timed_testset "SerialTree" begin + @testset "constructors" begin + @test_nowarn Trixi.SerialTree(Val(1), 10, 0.0, 1.0) + end - @testset "helper functions" begin - t = Trixi.SerialTree(Val(1), 10, 0.0, 1.0) - @test_nowarn display(t) - @test Trixi.ndims(t) == 1 - @test Trixi.has_any_neighbor(t, 1, 1) == true - @test Trixi.isperiodic(t, 1) == true - @test Trixi.n_children_per_cell(t) == 2 - @test Trixi.n_directions(t) == 2 - end + @testset "helper functions" begin + t = Trixi.SerialTree(Val(1), 10, 0.0, 1.0) + @test_nowarn display(t) + @test Trixi.ndims(t) == 1 + @test Trixi.has_any_neighbor(t, 1, 1) == true + @test Trixi.isperiodic(t, 1) == true + @test Trixi.n_children_per_cell(t) == 2 + @test Trixi.n_directions(t) == 2 + end - @testset "refine!/coarsen!" begin - t = Trixi.SerialTree(Val(1), 10, 0.0, 1.0) - @test Trixi.refine!(t) == [1] - @test Trixi.coarsen!(t) == [1] - @test Trixi.refine!(t) == [1] - @test Trixi.coarsen!(t, 1) == [1] - @test Trixi.coarsen!(t) == Int[] # Coarsen twice to check degenerate case of single-cell tree - @test Trixi.refine!(t) == [1] - @test Trixi.refine!(t) == [2, 3] - @test Trixi.coarsen_box!(t, [-0.5], [0.0]) == [2] - @test Trixi.coarsen_box!(t, 0.0, 0.5) == [3] - @test isnothing(Trixi.reset_data_structures!(t)) + @testset "refine!/coarsen!" begin + t = Trixi.SerialTree(Val(1), 10, 0.0, 1.0) + @test Trixi.refine!(t) == [1] + @test Trixi.coarsen!(t) == [1] + @test Trixi.refine!(t) == [1] + @test Trixi.coarsen!(t, 1) == [1] + @test Trixi.coarsen!(t) == Int[] # Coarsen twice to check degenerate case of single-cell tree + @test Trixi.refine!(t) == [1] + @test Trixi.refine!(t) == [2, 3] + @test Trixi.coarsen_box!(t, [-0.5], [0.0]) == [2] + @test Trixi.coarsen_box!(t, 0.0, 0.5) == [3] + @test isnothing(Trixi.reset_data_structures!(t)) + end end -end -@timed_testset "ParallelTree" begin - @testset "constructors" begin - @test_nowarn Trixi.ParallelTree(Val(1), 10, 0.0, 1.0) - end + @timed_testset "ParallelTree" begin + @testset "constructors" begin + @test_nowarn Trixi.ParallelTree(Val(1), 10, 0.0, 1.0) + end - @testset "helper functions" begin - t = Trixi.ParallelTree(Val(1), 10, 0.0, 1.0) - @test isnothing(display(t)) - @test isnothing(Trixi.reset_data_structures!(t)) + @testset "helper functions" begin + t = Trixi.ParallelTree(Val(1), 10, 0.0, 1.0) + @test isnothing(display(t)) + @test isnothing(Trixi.reset_data_structures!(t)) + end end -end -@timed_testset "TreeMesh" begin - @testset "constructors" begin - @test TreeMesh{1, Trixi.SerialTree{1}}(1, 5.0, 2.0) isa TreeMesh + @timed_testset "TreeMesh" begin + @testset "constructors" begin + @test TreeMesh{1, Trixi.SerialTree{1}}(1, 5.0, 2.0) isa TreeMesh + end end -end -@timed_testset "ParallelTreeMesh" begin - @testset "partition!" begin - @testset "mpi_nranks() = 2" begin - Trixi.mpi_nranks() = 2 - let - @test Trixi.mpi_nranks() == 2 - - mesh = TreeMesh{2, Trixi.ParallelTree{2}}(30, (0.0, 0.0), 1) - # Refine twice - Trixi.refine!(mesh.tree) - Trixi.refine!(mesh.tree) - - # allow_coarsening = true - Trixi.partition!(mesh) - # Use parent for OffsetArray - @test parent(mesh.n_cells_by_rank) == [11, 10] - @test mesh.tree.mpi_ranks[1:21] == - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - @test parent(mesh.first_cell_by_rank) == [1, 12] - - # allow_coarsening = false - Trixi.partition!(mesh; allow_coarsening = false) - @test parent(mesh.n_cells_by_rank) == [11, 10] - @test mesh.tree.mpi_ranks[1:21] == - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] - @test parent(mesh.first_cell_by_rank) == [1, 12] + @timed_testset "ParallelTreeMesh" begin + @testset "partition!" begin + @testset "mpi_nranks() = 2" begin + Trixi.mpi_nranks() = 2 + let + @test Trixi.mpi_nranks() == 2 + + mesh = TreeMesh{2, Trixi.ParallelTree{2}}(30, (0.0, 0.0), 1) + # Refine twice + Trixi.refine!(mesh.tree) + Trixi.refine!(mesh.tree) + + # allow_coarsening = true + Trixi.partition!(mesh) + # Use parent for OffsetArray + @test parent(mesh.n_cells_by_rank) == [11, 10] + @test mesh.tree.mpi_ranks[1:21] == + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + @test parent(mesh.first_cell_by_rank) == [1, 12] + + # allow_coarsening = false + Trixi.partition!(mesh; allow_coarsening = false) + @test parent(mesh.n_cells_by_rank) == [11, 10] + @test mesh.tree.mpi_ranks[1:21] == + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1] + @test parent(mesh.first_cell_by_rank) == [1, 12] + end + Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior end - Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior - end - - @testset "mpi_nranks() = 3" begin - Trixi.mpi_nranks() = 3 - let - @test Trixi.mpi_nranks() == 3 - - mesh = TreeMesh{2, Trixi.ParallelTree{2}}(100, (0.0, 0.0), 1) - # Refine twice - Trixi.refine!(mesh.tree) - Trixi.refine!(mesh.tree) - - # allow_coarsening = true - Trixi.partition!(mesh) - # Use parent for OffsetArray - @test parent(mesh.n_cells_by_rank) == [11, 5, 5] - @test mesh.tree.mpi_ranks[1:21] == - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2] - @test parent(mesh.first_cell_by_rank) == [1, 12, 17] - - # allow_coarsening = false - Trixi.partition!(mesh; allow_coarsening = false) - @test parent(mesh.n_cells_by_rank) == [9, 6, 6] - @test mesh.tree.mpi_ranks[1:21] == - [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2] - @test parent(mesh.first_cell_by_rank) == [1, 10, 16] + + @testset "mpi_nranks() = 3" begin + Trixi.mpi_nranks() = 3 + let + @test Trixi.mpi_nranks() == 3 + + mesh = TreeMesh{2, Trixi.ParallelTree{2}}(100, (0.0, 0.0), 1) + # Refine twice + Trixi.refine!(mesh.tree) + Trixi.refine!(mesh.tree) + + # allow_coarsening = true + Trixi.partition!(mesh) + # Use parent for OffsetArray + @test parent(mesh.n_cells_by_rank) == [11, 5, 5] + @test mesh.tree.mpi_ranks[1:21] == + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2] + @test parent(mesh.first_cell_by_rank) == [1, 12, 17] + + # allow_coarsening = false + Trixi.partition!(mesh; allow_coarsening = false) + @test parent(mesh.n_cells_by_rank) == [9, 6, 6] + @test mesh.tree.mpi_ranks[1:21] == + [0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 2] + @test parent(mesh.first_cell_by_rank) == [1, 10, 16] + end + Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior end - Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior - end - - @testset "mpi_nranks() = 9" begin - Trixi.mpi_nranks() = 9 - let - @test Trixi.mpi_nranks() == 9 - - mesh = TreeMesh{2, Trixi.ParallelTree{2}}(1000, (0.0, 0.0), 1) - # Refine twice - Trixi.refine!(mesh.tree) - Trixi.refine!(mesh.tree) - Trixi.refine!(mesh.tree) - Trixi.refine!(mesh.tree) - - # allow_coarsening = true - Trixi.partition!(mesh) - # Use parent for OffsetArray - @test parent(mesh.n_cells_by_rank) == - [44, 37, 38, 37, 37, 37, 38, 37, 36] - @test parent(mesh.first_cell_by_rank) == - [1, 45, 82, 120, 157, 194, 231, 269, 306] + + @testset "mpi_nranks() = 9" begin + Trixi.mpi_nranks() = 9 + let + @test Trixi.mpi_nranks() == 9 + + mesh = TreeMesh{2, Trixi.ParallelTree{2}}(1000, (0.0, 0.0), 1) + # Refine twice + Trixi.refine!(mesh.tree) + Trixi.refine!(mesh.tree) + Trixi.refine!(mesh.tree) + Trixi.refine!(mesh.tree) + + # allow_coarsening = true + Trixi.partition!(mesh) + # Use parent for OffsetArray + @test parent(mesh.n_cells_by_rank) == + [44, 37, 38, 37, 37, 37, 38, 37, 36] + @test parent(mesh.first_cell_by_rank) == + [1, 45, 82, 120, 157, 194, 231, 269, 306] + end + Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior end - Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior - end - - @testset "mpi_nranks() = 3 non-uniform" begin - Trixi.mpi_nranks() = 3 - let - @test Trixi.mpi_nranks() == 3 - - mesh = TreeMesh{2, Trixi.ParallelTree{2}}(100, (0.0, 0.0), 1) - # Refine whole tree - Trixi.refine!(mesh.tree) - # Refine left leaf - Trixi.refine!(mesh.tree, [2]) - - # allow_coarsening = true - Trixi.partition!(mesh) - # Use parent for OffsetArray - @test parent(mesh.n_cells_by_rank) == [6, 1, 2] - @test mesh.tree.mpi_ranks[1:9] == [0, 0, 0, 0, 0, 0, 1, 2, 2] - @test parent(mesh.first_cell_by_rank) == [1, 7, 8] - - # allow_coarsening = false - Trixi.partition!(mesh; allow_coarsening = false) - @test parent(mesh.n_cells_by_rank) == [5, 2, 2] - @test mesh.tree.mpi_ranks[1:9] == [0, 0, 0, 0, 0, 1, 1, 2, 2] - @test parent(mesh.first_cell_by_rank) == [1, 6, 8] + + @testset "mpi_nranks() = 3 non-uniform" begin + Trixi.mpi_nranks() = 3 + let + @test Trixi.mpi_nranks() == 3 + + mesh = TreeMesh{2, Trixi.ParallelTree{2}}(100, (0.0, 0.0), 1) + # Refine whole tree + Trixi.refine!(mesh.tree) + # Refine left leaf + Trixi.refine!(mesh.tree, [2]) + + # allow_coarsening = true + Trixi.partition!(mesh) + # Use parent for OffsetArray + @test parent(mesh.n_cells_by_rank) == [6, 1, 2] + @test mesh.tree.mpi_ranks[1:9] == [0, 0, 0, 0, 0, 0, 1, 2, 2] + @test parent(mesh.first_cell_by_rank) == [1, 7, 8] + + # allow_coarsening = false + Trixi.partition!(mesh; allow_coarsening = false) + @test parent(mesh.n_cells_by_rank) == [5, 2, 2] + @test mesh.tree.mpi_ranks[1:9] == [0, 0, 0, 0, 0, 1, 1, 2, 2] + @test parent(mesh.first_cell_by_rank) == [1, 6, 8] + end + Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior end - Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior - end - @testset "not enough ranks" begin - Trixi.mpi_nranks() = 3 - let - @test Trixi.mpi_nranks() == 3 + @testset "not enough ranks" begin + Trixi.mpi_nranks() = 3 + let + @test Trixi.mpi_nranks() == 3 - mesh = TreeMesh{2, Trixi.ParallelTree{2}}(100, (0.0, 0.0), 1) + mesh = TreeMesh{2, Trixi.ParallelTree{2}}(100, (0.0, 0.0), 1) - # Only one leaf - @test_throws AssertionError("Too many ranks to properly partition the mesh!") Trixi.partition!(mesh) + # Only one leaf + @test_throws AssertionError("Too many ranks to properly partition the mesh!") Trixi.partition!(mesh) - # Refine to 4 leaves - Trixi.refine!(mesh.tree) + # Refine to 4 leaves + Trixi.refine!(mesh.tree) - # All four leaves will need to be on one rank to allow coarsening - @test_throws AssertionError("Too many ranks to properly partition the mesh!") Trixi.partition!(mesh) - @test_nowarn Trixi.partition!(mesh; allow_coarsening = false) + # All four leaves will need to be on one rank to allow coarsening + @test_throws AssertionError("Too many ranks to properly partition the mesh!") Trixi.partition!(mesh) + @test_nowarn Trixi.partition!(mesh; allow_coarsening = false) + end + Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior end - Trixi.mpi_nranks() = Trixi.MPI_SIZE[] # restore the original behavior end end -end -@timed_testset "curved mesh" begin - @testset "calc_jacobian_matrix" begin - @testset "identity map" begin - basis = LobattoLegendreBasis(5) - nodes = Trixi.get_nodes(basis) - jacobian_matrix = Array{Float64, 5}(undef, 2, 2, 6, 6, 1) - - node_coordinates = Array{Float64, 4}(undef, 2, 6, 6, 1) - node_coordinates[1, :, :, 1] .= [nodes[i] for i in 1:6, j in 1:6] - node_coordinates[2, :, :, 1] .= [nodes[j] for i in 1:6, j in 1:6] - expected = zeros(2, 2, 6, 6, 1) - expected[1, 1, :, :, 1] .= 1 - expected[2, 2, :, :, 1] .= 1 - @test Trixi.calc_jacobian_matrix!(jacobian_matrix, 1, node_coordinates, - basis) ≈ expected - end - - @testset "maximum exact polydeg" begin - basis = LobattoLegendreBasis(3) - nodes = Trixi.get_nodes(basis) - jacobian_matrix = Array{Float64, 5}(undef, 2, 2, 4, 4, 1) - - # f(x, y) = [x^3, xy^2] - node_coordinates = Array{Float64, 4}(undef, 2, 4, 4, 1) - node_coordinates[1, :, :, 1] .= [nodes[i]^3 for i in 1:4, j in 1:4] - node_coordinates[2, :, :, 1] .= [nodes[i] * nodes[j]^2 - for i in 1:4, j in 1:4] - - # Df(x, y) = [3x^2 0; - # y^2 2xy] - expected = zeros(2, 2, 4, 4, 1) - expected[1, 1, :, :, 1] .= [3 * nodes[i]^2 for i in 1:4, j in 1:4] - expected[2, 1, :, :, 1] .= [nodes[j]^2 for i in 1:4, j in 1:4] - expected[2, 2, :, :, 1] .= [2 * nodes[i] * nodes[j] for i in 1:4, j in 1:4] - @test Trixi.calc_jacobian_matrix!(jacobian_matrix, 1, node_coordinates, - basis) ≈ expected + @timed_testset "curved mesh" begin + @testset "calc_jacobian_matrix" begin + @testset "identity map" begin + basis = LobattoLegendreBasis(5) + nodes = Trixi.get_nodes(basis) + jacobian_matrix = Array{Float64, 5}(undef, 2, 2, 6, 6, 1) + + node_coordinates = Array{Float64, 4}(undef, 2, 6, 6, 1) + node_coordinates[1, :, :, 1] .= [nodes[i] for i in 1:6, j in 1:6] + node_coordinates[2, :, :, 1] .= [nodes[j] for i in 1:6, j in 1:6] + expected = zeros(2, 2, 6, 6, 1) + expected[1, 1, :, :, 1] .= 1 + expected[2, 2, :, :, 1] .= 1 + @test Trixi.calc_jacobian_matrix!( + jacobian_matrix, 1, node_coordinates, + basis + ) ≈ expected + end + + @testset "maximum exact polydeg" begin + basis = LobattoLegendreBasis(3) + nodes = Trixi.get_nodes(basis) + jacobian_matrix = Array{Float64, 5}(undef, 2, 2, 4, 4, 1) + + # f(x, y) = [x^3, xy^2] + node_coordinates = Array{Float64, 4}(undef, 2, 4, 4, 1) + node_coordinates[1, :, :, 1] .= [nodes[i]^3 for i in 1:4, j in 1:4] + node_coordinates[2, :, :, 1] .= [ + nodes[i] * nodes[j]^2 + for i in 1:4, j in 1:4 + ] + + # Df(x, y) = [3x^2 0; + # y^2 2xy] + expected = zeros(2, 2, 4, 4, 1) + expected[1, 1, :, :, 1] .= [3 * nodes[i]^2 for i in 1:4, j in 1:4] + expected[2, 1, :, :, 1] .= [nodes[j]^2 for i in 1:4, j in 1:4] + expected[2, 2, :, :, 1] .= [2 * nodes[i] * nodes[j] for i in 1:4, j in 1:4] + @test Trixi.calc_jacobian_matrix!( + jacobian_matrix, 1, node_coordinates, + basis + ) ≈ expected + end end end -end -@timed_testset "interpolation" begin - @testset "nodes and weights" begin - @test Trixi.gauss_nodes_weights(1) == ([0.0], [2.0]) - end + @timed_testset "interpolation" begin + @testset "nodes and weights" begin + @test Trixi.gauss_nodes_weights(1) == ([0.0], [2.0]) + end - @testset "multiply_dimensionwise" begin - nodes_in = [0.0, 0.5, 1.0] - nodes_out = [0.0, 1 / 3, 2 / 3, 1.0] - matrix = Trixi.polynomial_interpolation_matrix(nodes_in, nodes_out) - data_in = [3.0 4.5 6.0] - @test isapprox(Trixi.multiply_dimensionwise(matrix, data_in), [3.0 4.0 5.0 6.0]) - - n_vars = 3 - size_in = 2 - size_out = 3 - matrix = randn(size_out, size_in) - # 1D - data_in = randn(n_vars, size_in) - data_out = Trixi.multiply_dimensionwise_naive(matrix, data_in) - @test isapprox(data_out, Trixi.multiply_dimensionwise(matrix, data_in)) - # 2D - data_in = randn(n_vars, size_in, size_in) - data_out = Trixi.multiply_dimensionwise_naive(matrix, data_in) - @test isapprox(data_out, Trixi.multiply_dimensionwise(matrix, data_in)) - # 3D - data_in = randn(n_vars, size_in, size_in, size_in) - data_out = Trixi.multiply_dimensionwise_naive(matrix, data_in) - @test isapprox(data_out, Trixi.multiply_dimensionwise(matrix, data_in)) + @testset "multiply_dimensionwise" begin + nodes_in = [0.0, 0.5, 1.0] + nodes_out = [0.0, 1 / 3, 2 / 3, 1.0] + matrix = Trixi.polynomial_interpolation_matrix(nodes_in, nodes_out) + data_in = [3.0 4.5 6.0] + @test isapprox(Trixi.multiply_dimensionwise(matrix, data_in), [3.0 4.0 5.0 6.0]) + + n_vars = 3 + size_in = 2 + size_out = 3 + matrix = randn(size_out, size_in) + # 1D + data_in = randn(n_vars, size_in) + data_out = Trixi.multiply_dimensionwise_naive(matrix, data_in) + @test isapprox(data_out, Trixi.multiply_dimensionwise(matrix, data_in)) + # 2D + data_in = randn(n_vars, size_in, size_in) + data_out = Trixi.multiply_dimensionwise_naive(matrix, data_in) + @test isapprox(data_out, Trixi.multiply_dimensionwise(matrix, data_in)) + # 3D + data_in = randn(n_vars, size_in, size_in, size_in) + data_out = Trixi.multiply_dimensionwise_naive(matrix, data_in) + @test isapprox(data_out, Trixi.multiply_dimensionwise(matrix, data_in)) + end end -end -@timed_testset "L2 projection" begin - @testset "calc_reverse_upper for LGL" begin - @test isapprox(Trixi.calc_reverse_upper(2, Val(:gauss_lobatto)), - [[0.25, 0.25] [0.0, 0.5]]) - end - @testset "calc_reverse_lower for LGL" begin - @test isapprox(Trixi.calc_reverse_lower(2, Val(:gauss_lobatto)), - [[0.5, 0.0] [0.25, 0.25]]) + @timed_testset "L2 projection" begin + @testset "calc_reverse_upper for LGL" begin + @test isapprox( + Trixi.calc_reverse_upper(2, Val(:gauss_lobatto)), + [[0.25, 0.25] [0.0, 0.5]] + ) + end + @testset "calc_reverse_lower for LGL" begin + @test isapprox( + Trixi.calc_reverse_lower(2, Val(:gauss_lobatto)), + [[0.5, 0.0] [0.25, 0.25]] + ) + end end -end -@testset "containers" begin - # Set up mock container - mutable struct MyContainer <: Trixi.AbstractContainer - data::Vector{Int} - capacity::Int - length::Int - dummy::Int - end - function MyContainer(data, capacity) - c = MyContainer(Vector{Int}(undef, capacity + 1), capacity, length(data), - capacity + 1) - c.data[eachindex(data)] .= data - return c - end - MyContainer(data::AbstractArray) = MyContainer(data, length(data)) - Trixi.invalidate!(c::MyContainer, first, last) = (c.data[first:last] .= 0; c) - function Trixi.raw_copy!(target::MyContainer, source::MyContainer, first, last, - destination) - Trixi.copy_data!(target.data, source.data, first, last, destination) - return target - end - Trixi.move_connectivity!(c::MyContainer, first, last, destination) = c - Trixi.delete_connectivity!(c::MyContainer, first, last) = c - function Trixi.reset_data_structures!(c::MyContainer) - (c.data = Vector{Int}(undef, - c.capacity + 1); - c) - end - function Base.:(==)(c1::MyContainer, c2::MyContainer) - return (c1.capacity == c2.capacity && - c1.length == c2.length && - c1.dummy == c2.dummy && - c1.data[1:(c1.length)] == c2.data[1:(c2.length)]) - end + @testset "containers" begin + # Set up mock container + mutable struct MyContainer <: Trixi.AbstractContainer + data::Vector{Int} + capacity::Int + length::Int + dummy::Int + end + function MyContainer(data, capacity) + c = MyContainer( + Vector{Int}(undef, capacity + 1), capacity, length(data), + capacity + 1 + ) + c.data[eachindex(data)] .= data + return c + end + MyContainer(data::AbstractArray) = MyContainer(data, length(data)) + Trixi.invalidate!(c::MyContainer, first, last) = (c.data[first:last] .= 0; c) + function Trixi.raw_copy!( + target::MyContainer, source::MyContainer, first, last, + destination + ) + Trixi.copy_data!(target.data, source.data, first, last, destination) + return target + end + Trixi.move_connectivity!(c::MyContainer, first, last, destination) = c + Trixi.delete_connectivity!(c::MyContainer, first, last) = c + function Trixi.reset_data_structures!(c::MyContainer) + ( + c.data = Vector{Int}( + undef, + c.capacity + 1 + ); + c + ) + end + function Base.:(==)(c1::MyContainer, c2::MyContainer) + return ( + c1.capacity == c2.capacity && + c1.length == c2.length && + c1.dummy == c2.dummy && + c1.data[1:(c1.length)] == c2.data[1:(c2.length)] + ) + end - @testset "size" begin - c = MyContainer([1, 2, 3]) - @test size(c) == (3,) - end + @testset "size" begin + c = MyContainer([1, 2, 3]) + @test size(c) == (3,) + end - @testset "resize!" begin - c = MyContainer([1, 2, 3]) - @test length(resize!(c, 2)) == 2 - end + @testset "resize!" begin + c = MyContainer([1, 2, 3]) + @test length(resize!(c, 2)) == 2 + end - @testset "copy!" begin - c1 = MyContainer([1, 2, 3]) - c2 = MyContainer([4, 5]) - @test Trixi.copy!(c1, c2, 2, 1, 2) == MyContainer([1, 2, 3]) # no-op + @testset "copy!" begin + c1 = MyContainer([1, 2, 3]) + c2 = MyContainer([4, 5]) + @test Trixi.copy!(c1, c2, 2, 1, 2) == MyContainer([1, 2, 3]) # no-op - c1 = MyContainer([1, 2, 3]) - c2 = MyContainer([4, 5]) - @test Trixi.copy!(c1, c2, 1, 2, 2) == MyContainer([1, 4, 5]) + c1 = MyContainer([1, 2, 3]) + c2 = MyContainer([4, 5]) + @test Trixi.copy!(c1, c2, 1, 2, 2) == MyContainer([1, 4, 5]) - c1 = MyContainer([1, 2, 3]) - @test Trixi.copy!(c1, c2, 1, 2) == MyContainer([1, 4, 3]) + c1 = MyContainer([1, 2, 3]) + @test Trixi.copy!(c1, c2, 1, 2) == MyContainer([1, 4, 3]) - c1 = MyContainer([1, 2, 3]) - @test Trixi.copy!(c1, 2, 3, 1) == MyContainer([2, 3, 3]) + c1 = MyContainer([1, 2, 3]) + @test Trixi.copy!(c1, 2, 3, 1) == MyContainer([2, 3, 3]) - c1 = MyContainer([1, 2, 3]) - @test Trixi.copy!(c1, 1, 3) == MyContainer([1, 2, 1]) - end + c1 = MyContainer([1, 2, 3]) + @test Trixi.copy!(c1, 1, 3) == MyContainer([1, 2, 1]) + end - @testset "move!" begin - c = MyContainer([1, 2, 3]) - @test Trixi.move!(c, 1, 1) == MyContainer([1, 2, 3]) # no-op + @testset "move!" begin + c = MyContainer([1, 2, 3]) + @test Trixi.move!(c, 1, 1) == MyContainer([1, 2, 3]) # no-op - c = MyContainer([1, 2, 3]) - @test Trixi.move!(c, 1, 2) == MyContainer([0, 1, 3]) - end + c = MyContainer([1, 2, 3]) + @test Trixi.move!(c, 1, 2) == MyContainer([0, 1, 3]) + end - @testset "swap!" begin - c = MyContainer([1, 2]) - @test Trixi.swap!(c, 1, 1) == MyContainer([1, 2]) # no-op + @testset "swap!" begin + c = MyContainer([1, 2]) + @test Trixi.swap!(c, 1, 1) == MyContainer([1, 2]) # no-op - c = MyContainer([1, 2]) - @test Trixi.swap!(c, 1, 2) == MyContainer([2, 1]) - end + c = MyContainer([1, 2]) + @test Trixi.swap!(c, 1, 2) == MyContainer([2, 1]) + end - @testset "erase!" begin - c = MyContainer([1, 2]) - @test Trixi.erase!(c, 2, 1) == MyContainer([1, 2]) # no-op + @testset "erase!" begin + c = MyContainer([1, 2]) + @test Trixi.erase!(c, 2, 1) == MyContainer([1, 2]) # no-op - c = MyContainer([1, 2]) - @test Trixi.erase!(c, 1) == MyContainer([0, 2]) - end + c = MyContainer([1, 2]) + @test Trixi.erase!(c, 1) == MyContainer([0, 2]) + end - @testset "remove_shift!" begin - c = MyContainer([1, 2, 3, 4]) - @test Trixi.remove_shift!(c, 2, 1) == MyContainer([1, 2, 3, 4]) # no-op + @testset "remove_shift!" begin + c = MyContainer([1, 2, 3, 4]) + @test Trixi.remove_shift!(c, 2, 1) == MyContainer([1, 2, 3, 4]) # no-op - c = MyContainer([1, 2, 3, 4]) - @test Trixi.remove_shift!(c, 2, 2) == MyContainer([1, 3, 4], 4) + c = MyContainer([1, 2, 3, 4]) + @test Trixi.remove_shift!(c, 2, 2) == MyContainer([1, 3, 4], 4) - c = MyContainer([1, 2, 3, 4]) - @test Trixi.remove_shift!(c, 2) == MyContainer([1, 3, 4], 4) - end + c = MyContainer([1, 2, 3, 4]) + @test Trixi.remove_shift!(c, 2) == MyContainer([1, 3, 4], 4) + end - @testset "remove_fill!" begin - c = MyContainer([1, 2, 3, 4]) - @test Trixi.remove_fill!(c, 2, 1) == MyContainer([1, 2, 3, 4]) # no-op + @testset "remove_fill!" begin + c = MyContainer([1, 2, 3, 4]) + @test Trixi.remove_fill!(c, 2, 1) == MyContainer([1, 2, 3, 4]) # no-op - c = MyContainer([1, 2, 3, 4]) - @test Trixi.remove_fill!(c, 2, 2) == MyContainer([1, 4, 3], 4) - end + c = MyContainer([1, 2, 3, 4]) + @test Trixi.remove_fill!(c, 2, 2) == MyContainer([1, 4, 3], 4) + end - @testset "reset!" begin - c = MyContainer([1, 2, 3]) - @test Trixi.reset!(c, 2) == MyContainer(Int[], 2) + @testset "reset!" begin + c = MyContainer([1, 2, 3]) + @test Trixi.reset!(c, 2) == MyContainer(Int[], 2) + end end -end - -@timed_testset "example elixirs" begin - @test basename(examples_dir()) == "examples" - @test !isempty(get_examples()) - @test endswith(default_example(), "elixir_advection_basic.jl") -end - -@timed_testset "HLL flux with vanishing wave speed estimates (#502)" begin - equations = CompressibleEulerEquations1D(1.4) - u = SVector(1.0, 0.0, 0.0) - @test !any(isnan, flux_hll(u, u, 1, equations)) -end -@timed_testset "DG L2 mortar container debug output" begin - c2d = Trixi.L2MortarContainer2D{Float64}(1, 1, 1) - @test isnothing(display(c2d)) - c3d = Trixi.L2MortarContainer3D{Float64}(1, 1, 1) - @test isnothing(display(c3d)) -end - -@timed_testset "Printing indicators/controllers" begin - # OBS! Constructing indicators/controllers using the parameters below doesn't make sense. It's - # just useful to run basic tests of `show` methods. - - c = ControllerThreeLevelCombined(1, 2, 3, 10.0, 11.0, 12.0, "primary", "secondary", - "cache") - @test_nowarn show(stdout, c) - - indicator_hg = IndicatorHennemannGassner(1.0, 0.0, true, "variable", "cache") - @test_nowarn show(stdout, indicator_hg) - - limiter_idp = SubcellLimiterIDP(true, [1], true, [1], ["variable"], 0.1, - true, [(Trixi.entropy_guermond_etal, min)], "cache", - 1, (1.0, 1.0), 1.0) - @test_nowarn show(stdout, limiter_idp) - - indicator_loehner = IndicatorLöhner(1.0, "variable", (; cache = nothing)) - @test_nowarn show(stdout, indicator_loehner) - - indicator_max = IndicatorMax("variable", (; cache = nothing)) - @test_nowarn show(stdout, indicator_max) -end - -@timed_testset "LBM 2D constructor" begin - # Neither Mach number nor velocity set - @test_throws ErrorException LatticeBoltzmannEquations2D(Ma = nothing, Re = 1000) - # Both Mach number and velocity set - @test_throws ErrorException LatticeBoltzmannEquations2D(Ma = 0.1, Re = 1000, - u0 = 1.0) - # Neither Reynolds number nor viscosity set - @test_throws ErrorException LatticeBoltzmannEquations2D(Ma = 0.1, Re = nothing) - # Both Reynolds number and viscosity set - @test_throws ErrorException LatticeBoltzmannEquations2D(Ma = 0.1, Re = 1000, - nu = 1.0) - - # No non-dimensional values set - @test LatticeBoltzmannEquations2D(Ma = nothing, Re = nothing, u0 = 1.0, - nu = 1.0) isa - LatticeBoltzmannEquations2D -end - -@timed_testset "LBM 3D constructor" begin - # Neither Mach number nor velocity set - @test_throws ErrorException LatticeBoltzmannEquations3D(Ma = nothing, Re = 1000) - # Both Mach number and velocity set - @test_throws ErrorException LatticeBoltzmannEquations3D(Ma = 0.1, Re = 1000, - u0 = 1.0) - # Neither Reynolds number nor viscosity set - @test_throws ErrorException LatticeBoltzmannEquations3D(Ma = 0.1, Re = nothing) - # Both Reynolds number and viscosity set - @test_throws ErrorException LatticeBoltzmannEquations3D(Ma = 0.1, Re = 1000, - nu = 1.0) - - # No non-dimensional values set - @test LatticeBoltzmannEquations3D(Ma = nothing, Re = nothing, u0 = 1.0, - nu = 1.0) isa - LatticeBoltzmannEquations3D -end - -@timed_testset "LBM 2D functions" begin - # Set up LBM struct and dummy distribution - equation = LatticeBoltzmannEquations2D(Ma = 0.1, Re = 1000) - u = Trixi.equilibrium_distribution(1, 2, 3, equation) - - # Component-wise velocity - @test isapprox(Trixi.velocity(u, 1, equation), 2) - @test isapprox(Trixi.velocity(u, 2, equation), 3) -end - -@timed_testset "LBM 3D functions" begin - # Set up LBM struct and dummy distribution - equation = LatticeBoltzmannEquations3D(Ma = 0.1, Re = 1000) - u = Trixi.equilibrium_distribution(1, 2, 3, 4, equation) - - # Component-wise velocity - @test isapprox(velocity(u, 1, equation), 2) - @test isapprox(velocity(u, 2, equation), 3) - @test isapprox(velocity(u, 3, equation), 4) -end - -@timed_testset "LBMCollisionCallback" begin - # Printing of LBM collision callback - callback = LBMCollisionCallback() - @test_nowarn show(stdout, callback) - println() - @test_nowarn show(stdout, "text/plain", callback) - println() -end - -@timed_testset "Acoustic perturbation 2D varnames" begin - v_mean_global = (0.0, 0.0) - c_mean_global = 1.0 - rho_mean_global = 1.0 - equations = AcousticPerturbationEquations2D(v_mean_global, c_mean_global, - rho_mean_global) - - @test Trixi.varnames(cons2state, equations) == - ("v1_prime", "v2_prime", "p_prime_scaled") - @test Trixi.varnames(cons2mean, equations) == - ("v1_mean", "v2_mean", "c_mean", "rho_mean") -end - -@timed_testset "Euler conversion between conservative/entropy variables" begin - rho, v1, v2, v3, p = 1.0, 0.1, 0.2, 0.3, 2.0 - - let equations = CompressibleEulerEquations1D(1.4) - cons_vars = prim2cons(SVector(rho, v1, p), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) - - # test tuple args - cons_vars = prim2cons((rho, v1, p), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) - end + @timed_testset "example elixirs" begin + @test basename(examples_dir()) == "examples" + @test !isempty(get_examples()) + @test endswith(default_example(), "elixir_advection_basic.jl") + end + + @timed_testset "HLL flux with vanishing wave speed estimates (#502)" begin + equations = CompressibleEulerEquations1D(1.4) + u = SVector(1.0, 0.0, 0.0) + @test !any(isnan, flux_hll(u, u, 1, equations)) + end + + @timed_testset "DG L2 mortar container debug output" begin + c2d = Trixi.L2MortarContainer2D{Float64}(1, 1, 1) + @test isnothing(display(c2d)) + c3d = Trixi.L2MortarContainer3D{Float64}(1, 1, 1) + @test isnothing(display(c3d)) + end + + @timed_testset "Printing indicators/controllers" begin + # OBS! Constructing indicators/controllers using the parameters below doesn't make sense. It's + # just useful to run basic tests of `show` methods. + + c = ControllerThreeLevelCombined( + 1, 2, 3, 10.0, 11.0, 12.0, "primary", "secondary", + "cache" + ) + @test_nowarn show(stdout, c) + + indicator_hg = IndicatorHennemannGassner(1.0, 0.0, true, "variable", "cache") + @test_nowarn show(stdout, indicator_hg) + + limiter_idp = SubcellLimiterIDP( + true, [1], true, [1], ["variable"], 0.1, + true, [(Trixi.entropy_guermond_etal, min)], "cache", + 1, (1.0, 1.0), 1.0 + ) + @test_nowarn show(stdout, limiter_idp) + + indicator_loehner = IndicatorLöhner(1.0, "variable", (; cache = nothing)) + @test_nowarn show(stdout, indicator_loehner) + + indicator_max = IndicatorMax("variable", (; cache = nothing)) + @test_nowarn show(stdout, indicator_max) + end + + @timed_testset "LBM 2D constructor" begin + # Neither Mach number nor velocity set + @test_throws ErrorException LatticeBoltzmannEquations2D(Ma = nothing, Re = 1000) + # Both Mach number and velocity set + @test_throws ErrorException LatticeBoltzmannEquations2D( + Ma = 0.1, Re = 1000, + u0 = 1.0 + ) + # Neither Reynolds number nor viscosity set + @test_throws ErrorException LatticeBoltzmannEquations2D(Ma = 0.1, Re = nothing) + # Both Reynolds number and viscosity set + @test_throws ErrorException LatticeBoltzmannEquations2D( + Ma = 0.1, Re = 1000, + nu = 1.0 + ) + + # No non-dimensional values set + @test LatticeBoltzmannEquations2D( + Ma = nothing, Re = nothing, u0 = 1.0, + nu = 1.0 + ) isa + LatticeBoltzmannEquations2D + end + + @timed_testset "LBM 3D constructor" begin + # Neither Mach number nor velocity set + @test_throws ErrorException LatticeBoltzmannEquations3D(Ma = nothing, Re = 1000) + # Both Mach number and velocity set + @test_throws ErrorException LatticeBoltzmannEquations3D( + Ma = 0.1, Re = 1000, + u0 = 1.0 + ) + # Neither Reynolds number nor viscosity set + @test_throws ErrorException LatticeBoltzmannEquations3D(Ma = 0.1, Re = nothing) + # Both Reynolds number and viscosity set + @test_throws ErrorException LatticeBoltzmannEquations3D( + Ma = 0.1, Re = 1000, + nu = 1.0 + ) + + # No non-dimensional values set + @test LatticeBoltzmannEquations3D( + Ma = nothing, Re = nothing, u0 = 1.0, + nu = 1.0 + ) isa + LatticeBoltzmannEquations3D + end + + @timed_testset "LBM 2D functions" begin + # Set up LBM struct and dummy distribution + equation = LatticeBoltzmannEquations2D(Ma = 0.1, Re = 1000) + u = Trixi.equilibrium_distribution(1, 2, 3, equation) + + # Component-wise velocity + @test isapprox(Trixi.velocity(u, 1, equation), 2) + @test isapprox(Trixi.velocity(u, 2, equation), 3) + end + + @timed_testset "LBM 3D functions" begin + # Set up LBM struct and dummy distribution + equation = LatticeBoltzmannEquations3D(Ma = 0.1, Re = 1000) + u = Trixi.equilibrium_distribution(1, 2, 3, 4, equation) + + # Component-wise velocity + @test isapprox(velocity(u, 1, equation), 2) + @test isapprox(velocity(u, 2, equation), 3) + @test isapprox(velocity(u, 3, equation), 4) + end + + @timed_testset "LBMCollisionCallback" begin + # Printing of LBM collision callback + callback = LBMCollisionCallback() + @test_nowarn show(stdout, callback) + println() + @test_nowarn show(stdout, "text/plain", callback) + println() + end + + @timed_testset "Acoustic perturbation 2D varnames" begin + v_mean_global = (0.0, 0.0) + c_mean_global = 1.0 + rho_mean_global = 1.0 + equations = AcousticPerturbationEquations2D( + v_mean_global, c_mean_global, + rho_mean_global + ) + + @test Trixi.varnames(cons2state, equations) == + ("v1_prime", "v2_prime", "p_prime_scaled") + @test Trixi.varnames(cons2mean, equations) == + ("v1_mean", "v2_mean", "c_mean", "rho_mean") + end + + @timed_testset "Euler conversion between conservative/entropy variables" begin + rho, v1, v2, v3, p = 1.0, 0.1, 0.2, 0.3, 2.0 + + let equations = CompressibleEulerEquations1D(1.4) + cons_vars = prim2cons(SVector(rho, v1, p), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) + + # test tuple args + cons_vars = prim2cons((rho, v1, p), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) + end - let equations = CompressibleEulerEquations2D(1.4) - cons_vars = prim2cons(SVector(rho, v1, v2, p), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) + let equations = CompressibleEulerEquations2D(1.4) + cons_vars = prim2cons(SVector(rho, v1, v2, p), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) - # test tuple args - cons_vars = prim2cons((rho, v1, v2, p), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) - end + # test tuple args + cons_vars = prim2cons((rho, v1, v2, p), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) + end - let equations = CompressibleEulerEquations3D(1.4) - cons_vars = prim2cons(SVector(rho, v1, v2, v3, p), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) + let equations = CompressibleEulerEquations3D(1.4) + cons_vars = prim2cons(SVector(rho, v1, v2, v3, p), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) - # test tuple args - cons_vars = prim2cons((rho, v1, v2, v3, p), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) + # test tuple args + cons_vars = prim2cons((rho, v1, v2, v3, p), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) + end end -end -@timed_testset "Shallow water conversion between conservative/entropy variables" begin - H, v1, v2, b, a = 3.5, 0.25, 0.1, 0.4, 0.3 + @timed_testset "Shallow water conversion between conservative/entropy variables" begin + H, v1, v2, b, a = 3.5, 0.25, 0.1, 0.4, 0.3 - let equations = ShallowWaterEquations1D(gravity_constant = 9.8) - cons_vars = prim2cons(SVector(H, v1, b), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) + let equations = ShallowWaterEquations1D(gravity_constant = 9.8) + cons_vars = prim2cons(SVector(H, v1, b), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) - total_energy = energy_total(cons_vars, equations) - @test total_energy ≈ entropy(cons_vars, equations) + total_energy = energy_total(cons_vars, equations) + @test total_energy ≈ entropy(cons_vars, equations) - # test tuple args - cons_vars = prim2cons((H, v1, b), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) - end + # test tuple args + cons_vars = prim2cons((H, v1, b), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) + end - let equations = ShallowWaterEquations2D(gravity_constant = 9.8) - cons_vars = prim2cons(SVector(H, v1, v2, b), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) + let equations = ShallowWaterEquations2D(gravity_constant = 9.8) + cons_vars = prim2cons(SVector(H, v1, v2, b), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) - total_energy = energy_total(cons_vars, equations) - @test total_energy ≈ entropy(cons_vars, equations) + total_energy = energy_total(cons_vars, equations) + @test total_energy ≈ entropy(cons_vars, equations) - # test tuple args - cons_vars = prim2cons((H, v1, v2, b), equations) - entropy_vars = cons2entropy(cons_vars, equations) - @test cons_vars ≈ entropy2cons(entropy_vars, equations) - end + # test tuple args + cons_vars = prim2cons((H, v1, v2, b), equations) + entropy_vars = cons2entropy(cons_vars, equations) + @test cons_vars ≈ entropy2cons(entropy_vars, equations) + end - let equations = ShallowWaterEquationsQuasi1D(gravity_constant = 9.8) - cons_vars = prim2cons(SVector(H, v1, b, a), equations) - entropy_vars = cons2entropy(cons_vars, equations) + let equations = ShallowWaterEquationsQuasi1D(gravity_constant = 9.8) + cons_vars = prim2cons(SVector(H, v1, b, a), equations) + entropy_vars = cons2entropy(cons_vars, equations) - total_energy = energy_total(cons_vars, equations) - @test entropy(cons_vars, equations) ≈ a * total_energy + total_energy = energy_total(cons_vars, equations) + @test entropy(cons_vars, equations) ≈ a * total_energy + end end -end -@timed_testset "boundary_condition_do_nothing" begin - rho, v1, v2, p = 1.0, 0.1, 0.2, 0.3, 2.0 - - let equations = CompressibleEulerEquations2D(1.4) - u = prim2cons(SVector(rho, v1, v2, p), equations) - x = SVector(1.0, 2.0) - t = 0.5 - surface_flux = flux_lax_friedrichs - - outward_direction = SVector(0.2, -0.3) - @test flux(u, outward_direction, equations) ≈ - boundary_condition_do_nothing(u, outward_direction, x, t, surface_flux, - equations) - - orientation = 2 - direction = 4 - @test flux(u, orientation, equations) ≈ - boundary_condition_do_nothing(u, orientation, direction, x, t, - surface_flux, equations) + @timed_testset "boundary_condition_do_nothing" begin + rho, v1, v2, p = 1.0, 0.1, 0.2, 0.3, 2.0 + + let equations = CompressibleEulerEquations2D(1.4) + u = prim2cons(SVector(rho, v1, v2, p), equations) + x = SVector(1.0, 2.0) + t = 0.5 + surface_flux = flux_lax_friedrichs + + outward_direction = SVector(0.2, -0.3) + @test flux(u, outward_direction, equations) ≈ + boundary_condition_do_nothing( + u, outward_direction, x, t, surface_flux, + equations + ) + + orientation = 2 + direction = 4 + @test flux(u, orientation, equations) ≈ + boundary_condition_do_nothing( + u, orientation, direction, x, t, + surface_flux, equations + ) + end end -end - -@timed_testset "TimeSeriesCallback" begin - # Test the 2D TreeMesh version of the callback and some warnings - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_acoustics_gaussian_source.jl"), - tspan = (0, 0.05)) - - point_data_1 = time_series.affect!.point_data[1] - @test all(isapprox.(point_data_1[1:7], - [-2.4417734981719132e-5, -3.4296207289200194e-5, - 0.0018130846385739788, -0.5, 0.25, 1.0, 1.0])) - @test_throws DimensionMismatch Trixi.get_elements_by_coordinates!([1, 2], - rand(2, 4), mesh, - solver, nothing) - @test_nowarn show(stdout, time_series) - @test_throws ArgumentError TimeSeriesCallback(semi, [(1.0, 1.0)]; interval = -1) - @test_throws ArgumentError TimeSeriesCallback(semi, [1.0 1.0 1.0; 2.0 2.0 2.0]) -end -@timed_testset "Consistency check for single point flux: CEMCE" begin - equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.4), - gas_constants = (0.4, 0.4)) - u = SVector(0.1, -0.5, 1.0, 1.0, 2.0) + @timed_testset "TimeSeriesCallback" begin + # Test the 2D TreeMesh version of the callback and some warnings + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_acoustics_gaussian_source.jl" + ), + tspan = (0, 0.05) + ) + + point_data_1 = time_series.affect!.point_data[1] + @test all( + isapprox.( + point_data_1[1:7], + [ + -2.4417734981719132e-5, -3.4296207289200194e-5, + 0.0018130846385739788, -0.5, 0.25, 1.0, 1.0, + ] + ) + ) + @test_throws DimensionMismatch Trixi.get_elements_by_coordinates!( + [1, 2], + rand(2, 4), mesh, + solver, nothing + ) + @test_nowarn show(stdout, time_series) + @test_throws ArgumentError TimeSeriesCallback(semi, [(1.0, 1.0)]; interval = -1) + @test_throws ArgumentError TimeSeriesCallback(semi, [1.0 1.0 1.0; 2.0 2.0 2.0]) + end + + @timed_testset "Consistency check for single point flux: CEMCE" begin + equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.4), + gas_constants = (0.4, 0.4) + ) + u = SVector(0.1, -0.5, 1.0, 1.0, 2.0) - orientations = [1, 2] - for orientation in orientations - @test flux(u, orientation, equations) ≈ - flux_ranocha(u, u, orientation, equations) + orientations = [1, 2] + for orientation in orientations + @test flux(u, orientation, equations) ≈ + flux_ranocha(u, u, orientation, equations) + end end -end -@timed_testset "Consistency check for HLL flux (naive): CEE" begin - flux_hll = FluxHLL(min_max_speed_naive) + @timed_testset "Consistency check for HLL flux (naive): CEE" begin + flux_hll = FluxHLL(min_max_speed_naive) - # Set up equations and dummy conservative variables state - equations = CompressibleEulerEquations1D(1.4) - u = SVector(1.1, 2.34, 5.5) + # Set up equations and dummy conservative variables state + equations = CompressibleEulerEquations1D(1.4) + u = SVector(1.1, 2.34, 5.5) - orientations = [1] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + orientations = [1] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - equations = CompressibleEulerEquations2D(1.4) - u = SVector(1.1, -0.5, 2.34, 5.5) + equations = CompressibleEulerEquations2D(1.4) + u = SVector(1.1, -0.5, 2.34, 5.5) - orientations = [1, 2] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + orientations = [1, 2] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - equations = CompressibleEulerEquations3D(1.4) - u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) + equations = CompressibleEulerEquations3D(1.4) + u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) - orientations = [1, 2, 3] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + orientations = [1, 2, 3] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end end -end -@timed_testset "Consistency check for flux_chan_etal: CEEQ" begin + @timed_testset "Consistency check for flux_chan_etal: CEEQ" begin - # Set up equations and dummy conservative variables state - equations = CompressibleEulerEquationsQuasi1D(1.4) - u = SVector(1.1, 2.34, 5.5, 2.73) + # Set up equations and dummy conservative variables state + equations = CompressibleEulerEquationsQuasi1D(1.4) + u = SVector(1.1, 2.34, 5.5, 2.73) - orientations = [1] - for orientation in orientations - @test flux_chan_etal(u, u, orientation, equations) ≈ - flux(u, orientation, equations) + orientations = [1] + for orientation in orientations + @test flux_chan_etal(u, u, orientation, equations) ≈ + flux(u, orientation, equations) + end end -end -@timed_testset "Consistency check for HLL flux (naive): LEE" begin - flux_hll = FluxHLL(min_max_speed_naive) + @timed_testset "Consistency check for HLL flux (naive): LEE" begin + flux_hll = FluxHLL(min_max_speed_naive) - equations = LinearizedEulerEquations2D(SVector(1.0, 1.0), 1.0, 1.0) - u = SVector(1.1, -0.5, 2.34, 5.5) + equations = LinearizedEulerEquations2D(SVector(1.0, 1.0), 1.0, 1.0) + u = SVector(1.1, -0.5, 2.34, 5.5) - orientations = [1, 2] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end - - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] + orientations = [1, 2] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - for normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end -end + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] -@timed_testset "Consistency check for HLL flux (naive): SWE" begin - flux_hll = FluxHLL(min_max_speed_naive) - - equations = ShallowWaterEquations1D(gravity_constant = 9.81) - u = SVector(1, 0.5, 0.0) - @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) - - u_ll = SVector(0.1, 1.0, 0.0) - u_rr = SVector(0.1, 1.0, 0.0) - @test flux_hll(u_ll, u_rr, 1, equations) ≈ flux(u_ll, 1, equations) - - u_ll = SVector(0.1, -1.0, 0.0) - u_rr = SVector(0.1, -1.0, 0.0) - @test flux_hll(u_ll, u_rr, 1, equations) ≈ flux(u_rr, 1, equations) - - equations = ShallowWaterEquations2D(gravity_constant = 9.81) - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - u = SVector(1, 0.5, 0.5, 0.0) - for normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end end - normal_direction = SVector(1.0, 0.0, 0.0) - u_ll = SVector(0.1, 1.0, 1.0, 0.0) - u_rr = SVector(0.1, 1.0, 1.0, 0.0) - @test flux_hll(u_ll, u_rr, normal_direction, equations) ≈ - flux(u_ll, normal_direction, equations) - - u_ll = SVector(0.1, -1.0, -1.0, 0.0) - u_rr = SVector(0.1, -1.0, -1.0, 0.0) - @test flux_hll(u_ll, u_rr, normal_direction, equations) ≈ - flux(u_rr, normal_direction, equations) -end - -@timed_testset "Consistency check for HLL flux (naive): MHD" begin - flux_hll = FluxHLL(min_max_speed_naive) - - equations = IdealGlmMhdEquations1D(1.4) - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2)] + @timed_testset "Consistency check for HLL flux (naive): SWE" begin + flux_hll = FluxHLL(min_max_speed_naive) - for u in u_values + equations = ShallowWaterEquations1D(gravity_constant = 9.81) + u = SVector(1, 0.5, 0.0) @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) - end - - equations = IdealGlmMhdEquations2D(1.4, 5.0) #= c_h =# - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - orientations = [1, 2] - - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2)] - - for u in u_values, orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end - for u in u_values, normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end + u_ll = SVector(0.1, 1.0, 0.0) + u_rr = SVector(0.1, 1.0, 0.0) + @test flux_hll(u_ll, u_rr, 1, equations) ≈ flux(u_ll, 1, equations) - equations = IdealGlmMhdEquations3D(1.4, 5.0) #= c_h =# - normal_directions = [SVector(1.0, 0.0, 0.0), - SVector(0.0, 1.0, 0.0), - SVector(0.0, 0.0, 1.0), - SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] - orientations = [1, 2, 3] + u_ll = SVector(0.1, -1.0, 0.0) + u_rr = SVector(0.1, -1.0, 0.0) + @test flux_hll(u_ll, u_rr, 1, equations) ≈ flux(u_rr, 1, equations) - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2)] + equations = ShallowWaterEquations2D(gravity_constant = 9.81) + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + u = SVector(1, 0.5, 0.5, 0.0) + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end - for u in u_values, orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + normal_direction = SVector(1.0, 0.0, 0.0) + u_ll = SVector(0.1, 1.0, 1.0, 0.0) + u_rr = SVector(0.1, 1.0, 1.0, 0.0) + @test flux_hll(u_ll, u_rr, normal_direction, equations) ≈ + flux(u_ll, normal_direction, equations) - for u in u_values, normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) + u_ll = SVector(0.1, -1.0, -1.0, 0.0) + u_rr = SVector(0.1, -1.0, -1.0, 0.0) + @test flux_hll(u_ll, u_rr, normal_direction, equations) ≈ + flux(u_rr, normal_direction, equations) end -end -@timed_testset "Consistency check for HLL flux with Davis wave speed estimates: CEE" begin - flux_hll = FluxHLL(min_max_speed_davis) + @timed_testset "Consistency check for HLL flux (naive): MHD" begin + flux_hll = FluxHLL(min_max_speed_naive) - # Set up equations and dummy conservative variables state - equations = CompressibleEulerEquations1D(1.4) - u = SVector(1.1, 2.34, 5.5) + equations = IdealGlmMhdEquations1D(1.4) + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2), + ] - orientations = [1] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + for u in u_values + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + end - equations = CompressibleEulerEquations2D(1.4) - u = SVector(1.1, -0.5, 2.34, 5.5) + equations = IdealGlmMhdEquations2D(1.4, 5.0) #= c_h =# + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + orientations = [1, 2] - orientations = [1, 2] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2), + ] - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - for normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end - equations = CompressibleEulerEquations3D(1.4) - u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) + equations = IdealGlmMhdEquations3D(1.4, 5.0) #= c_h =# + normal_directions = [ + SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4), + ] + orientations = [1, 2, 3] - orientations = [1, 2, 3] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2), + ] - normal_directions = [SVector(1.0, 0.0, 0.0), - SVector(0.0, 1.0, 0.0), - SVector(0.0, 0.0, 1.0), - SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - for normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end end -end -@timed_testset "Consistency check for HLL flux with Davis wave speed estimates: Polytropic CEE" begin - flux_hll = FluxHLL(min_max_speed_davis) + @timed_testset "Consistency check for HLL flux with Davis wave speed estimates: CEE" begin + flux_hll = FluxHLL(min_max_speed_davis) - gamma = 1.4 - kappa = 0.5 # Scaling factor for the pressure. - equations = PolytropicEulerEquations2D(gamma, kappa) - u = SVector(1.1, -0.5, 2.34) + # Set up equations and dummy conservative variables state + equations = CompressibleEulerEquations1D(1.4) + u = SVector(1.1, 2.34, 5.5) - orientations = [1, 2] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end - - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - - for normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end -end + orientations = [1] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end -@timed_testset "Consistency check for Winters flux: Polytropic CEE" begin - for gamma in [1.4, 1.0, 5 / 3] - kappa = 0.5 # Scaling factor for the pressure. - equations = PolytropicEulerEquations2D(gamma, kappa) - u = SVector(1.1, -0.5, 2.34) + equations = CompressibleEulerEquations2D(1.4) + u = SVector(1.1, -0.5, 2.34, 5.5) orientations = [1, 2] for orientation in orientations - @test flux_winters_etal(u, u, orientation, equations) ≈ - flux(u, orientation, equations) + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) end - normal_directions = [SVector(1.0, 0.0), + normal_directions = [ + SVector(1.0, 0.0), SVector(0.0, 1.0), SVector(0.5, -0.5), - SVector(-1.2, 0.3)] + SVector(-1.2, 0.3), + ] + + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end + + equations = CompressibleEulerEquations3D(1.4) + u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) + + orientations = [1, 2, 3] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end + + normal_directions = [ + SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4), + ] for normal_direction in normal_directions - @test flux_winters_etal(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) end end -end -@timed_testset "Consistency check for Lax-Friedrich flux: Polytropic CEE" begin - for gamma in [1.4, 1.0, 5 / 3] + @timed_testset "Consistency check for HLL flux with Davis wave speed estimates: Polytropic CEE" begin + flux_hll = FluxHLL(min_max_speed_davis) + + gamma = 1.4 kappa = 0.5 # Scaling factor for the pressure. equations = PolytropicEulerEquations2D(gamma, kappa) u = SVector(1.1, -0.5, 2.34) orientations = [1, 2] for orientation in orientations - @test flux_lax_friedrichs(u, u, orientation, equations) ≈ - flux(u, orientation, equations) + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) end - normal_directions = [SVector(1.0, 0.0), + normal_directions = [ + SVector(1.0, 0.0), SVector(0.0, 1.0), SVector(0.5, -0.5), - SVector(-1.2, 0.3)] + SVector(-1.2, 0.3), + ] for normal_direction in normal_directions - @test flux_lax_friedrichs(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) end end -end -@timed_testset "Consistency check for HLL flux with Davis wave speed estimates: LEE" begin - flux_hll = FluxHLL(min_max_speed_davis) + @timed_testset "Consistency check for Winters flux: Polytropic CEE" begin + for gamma in [1.4, 1.0, 5 / 3] + kappa = 0.5 # Scaling factor for the pressure. + equations = PolytropicEulerEquations2D(gamma, kappa) + u = SVector(1.1, -0.5, 2.34) - equations = LinearizedEulerEquations2D(SVector(1.0, 1.0), 1.0, 1.0) - u = SVector(1.1, -0.5, 2.34, 5.5) + orientations = [1, 2] + for orientation in orientations + @test flux_winters_etal(u, u, orientation, equations) ≈ + flux(u, orientation, equations) + end - orientations = [1, 2] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + + for normal_direction in normal_directions + @test flux_winters_etal(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end + end end - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] + @timed_testset "Consistency check for Lax-Friedrich flux: Polytropic CEE" begin + for gamma in [1.4, 1.0, 5 / 3] + kappa = 0.5 # Scaling factor for the pressure. + equations = PolytropicEulerEquations2D(gamma, kappa) + u = SVector(1.1, -0.5, 2.34) - for normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end -end + orientations = [1, 2] + for orientation in orientations + @test flux_lax_friedrichs(u, u, orientation, equations) ≈ + flux(u, orientation, equations) + end -@timed_testset "Consistency check for HLL flux with Davis wave speed estimates: SWE" begin - flux_hll = FluxHLL(min_max_speed_davis) - - equations = ShallowWaterEquations1D(gravity_constant = 9.81) - u = SVector(1, 0.5, 0.0) - @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) - - equations = ShallowWaterEquations2D(gravity_constant = 9.81) - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - u = SVector(1, 0.5, 0.5, 0.0) - for normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] - orientations = [1, 2] - for orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + for normal_direction in normal_directions + @test flux_lax_friedrichs(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end + end end -end - -@timed_testset "Consistency check for HLL flux with Davis wave speed estimates: MHD" begin - flux_hll = FluxHLL(min_max_speed_davis) - equations = IdealGlmMhdEquations1D(1.4) - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2)] + @timed_testset "Consistency check for HLL flux with Davis wave speed estimates: LEE" begin + flux_hll = FluxHLL(min_max_speed_davis) - for u in u_values - @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) - end + equations = LinearizedEulerEquations2D(SVector(1.0, 1.0), 1.0, 1.0) + u = SVector(1.1, -0.5, 2.34, 5.5) - equations = IdealGlmMhdEquations2D(1.4, 5.0) #= c_h =# - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - orientations = [1, 2] + orientations = [1, 2] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2)] + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] - for u in u_values, orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end end - for u in u_values, normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end + @timed_testset "Consistency check for HLL flux with Davis wave speed estimates: SWE" begin + flux_hll = FluxHLL(min_max_speed_davis) - equations = IdealGlmMhdEquations3D(1.4, 5.0) #= c_h =# - normal_directions = [SVector(1.0, 0.0, 0.0), - SVector(0.0, 1.0, 0.0), - SVector(0.0, 0.0, 1.0), - SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] - orientations = [1, 2, 3] + equations = ShallowWaterEquations1D(gravity_constant = 9.81) + u = SVector(1, 0.5, 0.0) + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2)] + equations = ShallowWaterEquations2D(gravity_constant = 9.81) + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + u = SVector(1, 0.5, 0.5, 0.0) + for normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end - for u in u_values, orientation in orientations - @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + orientations = [1, 2] + for orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end end - for u in u_values, normal_direction in normal_directions - @test flux_hll(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end -end + @timed_testset "Consistency check for HLL flux with Davis wave speed estimates: MHD" begin + flux_hll = FluxHLL(min_max_speed_davis) -@timed_testset "Consistency check for HLLE flux: CEE" begin - # Set up equations and dummy conservative variables state - equations = CompressibleEulerEquations1D(1.4) - u = SVector(1.1, 2.34, 5.5) + equations = IdealGlmMhdEquations1D(1.4) + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2), + ] - orientations = [1] - for orientation in orientations - @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + for u in u_values + @test flux_hll(u, u, 1, equations) ≈ flux(u, 1, equations) + end - equations = CompressibleEulerEquations2D(1.4) - u = SVector(1.1, -0.5, 2.34, 5.5) + equations = IdealGlmMhdEquations2D(1.4, 5.0) #= c_h =# + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + orientations = [1, 2] - orientations = [1, 2] - for orientation in orientations - @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2), + ] - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - for normal_direction in normal_directions - @test flux_hlle(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end - equations = CompressibleEulerEquations3D(1.4) - u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) + equations = IdealGlmMhdEquations3D(1.4, 5.0) #= c_h =# + normal_directions = [ + SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4), + ] + orientations = [1, 2, 3] - orientations = [1, 2, 3] - for orientation in orientations - @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2), + ] - normal_directions = [SVector(1.0, 0.0, 0.0), - SVector(0.0, 1.0, 0.0), - SVector(0.0, 0.0, 1.0), - SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] + for u in u_values, orientation in orientations + @test flux_hll(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - for normal_direction in normal_directions - @test flux_hlle(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) + for u in u_values, normal_direction in normal_directions + @test flux_hll(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end end -end - -@timed_testset "Consistency check for HLLE flux: SWE" begin - equations = ShallowWaterEquations1D(gravity_constant = 9.81) - u = SVector(1, 0.5, 0.0) - @test flux_hlle(u, u, 1, equations) ≈ flux(u, 1, equations) - equations = ShallowWaterEquations2D(gravity_constant = 9.81) - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - orientations = [1, 2] + @timed_testset "Consistency check for HLLE flux: CEE" begin + # Set up equations and dummy conservative variables state + equations = CompressibleEulerEquations1D(1.4) + u = SVector(1.1, 2.34, 5.5) - u = SVector(1, 0.5, 0.5, 0.0) + orientations = [1] + for orientation in orientations + @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - for orientation in orientations - @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + equations = CompressibleEulerEquations2D(1.4) + u = SVector(1.1, -0.5, 2.34, 5.5) - for normal_direction in normal_directions - @test flux_hlle(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end -end + orientations = [1, 2] + for orientation in orientations + @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end -@timed_testset "Consistency check for HLLE flux: MHD" begin - equations = IdealGlmMhdEquations1D(1.4) - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2)] + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] - for u in u_values - @test flux_hlle(u, u, 1, equations) ≈ flux(u, 1, equations) - @test flux_hllc(u, u, 1, equations) ≈ flux(u, 1, equations) - end + for normal_direction in normal_directions + @test flux_hlle(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end - equations = IdealGlmMhdEquations2D(1.4, 5.0) #= c_h =# - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - orientations = [1, 2] + equations = CompressibleEulerEquations3D(1.4) + u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2)] + orientations = [1, 2, 3] + for orientation in orientations + @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - for u in u_values, orientation in orientations - @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + normal_directions = [ + SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4), + ] - for u in u_values, normal_direction in normal_directions - @test flux_hlle(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) + for normal_direction in normal_directions + @test flux_hlle(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end end - equations = IdealGlmMhdEquations3D(1.4, 5.0) #= c_h =# - normal_directions = [SVector(1.0, 0.0, 0.0), - SVector(0.0, 1.0, 0.0), - SVector(0.0, 0.0, 1.0), - SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] - orientations = [1, 2, 3] - - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2)] + @timed_testset "Consistency check for HLLE flux: SWE" begin + equations = ShallowWaterEquations1D(gravity_constant = 9.81) + u = SVector(1, 0.5, 0.0) + @test flux_hlle(u, u, 1, equations) ≈ flux(u, 1, equations) - for u in u_values, orientation in orientations - @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + equations = ShallowWaterEquations2D(gravity_constant = 9.81) + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + orientations = [1, 2] - for u in u_values, normal_direction in normal_directions - @test flux_hlle(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end -end + u = SVector(1, 0.5, 0.5, 0.0) -@timed_testset "Consistency check for HLLC flux: CEE" begin - # Set up equations and dummy conservative variables state - equations = CompressibleEulerEquations2D(1.4) - u = SVector(1.1, -0.5, 2.34, 5.5) + for orientation in orientations + @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - orientations = [1, 2] - for orientation in orientations - @test flux_hllc(u, u, orientation, equations) ≈ flux(u, orientation, equations) + for normal_direction in normal_directions + @test flux_hlle(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end end - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - - for normal_direction in normal_directions - @test flux_hllc(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end + @timed_testset "Consistency check for HLLE flux: MHD" begin + equations = IdealGlmMhdEquations1D(1.4) + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2), + ] - equations = CompressibleEulerEquations3D(1.4) - u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) + for u in u_values + @test flux_hlle(u, u, 1, equations) ≈ flux(u, 1, equations) + @test flux_hllc(u, u, 1, equations) ≈ flux(u, 1, equations) + end - orientations = [1, 2, 3] - for orientation in orientations - @test flux_hllc(u, u, orientation, equations) ≈ flux(u, orientation, equations) - end + equations = IdealGlmMhdEquations2D(1.4, 5.0) #= c_h =# + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + orientations = [1, 2] - normal_directions = [SVector(1.0, 0.0, 0.0), - SVector(0.0, 1.0, 0.0), - SVector(0.0, 0.0, 1.0), - SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2), + ] - for normal_direction in normal_directions - @test flux_hllc(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end -end + for u in u_values, orientation in orientations + @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end -@timed_testset "Consistency check for Godunov flux" begin - # Set up equations and dummy conservative variables state - # Burgers' Equation + for u in u_values, normal_direction in normal_directions + @test flux_hlle(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end - equation = InviscidBurgersEquation1D() - u_values = [SVector(42.0), SVector(-42.0)] + equations = IdealGlmMhdEquations3D(1.4, 5.0) #= c_h =# + normal_directions = [ + SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4), + ] + orientations = [1, 2, 3] - orientations = [1] - for orientation in orientations, u in u_values - @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) - end + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2), + ] - # Linear Advection 1D - equation = LinearScalarAdvectionEquation1D(-4.2) - u = SVector(3.14159) + for u in u_values, orientation in orientations + @test flux_hlle(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - orientations = [1] - for orientation in orientations - @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) + for u in u_values, normal_direction in normal_directions + @test flux_hlle(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end end - # Linear Advection 2D - equation = LinearScalarAdvectionEquation2D(-4.2, 2.4) - u = SVector(3.14159) + @timed_testset "Consistency check for HLLC flux: CEE" begin + # Set up equations and dummy conservative variables state + equations = CompressibleEulerEquations2D(1.4) + u = SVector(1.1, -0.5, 2.34, 5.5) - orientations = [1, 2] - for orientation in orientations - @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) - end + orientations = [1, 2] + for orientation in orientations + @test flux_hllc(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] - for normal_direction in normal_directions - @test flux_godunov(u, u, normal_direction, equation) ≈ - flux(u, normal_direction, equation) - end + for normal_direction in normal_directions + @test flux_hllc(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end - # Linear Advection 3D - equation = LinearScalarAdvectionEquation3D(-4.2, 2.4, 1.2) - u = SVector(3.14159) + equations = CompressibleEulerEquations3D(1.4) + u = SVector(1.1, -0.5, 2.34, 2.4, 5.5) - orientations = [1, 2, 3] - for orientation in orientations - @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) - end + orientations = [1, 2, 3] + for orientation in orientations + @test flux_hllc(u, u, orientation, equations) ≈ flux(u, orientation, equations) + end - normal_directions = [SVector(1.0, 0.0, 0.0), - SVector(0.0, 1.0, 0.0), - SVector(0.0, 0.0, 1.0), - SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] + normal_directions = [ + SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4), + ] - for normal_direction in normal_directions - @test flux_godunov(u, u, normal_direction, equation) ≈ - flux(u, normal_direction, equation) + for normal_direction in normal_directions + @test flux_hllc(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) + end end - # Linearized Euler 2D - equation = LinearizedEulerEquations2D(v_mean_global = (0.5, -0.7), - c_mean_global = 1.1, - rho_mean_global = 1.2) - u_values = [SVector(1.0, 0.5, -0.7, 1.0), - SVector(1.5, -0.2, 0.1, 5.0)] + @timed_testset "Consistency check for Godunov flux" begin + # Set up equations and dummy conservative variables state + # Burgers' Equation - orientations = [1, 2] - for orientation in orientations, u in u_values - @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) - end + equation = InviscidBurgersEquation1D() + u_values = [SVector(42.0), SVector(-42.0)] - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] + orientations = [1] + for orientation in orientations, u in u_values + @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) + end - for normal_direction in normal_directions, u in u_values - @test flux_godunov(u, u, normal_direction, equation) ≈ - flux(u, normal_direction, equation) - end -end + # Linear Advection 1D + equation = LinearScalarAdvectionEquation1D(-4.2) + u = SVector(3.14159) -@timed_testset "Consistency check for Engquist-Osher flux" begin - # Set up equations and dummy conservative variables state - equation = InviscidBurgersEquation1D() - u_values = [SVector(42.0), SVector(-42.0)] + orientations = [1] + for orientation in orientations + @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) + end - orientations = [1] - for orientation in orientations, u in u_values - @test Trixi.flux_engquist_osher(u, u, orientation, equation) ≈ - flux(u, orientation, equation) - end + # Linear Advection 2D + equation = LinearScalarAdvectionEquation2D(-4.2, 2.4) + u = SVector(3.14159) - equation = LinearScalarAdvectionEquation1D(-4.2) - u = SVector(3.14159) + orientations = [1, 2] + for orientation in orientations + @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) + end - orientations = [1] - for orientation in orientations - @test Trixi.flux_engquist_osher(u, u, orientation, equation) ≈ - flux(u, orientation, equation) - end -end + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] -@testset "Consistency check for `gradient_conservative` routine" begin - # Set up conservative variables, equations - u = [ - 0.5011914484393387, - 0.8829127712445113, - 0.43024132987932817, - 0.7560616633050348, - ] + for normal_direction in normal_directions + @test flux_godunov(u, u, normal_direction, equation) ≈ + flux(u, normal_direction, equation) + end - equations = CompressibleEulerEquations2D(1.4) + # Linear Advection 3D + equation = LinearScalarAdvectionEquation3D(-4.2, 2.4, 1.2) + u = SVector(3.14159) - # Define wrapper function for pressure in order to call default implementation - function pressure_test(u, equations) - return pressure(u, equations) - end + orientations = [1, 2, 3] + for orientation in orientations + @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) + end - @test Trixi.gradient_conservative(pressure_test, u, equations) ≈ - Trixi.gradient_conservative(pressure, u, equations) -end + normal_directions = [ + SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4), + ] -@testset "Equivalent Fluxes" begin - # Set up equations and dummy conservative variables state - # Burgers' Equation + for normal_direction in normal_directions + @test flux_godunov(u, u, normal_direction, equation) ≈ + flux(u, normal_direction, equation) + end - equation = InviscidBurgersEquation1D() - u_values = [SVector(42.0), SVector(-42.0)] + # Linearized Euler 2D + equation = LinearizedEulerEquations2D( + v_mean_global = (0.5, -0.7), + c_mean_global = 1.1, + rho_mean_global = 1.2 + ) + u_values = [ + SVector(1.0, 0.5, -0.7, 1.0), + SVector(1.5, -0.2, 0.1, 5.0), + ] - orientations = [1] - for orientation in orientations, u in u_values - @test flux_godunov(0.75 * u, u, orientation, equation) ≈ - Trixi.flux_engquist_osher(0.75 * u, u, orientation, equation) - end + orientations = [1, 2] + for orientation in orientations, u in u_values + @test flux_godunov(u, u, orientation, equation) ≈ flux(u, orientation, equation) + end - # Linear Advection 1D - equation = LinearScalarAdvectionEquation1D(-4.2) - u = SVector(3.14159) + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] - orientations = [1] - for orientation in orientations - @test flux_godunov(0.5 * u, u, orientation, equation) ≈ - flux_lax_friedrichs(0.5 * u, u, orientation, equation) - @test flux_godunov(2 * u, u, orientation, equation) ≈ - Trixi.flux_engquist_osher(2 * u, u, orientation, equation) + for normal_direction in normal_directions, u in u_values + @test flux_godunov(u, u, normal_direction, equation) ≈ + flux(u, normal_direction, equation) + end end - # Linear Advection 2D - equation = LinearScalarAdvectionEquation2D(-4.2, 2.4) - u = SVector(3.14159) + @timed_testset "Consistency check for Engquist-Osher flux" begin + # Set up equations and dummy conservative variables state + equation = InviscidBurgersEquation1D() + u_values = [SVector(42.0), SVector(-42.0)] - orientations = [1, 2] - for orientation in orientations - @test flux_godunov(0.25 * u, u, orientation, equation) ≈ - flux_lax_friedrichs(0.25 * u, u, orientation, equation) - end + orientations = [1] + for orientation in orientations, u in u_values + @test Trixi.flux_engquist_osher(u, u, orientation, equation) ≈ + flux(u, orientation, equation) + end - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] + equation = LinearScalarAdvectionEquation1D(-4.2) + u = SVector(3.14159) - for normal_direction in normal_directions - @test flux_godunov(3 * u, u, normal_direction, equation) ≈ - flux_lax_friedrichs(3 * u, u, normal_direction, equation) + orientations = [1] + for orientation in orientations + @test Trixi.flux_engquist_osher(u, u, orientation, equation) ≈ + flux(u, orientation, equation) + end end - # Linear Advection 3D - equation = LinearScalarAdvectionEquation3D(-4.2, 2.4, 1.2) - u = SVector(3.14159) + @testset "Consistency check for `gradient_conservative` routine" begin + # Set up conservative variables, equations + u = [ + 0.5011914484393387, + 0.8829127712445113, + 0.43024132987932817, + 0.7560616633050348, + ] - orientations = [1, 2, 3] - for orientation in orientations - @test flux_godunov(1.5 * u, u, orientation, equation) ≈ - flux_lax_friedrichs(1.5 * u, u, orientation, equation) - end + equations = CompressibleEulerEquations2D(1.4) - normal_directions = [SVector(1.0, 0.0, 0.0), - SVector(0.0, 1.0, 0.0), - SVector(0.0, 0.0, 1.0), - SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] + # Define wrapper function for pressure in order to call default implementation + function pressure_test(u, equations) + return pressure(u, equations) + end - for normal_direction in normal_directions - @test flux_godunov(1.3 * u, u, normal_direction, equation) ≈ - flux_lax_friedrichs(1.3 * u, u, normal_direction, equation) + @test Trixi.gradient_conservative(pressure_test, u, equations) ≈ + Trixi.gradient_conservative(pressure, u, equations) end -end -@timed_testset "Consistency check for LMARS flux" begin - equations = CompressibleEulerEquations2D(1.4) - flux_lmars = FluxLMARS(340) - - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - orientations = [1, 2] - u_values = [SVector(1.0, 0.5, -0.7, 1.0), - SVector(1.5, -0.2, 0.1, 5.0)] - - for u in u_values, orientation in orientations - @test flux_lmars(u, u, orientation, equations) ≈ - flux(u, orientation, equations) - end + @testset "Equivalent Fluxes" begin + # Set up equations and dummy conservative variables state + # Burgers' Equation - for u in u_values, normal_direction in normal_directions - @test flux_lmars(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end + equation = InviscidBurgersEquation1D() + u_values = [SVector(42.0), SVector(-42.0)] - equations = CompressibleEulerEquations3D(1.4) - normal_directions = [SVector(1.0, 0.0, 0.0), - SVector(0.0, 1.0, 0.0), - SVector(0.0, 0.0, 1.0), - SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] - orientations = [1, 2, 3] - u_values = [SVector(1.0, 0.5, -0.7, 0.1, 1.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0)] - - for u in u_values, orientation in orientations - @test flux_lmars(u, u, orientation, equations) ≈ - flux(u, orientation, equations) - end + orientations = [1] + for orientation in orientations, u in u_values + @test flux_godunov(0.75 * u, u, orientation, equation) ≈ + Trixi.flux_engquist_osher(0.75 * u, u, orientation, equation) + end - for u in u_values, normal_direction in normal_directions - @test flux_lmars(u, u, normal_direction, equations) ≈ - flux(u, normal_direction, equations) - end -end + # Linear Advection 1D + equation = LinearScalarAdvectionEquation1D(-4.2) + u = SVector(3.14159) -@testset "FluxRotated vs. direct implementation" begin - @timed_testset "CompressibleEulerMulticomponentEquations2D" begin - equations = CompressibleEulerMulticomponentEquations2D(gammas = (1.4, 1.4), - gas_constants = (0.4, - 0.4)) - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - u_values = [SVector(0.1, -0.5, 1.0, 1.0, 2.0), - SVector(-0.1, -0.3, 1.2, 1.3, 1.4)] + orientations = [1] + for orientation in orientations + @test flux_godunov(0.5 * u, u, orientation, equation) ≈ + flux_lax_friedrichs(0.5 * u, u, orientation, equation) + @test flux_godunov(2 * u, u, orientation, equation) ≈ + Trixi.flux_engquist_osher(2 * u, u, orientation, equation) + end - f_std = flux - f_rot = FluxRotated(f_std) - println(typeof(f_std)) - println(typeof(f_rot)) - for u in u_values, - normal_direction in normal_directions + # Linear Advection 2D + equation = LinearScalarAdvectionEquation2D(-4.2, 2.4) + u = SVector(3.14159) - @test f_rot(u, normal_direction, equations) ≈ - f_std(u, normal_direction, equations) + orientations = [1, 2] + for orientation in orientations + @test flux_godunov(0.25 * u, u, orientation, equation) ≈ + flux_lax_friedrichs(0.25 * u, u, orientation, equation) end - end - @timed_testset "CompressibleEulerEquations2D" begin - equations = CompressibleEulerEquations2D(1.4) - normal_directions = [SVector(1.0, 0.0), + normal_directions = [ + SVector(1.0, 0.0), SVector(0.0, 1.0), SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - u_values = [SVector(1.0, 0.5, -0.7, 1.0), - SVector(1.5, -0.2, 0.1, 5.0)] - fluxes = [flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber, - FluxLMARS(340), flux_hll, FluxHLL(min_max_speed_davis), flux_hlle, - flux_hllc, flux_chandrashekar, + SVector(-1.2, 0.3), ] - for f_std in fluxes - f_rot = FluxRotated(f_std) - for u_ll in u_values, u_rr in u_values, - normal_direction in normal_directions + for normal_direction in normal_directions + @test flux_godunov(3 * u, u, normal_direction, equation) ≈ + flux_lax_friedrichs(3 * u, u, normal_direction, equation) + end - @test f_rot(u_ll, u_rr, normal_direction, equations) ≈ - f_std(u_ll, u_rr, normal_direction, equations) - end + # Linear Advection 3D + equation = LinearScalarAdvectionEquation3D(-4.2, 2.4, 1.2) + u = SVector(3.14159) + + orientations = [1, 2, 3] + for orientation in orientations + @test flux_godunov(1.5 * u, u, orientation, equation) ≈ + flux_lax_friedrichs(1.5 * u, u, orientation, equation) end - end - @timed_testset "CompressibleEulerEquations3D" begin - equations = CompressibleEulerEquations3D(1.4) - normal_directions = [SVector(1.0, 0.0, 0.0), + normal_directions = [ + SVector(1.0, 0.0, 0.0), SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0), SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] - u_values = [SVector(1.0, 0.5, -0.7, 0.1, 1.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0)] - fluxes = [flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber, - FluxLMARS(340), flux_hll, FluxHLL(min_max_speed_davis), flux_hlle, - flux_hllc, flux_chandrashekar, + SVector(-1.2, 0.3, 1.4), ] - for f_std in fluxes - f_rot = FluxRotated(f_std) - for u_ll in u_values, u_rr in u_values, - normal_direction in normal_directions - - @test f_rot(u_ll, u_rr, normal_direction, equations) ≈ - f_std(u_ll, u_rr, normal_direction, equations) - end + for normal_direction in normal_directions + @test flux_godunov(1.3 * u, u, normal_direction, equation) ≈ + flux_lax_friedrichs(1.3 * u, u, normal_direction, equation) end end - @timed_testset "ShallowWaterEquations2D" begin - equations = ShallowWaterEquations2D(gravity_constant = 9.81) - normal_directions = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - - u = SVector(1, 0.5, 0.5, 0.0) - - fluxes = [flux_central, flux_fjordholm_etal, flux_wintermeyer_etal, - flux_hll, FluxHLL(min_max_speed_davis), flux_hlle] - end + @timed_testset "Consistency check for LMARS flux" begin + equations = CompressibleEulerEquations2D(1.4) + flux_lmars = FluxLMARS(340) - @timed_testset "IdealGlmMhdEquations2D" begin - equations = IdealGlmMhdEquations2D(1.4, 5.0) #= c_h =# - normal_directions = [SVector(1.0, 0.0), + normal_directions = [ + SVector(1.0, 0.0), SVector(0.0, 1.0), SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2)] - fluxes = [ - flux_central, - flux_hindenlang_gassner, - FluxHLL(min_max_speed_davis), - flux_hlle, + SVector(-1.2, 0.3), + ] + orientations = [1, 2] + u_values = [ + SVector(1.0, 0.5, -0.7, 1.0), + SVector(1.5, -0.2, 0.1, 5.0), ] - for f_std in fluxes - f_rot = FluxRotated(f_std) - for u_ll in u_values, u_rr in u_values, - normal_direction in normal_directions + for u in u_values, orientation in orientations + @test flux_lmars(u, u, orientation, equations) ≈ + flux(u, orientation, equations) + end - @test f_rot(u_ll, u_rr, normal_direction, equations) ≈ - f_std(u_ll, u_rr, normal_direction, equations) - end + for u in u_values, normal_direction in normal_directions + @test flux_lmars(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) end - end - @timed_testset "IdealGlmMhdEquations3D" begin - equations = IdealGlmMhdEquations3D(1.4, 5.0) #= c_h =# - normal_directions = [SVector(1.0, 0.0, 0.0), + equations = CompressibleEulerEquations3D(1.4) + normal_directions = [ + SVector(1.0, 0.0, 0.0), SVector(0.0, 1.0, 0.0), SVector(0.0, 0.0, 1.0), SVector(0.5, -0.5, 0.2), - SVector(-1.2, 0.3, 1.4)] - u_values = [SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), - SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2)] - fluxes = [ - flux_central, - flux_hindenlang_gassner, - FluxHLL(min_max_speed_davis), - flux_hlle, + SVector(-1.2, 0.3, 1.4), + ] + orientations = [1, 2, 3] + u_values = [ + SVector(1.0, 0.5, -0.7, 0.1, 1.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0), ] - for f_std in fluxes - f_rot = FluxRotated(f_std) - for u_ll in u_values, u_rr in u_values, - normal_direction in normal_directions - - @test f_rot(u_ll, u_rr, normal_direction, equations) ≈ - f_std(u_ll, u_rr, normal_direction, equations) - end + for u in u_values, orientation in orientations + @test flux_lmars(u, u, orientation, equations) ≈ + flux(u, orientation, equations) end - end -end - -@testset "Equivalent Wave Speed Estimates" begin - @timed_testset "Linearized Euler 3D" begin - equations = LinearizedEulerEquations3D(v_mean_global = (0.42, 0.37, 0.7), - c_mean_global = 1.0, - rho_mean_global = 1.0) - - normal_x = SVector(1.0, 0.0, 0.0) - normal_y = SVector(0.0, 1.0, 0.0) - normal_z = SVector(0.0, 0.0, 1.0) - - u_ll = SVector(0.3, 0.5, -0.7, 0.1, 1.0) - u_rr = SVector(0.5, -0.2, 0.1, 0.2, 5.0) - - @test all(isapprox(x, y) - for (x, y) in zip(max_abs_speed_naive(u_ll, u_rr, 1, equations), - max_abs_speed_naive(u_ll, u_rr, normal_x, - equations))) - @test all(isapprox(x, y) - for (x, y) in zip(max_abs_speed_naive(u_ll, u_rr, 2, equations), - max_abs_speed_naive(u_ll, u_rr, normal_y, - equations))) - @test all(isapprox(x, y) - for (x, y) in zip(max_abs_speed_naive(u_ll, u_rr, 3, equations), - max_abs_speed_naive(u_ll, u_rr, normal_z, - equations))) - - @test all(isapprox(x, y) - for (x, y) in zip(min_max_speed_naive(u_ll, u_rr, 1, equations), - min_max_speed_naive(u_ll, u_rr, normal_x, - equations))) - @test all(isapprox(x, y) - for (x, y) in zip(min_max_speed_naive(u_ll, u_rr, 2, equations), - min_max_speed_naive(u_ll, u_rr, normal_y, - equations))) - @test all(isapprox(x, y) - for (x, y) in zip(min_max_speed_naive(u_ll, u_rr, 3, equations), - min_max_speed_naive(u_ll, u_rr, normal_z, - equations))) - - @test all(isapprox(x, y) - for (x, y) in zip(min_max_speed_davis(u_ll, u_rr, 1, equations), - min_max_speed_davis(u_ll, u_rr, normal_x, - equations))) - @test all(isapprox(x, y) - for (x, y) in zip(min_max_speed_davis(u_ll, u_rr, 2, equations), - min_max_speed_davis(u_ll, u_rr, normal_y, - equations))) - @test all(isapprox(x, y) - for (x, y) in zip(min_max_speed_davis(u_ll, u_rr, 3, equations), - min_max_speed_davis(u_ll, u_rr, normal_z, - equations))) - end - - @timed_testset "Maxwell 1D" begin - equations = MaxwellEquations1D() - u_values_left = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - - u_values_right = [SVector(1.0, 0.0), - SVector(0.0, 1.0), - SVector(0.5, -0.5), - SVector(-1.2, 0.3)] - for u_ll in u_values_left, u_rr in u_values_right - @test all(isapprox(x, y) - for (x, y) in zip(min_max_speed_naive(u_ll, u_rr, 1, equations), - min_max_speed_davis(u_ll, u_rr, 1, equations))) + for u in u_values, normal_direction in normal_directions + @test flux_lmars(u, u, normal_direction, equations) ≈ + flux(u, normal_direction, equations) end end -end - -@testset "SimpleKronecker" begin - N = 3 - NDIMS = 2 - r, s = StartUpDG.nodes(Quad(), N) - V = StartUpDG.vandermonde(Quad(), N, r, s) - r1D = StartUpDG.nodes(Line(), N) - V1D = StartUpDG.vandermonde(Line(), N, r1D) - - x = r + s - V_kron = Trixi.SimpleKronecker(NDIMS, V1D, eltype(x)) - - b = similar(x) - b_kron = similar(x) - Trixi.mul!(b, V, x) - Trixi.mul!(b_kron, V_kron, x) - @test b ≈ b_kron -end - -@testset "SummationByPartsOperators + StartUpDG" begin - global D = derivative_operator(SummationByPartsOperators.MattssonNordström2004(), - derivative_order = 1, - accuracy_order = 4, - xmin = 0.0, xmax = 1.0, - N = 10) - dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = D) - - @test StartUpDG.inverse_trace_constant(dg.basis) ≈ 50.8235294117647 -end - -@testset "1D non-periodic DGMultiMesh" begin - # checks whether or not boundary faces are initialized correctly for DGMultiMesh in 1D - dg = DGMulti(polydeg = 1, element_type = Line(), approximation_type = Polynomial(), - surface_integral = SurfaceIntegralWeakForm(flux_central), - volume_integral = VolumeIntegralFluxDifferencing(flux_central)) - cells_per_dimension = (1,) - mesh = DGMultiMesh(dg, cells_per_dimension, periodicity = false) + @testset "FluxRotated vs. direct implementation" begin + @timed_testset "CompressibleEulerMulticomponentEquations2D" begin + equations = CompressibleEulerMulticomponentEquations2D( + gammas = (1.4, 1.4), + gas_constants = ( + 0.4, + 0.4, + ) + ) + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + u_values = [ + SVector(0.1, -0.5, 1.0, 1.0, 2.0), + SVector(-0.1, -0.3, 1.2, 1.3, 1.4), + ] + + f_std = flux + f_rot = FluxRotated(f_std) + println(typeof(f_std)) + println(typeof(f_rot)) + for u in u_values, + normal_direction in normal_directions - @test mesh.boundary_faces[:entire_boundary] == [1, 2] -end + @test f_rot(u, normal_direction, equations) ≈ + f_std(u, normal_direction, equations) + end + end -@testset "PERK Single p2 Constructors" begin - path_coeff_file = mktempdir() - Trixi.download("https://gist.githubusercontent.com/DanielDoehring/8db0808b6f80e59420c8632c0d8e2901/raw/39aacf3c737cd642636dd78592dbdfe4cb9499af/MonCoeffsS6p2.txt", - joinpath(path_coeff_file, "gamma_6.txt")) - - ode_algorithm = Trixi.PairedExplicitRK2(6, path_coeff_file) - - @test isapprox(ode_algorithm.a_matrix, - [0.12405417889682908 0.07594582110317093 - 0.16178873711001726 0.13821126288998273 - 0.16692313960864164 0.2330768603913584 - 0.12281292901258256 0.37718707098741744], atol = 1e-13) - - Trixi.download("https://gist.githubusercontent.com/DanielDoehring/c7a89eaaa857e87dde055f78eae9b94a/raw/2937f8872ffdc08e0dcf444ee35f9ebfe18735b0/Spectrum_2D_IsentropicVortex_CEE.txt", - joinpath(path_coeff_file, "spectrum_2d.txt")) - - eig_vals = readdlm(joinpath(path_coeff_file, "spectrum_2d.txt"), ComplexF64) - tspan = (0.0, 1.0) - ode_algorithm = Trixi.PairedExplicitRK2(12, tspan, vec(eig_vals)) - - @test isapprox(ode_algorithm.a_matrix, - [0.06453812656711647 0.02637096434197444 - 0.09470601372274887 0.041657622640887494 - 0.12332877820069793 0.058489403617483886 - 0.14987015032771522 0.07740257694501203 - 0.1734211495362651 0.0993061231910076 - 0.19261978147948638 0.1255620367023318 - 0.20523340226247055 0.1584029613738931 - 0.20734890429023528 0.20174200480067384 - 0.1913514234997008 0.26319403104575373 - 0.13942836392866081 0.3605716360713392], atol = 1e-13) -end + @timed_testset "CompressibleEulerEquations2D" begin + equations = CompressibleEulerEquations2D(1.4) + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + u_values = [ + SVector(1.0, 0.5, -0.7, 1.0), + SVector(1.5, -0.2, 0.1, 5.0), + ] + fluxes = [ + flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber, + FluxLMARS(340), flux_hll, FluxHLL(min_max_speed_davis), flux_hlle, + flux_hllc, flux_chandrashekar, + ] + + for f_std in fluxes + f_rot = FluxRotated(f_std) + for u_ll in u_values, u_rr in u_values, + normal_direction in normal_directions + + @test f_rot(u_ll, u_rr, normal_direction, equations) ≈ + f_std(u_ll, u_rr, normal_direction, equations) + end + end + end -@testset "Sutherlands Law" begin - function mu(u, equations) - T_ref = 291.15 + @timed_testset "CompressibleEulerEquations3D" begin + equations = CompressibleEulerEquations3D(1.4) + normal_directions = [ + SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4), + ] + u_values = [ + SVector(1.0, 0.5, -0.7, 0.1, 1.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0), + ] + fluxes = [ + flux_central, flux_ranocha, flux_shima_etal, flux_kennedy_gruber, + FluxLMARS(340), flux_hll, FluxHLL(min_max_speed_davis), flux_hlle, + flux_hllc, flux_chandrashekar, + ] + + for f_std in fluxes + f_rot = FluxRotated(f_std) + for u_ll in u_values, u_rr in u_values, + normal_direction in normal_directions + + @test f_rot(u_ll, u_rr, normal_direction, equations) ≈ + f_std(u_ll, u_rr, normal_direction, equations) + end + end + end - R_specific_air = 287.052874 - T = R_specific_air * Trixi.temperature(u, equations) + @timed_testset "ShallowWaterEquations2D" begin + equations = ShallowWaterEquations2D(gravity_constant = 9.81) + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + + u = SVector(1, 0.5, 0.5, 0.0) + + fluxes = [ + flux_central, flux_fjordholm_etal, flux_wintermeyer_etal, + flux_hll, FluxHLL(min_max_speed_davis), flux_hlle, + ] + end - C_air = 120.0 - mu_ref_air = 1.827e-5 + @timed_testset "IdealGlmMhdEquations2D" begin + equations = IdealGlmMhdEquations2D(1.4, 5.0) #= c_h =# + normal_directions = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2), + ] + fluxes = [ + flux_central, + flux_hindenlang_gassner, + FluxHLL(min_max_speed_davis), + flux_hlle, + ] + + for f_std in fluxes + f_rot = FluxRotated(f_std) + for u_ll in u_values, u_rr in u_values, + normal_direction in normal_directions + + @test f_rot(u_ll, u_rr, normal_direction, equations) ≈ + f_std(u_ll, u_rr, normal_direction, equations) + end + end + end - return mu_ref_air * (T_ref + C_air) / (T + C_air) * (T / T_ref)^1.5 + @timed_testset "IdealGlmMhdEquations3D" begin + equations = IdealGlmMhdEquations3D(1.4, 5.0) #= c_h =# + normal_directions = [ + SVector(1.0, 0.0, 0.0), + SVector(0.0, 1.0, 0.0), + SVector(0.0, 0.0, 1.0), + SVector(0.5, -0.5, 0.2), + SVector(-1.2, 0.3, 1.4), + ] + u_values = [ + SVector(1.0, 0.4, -0.5, 0.1, 1.0, 0.1, -0.2, 0.1, 0.0), + SVector(1.5, -0.2, 0.1, 0.2, 5.0, -0.1, 0.1, 0.2, 0.2), + ] + fluxes = [ + flux_central, + flux_hindenlang_gassner, + FluxHLL(min_max_speed_davis), + flux_hlle, + ] + + for f_std in fluxes + f_rot = FluxRotated(f_std) + for u_ll in u_values, u_rr in u_values, + normal_direction in normal_directions + + @test f_rot(u_ll, u_rr, normal_direction, equations) ≈ + f_std(u_ll, u_rr, normal_direction, equations) + end + end + end end - function mu_control(u, equations, T_ref, R_specific, C, mu_ref) - T = R_specific * Trixi.temperature(u, equations) + @testset "Equivalent Wave Speed Estimates" begin + @timed_testset "Linearized Euler 3D" begin + equations = LinearizedEulerEquations3D( + v_mean_global = (0.42, 0.37, 0.7), + c_mean_global = 1.0, + rho_mean_global = 1.0 + ) + + normal_x = SVector(1.0, 0.0, 0.0) + normal_y = SVector(0.0, 1.0, 0.0) + normal_z = SVector(0.0, 0.0, 1.0) + + u_ll = SVector(0.3, 0.5, -0.7, 0.1, 1.0) + u_rr = SVector(0.5, -0.2, 0.1, 0.2, 5.0) + + @test all( + isapprox(x, y) + for (x, y) in zip( + max_abs_speed_naive(u_ll, u_rr, 1, equations), + max_abs_speed_naive( + u_ll, u_rr, normal_x, + equations + ) + ) + ) + @test all( + isapprox(x, y) + for (x, y) in zip( + max_abs_speed_naive(u_ll, u_rr, 2, equations), + max_abs_speed_naive( + u_ll, u_rr, normal_y, + equations + ) + ) + ) + @test all( + isapprox(x, y) + for (x, y) in zip( + max_abs_speed_naive(u_ll, u_rr, 3, equations), + max_abs_speed_naive( + u_ll, u_rr, normal_z, + equations + ) + ) + ) + + @test all( + isapprox(x, y) + for (x, y) in zip( + min_max_speed_naive(u_ll, u_rr, 1, equations), + min_max_speed_naive( + u_ll, u_rr, normal_x, + equations + ) + ) + ) + @test all( + isapprox(x, y) + for (x, y) in zip( + min_max_speed_naive(u_ll, u_rr, 2, equations), + min_max_speed_naive( + u_ll, u_rr, normal_y, + equations + ) + ) + ) + @test all( + isapprox(x, y) + for (x, y) in zip( + min_max_speed_naive(u_ll, u_rr, 3, equations), + min_max_speed_naive( + u_ll, u_rr, normal_z, + equations + ) + ) + ) + + @test all( + isapprox(x, y) + for (x, y) in zip( + min_max_speed_davis(u_ll, u_rr, 1, equations), + min_max_speed_davis( + u_ll, u_rr, normal_x, + equations + ) + ) + ) + @test all( + isapprox(x, y) + for (x, y) in zip( + min_max_speed_davis(u_ll, u_rr, 2, equations), + min_max_speed_davis( + u_ll, u_rr, normal_y, + equations + ) + ) + ) + @test all( + isapprox(x, y) + for (x, y) in zip( + min_max_speed_davis(u_ll, u_rr, 3, equations), + min_max_speed_davis( + u_ll, u_rr, normal_z, + equations + ) + ) + ) + end - return mu_ref * (T_ref + C) / (T + C) * (T / T_ref)^1.5 + @timed_testset "Maxwell 1D" begin + equations = MaxwellEquations1D() + + u_values_left = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + + u_values_right = [ + SVector(1.0, 0.0), + SVector(0.0, 1.0), + SVector(0.5, -0.5), + SVector(-1.2, 0.3), + ] + for u_ll in u_values_left, u_rr in u_values_right + @test all( + isapprox(x, y) + for (x, y) in zip( + min_max_speed_naive(u_ll, u_rr, 1, equations), + min_max_speed_davis(u_ll, u_rr, 1, equations) + ) + ) + end + end end - # Dry air (values from Wikipedia: https://de.wikipedia.org/wiki/Sutherland-Modell) - T_ref = 291.15 - C = 120.0 # Sutherland's constant - R_specific = 287.052874 - mu_ref = 1.827e-5 - prandtl_number() = 0.72 - gamma = 1.4 + @testset "SimpleKronecker" begin + N = 3 + + NDIMS = 2 + r, s = StartUpDG.nodes(Quad(), N) + V = StartUpDG.vandermonde(Quad(), N, r, s) + r1D = StartUpDG.nodes(Line(), N) + V1D = StartUpDG.vandermonde(Line(), N, r1D) + + x = r + s + V_kron = Trixi.SimpleKronecker(NDIMS, V1D, eltype(x)) + + b = similar(x) + b_kron = similar(x) + Trixi.mul!(b, V, x) + Trixi.mul!(b_kron, V_kron, x) + @test b ≈ b_kron + end + + @testset "SummationByPartsOperators + StartUpDG" begin + global D = derivative_operator( + SummationByPartsOperators.MattssonNordström2004(), + derivative_order = 1, + accuracy_order = 4, + xmin = 0.0, xmax = 1.0, + N = 10 + ) + dg = DGMulti(polydeg = 3, element_type = Quad(), approximation_type = D) + + @test StartUpDG.inverse_trace_constant(dg.basis) ≈ 50.8235294117647 + end + + @testset "1D non-periodic DGMultiMesh" begin + # checks whether or not boundary faces are initialized correctly for DGMultiMesh in 1D + dg = DGMulti( + polydeg = 1, element_type = Line(), approximation_type = Polynomial(), + surface_integral = SurfaceIntegralWeakForm(flux_central), + volume_integral = VolumeIntegralFluxDifferencing(flux_central) + ) + cells_per_dimension = (1,) + mesh = DGMultiMesh(dg, cells_per_dimension, periodicity = false) + + @test mesh.boundary_faces[:entire_boundary] == [1, 2] + end + + @testset "PERK Single p2 Constructors" begin + path_coeff_file = mktempdir() + Trixi.download( + "https://gist.githubusercontent.com/DanielDoehring/8db0808b6f80e59420c8632c0d8e2901/raw/39aacf3c737cd642636dd78592dbdfe4cb9499af/MonCoeffsS6p2.txt", + joinpath(path_coeff_file, "gamma_6.txt") + ) + + ode_algorithm = Trixi.PairedExplicitRK2(6, path_coeff_file) + + @test isapprox( + ode_algorithm.a_matrix, + [ + 0.12405417889682908 0.07594582110317093 + 0.16178873711001726 0.13821126288998273 + 0.16692313960864164 0.2330768603913584 + 0.12281292901258256 0.37718707098741744 + ], atol = 1.0e-13 + ) + + Trixi.download( + "https://gist.githubusercontent.com/DanielDoehring/c7a89eaaa857e87dde055f78eae9b94a/raw/2937f8872ffdc08e0dcf444ee35f9ebfe18735b0/Spectrum_2D_IsentropicVortex_CEE.txt", + joinpath(path_coeff_file, "spectrum_2d.txt") + ) + + eig_vals = readdlm(joinpath(path_coeff_file, "spectrum_2d.txt"), ComplexF64) + tspan = (0.0, 1.0) + ode_algorithm = Trixi.PairedExplicitRK2(12, tspan, vec(eig_vals)) + + @test isapprox( + ode_algorithm.a_matrix, + [ + 0.06453812656711647 0.02637096434197444 + 0.09470601372274887 0.041657622640887494 + 0.12332877820069793 0.058489403617483886 + 0.14987015032771522 0.07740257694501203 + 0.1734211495362651 0.0993061231910076 + 0.19261978147948638 0.1255620367023318 + 0.20523340226247055 0.1584029613738931 + 0.20734890429023528 0.20174200480067384 + 0.1913514234997008 0.26319403104575373 + 0.13942836392866081 0.3605716360713392 + ], atol = 1.0e-13 + ) + end + + @testset "Sutherlands Law" begin + function mu(u, equations) + T_ref = 291.15 + + R_specific_air = 287.052874 + T = R_specific_air * Trixi.temperature(u, equations) + + C_air = 120.0 + mu_ref_air = 1.827e-5 + + return mu_ref_air * (T_ref + C_air) / (T + C_air) * (T / T_ref)^1.5 + end - equations = CompressibleEulerEquations2D(gamma) - equations_parabolic = CompressibleNavierStokesDiffusion2D(equations, mu = mu, - Prandtl = prandtl_number()) + function mu_control(u, equations, T_ref, R_specific, C, mu_ref) + T = R_specific * Trixi.temperature(u, equations) - # Flow at rest - u = prim2cons(SVector(1.0, 0.0, 0.0, 1.0), equations_parabolic) + return mu_ref * (T_ref + C) / (T + C) * (T / T_ref)^1.5 + end - # Comparison value from https://www.engineeringtoolbox.com/air-absolute-kinematic-viscosity-d_601.html at 18°C - @test isapprox(mu_control(u, equations_parabolic, T_ref, R_specific, C, mu_ref), - 1.803e-5, atol = 5e-8) -end + # Dry air (values from Wikipedia: https://de.wikipedia.org/wiki/Sutherland-Modell) + T_ref = 291.15 + C = 120.0 # Sutherland's constant + R_specific = 287.052874 + mu_ref = 1.827e-5 + prandtl_number() = 0.72 + gamma = 1.4 + + equations = CompressibleEulerEquations2D(gamma) + equations_parabolic = CompressibleNavierStokesDiffusion2D( + equations, mu = mu, + Prandtl = prandtl_number() + ) + + # Flow at rest + u = prim2cons(SVector(1.0, 0.0, 0.0, 1.0), equations_parabolic) + + # Comparison value from https://www.engineeringtoolbox.com/air-absolute-kinematic-viscosity-d_601.html at 18°C + @test isapprox( + mu_control(u, equations_parabolic, T_ref, R_specific, C, mu_ref), + 1.803e-5, atol = 5.0e-8 + ) + end end end #module diff --git a/test/test_unstructured_2d.jl b/test/test_unstructured_2d.jl index 43b911cc66..aab5fa582d 100644 --- a/test/test_unstructured_2d.jl +++ b/test/test_unstructured_2d.jl @@ -12,752 +12,884 @@ outdir = "out" isdir(outdir) && rm(outdir, recursive = true) @testset "UnstructuredMesh2D" begin -#! format: noindent - -@trixi_testset "elixir_euler_periodic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_periodic.jl"), - l2=[ - 0.0001099216141882387, 0.0001303795774982892, - 0.00013037957749794242, 0.0002993727892598759, - ], - linf=[ - 0.006407280810928562, 0.009836067015418948, - 0.009836067015398076, 0.021903519038095176, - ]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + #! format: noindent + + @trixi_testset "elixir_euler_periodic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_periodic.jl"), + l2 = [ + 0.0001099216141882387, 0.0001303795774982892, + 0.00013037957749794242, 0.0002993727892598759, + ], + linf = [ + 0.006407280810928562, 0.009836067015418948, + 0.009836067015398076, 0.021903519038095176, + ] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_free_stream.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), - l2=[ - 3.3937365073416665e-14, 2.44759188939065e-13, - 1.4585198700082895e-13, 4.716940764877479e-13, - ], - linf=[ - 7.774003663030271e-12, 9.183176441496244e-11, - 4.5685344396417804e-11, 1.0534506600379245e-10, - ], - tspan=(0.0, 0.1), - atol=3.0e-13) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_free_stream.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_free_stream.jl"), + l2 = [ + 3.3937365073416665e-14, 2.44759188939065e-13, + 1.4585198700082895e-13, 4.716940764877479e-13, + ], + linf = [ + 7.774003663030271e-12, 9.183176441496244e-11, + 4.5685344396417804e-11, 1.0534506600379245e-10, + ], + tspan = (0.0, 0.1), + atol = 3.0e-13 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_wall_bc.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_wall_bc.jl"), - l2=[ - 0.040189107976346644, - 0.04256154998030852, - 0.03734120743842209, - 0.10057425897733507, - ], - linf=[ - 0.24455374304626365, - 0.2970686406973577, - 0.29339040847600434, - 0.5915610037764794, - ], - tspan=(0.0, 0.25), - surface_flux=FluxHLL(min_max_speed_naive)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_wall_bc.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_wall_bc.jl"), + l2 = [ + 0.040189107976346644, + 0.04256154998030852, + 0.03734120743842209, + 0.10057425897733507, + ], + linf = [ + 0.24455374304626365, + 0.2970686406973577, + 0.29339040847600434, + 0.5915610037764794, + ], + tspan = (0.0, 0.25), + surface_flux = FluxHLL(min_max_speed_naive) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_basic.jl" begin - @test_trixi_include(default_example_unstructured(), - l2=[ - 0.0007213418215265047, - 0.0006752337675043779, - 0.0006437485997536973, - 0.0014782883071363362, - ], - linf=[ - 0.004301288971032324, - 0.005243995459478956, - 0.004685630332338153, - 0.01750217718347713, - ], - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_basic.jl" begin + @test_trixi_include( + default_example_unstructured(), + l2 = [ + 0.0007213418215265047, + 0.0006752337675043779, + 0.0006437485997536973, + 0.0014782883071363362, + ], + linf = [ + 0.004301288971032324, + 0.005243995459478956, + 0.004685630332338153, + 0.01750217718347713, + ], + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_restart.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_restart.jl"), - l2=[ - 0.0007213418215265047, - 0.0006752337675043779, - 0.0006437485997536973, - 0.0014782883071363362, - ], - linf=[ - 0.004301288971032324, - 0.005243995459478956, - 0.004685630332338153, - 0.01750217718347713, - ], - # With the default `maxiters = 1` in coverage tests, - # there would be no time steps after the restart. - coverage_override=(maxiters = 100_000,)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_restart.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_restart.jl"), + l2 = [ + 0.0007213418215265047, + 0.0006752337675043779, + 0.0006437485997536973, + 0.0014782883071363362, + ], + linf = [ + 0.004301288971032324, + 0.005243995459478956, + 0.004685630332338153, + 0.01750217718347713, + ], + # With the default `maxiters = 1` in coverage tests, + # there would be no time steps after the restart. + coverage_override = (maxiters = 100_000,) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), - l2=[ - 0.06594600495903137, - 0.10803914821786433, - 0.10805946357846291, - 0.1738171782368222, - ], - linf=[ - 0.31880214280781305, - 0.3468488554333352, - 0.34592958184413264, - 0.784555926860546, - ], - tspan=(0.0, 1.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_ec.jl"), + l2 = [ + 0.06594600495903137, + 0.10803914821786433, + 0.10805946357846291, + 0.1738171782368222, + ], + linf = [ + 0.31880214280781305, + 0.3468488554333352, + 0.34592958184413264, + 0.784555926860546, + ], + tspan = (0.0, 1.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), - l2=[0.00018729339078205488], - linf=[0.0018997287705734278]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_advection_basic.jl"), + l2 = [0.00018729339078205488], + linf = [0.0018997287705734278] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_sedov.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), - l2=[ - 2.19945600e-01, - 1.71050453e-01, - 1.71050453e-01, - 1.21719195e+00, - ], - linf=[ - 7.44218635e-01, - 7.02887039e-01, - 7.02887039e-01, - 6.11732719e+00, - ], - tspan=(0.0, 0.3)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_sedov.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_sedov.jl"), + l2 = [ + 2.199456e-1, + 1.71050453e-1, + 1.71050453e-1, + 1.21719195e+0, + ], + linf = [ + 7.44218635e-1, + 7.02887039e-1, + 7.02887039e-1, + 6.11732719e+0, + ], + tspan = (0.0, 0.3) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_euler_time_series.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_time_series.jl"), - l2=[ - 6.984024099236519e-5, - 6.289022520363763e-5, - 6.550951878107466e-5, - 0.00016222767700879948, - ], - linf=[ - 0.0005367823248620951, - 0.000671293180158461, - 0.0005656680962440319, - 0.0013910024779804075, - ], - tspan=(0.0, 0.2), - # With the default `maxiters = 1` in coverage tests, - # there would be no time series to check against. - coverage_override=(maxiters = 20,)) - # Extra test that the `TimeSeries` callback creates reasonable data - point_data_1 = time_series.affect!.point_data[1] - @test all(isapprox.(point_data_1[1:4], - [1.9546882708551676, 1.9547149531788077, - 1.9547142161310154, 3.821066781119142])) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_euler_time_series.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_euler_time_series.jl"), + l2 = [ + 6.984024099236519e-5, + 6.289022520363763e-5, + 6.550951878107466e-5, + 0.00016222767700879948, + ], + linf = [ + 0.0005367823248620951, + 0.000671293180158461, + 0.0005656680962440319, + 0.0013910024779804075, + ], + tspan = (0.0, 0.2), + # With the default `maxiters = 1` in coverage tests, + # there would be no time series to check against. + coverage_override = (maxiters = 20,) + ) + # Extra test that the `TimeSeries` callback creates reasonable data + point_data_1 = time_series.affect!.point_data[1] + @test all( + isapprox.( + point_data_1[1:4], + [ + 1.9546882708551676, 1.9547149531788077, + 1.9547142161310154, 3.821066781119142, + ] + ) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_acoustics_gauss_wall.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_acoustics_gauss_wall.jl"), - l2=[0.029330394861252995, 0.029345079728907965, - 0.03803795043486467, 0.0, - 7.175152371650832e-16, 1.4350304743301665e-15, - 1.4350304743301665e-15], - linf=[0.36236334472179443, 0.3690785638275256, - 0.8475748723784078, 0.0, - 8.881784197001252e-16, 1.7763568394002505e-15, - 1.7763568394002505e-15], - tspan=(0.0, 5.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_acoustics_gauss_wall.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_acoustics_gauss_wall.jl"), + l2 = [ + 0.029330394861252995, 0.029345079728907965, + 0.03803795043486467, 0.0, + 7.175152371650832e-16, 1.4350304743301665e-15, + 1.4350304743301665e-15, + ], + linf = [ + 0.36236334472179443, 0.3690785638275256, + 0.8475748723784078, 0.0, + 8.881784197001252e-16, 1.7763568394002505e-15, + 1.7763568394002505e-15, + ], + tspan = (0.0, 5.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), - l2=[0.06418293357851637, 0.12085176618704108, - 0.12085099342419513, 0.07743005602933221, - 0.1622218916638482, 0.04044434425257972, - 0.04044440614962498, 0.05735896706356321, - 0.0020992340041681734], - linf=[0.1417000509328017, 0.3210578460652491, 0.335041095545175, - 0.22500796423572675, - 0.44230628074326406, 0.16743171716317784, - 0.16745989278866702, 0.17700588224362557, - 0.02692320090677309], - tspan=(0.0, 0.5)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_ec.jl"), + l2 = [ + 0.06418293357851637, 0.12085176618704108, + 0.12085099342419513, 0.07743005602933221, + 0.1622218916638482, 0.04044434425257972, + 0.04044440614962498, 0.05735896706356321, + 0.0020992340041681734, + ], + linf = [ + 0.1417000509328017, 0.3210578460652491, 0.335041095545175, + 0.22500796423572675, + 0.44230628074326406, 0.16743171716317784, + 0.16745989278866702, 0.17700588224362557, + 0.02692320090677309, + ], + tspan = (0.0, 0.5) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_mhd_alfven_wave.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), - l2=[5.377518922553881e-5, 0.09999999206243514, - 0.09999999206243441, 0.1414213538550799, - 8.770450430886394e-6, 0.0999999926130084, - 0.0999999926130088, 0.14142135396487032, - 1.1553833987291942e-5], - linf=[0.00039334982566352483, 0.14144904937275282, - 0.14144904937277897, 0.20003315928443416, - 6.826863293230012e-5, 0.14146512909995967, - 0.14146512909994702, 0.20006706837452526, - 0.00013645610312810813], - tspan=(0.0, 0.5)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_mhd_alfven_wave.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_mhd_alfven_wave.jl"), + l2 = [ + 5.377518922553881e-5, 0.09999999206243514, + 0.09999999206243441, 0.1414213538550799, + 8.770450430886394e-6, 0.0999999926130084, + 0.0999999926130088, 0.14142135396487032, + 1.1553833987291942e-5, + ], + linf = [ + 0.00039334982566352483, 0.14144904937275282, + 0.14144904937277897, 0.20003315928443416, + 6.826863293230012e-5, 0.14146512909995967, + 0.14146512909994702, 0.20006706837452526, + 0.00013645610312810813, + ], + tspan = (0.0, 0.5) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_ec.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), - l2=[ - 0.6107326269462766, - 0.48666631722018877, - 0.48309775159067053, - 0.29467422718511704, - ], - linf=[ - 2.776782342826098, - 3.2158378644333707, - 3.652920889487258, - 2.052861364219655, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_ec.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec.jl"), + l2 = [ + 0.6107326269462766, + 0.48666631722018877, + 0.48309775159067053, + 0.29467422718511704, + ], + linf = [ + 2.776782342826098, + 3.2158378644333707, + 3.652920889487258, + 2.052861364219655, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_ec_float32.jl" begin - # Expected errors are nearly all taken from elixir_shallowwater_ec.jl - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec_float32.jl"), - l2=[ - Float32(0.6107326269462766), - Float32(0.48666631722018877), - Float32(0.48309775159067053), - Float32(0.29467422718511704), - ], - linf=[ - Float32(2.776782342826098), - 3.2162943f0, # this needs to be adapted - 3.6683278f0, # this needed to be adapted - Float32(2.052861364219655), - ], - tspan=(0.0f0, 0.25f0), - RealT=Float32) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_ec_float32.jl" begin + # Expected errors are nearly all taken from elixir_shallowwater_ec.jl + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_ec_float32.jl"), + l2 = [ + Float32(0.6107326269462766), + Float32(0.48666631722018877), + Float32(0.48309775159067053), + Float32(0.29467422718511704), + ], + linf = [ + Float32(2.776782342826098), + 3.2162943f0, # this needs to be adapted + 3.6683278f0, # this needed to be adapted + Float32(2.052861364219655), + ], + tspan = (0.0f0, 0.25f0), + RealT = Float32 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 1.2164292510839076, - 2.6118925543469468e-12, - 2.459878823146057e-12, - 1.2164292510839079, - ], - linf=[ - 1.5138512282315846, - 4.706289937431355e-11, - 4.913910192312011e-11, - 1.513851228231574, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 1.2164292510839076, + 2.6118925543469468e-12, + 2.459878823146057e-12, + 1.2164292510839079, + ], + linf = [ + 1.5138512282315846, + 4.706289937431355e-11, + 4.913910192312011e-11, + 1.513851228231574, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl with FluxHydrostaticReconstruction" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 1.2164292510839063, - 1.2676379081600215e-12, - 1.255855785593831e-12, - 1.2164292510839074, - ], - linf=[ - 1.5138512282315604, - 1.658245722058109e-11, - 1.8665562182185795e-11, - 1.5138512282315737, - ], - surface_flux=(FluxHydrostaticReconstruction(flux_lax_friedrichs, - hydrostatic_reconstruction_audusse_etal), - flux_nonconservative_audusse_etal), - tspan=(0.0, 0.2)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl with FluxHydrostaticReconstruction" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 1.2164292510839063, + 1.2676379081600215e-12, + 1.255855785593831e-12, + 1.2164292510839074, + ], + linf = [ + 1.5138512282315604, + 1.658245722058109e-11, + 1.8665562182185795e-11, + 1.5138512282315737, + ], + surface_flux = ( + FluxHydrostaticReconstruction( + flux_lax_friedrichs, + hydrostatic_reconstruction_audusse_etal + ), + flux_nonconservative_audusse_etal, + ), + tspan = (0.0, 0.2) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_well_balanced.jl with flux_nonconservative_wintermeyer_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), - l2=[ - 1.2164292510839083, - 2.590643638636187e-12, - 2.388742604639019e-12, - 1.2164292510839079, - ], - linf=[ - 1.5138512282315792, - 4.761278694199934e-11, - 4.910549479958249e-11, - 1.513851228231574, - ], - surface_flux=(flux_wintermeyer_etal, - flux_nonconservative_wintermeyer_etal), - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_well_balanced.jl with flux_nonconservative_wintermeyer_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_well_balanced.jl"), + l2 = [ + 1.2164292510839083, + 2.590643638636187e-12, + 2.388742604639019e-12, + 1.2164292510839079, + ], + linf = [ + 1.5138512282315792, + 4.761278694199934e-11, + 4.910549479958249e-11, + 1.513851228231574, + ], + surface_flux = ( + flux_wintermeyer_etal, + flux_nonconservative_wintermeyer_etal, + ), + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.001118134082248467, - 0.044560486817464634, - 0.01430926600634214, - 5.089218476759981e-6, - ], - linf=[ - 0.007798727223654822, - 0.34782952734839157, - 0.11161614702628064, - 2.6407324614341476e-5, - ], - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.001118134082248467, + 0.044560486817464634, + 0.01430926600634214, + 5.089218476759981e-6, + ], + linf = [ + 0.007798727223654822, + 0.34782952734839157, + 0.11161614702628064, + 2.6407324614341476e-5, + ], + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl with FluxHydrostaticReconstruction" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.0011196838135485918, - 0.01542895635133927, - 0.017082803023121197, - 5.089218476759981e-6, - ], - linf=[ - 0.014299541415654371, - 0.12783948113206955, - 0.17626489583921323, - 2.6407324614341476e-5, - ], - surface_flux=(FluxHydrostaticReconstruction(flux_hll, - hydrostatic_reconstruction_audusse_etal), - flux_nonconservative_audusse_etal), - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl with FluxHydrostaticReconstruction" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.0011196838135485918, + 0.01542895635133927, + 0.017082803023121197, + 5.089218476759981e-6, + ], + linf = [ + 0.014299541415654371, + 0.12783948113206955, + 0.17626489583921323, + 2.6407324614341476e-5, + ], + surface_flux = ( + FluxHydrostaticReconstruction( + flux_hll, + hydrostatic_reconstruction_audusse_etal + ), + flux_nonconservative_audusse_etal, + ), + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl with flux_nonconservative_wintermeyer_etal" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.001118046975499805, - 0.04455969246244461, - 0.014298120235633432, - 5.089218476759981e-6, - ], - linf=[ - 0.007776521213640031, - 0.34768318303226353, - 0.11075311228066198, - 2.6407324614341476e-5, - ], - surface_flux=(flux_wintermeyer_etal, - flux_nonconservative_wintermeyer_etal), - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl with flux_nonconservative_wintermeyer_etal" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.001118046975499805, + 0.04455969246244461, + 0.014298120235633432, + 5.089218476759981e-6, + ], + linf = [ + 0.007776521213640031, + 0.34768318303226353, + 0.11075311228066198, + 2.6407324614341476e-5, + ], + surface_flux = ( + flux_wintermeyer_etal, + flux_nonconservative_wintermeyer_etal, + ), + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_source_terms.jl with flux_hll" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), - l2=[ - 0.0011196838135486059, - 0.015428956351339451, - 0.017082803023120943, - 5.089218476759981e-6, - ], - linf=[ - 0.01429954141565526, - 0.12783948113205668, - 0.176264895839215, - 2.6407324614341476e-5, - ], - surface_flux=(flux_hll, - flux_nonconservative_fjordholm_etal), - tspan=(0.0, 0.025)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_source_terms.jl with flux_hll" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_source_terms.jl"), + l2 = [ + 0.0011196838135486059, + 0.015428956351339451, + 0.017082803023120943, + 5.089218476759981e-6, + ], + linf = [ + 0.01429954141565526, + 0.12783948113205668, + 0.176264895839215, + 2.6407324614341476e-5, + ], + surface_flux = ( + flux_hll, + flux_nonconservative_fjordholm_etal, + ), + tspan = (0.0, 0.025) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_dirichlet.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_shallowwater_dirichlet.jl"), - l2=[ - 1.1577518608950964e-5, - 4.761947272222427e-13, - 4.546045873135486e-13, - 1.157751860893347e-5, - ], - linf=[ - 8.394063879002545e-5, - 1.1211566736150389e-10, - 1.0890426250906834e-10, - 8.394063879602065e-5, - ], - tspan=(0.0, 2.0)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_dirichlet.jl" begin + @test_trixi_include( + joinpath(EXAMPLES_DIR, "elixir_shallowwater_dirichlet.jl"), + l2 = [ + 1.1577518608950964e-5, + 4.761947272222427e-13, + 4.546045873135486e-13, + 1.157751860893347e-5, + ], + linf = [ + 8.394063879002545e-5, + 1.1211566736150389e-10, + 1.0890426250906834e-10, + 8.394063879602065e-5, + ], + tspan = (0.0, 2.0) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_wall_bc_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_wall_bc_shockcapturing.jl"), - l2=[ - 0.0442113635677511, 0.1537465759364839, 0.16003586586203947, - 6.225080477067782e-8, - ], - linf=[ - 0.6347820607387928, 2.0078125433846736, 2.530726684667019, - 3.982097165344811e-7, - ], - tspan=(0.0, 0.05)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_wall_bc_shockcapturing.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_wall_bc_shockcapturing.jl" + ), + l2 = [ + 0.0442113635677511, 0.1537465759364839, 0.16003586586203947, + 6.225080477067782e-8, + ], + linf = [ + 0.6347820607387928, 2.0078125433846736, 2.530726684667019, + 3.982097165344811e-7, + ], + tspan = (0.0, 0.05) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "elixir_shallowwater_ec_shockcapturing.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, - "elixir_shallowwater_ec_shockcapturing.jl"), - l2=[ - 0.612551520607341, - 0.5039173660221961, - 0.49136517934903523, - 0.29467422718511704, - ], - linf=[ - 2.7636771472622197, - 3.236168963021072, - 3.3363936775653826, - 2.052861364219655, - ], - tspan=(0.0, 0.25)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "elixir_shallowwater_ec_shockcapturing.jl" begin + @test_trixi_include( + joinpath( + EXAMPLES_DIR, + "elixir_shallowwater_ec_shockcapturing.jl" + ), + l2 = [ + 0.612551520607341, + 0.5039173660221961, + 0.49136517934903523, + 0.29467422718511704, + ], + linf = [ + 2.7636771472622197, + 3.236168963021072, + 3.3363936775653826, + 2.052861364219655, + ], + tspan = (0.0, 0.25) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -# TODO: FD; for now put the unstructured tests for the 2D FDSBP here. -@trixi_testset "FDSBP (central): elixir_advection_basic.jl" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), - "elixir_advection_basic.jl"), - l2=[0.0001105211407319266], - linf=[0.0004199363734466166]) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + # TODO: FD; for now put the unstructured tests for the 2D FDSBP here. + @trixi_testset "FDSBP (central): elixir_advection_basic.jl" begin + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), + "elixir_advection_basic.jl" + ), + l2 = [0.0001105211407319266], + linf = [0.0004199363734466166] + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "FDSBP (central): elixir_euler_source_terms.jl" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), - "elixir_euler_source_terms.jl"), - l2=[8.155544666380138e-5, - 0.0001477863788446318, - 0.00014778637884460072, - 0.00045584189984542687], - linf=[0.0002670775876922882, - 0.0005683064706873964, - 0.0005683064706762941, - 0.0017770812025146299], - tspan=(0.0, 0.05)) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "FDSBP (central): elixir_euler_source_terms.jl" begin + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), + "elixir_euler_source_terms.jl" + ), + l2 = [ + 8.155544666380138e-5, + 0.0001477863788446318, + 0.00014778637884460072, + 0.00045584189984542687, + ], + linf = [ + 0.0002670775876922882, + 0.0005683064706873964, + 0.0005683064706762941, + 0.0017770812025146299, + ], + tspan = (0.0, 0.05) + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "FDSBP (central): elixir_euler_free_stream.jl" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), - "elixir_euler_free_stream.jl"), - l2=[5.4329175009362306e-14, - 1.0066867437607972e-13, - 6.889210012578449e-14, - 1.568290814572709e-13], - linf=[5.6139981552405516e-11, - 2.842849566864203e-11, - 1.8290174930157832e-11, - 4.61017890529547e-11], - tspan=(0.0, 0.1), - atol=1.0e-10) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "FDSBP (central): elixir_euler_free_stream.jl" begin + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), + "elixir_euler_free_stream.jl" + ), + l2 = [ + 5.4329175009362306e-14, + 1.0066867437607972e-13, + 6.889210012578449e-14, + 1.568290814572709e-13, + ], + linf = [ + 5.6139981552405516e-11, + 2.842849566864203e-11, + 1.8290174930157832e-11, + 4.61017890529547e-11, + ], + tspan = (0.0, 0.1), + atol = 1.0e-10 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "FDSBP (upwind): elixir_euler_source_terms_upwind.jl" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), - "elixir_euler_source_terms_upwind.jl"), - l2=[4.085391175504837e-5, - 7.19179253772227e-5, - 7.191792537723135e-5, - 0.0002177522206115571], - linf=[0.0004054489124620808, - 0.0006164432358217731, - 0.0006164432358186644, - 0.001363103391379461], - tspan=(0.0, 0.05), - atol=1.0e-10) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "FDSBP (upwind): elixir_euler_source_terms_upwind.jl" begin + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), + "elixir_euler_source_terms_upwind.jl" + ), + l2 = [ + 4.085391175504837e-5, + 7.19179253772227e-5, + 7.191792537723135e-5, + 0.0002177522206115571, + ], + linf = [ + 0.0004054489124620808, + 0.0006164432358217731, + 0.0006164432358186644, + 0.001363103391379461, + ], + tspan = (0.0, 0.05), + atol = 1.0e-10 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "FDSBP (upwind): elixir_euler_source_terms_upwind.jl with LF splitting" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), - "elixir_euler_source_terms_upwind.jl"), - l2=[3.8300267071890586e-5, - 5.295846741663533e-5, - 5.295846741663526e-5, - 0.00017564759295593478], - linf=[0.00018810716496542312, - 0.0003794187430412599, - 0.0003794187430412599, - 0.0009632958510650269], - tspan=(0.0, 0.025), - flux_splitting=splitting_lax_friedrichs, - atol=1.0e-10) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "FDSBP (upwind): elixir_euler_source_terms_upwind.jl with LF splitting" begin + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), + "elixir_euler_source_terms_upwind.jl" + ), + l2 = [ + 3.8300267071890586e-5, + 5.295846741663533e-5, + 5.295846741663526e-5, + 0.00017564759295593478, + ], + linf = [ + 0.00018810716496542312, + 0.0003794187430412599, + 0.0003794187430412599, + 0.0009632958510650269, + ], + tspan = (0.0, 0.025), + flux_splitting = splitting_lax_friedrichs, + atol = 1.0e-10 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "FDSBP (upwind): elixir_euler_free_stream_upwind.jl" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), - "elixir_euler_free_stream_upwind.jl"), - l2=[3.2114065566681054e-14, - 2.132488788134846e-14, - 2.106144937311659e-14, - 8.609642264224197e-13], - linf=[3.354871935812298e-11, - 7.006478730531285e-12, - 1.148153794261475e-11, - 7.461231632532872e-10], - tspan=(0.0, 0.05), - atol=1.0e-10) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "FDSBP (upwind): elixir_euler_free_stream_upwind.jl" begin + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), + "elixir_euler_free_stream_upwind.jl" + ), + l2 = [ + 3.2114065566681054e-14, + 2.132488788134846e-14, + 2.106144937311659e-14, + 8.609642264224197e-13, + ], + linf = [ + 3.354871935812298e-11, + 7.006478730531285e-12, + 1.148153794261475e-11, + 7.461231632532872e-10, + ], + tspan = (0.0, 0.05), + atol = 1.0e-10 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end -end -@trixi_testset "FDSBP (upwind): elixir_euler_free_stream_upwind_float32.jl" begin - @test_trixi_include(joinpath(pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), - "elixir_euler_free_stream_upwind_float32.jl"), - l2=[0, 0, 0, 0], - linf=[0, 0, 0, 0], - tspan=(0.0f0, 0.05f0), - atol=9.0f-4) - # Ensure that we do not have excessive memory allocations - # (e.g., from type instabilities) - let - t = sol.t[end] - u_ode = sol.u[end] - du_ode = similar(u_ode) - @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + @trixi_testset "FDSBP (upwind): elixir_euler_free_stream_upwind_float32.jl" begin + @test_trixi_include( + joinpath( + pkgdir(Trixi, "examples", "unstructured_2d_fdsbp"), + "elixir_euler_free_stream_upwind_float32.jl" + ), + l2 = [0, 0, 0, 0], + linf = [0, 0, 0, 0], + tspan = (0.0f0, 0.05f0), + atol = 9.0f-4 + ) + # Ensure that we do not have excessive memory allocations + # (e.g., from type instabilities) + let + t = sol.t[end] + u_ode = sol.u[end] + du_ode = similar(u_ode) + @test (@allocated Trixi.rhs!(du_ode, u_ode, semi, t)) < 1000 + end end end -end # Clean up afterwards: delete Trixi.jl output directory @test_nowarn rm(outdir, recursive = true) diff --git a/test/test_visualization.jl b/test/test_visualization.jl index 5c7e5dbbd1..135d6d9f5f 100644 --- a/test/test_visualization.jl +++ b/test/test_visualization.jl @@ -19,380 +19,442 @@ isdir(outdir) && rm(outdir, recursive = true) # Run various visualization tests @testset "Visualization tests" begin -#! format: noindent - -# Run 2D tests with elixirs for all mesh types -test_examples_2d = Dict("TreeMesh" => ("tree_2d_dgsem", - "elixir_euler_blast_wave_amr.jl"), - "StructuredMesh" => ("structured_2d_dgsem", - "elixir_euler_source_terms_waving_flag.jl"), - "UnstructuredMesh" => ("unstructured_2d_dgsem", - "elixir_euler_basic.jl"), - "P4estMesh" => ("p4est_2d_dgsem", - "elixir_euler_source_terms_nonconforming_unstructured_flag.jl"), - "DGMulti" => ("dgmulti_2d", "elixir_euler_weakform.jl")) - -@testset "PlotData2D, PlotDataSeries, PlotMesh with $mesh" for mesh in keys(test_examples_2d) - # Run Trixi.jl - directory, elixir = test_examples_2d[mesh] - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), directory, elixir), - tspan = (0, 0.1)) - - # Constructor tests - if mesh == "TreeMesh" - @test PlotData2D(sol) isa Trixi.PlotData2DCartesian - @test PlotData2D(sol; nvisnodes = 0, grid_lines = false, - solution_variables = cons2cons) isa Trixi.PlotData2DCartesian - @test Trixi.PlotData2DTriangulated(sol) isa Trixi.PlotData2DTriangulated - else - @test PlotData2D(sol) isa Trixi.PlotData2DTriangulated - @test PlotData2D(sol; nvisnodes = 0, solution_variables = cons2cons) isa - Trixi.PlotData2DTriangulated - end - pd = PlotData2D(sol) - - # show - @test_nowarn_mod show(stdout, pd) - println(stdout) - - # getindex - @test pd["rho"] == Trixi.PlotDataSeries(pd, 1) - @test pd["v1"] == Trixi.PlotDataSeries(pd, 2) - @test pd["v2"] == Trixi.PlotDataSeries(pd, 3) - @test pd["p"] == Trixi.PlotDataSeries(pd, 4) - @test_throws KeyError pd["does not exist"] - - # convenience methods for mimicking a dictionary - @test pd[begin] == Trixi.PlotDataSeries(pd, 1) - @test pd[end] == Trixi.PlotDataSeries(pd, 4) - @test length(pd) == 4 - @test size(pd) == (4,) - @test keys(pd) == ("rho", "v1", "v2", "p") - @test eltype(pd) <: Pair{String, <:Trixi.PlotDataSeries} - @test [v for v in pd] == ["rho" => Trixi.PlotDataSeries(pd, 1), - "v1" => Trixi.PlotDataSeries(pd, 2), - "v2" => Trixi.PlotDataSeries(pd, 3), - "p" => Trixi.PlotDataSeries(pd, 4)] - - # PlotDataSeries - pds = pd["p"] - @test pds.plot_data == pd - @test pds.variable_id == 4 - @test_nowarn_mod show(stdout, pds) - println(stdout) - - # getmesh/PlotMesh - @test getmesh(pd) == Trixi.PlotMesh(pd) - @test getmesh(pd).plot_data == pd - @test_nowarn_mod show(stdout, getmesh(pd)) - println(stdout) - - @testset "2D plot recipes" begin + #! format: noindent + + # Run 2D tests with elixirs for all mesh types + test_examples_2d = Dict( + "TreeMesh" => ( + "tree_2d_dgsem", + "elixir_euler_blast_wave_amr.jl", + ), + "StructuredMesh" => ( + "structured_2d_dgsem", + "elixir_euler_source_terms_waving_flag.jl", + ), + "UnstructuredMesh" => ( + "unstructured_2d_dgsem", + "elixir_euler_basic.jl", + ), + "P4estMesh" => ( + "p4est_2d_dgsem", + "elixir_euler_source_terms_nonconforming_unstructured_flag.jl", + ), + "DGMulti" => ("dgmulti_2d", "elixir_euler_weakform.jl") + ) + + @testset "PlotData2D, PlotDataSeries, PlotMesh with $mesh" for mesh in keys(test_examples_2d) + # Run Trixi.jl + directory, elixir = test_examples_2d[mesh] + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath(examples_dir(), directory, elixir), + tspan = (0, 0.1) + ) + + # Constructor tests + if mesh == "TreeMesh" + @test PlotData2D(sol) isa Trixi.PlotData2DCartesian + @test PlotData2D( + sol; nvisnodes = 0, grid_lines = false, + solution_variables = cons2cons + ) isa Trixi.PlotData2DCartesian + @test Trixi.PlotData2DTriangulated(sol) isa Trixi.PlotData2DTriangulated + else + @test PlotData2D(sol) isa Trixi.PlotData2DTriangulated + @test PlotData2D(sol; nvisnodes = 0, solution_variables = cons2cons) isa + Trixi.PlotData2DTriangulated + end pd = PlotData2D(sol) - @test_nowarn_mod Plots.plot(sol) - @test_nowarn_mod Plots.plot(pd) - @test_nowarn_mod Plots.plot(pd["p"]) - @test_nowarn_mod Plots.plot(getmesh(pd)) + # show + @test_nowarn_mod show(stdout, pd) + println(stdout) - semi = sol.prob.p - if mesh == "DGMulti" - scalar_data = StructArrays.component(sol.u[end], 1) - @test_nowarn_mod Plots.plot(ScalarPlotData2D(scalar_data, semi)) - else - cache = semi.cache - x = view(cache.elements.node_coordinates, 1, :, :, :) - @test_nowarn_mod Plots.plot(ScalarPlotData2D(x, semi)) - end - end + # getindex + @test pd["rho"] == Trixi.PlotDataSeries(pd, 1) + @test pd["v1"] == Trixi.PlotDataSeries(pd, 2) + @test pd["v2"] == Trixi.PlotDataSeries(pd, 3) + @test pd["p"] == Trixi.PlotDataSeries(pd, 4) + @test_throws KeyError pd["does not exist"] + + # convenience methods for mimicking a dictionary + @test pd[begin] == Trixi.PlotDataSeries(pd, 1) + @test pd[end] == Trixi.PlotDataSeries(pd, 4) + @test length(pd) == 4 + @test size(pd) == (4,) + @test keys(pd) == ("rho", "v1", "v2", "p") + @test eltype(pd) <: Pair{String, <:Trixi.PlotDataSeries} + @test [v for v in pd] == [ + "rho" => Trixi.PlotDataSeries(pd, 1), + "v1" => Trixi.PlotDataSeries(pd, 2), + "v2" => Trixi.PlotDataSeries(pd, 3), + "p" => Trixi.PlotDataSeries(pd, 4), + ] + + # PlotDataSeries + pds = pd["p"] + @test pds.plot_data == pd + @test pds.variable_id == 4 + @test_nowarn_mod show(stdout, pds) + println(stdout) - @testset "1D plot from 2D solution" begin - if mesh != "DGMulti" - @testset "Create 1D plot as slice" begin - @test_nowarn_mod PlotData1D(sol, slice = :y, point = (0.5, 0.0)) isa - PlotData1D - @test_nowarn_mod PlotData1D(sol, slice = :x, point = (0.5, 0.0)) isa - PlotData1D - pd1D = PlotData1D(sol, slice = :y, point = (0.5, 0.0)) - @test_nowarn_mod Plots.plot(pd1D) + # getmesh/PlotMesh + @test getmesh(pd) == Trixi.PlotMesh(pd) + @test getmesh(pd).plot_data == pd + @test_nowarn_mod show(stdout, getmesh(pd)) + println(stdout) + + @testset "2D plot recipes" begin + pd = PlotData2D(sol) + + @test_nowarn_mod Plots.plot(sol) + @test_nowarn_mod Plots.plot(pd) + @test_nowarn_mod Plots.plot(pd["p"]) + @test_nowarn_mod Plots.plot(getmesh(pd)) + + semi = sol.prob.p + if mesh == "DGMulti" + scalar_data = StructArrays.component(sol.u[end], 1) + @test_nowarn_mod Plots.plot(ScalarPlotData2D(scalar_data, semi)) + else + cache = semi.cache + x = view(cache.elements.node_coordinates, 1, :, :, :) + @test_nowarn_mod Plots.plot(ScalarPlotData2D(x, semi)) + end + end - @testset "Create 1D plot along curve" begin - curve = zeros(2, 10) - curve[1, :] = range(-1, 1, length = 10) - @test_nowarn_mod PlotData1D(sol, curve = curve) isa PlotData1D - pd1D = PlotData1D(sol, curve = curve) + @testset "1D plot from 2D solution" begin + if mesh != "DGMulti" + @testset "Create 1D plot as slice" begin + @test_nowarn_mod PlotData1D(sol, slice = :y, point = (0.5, 0.0)) isa + PlotData1D + @test_nowarn_mod PlotData1D(sol, slice = :x, point = (0.5, 0.0)) isa + PlotData1D + pd1D = PlotData1D(sol, slice = :y, point = (0.5, 0.0)) @test_nowarn_mod Plots.plot(pd1D) + + @testset "Create 1D plot along curve" begin + curve = zeros(2, 10) + curve[1, :] = range(-1, 1, length = 10) + @test_nowarn_mod PlotData1D(sol, curve = curve) isa PlotData1D + pd1D = PlotData1D(sol, curve = curve) + @test_nowarn_mod Plots.plot(pd1D) + end end end end end -end -@timed_testset "PlotData1D, PlotDataSeries, PlotMesh" begin - # Run Trixi.jl - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_1d_dgsem", - "elixir_euler_blast_wave.jl"), - tspan = (0, 0.1)) - - # Constructor - @test PlotData1D(sol) isa PlotData1D - pd = PlotData1D(sol) - - # show - @test_nowarn_mod show(stdout, pd) - println(stdout) - - # getindex - @test pd["rho"] == Trixi.PlotDataSeries(pd, 1) - @test pd["v1"] == Trixi.PlotDataSeries(pd, 2) - @test pd["p"] == Trixi.PlotDataSeries(pd, 3) - @test_throws KeyError pd["does not exist"] - - # convenience methods for mimicking a dictionary - @test pd[begin] == Trixi.PlotDataSeries(pd, 1) - @test pd[end] == Trixi.PlotDataSeries(pd, 3) - @test length(pd) == 3 - @test size(pd) == (3,) - @test keys(pd) == ("rho", "v1", "p") - @test eltype(pd) <: Pair{String, <:Trixi.PlotDataSeries} - @test [v for v in pd] == ["rho" => Trixi.PlotDataSeries(pd, 1), - "v1" => Trixi.PlotDataSeries(pd, 2), - "p" => Trixi.PlotDataSeries(pd, 3)] - - # PlotDataSeries - pds = pd["p"] - @test pds.plot_data == pd - @test pds.variable_id == 3 - @test_nowarn_mod show(stdout, pds) - println(stdout) - - # getmesh/PlotMesh - @test getmesh(pd) == Trixi.PlotMesh(pd) - @test getmesh(pd).plot_data == pd - @test_nowarn_mod show(stdout, getmesh(pd)) - println(stdout) - - # nvisnodes - @test size(pd.data) == (512, 3) - pd0 = PlotData1D(sol, nvisnodes = 0) - @test size(pd0.data) == (256, 3) - pd2 = PlotData1D(sol, nvisnodes = 2) - @test size(pd2.data) == (128, 3) - - @testset "1D plot recipes" begin + @timed_testset "PlotData1D, PlotDataSeries, PlotMesh" begin + # Run Trixi.jl + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "tree_1d_dgsem", + "elixir_euler_blast_wave.jl" + ), + tspan = (0, 0.1) + ) + + # Constructor + @test PlotData1D(sol) isa PlotData1D pd = PlotData1D(sol) - @test_nowarn_mod Plots.plot(sol) - @test_nowarn_mod Plots.plot(pd) - @test_nowarn_mod Plots.plot(pd["p"]) - @test_nowarn_mod Plots.plot(getmesh(pd)) + # show + @test_nowarn_mod show(stdout, pd) + println(stdout) + + # getindex + @test pd["rho"] == Trixi.PlotDataSeries(pd, 1) + @test pd["v1"] == Trixi.PlotDataSeries(pd, 2) + @test pd["p"] == Trixi.PlotDataSeries(pd, 3) + @test_throws KeyError pd["does not exist"] + + # convenience methods for mimicking a dictionary + @test pd[begin] == Trixi.PlotDataSeries(pd, 1) + @test pd[end] == Trixi.PlotDataSeries(pd, 3) + @test length(pd) == 3 + @test size(pd) == (3,) + @test keys(pd) == ("rho", "v1", "p") + @test eltype(pd) <: Pair{String, <:Trixi.PlotDataSeries} + @test [v for v in pd] == [ + "rho" => Trixi.PlotDataSeries(pd, 1), + "v1" => Trixi.PlotDataSeries(pd, 2), + "p" => Trixi.PlotDataSeries(pd, 3), + ] + + # PlotDataSeries + pds = pd["p"] + @test pds.plot_data == pd + @test pds.variable_id == 3 + @test_nowarn_mod show(stdout, pds) + println(stdout) + + # getmesh/PlotMesh + @test getmesh(pd) == Trixi.PlotMesh(pd) + @test getmesh(pd).plot_data == pd + @test_nowarn_mod show(stdout, getmesh(pd)) + println(stdout) + + # nvisnodes + @test size(pd.data) == (512, 3) + pd0 = PlotData1D(sol, nvisnodes = 0) + @test size(pd0.data) == (256, 3) + pd2 = PlotData1D(sol, nvisnodes = 2) + @test size(pd2.data) == (128, 3) + + @testset "1D plot recipes" begin + pd = PlotData1D(sol) + + @test_nowarn_mod Plots.plot(sol) + @test_nowarn_mod Plots.plot(pd) + @test_nowarn_mod Plots.plot(pd["p"]) + @test_nowarn_mod Plots.plot(getmesh(pd)) + end + + # Fake a PlotDataXD objects to test code for plotting multiple variables on at least two rows + # with at least one plot remaining empty + @testset "plotting multiple variables" begin + x = collect(0.0:0.1:1.0) + data1d = rand(5, 11) + variable_names = string.('a':'e') + mesh_vertices_x1d = [x[begin], x[end]] + fake1d = PlotData1D(x, data1d, variable_names, mesh_vertices_x1d, 0) + @test_nowarn_mod Plots.plot(fake1d) + + y = x + data2d = [rand(11, 11) for _ in 1:5] + mesh_vertices_x2d = [0.0, 1.0, 1.0, 0.0] + mesh_vertices_y2d = [0.0, 0.0, 1.0, 1.0] + fake2d = Trixi.PlotData2DCartesian( + x, y, data2d, variable_names, + mesh_vertices_x2d, mesh_vertices_y2d, 0, 0 + ) + @test_nowarn_mod Plots.plot(fake2d) + end end - # Fake a PlotDataXD objects to test code for plotting multiple variables on at least two rows - # with at least one plot remaining empty - @testset "plotting multiple variables" begin - x = collect(0.0:0.1:1.0) - data1d = rand(5, 11) - variable_names = string.('a':'e') - mesh_vertices_x1d = [x[begin], x[end]] - fake1d = PlotData1D(x, data1d, variable_names, mesh_vertices_x1d, 0) - @test_nowarn_mod Plots.plot(fake1d) - - y = x - data2d = [rand(11, 11) for _ in 1:5] - mesh_vertices_x2d = [0.0, 1.0, 1.0, 0.0] - mesh_vertices_y2d = [0.0, 0.0, 1.0, 1.0] - fake2d = Trixi.PlotData2DCartesian(x, y, data2d, variable_names, - mesh_vertices_x2d, mesh_vertices_y2d, 0, 0) - @test_nowarn_mod Plots.plot(fake2d) + @timed_testset "PlotData1D (DGMulti)" begin + # Test two different approximation types since these use different memory layouts: + # - structure of arrays for `Polynomial()` + # - array of structures for `SBP()` + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "dgmulti_1d", + "elixir_euler_flux_diff.jl" + ), + tspan = (0.0, 0.0), + approximation_type = Polynomial() + ) + @test PlotData1D(sol) isa PlotData1D + + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "dgmulti_1d", + "elixir_euler_flux_diff.jl" + ), + tspan = (0.0, 0.0), + approximation_type = SBP() + ) + @test PlotData1D(sol) isa PlotData1D end -end -@timed_testset "PlotData1D (DGMulti)" begin - # Test two different approximation types since these use different memory layouts: - # - structure of arrays for `Polynomial()` - # - array of structures for `SBP()` - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "dgmulti_1d", - "elixir_euler_flux_diff.jl"), - tspan = (0.0, 0.0), - approximation_type = Polynomial()) - @test PlotData1D(sol) isa PlotData1D - - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "dgmulti_1d", - "elixir_euler_flux_diff.jl"), - tspan = (0.0, 0.0), - approximation_type = SBP()) - @test PlotData1D(sol) isa PlotData1D -end + @timed_testset "plot time series" begin + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_acoustics_gaussian_source.jl" + ), + tspan = (0, 0.05) + ) + + @test_nowarn_mod Plots.plot(time_series, 1) + @test PlotData1D(time_series, 1) isa PlotData1D + end + + @timed_testset "adapt_to_mesh_level" begin + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_basic.jl" + ), + analysis_callback = Trixi.TrivialCallback() + ) + @test adapt_to_mesh_level(sol, 5) isa Tuple + + u_ode_level5, semi_level5 = adapt_to_mesh_level(sol, 5) + u_ode_level4, semi_level4 = adapt_to_mesh_level(u_ode_level5, semi_level5, 4) + @test isapprox(sol.u[end], u_ode_level4, atol = 1.0e-13) + + @test adapt_to_mesh_level!(sol, 5) isa Tuple + @test isapprox(sol.u[end], u_ode_level5, atol = 1.0e-13) + end -@timed_testset "plot time series" begin - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_acoustics_gaussian_source.jl"), - tspan = (0, 0.05)) + @timed_testset "plot 3D" begin + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "tree_3d_dgsem", + "elixir_advection_basic.jl" + ), + analysis_callback = Trixi.TrivialCallback(), + initial_refinement_level = 1 + ) + @test PlotData2D(sol) isa Trixi.PlotData2DCartesian + @test PlotData2D(sol, slice = :yz) isa Trixi.PlotData2DCartesian + @test PlotData2D(sol, slice = :xz) isa Trixi.PlotData2DCartesian - @test_nowarn_mod Plots.plot(time_series, 1) - @test PlotData1D(time_series, 1) isa PlotData1D -end + @testset "1D plot from 3D solution and Tree-mesh" begin + @testset "Create 1D plot as slice" begin + @test_nowarn_mod PlotData1D(sol) isa PlotData1D + pd1D = PlotData1D(sol) + @test_nowarn_mod Plots.plot(pd1D) + @test_nowarn_mod PlotData1D(sol, slice = :y, point = (0.5, 0.3, 0.1)) isa + PlotData1D + @test_nowarn_mod PlotData1D(sol, slice = :z, point = (0.1, 0.3, 0.3)) isa + PlotData1D + end -@timed_testset "adapt_to_mesh_level" begin - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_basic.jl"), - analysis_callback = Trixi.TrivialCallback()) - @test adapt_to_mesh_level(sol, 5) isa Tuple + @testset "Create 1D plot along curve" begin + curve = zeros(3, 10) + curve[1, :] = range(-1.0, -0.5, length = 10) + @test_nowarn_mod PlotData1D(sol, curve = curve) isa PlotData1D + pd1D = PlotData1D(sol, curve = curve) + @test_nowarn_mod Plots.plot(pd1D) + end + end - u_ode_level5, semi_level5 = adapt_to_mesh_level(sol, 5) - u_ode_level4, semi_level4 = adapt_to_mesh_level(u_ode_level5, semi_level5, 4) - @test isapprox(sol.u[end], u_ode_level4, atol = 1e-13) + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "structured_3d_dgsem", + "elixir_advection_basic.jl" + ) + ) - @test adapt_to_mesh_level!(sol, 5) isa Tuple - @test isapprox(sol.u[end], u_ode_level5, atol = 1e-13) -end + @testset "1D plot from 3D solution and general mesh" begin + @testset "Create 1D plot as slice" begin + @test_nowarn_mod PlotData1D(sol) isa PlotData1D + pd1D = PlotData1D(sol) + @test_nowarn_mod Plots.plot(pd1D) + @test_nowarn_mod PlotData1D(sol, slice = :y, point = (0.5, 0.3, 0.1)) isa + PlotData1D + @test_nowarn_mod PlotData1D(sol, slice = :z, point = (0.1, 0.3, 0.3)) isa + PlotData1D + end -@timed_testset "plot 3D" begin - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_3d_dgsem", - "elixir_advection_basic.jl"), - analysis_callback = Trixi.TrivialCallback(), - initial_refinement_level = 1) - @test PlotData2D(sol) isa Trixi.PlotData2DCartesian - @test PlotData2D(sol, slice = :yz) isa Trixi.PlotData2DCartesian - @test PlotData2D(sol, slice = :xz) isa Trixi.PlotData2DCartesian - - @testset "1D plot from 3D solution and Tree-mesh" begin - @testset "Create 1D plot as slice" begin - @test_nowarn_mod PlotData1D(sol) isa PlotData1D - pd1D = PlotData1D(sol) - @test_nowarn_mod Plots.plot(pd1D) - @test_nowarn_mod PlotData1D(sol, slice = :y, point = (0.5, 0.3, 0.1)) isa - PlotData1D - @test_nowarn_mod PlotData1D(sol, slice = :z, point = (0.1, 0.3, 0.3)) isa - PlotData1D + @testset "Create 1D plot along curve" begin + curve = zeros(3, 10) + curve[1, :] = range(-1.0, 1.0, length = 10) + @test_nowarn_mod PlotData1D(sol, curve = curve) isa PlotData1D + pd1D = PlotData1D(sol, curve = curve) + @test_nowarn_mod Plots.plot(pd1D) + end end + end - @testset "Create 1D plot along curve" begin - curve = zeros(3, 10) - curve[1, :] = range(-1.0, -0.5, length = 10) - @test_nowarn_mod PlotData1D(sol, curve = curve) isa PlotData1D - pd1D = PlotData1D(sol, curve = curve) - @test_nowarn_mod Plots.plot(pd1D) - end + @timed_testset "plotting TimeIntegratorSolution" begin + @test_trixi_include( + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_hypdiff_lax_friedrichs.jl" + ), + maxiters = 1, analysis_callback = Trixi.TrivialCallback(), + initial_refinement_level = 1 + ) + @test_nowarn_mod Plots.plot(sol) end - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "structured_3d_dgsem", - "elixir_advection_basic.jl")) - - @testset "1D plot from 3D solution and general mesh" begin - @testset "Create 1D plot as slice" begin - @test_nowarn_mod PlotData1D(sol) isa PlotData1D - pd1D = PlotData1D(sol) - @test_nowarn_mod Plots.plot(pd1D) - @test_nowarn_mod PlotData1D(sol, slice = :y, point = (0.5, 0.3, 0.1)) isa - PlotData1D - @test_nowarn_mod PlotData1D(sol, slice = :z, point = (0.1, 0.3, 0.3)) isa - PlotData1D + @timed_testset "VisualizationCallback" begin + # To make CI tests work, disable showing a plot window with the GR backend of the Plots package + # Xref: https://github.com/jheinen/GR.jl/issues/278 + # Xref: https://github.com/JuliaPlots/Plots.jl/blob/8cc6d9d48755ba452a2835f9b89d3880e9945377/test/runtests.jl#L103 + if !isinteractive() + restore = get(ENV, "GKSwstype", nothing) + ENV["GKSwstype"] = "100" end - @testset "Create 1D plot along curve" begin - curve = zeros(3, 10) - curve[1, :] = range(-1.0, 1.0, length = 10) - @test_nowarn_mod PlotData1D(sol, curve = curve) isa PlotData1D - pd1D = PlotData1D(sol, curve = curve) - @test_nowarn_mod Plots.plot(pd1D) + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "tree_2d_dgsem", + "elixir_advection_amr_visualization.jl" + ), + visualization = VisualizationCallback( + interval = 20, + clims = (0, 1), + plot_creator = Trixi.save_plot + ), + tspan = (0.0, 3.0) + ) + + @testset "elixir_advection_amr_visualization.jl with save_plot" begin + @test isfile(joinpath(outdir, "solution_000000000.png")) + @test isfile(joinpath(outdir, "solution_000000020.png")) + @test isfile(joinpath(outdir, "solution_000000022.png")) end - end -end -@timed_testset "plotting TimeIntegratorSolution" begin - @test_trixi_include(joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_hypdiff_lax_friedrichs.jl"), - maxiters=1, analysis_callback=Trixi.TrivialCallback(), - initial_refinement_level=1) - @test_nowarn_mod Plots.plot(sol) -end + @testset "show" begin + @test_nowarn_mod show(stdout, visualization) + println(stdout) -@timed_testset "VisualizationCallback" begin - # To make CI tests work, disable showing a plot window with the GR backend of the Plots package - # Xref: https://github.com/jheinen/GR.jl/issues/278 - # Xref: https://github.com/JuliaPlots/Plots.jl/blob/8cc6d9d48755ba452a2835f9b89d3880e9945377/test/runtests.jl#L103 - if !isinteractive() - restore = get(ENV, "GKSwstype", nothing) - ENV["GKSwstype"] = "100" - end + @test_nowarn_mod show(stdout, "text/plain", visualization) + println(stdout) + end - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "tree_2d_dgsem", - "elixir_advection_amr_visualization.jl"), - visualization = VisualizationCallback(interval = 20, - clims = (0, 1), - plot_creator = Trixi.save_plot), - tspan = (0.0, 3.0)) - - @testset "elixir_advection_amr_visualization.jl with save_plot" begin - @test isfile(joinpath(outdir, "solution_000000000.png")) - @test isfile(joinpath(outdir, "solution_000000020.png")) - @test isfile(joinpath(outdir, "solution_000000022.png")) + # Restore GKSwstype to previous value (if it was set) + if !isinteractive() + if isnothing(restore) + delete!(ENV, "GKSwstype") + else + ENV["GKSwstype"] = restore + end + end end - @testset "show" begin - @test_nowarn_mod show(stdout, visualization) - println(stdout) + @timed_testset "Makie visualization tests for UnstructuredMesh2D" begin + @test_nowarn_mod trixi_include( + @__MODULE__, + joinpath( + examples_dir(), "unstructured_2d_dgsem", + "elixir_euler_wall_bc.jl" + ) + ) - @test_nowarn_mod show(stdout, "text/plain", visualization) - println(stdout) - end + # test interactive surface plot + @test_nowarn_mod Trixi.iplot(sol) - # Restore GKSwstype to previous value (if it was set) - if !isinteractive() - if isnothing(restore) - delete!(ENV, "GKSwstype") - else - ENV["GKSwstype"] = restore - end - end -end + # also test when using PlotData2D object + @test PlotData2D(sol) isa Trixi.PlotData2DTriangulated + @test_nowarn_mod Makie.plot(PlotData2D(sol)) -@timed_testset "Makie visualization tests for UnstructuredMesh2D" begin - @test_nowarn_mod trixi_include(@__MODULE__, - joinpath(examples_dir(), "unstructured_2d_dgsem", - "elixir_euler_wall_bc.jl")) - - # test interactive surface plot - @test_nowarn_mod Trixi.iplot(sol) - - # also test when using PlotData2D object - @test PlotData2D(sol) isa Trixi.PlotData2DTriangulated - @test_nowarn_mod Makie.plot(PlotData2D(sol)) - - # test interactive ScalarPlotData2D plotting - semi = sol.prob.p - x = view(semi.cache.elements.node_coordinates, 1, :, :, :) # extracts the node x coordinates - y = view(semi.cache.elements.node_coordinates, 2, :, :, :) # extracts the node x coordinates - @test_nowarn_mod iplot(ScalarPlotData2D(x .+ y, semi), plot_mesh = true) - - # test heatmap plot - @test_nowarn_mod Makie.plot(sol, plot_mesh = true) - - # test unpacking/iteration for FigureAndAxes - fa = Makie.plot(sol) - fig, axes = fa - @test_nowarn_mod Base.show(fa) === nothing - @test_nowarn_mod typeof(fig) <: Makie.Figure - @test_nowarn_mod typeof(axes) <: AbstractArray{<:Makie.Axis} - - # test plotting of constant solutions with Makie - # related issue: https://github.com/MakieOrg/Makie.jl/issues/931 - for i in eachindex(sol.u) - fill!(sol.u[i], one(eltype(sol.u[i]))) + # test interactive ScalarPlotData2D plotting + semi = sol.prob.p + x = view(semi.cache.elements.node_coordinates, 1, :, :, :) # extracts the node x coordinates + y = view(semi.cache.elements.node_coordinates, 2, :, :, :) # extracts the node x coordinates + @test_nowarn_mod iplot(ScalarPlotData2D(x .+ y, semi), plot_mesh = true) + + # test heatmap plot + @test_nowarn_mod Makie.plot(sol, plot_mesh = true) + + # test unpacking/iteration for FigureAndAxes + fa = Makie.plot(sol) + fig, axes = fa + @test_nowarn_mod Base.show(fa) === nothing + @test_nowarn_mod typeof(fig) <: Makie.Figure + @test_nowarn_mod typeof(axes) <: AbstractArray{<:Makie.Axis} + + # test plotting of constant solutions with Makie + # related issue: https://github.com/MakieOrg/Makie.jl/issues/931 + for i in eachindex(sol.u) + fill!(sol.u[i], one(eltype(sol.u[i]))) + end + @test_nowarn_mod Trixi.iplot(sol) end - @test_nowarn_mod Trixi.iplot(sol) -end end end #module diff --git a/utils/build_sysimage.jl b/utils/build_sysimage.jl index 69bce54b26..e32ed826ef 100755 --- a/utils/build_sysimage.jl +++ b/utils/build_sysimage.jl @@ -62,16 +62,20 @@ else # Otherwise, figure out all direct dependencies and add them instead # Inspired by: https://github.com/CliMA/ClimateMachine.jl/blob/8c57fb55acc20ee824ea37478395a7cb07c5a93c/.dev/systemimage/climate_machine_image.jl trixi_uuid = Base.UUID("a7f1ee26-1774-49b1-8366-f1abc58fbfcb") - append!(packages, - Symbol[Symbol(v) for v in keys(Pkg.dependencies()[trixi_uuid].dependencies)]) + append!( + packages, + Symbol[Symbol(v) for v in keys(Pkg.dependencies()[trixi_uuid].dependencies)] + ) end map(Pkg.add ∘ string, packages) Pkg.precompile() # Collect remaining arguments -sysimage_path = get(ENV, "TRIXI_SYSIMAGE_PATH", - joinpath(@__DIR__, "TrixiSysimage." * Libdl.dlext)) +sysimage_path = get( + ENV, "TRIXI_SYSIMAGE_PATH", + joinpath(@__DIR__, "TrixiSysimage." * Libdl.dlext) +) precompile_execution_file = joinpath(@__DIR__, "precompile_execution_file.jl") # Create system image @@ -80,10 +84,12 @@ precompile_execution_file = joinpath(@__DIR__, "precompile_execution_file.jl") @info "Precompile execution file: $precompile_execution_file" using PackageCompiler -PackageCompiler.create_sysimage(packages, - sysimage_path = sysimage_path, - precompile_execution_file = precompile_execution_file, - cpu_target = PackageCompiler.default_app_cpu_target()) +PackageCompiler.create_sysimage( + packages, + sysimage_path = sysimage_path, + precompile_execution_file = precompile_execution_file, + cpu_target = PackageCompiler.default_app_cpu_target() +) duration = time() - start_time @info "Done. Created sysimage in $duration seconds." diff --git a/utils/trixi-format-file.jl b/utils/trixi-format-file.jl index 9b9a0e4949..918e89709e 100755 --- a/utils/trixi-format-file.jl +++ b/utils/trixi-format-file.jl @@ -2,8 +2,10 @@ using Pkg Pkg.activate(; temp = true, io = devnull) -Pkg.add(PackageSpec(name = "JuliaFormatter", version = "1.0.45"); preserve = PRESERVE_ALL, - io = devnull) +Pkg.add( + PackageSpec(name = "JuliaFormatter", version = "1.0.45"); preserve = PRESERVE_ALL, + io = devnull +) using JuliaFormatter: format_file diff --git a/utils/trixi-format.jl b/utils/trixi-format.jl index 63f1407880..b771e7b4a4 100755 --- a/utils/trixi-format.jl +++ b/utils/trixi-format.jl @@ -2,8 +2,10 @@ using Pkg Pkg.activate(; temp = true, io = devnull) -Pkg.add(PackageSpec(name = "JuliaFormatter", version = "1.0.45"); preserve = PRESERVE_ALL, - io = devnull) +Pkg.add( + PackageSpec(name = "JuliaFormatter", version = "1.0.45"); preserve = PRESERVE_ALL, + io = devnull +) using JuliaFormatter: format diff --git a/utils/trixi2tec.jl b/utils/trixi2tec.jl index fc5f3e705c..a21ee8a87d 100644 --- a/utils/trixi2tec.jl +++ b/utils/trixi2tec.jl @@ -32,8 +32,10 @@ julia> trixi2tec(sol, "mydata_primitive.tec", solution_variables=cons2prim) This is an experimental feature and *not* part of the official Trixi.jl API. Specifically, this function may change (or even be removed) in future releases without warning. """ -function trixi2tec(u, semi, filename; title = basename(filename), - solution_variables = cons2cons) +function trixi2tec( + u, semi, filename; title = basename(filename), + solution_variables = cons2cons + ) # Extract fundamental building blocks and auxiliary data mesh, equations, solver, cache = Trixi.mesh_equations_solver_cache(semi) @unpack node_coordinates = cache.elements @@ -71,10 +73,16 @@ function trixi2tec(u, semi, filename; title = basename(filename), for element in eachelement(solver, cache) write(io, zone_info) for ci in indices - node_coords = Trixi.get_node_coords(node_coordinates, equations, solver, ci, - element) - node_vars = solution_variables(Trixi.get_node_vars(u, equations, solver, ci, - element), equations) + node_coords = Trixi.get_node_coords( + node_coordinates, equations, solver, ci, + element + ) + node_vars = solution_variables( + Trixi.get_node_vars( + u, equations, solver, ci, + element + ), equations + ) print(io, join(node_coords, " ")) write(io, " ") print(io, join(node_vars, " ")) diff --git a/utils/trixi2txt.jl b/utils/trixi2txt.jl index 52ee904d2f..0945f0ef18 100644 --- a/utils/trixi2txt.jl +++ b/utils/trixi2txt.jl @@ -34,9 +34,11 @@ include("../src/basic_types.jl") include("../src/solvers/dgsem/basis_lobatto_legendre.jl") include("../src/solvers/dgsem/interpolation.jl") -function trixi2txt(filename::AbstractString...; - variables = [], output_directory = ".", nvisnodes = nothing, - max_supported_level = 11) +function trixi2txt( + filename::AbstractString...; + variables = [], output_directory = ".", nvisnodes = nothing, + max_supported_level = 11 + ) # Convert filenames to a single list of strings if isempty(filename) error("no input file was provided") @@ -74,16 +76,20 @@ function trixi2txt(filename::AbstractString...; # Check if dimensions match if length(leaf_cells) != n_elements - error("number of elements in '$(filename)' do not match number of leaf cells in " * - "'$(meshfile)' " * - "(did you forget to clean your 'out/' directory between different runs?)") + error( + "number of elements in '$(filename)' do not match number of leaf cells in " * + "'$(meshfile)' " * + "(did you forget to clean your 'out/' directory between different runs?)" + ) end # Determine resolution for data interpolation max_level = maximum(levels) if max_level > max_supported_level - error("Maximum refinement level in data file $max_level is higher than " * - "maximum supported level $max_supported_level") + error( + "Maximum refinement level in data file $max_level is higher than " * + "maximum supported level $max_supported_level" + ) end max_available_nodes_per_finest_element = 2^(max_supported_level - max_level) if nvisnodes === nothing @@ -95,19 +101,23 @@ function trixi2txt(filename::AbstractString...; end nvisnodes_at_max_level = min(max_available_nodes_per_finest_element, max_nvisnodes) resolution = nvisnodes_at_max_level * 2^max_level - nvisnodes_per_level = [2^(max_level - level) * nvisnodes_at_max_level - for level in 0:max_level] + nvisnodes_per_level = [ + 2^(max_level - level) * nvisnodes_at_max_level + for level in 0:max_level + ] # Interpolate data - structured_data = unstructured2structured(data, levels, resolution, - nvisnodes_per_level) + structured_data = unstructured2structured( + data, levels, resolution, + nvisnodes_per_level + ) # Interpolate cell-centered values to node-centered values node_centered_data = cell2node(structured_data) # Determine x coordinates xs = collect(range(-1, 1, length = resolution + 1)) .* length_level_0 / 2 .+ - center_level_0[1] + center_level_0[1] # Check that all variables exist in data file if isempty(variables) @@ -277,9 +287,11 @@ function read_datafile(filename::String) end # Interpolate unstructured DG data to structured data (cell-centered) -function unstructured2structured(unstructured_data::AbstractArray{Float64}, - levels::AbstractArray{Int}, resolution::Int, - nvisnodes_per_level::AbstractArray{Int}) +function unstructured2structured( + unstructured_data::AbstractArray{Float64}, + levels::AbstractArray{Int}, resolution::Int, + nvisnodes_per_level::AbstractArray{Int} + ) # Extract data shape information n_nodes_in, n_elements, n_variables = size(unstructured_data) @@ -304,8 +316,10 @@ function unstructured2structured(unstructured_data::AbstractArray{Float64}, first = 1 # Reshape data array for use in interpolate_nodes function - @views reshaped_data = reshape(unstructured_data[:, :, v], 1, n_nodes_in, - n_elements) + @views reshaped_data = reshape( + unstructured_data[:, :, v], 1, n_nodes_in, + n_elements + ) for element_id in 1:n_elements # Extract level for convenience @@ -317,11 +331,19 @@ function unstructured2structured(unstructured_data::AbstractArray{Float64}, # Interpolate data vandermonde = vandermonde_per_level[level + 1] - @views structured[first:last, v] .= (reshape(multiply_dimensionwise_naive(reshaped_data[:, - :, - element_id], - vandermonde), - n_nodes_out)) + @views structured[first:last, v] .= ( + reshape( + multiply_dimensionwise_naive( + reshaped_data[ + :, + :, + element_id, + ], + vandermonde + ), + n_nodes_out + ) + ) # Update first index for next iteration first += n_nodes_out