From 03442fda95d3bd93c9fcb6dbc65eacc7d182bed1 Mon Sep 17 00:00:00 2001 From: Andrew Winters Date: Mon, 28 Nov 2022 12:30:03 +0100 Subject: [PATCH 1/7] WIP: Upwind finite difference SBP solver (#1270) * add new VolumeIntegral type * add polydeg to fdsbp such that SaveSolutionCallback works * give vol int Upwind a splitting function * create new SurfaceIntegralUpwind * add TODOs * add Steger-Warming splitting and new surface terms * upwind solver runs * try to remove allocations from vol int * add elixirs for upwind solver * cleaner Val plus and minus calls * fix broken multiple dispatch on the new surface integral * add vanleer flux vector splitting vanleer_splitting * add lax friedrichs type flux vector splitting * add 3D variant of upwind solver * generalize the integrate dispatching * add TGV example for upwind finite difference * save analysis quantities for the TGV elixir * add extra analysis quantites and save to a file * added 3d convergence test for upwind solver * remove unneeded parentheses * remove extra spaces * remove extra spaces * add 1D variant of the upwind solver * spacing fixes * add TODO docstrings * fix typos. add docstring for van Leer splitting * add Lax-Friedrichs splitting docstring * adjust parameters for 1d convergence elixir * rename a splitting and fix docstrings * add comments about the new upwind SATs * rename vanleer splitting in elixir * adjust analysis interval in 2D KHI * add 1D tests * add 2D upwind tests * add upwind solver tests in 1D, 2D och 3D * add 3D tests for upwind solver * add test to exercise vanleer_haenel_splitting in 1D * implement flux splitting for Burgers * add tests for Burgers splitting * new reference test values * change test setup of sensitive TGV * adjust test settings of 1D Euler density wave Co-authored-by: Gregor Gassner Co-authored-by: Hendrik Ranocha --- .../tree_1d_fdsbp/elixir_burgers_basic.jl | 71 ++++ .../elixir_burgers_linear_stability.jl | 67 +++ .../tree_1d_fdsbp/elixir_euler_convergence.jl | 69 ++++ .../elixir_euler_density_wave.jl | 64 +++ .../tree_2d_fdsbp/elixir_euler_convergence.jl | 71 ++++ examples/tree_2d_fdsbp/elixir_euler_khi.jl | 90 ++++ examples/tree_2d_fdsbp/elixir_euler_vortex.jl | 111 +++++ .../tree_3d_fdsbp/elixir_euler_convergence.jl | 71 ++++ .../elixir_euler_taylor_green_vortex.jl | 89 ++++ src/Trixi.jl | 5 + src/callbacks_step/analysis_dg1d.jl | 2 +- src/callbacks_step/analysis_dg3d.jl | 2 +- src/equations/compressible_euler_1d.jl | 212 +++++++++- src/equations/compressible_euler_2d.jl | 280 ++++++++++++- src/equations/compressible_euler_3d.jl | 141 +++++++ src/equations/inviscid_burgers_1d.jl | 26 +- src/solvers/dg.jl | 63 ++- src/solvers/fdsbp_tree/fdsbp.jl | 46 +++ src/solvers/fdsbp_tree/fdsbp_1d.jl | 286 +++++++++++++ src/solvers/fdsbp_tree/fdsbp_2d.jl | 198 +++++++-- src/solvers/fdsbp_tree/fdsbp_3d.jl | 387 ++++++++++++++++++ test/test_tree_1d.jl | 6 +- test/test_tree_1d_fdsbp.jl | 51 +++ test/test_tree_2d_fdsbp.jl | 25 +- test/test_tree_3d_fdsbp.jl | 27 ++ test/test_tree_3d_part3.jl | 3 + 26 files changed, 2420 insertions(+), 43 deletions(-) create mode 100644 examples/tree_1d_fdsbp/elixir_burgers_basic.jl create mode 100644 examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl create mode 100644 examples/tree_1d_fdsbp/elixir_euler_convergence.jl create mode 100644 examples/tree_1d_fdsbp/elixir_euler_density_wave.jl create mode 100644 examples/tree_2d_fdsbp/elixir_euler_convergence.jl create mode 100644 examples/tree_2d_fdsbp/elixir_euler_khi.jl create mode 100644 examples/tree_2d_fdsbp/elixir_euler_vortex.jl create mode 100644 examples/tree_3d_fdsbp/elixir_euler_convergence.jl create mode 100644 examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl create mode 100644 src/solvers/fdsbp_tree/fdsbp.jl create mode 100644 src/solvers/fdsbp_tree/fdsbp_1d.jl create mode 100644 src/solvers/fdsbp_tree/fdsbp_3d.jl create mode 100644 test/test_tree_1d_fdsbp.jl create mode 100644 test/test_tree_3d_fdsbp.jl diff --git a/examples/tree_1d_fdsbp/elixir_burgers_basic.jl b/examples/tree_1d_fdsbp/elixir_burgers_basic.jl new file mode 100644 index 00000000000..fb8dea2055e --- /dev/null +++ b/examples/tree_1d_fdsbp/elixir_burgers_basic.jl @@ -0,0 +1,71 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the (inviscid) Burgers' equation + +equations = InviscidBurgersEquation1D() + +initial_condition = initial_condition_convergence_test + +D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=32) +D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=32) + +# TODO: Super hacky. +# Abuse the mortars to save the second derivative operator and get it into the run +flux_splitting = lax_friedrichs_splitting +solver = DG(D_plus, D_minus #= mortar =#, + SurfaceIntegralUpwind(flux_splitting), + 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) + + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms=source_terms_convergence_test) + + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +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)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +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) + + +############################################################################### +# run the simulation + +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/tree_1d_fdsbp/elixir_burgers_linear_stability.jl b/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl new file mode 100644 index 00000000000..48cd5090e2c --- /dev/null +++ b/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl @@ -0,0 +1,67 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the (inviscid) Burgers' equation + +equations = InviscidBurgersEquation1D() + +function initial_condition_linear_stability(x, t, equation::InviscidBurgersEquation1D) + k = 1 + 2 + sinpi(k * (x[1] - 0.7)) |> SVector +end + +D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) +D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) + +# TODO: Super hacky. +# Abuse the mortars to save the second derivative operator and get it into the run +flux_splitting = lax_friedrichs_splitting +solver = DG(D_plus, D_minus #= mortar =#, + SurfaceIntegralUpwind(flux_splitting), + 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) + + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_linear_stability, solver) + + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +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)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +callbacks = CallbackSet(summary_callback, + analysis_callback, alive_callback) + + +############################################################################### +# run the simulation + +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/examples/tree_1d_fdsbp/elixir_euler_convergence.jl b/examples/tree_1d_fdsbp/elixir_euler_convergence.jl new file mode 100644 index 00000000000..6760680745f --- /dev/null +++ b/examples/tree_1d_fdsbp/elixir_euler_convergence.jl @@ -0,0 +1,69 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations1D(1.4) + +initial_condition = initial_condition_convergence_test + +# Note that the expected EOC of 4 when the value of N is increased +D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=32) +D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=32) + +# TODO: Super hacky. +# Abuse the mortars to save the second derivative operator and get it into the run +flux_splitting = steger_warming_splitting +solver = DG(D_plus, D_minus #= mortar =#, + SurfaceIntegralUpwind(flux_splitting), + 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) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms=source_terms_convergence_test) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +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)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +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) + +############################################################################### +# run the simulation + +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/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl b/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl new file mode 100644 index 00000000000..c82b310e540 --- /dev/null +++ b/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl @@ -0,0 +1,64 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations +equations = CompressibleEulerEquations1D(1.4) + +initial_condition = initial_condition_density_wave + +D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) +D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) + +# TODO: Super hacky. +# Abuse the mortars to save the second derivative operator and get it into the run +flux_splitting = coirier_vanleer_splitting +solver = DG(D_plus, D_minus #= mortar =#, + SurfaceIntegralUpwind(flux_splitting), + 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) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 100.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 10000 +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) + +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, + save_everystep=false, callback=callbacks) +summary_callback() # print the timer summary diff --git a/examples/tree_2d_fdsbp/elixir_euler_convergence.jl b/examples/tree_2d_fdsbp/elixir_euler_convergence.jl new file mode 100644 index 00000000000..c394ded1a7f --- /dev/null +++ b/examples/tree_2d_fdsbp/elixir_euler_convergence.jl @@ -0,0 +1,71 @@ +# TODO: FD +# !!! warning "Experimental feature" +# This is an experimental feature and may change in any future releases. + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linear advection equation +equations = CompressibleEulerEquations2D(1.4) + +initial_condition = initial_condition_convergence_test +source_terms = source_terms_convergence_test + +D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) +D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) + +# TODO: Super hacky. +# Abuse the mortars to save the second derivative operator and get it into the run +flux_splitting = steger_warming_splitting +solver = DG(D_plus, D_minus #= mortar =#, + SurfaceIntegralUpwind(flux_splitting), + 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) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 2.0) +ode = semidiscretize(semi, tspan); + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +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) + +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, + save_everystep=false, callback=callbacks) +summary_callback() diff --git a/examples/tree_2d_fdsbp/elixir_euler_khi.jl b/examples/tree_2d_fdsbp/elixir_euler_khi.jl new file mode 100644 index 00000000000..18edac8294e --- /dev/null +++ b/examples/tree_2d_fdsbp/elixir_euler_khi.jl @@ -0,0 +1,90 @@ +# TODO: FD +# !!! warning "Experimental feature" +# This is an experimental feature and may change in any future releases. +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linear advection equation +equations = CompressibleEulerEquations2D(1.4) + +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 + slope = 15 + amplitude = 0.02 + B = tanh(slope * x[2] + 7.5) - tanh(slope * x[2] - 7.5) + rho = 0.5 + 0.75 * B + v1 = 0.5 * (B - 1) + v2 = 0.1 * sin(2 * pi * x[1]) + p = 1.0 + return prim2cons(SVector(rho, v1, v2, p), equations) +end + +initial_condition = initial_condition_kelvin_helmholtz_instability + +D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), +#D_plus = derivative_operator(SummationByPartsOperators.WIP(:plus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) +D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), +#D_minus = derivative_operator(SummationByPartsOperators.WIP(:minus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) +# TODO: Super hacky. +# Abuse the mortars to save the second derivative operator and get it into the run +#flux_splitting = steger_warming_splitting +flux_splitting = vanleer_haenel_splitting +#flux_splitting = lax_friedrichs_splitting +#surface_flux = flux_hllc +solver = DG(D_plus, D_minus #= mortar =#, + SurfaceIntegralUpwind(flux_splitting), + #SurfaceIntegralStrongForm(surface_flux), + 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) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 3.7) +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,)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +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) + +############################################################################### +# run the simulation + +sol = solve(ode, SSPRK43(), abstol=1.0e-6, reltol=1.0e-6, dt=1e-3, + save_everystep=false, callback=callbacks) +summary_callback() diff --git a/examples/tree_2d_fdsbp/elixir_euler_vortex.jl b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl new file mode 100644 index 00000000000..8effc717715 --- /dev/null +++ b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl @@ -0,0 +1,111 @@ +# TODO: FD +# !!! warning "Experimental feature" +# This is an experimental feature and may change in any future releases. + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the linear advection equation +equations = CompressibleEulerEquations2D(1.4) + +""" + initial_condition_isentropic_vortex(x, t, equations::CompressibleEulerEquations2D) + +The classical isentropic vortex test case of +- Chi-Wang Shu (1997) + Essentially Non-Oscillatory and Weighted Essentially Non-Oscillatory + Schemes for Hyperbolic Conservation Laws + [NASA/CR-97-206253](https://ntrs.nasa.gov/citations/19980007543) +""" +function initial_condition_isentropic_vortex(x, t, equations::CompressibleEulerEquations2D) + # needs appropriate mesh size, e.g. [-10,-10]x[10,10] + # for error convergence: make sure that the end time such, that the is back at the initial state!! + # for the current vel and domain size: t_end should be a multiple of 20s + # initial center of the vortex + inicenter = SVector(0.0, 0.0) + # size and strength of the vortex + iniamplitude = 5.0 + # base flow + rho = 1.0 + v1 = 1.0 + v2 = 1.0 + vel = SVector(v1, v2) + p = 25.0 + rt = p / rho # ideal gas equation + t_loc = 0.0 + cent = inicenter + vel*t_loc # advection of center + # ATTENTION: handle periodic BC, but only for v1 = v2 = 1.0 (!!!!) + + cent = x - cent # distance to center point + + #cent=cross(iniaxis,cent) # distance to axis, tangent vector, length r + # cross product with iniaxis = [0, 0, 1] + cent = SVector(-cent[2], cent[1]) + r2 = cent[1]^2 + cent[2]^2 + du = iniamplitude/(2*π)*exp(0.5*(1-r2)) # vel. perturbation + dtemp = -(equations.gamma-1)/(2*equations.gamma*rt)*du^2 # isentropic + rho = rho * (1+dtemp)^(1/(equations.gamma-1)) + vel = vel + du*cent + v1, v2 = vel + p = p * (1+dtemp)^(equations.gamma/(equations.gamma-1)) + prim = SVector(rho, v1, v2, p) + return prim2cons(prim, equations) +end + +initial_condition = initial_condition_isentropic_vortex + +D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) +D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) + +# TODO: Super hacky. +# Abuse the mortars to save the second derivative operator and get it into the run +flux_splitting = steger_warming_splitting +solver = DG(D_plus, D_minus #= mortar =#, + SurfaceIntegralUpwind(flux_splitting), + 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) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 20.0) +ode = semidiscretize(semi, tspan); + +summary_callback = SummaryCallback() + +analysis_interval = 1000 +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) + +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, + save_everystep=false, callback=callbacks) +summary_callback() diff --git a/examples/tree_3d_fdsbp/elixir_euler_convergence.jl b/examples/tree_3d_fdsbp/elixir_euler_convergence.jl new file mode 100644 index 00000000000..d6e0cd2bc33 --- /dev/null +++ b/examples/tree_3d_fdsbp/elixir_euler_convergence.jl @@ -0,0 +1,71 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations3D(1.4) + +initial_condition = initial_condition_convergence_test + +D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) +D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) + +# TODO: Super hacky. +# Abuse the mortars to save the second derivative operator and get it into the run +flux_splitting = steger_warming_splitting +solver = DG(D_plus, D_minus #= mortar =#, + SurfaceIntegralUpwind(flux_splitting), + 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) + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, + source_terms=source_terms_convergence_test) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 1.0) +ode = semidiscretize(semi, tspan) + +summary_callback = SummaryCallback() + +analysis_interval = 100 +analysis_callback = AnalysisCallback(semi, interval=analysis_interval) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +save_solution = SaveSolutionCallback(interval=100, + save_initial_solution=true, + save_final_solution=true, + solution_variables=cons2prim) + +stepsize_callback = StepsizeCallback(cfl=1.1) + +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); +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 new file mode 100644 index 00000000000..9a1e347c040 --- /dev/null +++ b/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl @@ -0,0 +1,89 @@ + +using OrdinaryDiffEq +using Trixi + +############################################################################### +# semidiscretization of the compressible Euler equations + +equations = CompressibleEulerEquations3D(1.4) + +""" + 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) + A = 1.0 # magnitude of speed + Ms = 0.1 # maximum Mach number + + rho = 1.0 + v1 = A * sin(x[1]) * cos(x[2]) * cos(x[3]) + v2 = -A * cos(x[1]) * sin(x[2]) * cos(x[3]) + v3 = 0.0 + 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])) + + return prim2cons(SVector(rho, v1, v2, v3, p), equations) +end +initial_condition = initial_condition_taylor_green_vortex + +D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) +D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) + +# TODO: Super hacky. +# Abuse the mortars to save the second derivative operator and get it into the run +flux_splitting = steger_warming_splitting +solver = DG(D_plus, D_minus #= mortar =#, + SurfaceIntegralUpwind(flux_splitting), + 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) + + +semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + +############################################################################### +# ODE solvers, callbacks etc. + +tspan = (0.0, 10.0) +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,)) + +alive_callback = AliveCallback(analysis_interval=analysis_interval) + +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) + +############################################################################### +# run the simulation + +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/src/Trixi.jl b/src/Trixi.jl index 5811a950505..61ab53a948d 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -154,6 +154,9 @@ export flux, flux_central, flux_lax_friedrichs, flux_hll, flux_hllc, flux_hlle, flux_shima_etal_turbo, flux_ranocha_turbo, FluxHydrostaticReconstruction +export steger_warming_splitting, vanleer_haenel_splitting, + coirier_vanleer_splitting, lax_friedrichs_splitting + export initial_condition_constant, initial_condition_gauss, initial_condition_density_wave, @@ -188,7 +191,9 @@ export DG, VolumeIntegralFluxDifferencing, VolumeIntegralPureLGLFiniteVolume, VolumeIntegralShockCapturingHG, IndicatorHennemannGassner, + VolumeIntegralUpwind, SurfaceIntegralWeakForm, SurfaceIntegralStrongForm, + SurfaceIntegralUpwind, MortarL2 export nelements, nnodes, nvariables, diff --git a/src/callbacks_step/analysis_dg1d.jl b/src/callbacks_step/analysis_dg1d.jl index b0e0c6cb868..e92701dc1fb 100644 --- a/src/callbacks_step/analysis_dg1d.jl +++ b/src/callbacks_step/analysis_dg1d.jl @@ -168,7 +168,7 @@ end function integrate(func::Func, u, mesh::Union{TreeMesh{1},StructuredMesh{1}}, - equations, dg::DGSEM, cache; normalize=true) where {Func} + 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) diff --git a/src/callbacks_step/analysis_dg3d.jl b/src/callbacks_step/analysis_dg3d.jl index 5267960e2f0..69bfb291472 100644 --- a/src/callbacks_step/analysis_dg3d.jl +++ b/src/callbacks_step/analysis_dg3d.jl @@ -191,7 +191,7 @@ end function integrate(func::Func, u, mesh::Union{TreeMesh{3}, StructuredMesh{3}, P4estMesh{3}}, - equations, dg::DGSEM, cache; normalize=true) where {Func} + 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) diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 400650ed885..57d014f136c 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -360,6 +360,216 @@ end end +""" + steger_warming_splitting(u, ::Symbol, orientation::Integer, + equations::CompressibleEulerEquations1D) + +Splitting of the compressible Euler flux of Steger and Warming. The `Symbol` +indicates if the routine computes all the components with positive eigenvalue `:plus` +or all the negative eigenvalue components `:minus`. +- 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 steger_warming_splitting(u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5 * rho_v1 * v1) + a = sqrt(equations.gamma * p / rho) + + lambda1 = v1 + lambda2 = v1 + a + lambda3 = v1 - a + + lambda1_p = 0.5 * (lambda1 + abs(lambda1)) + lambda2_p = 0.5 * (lambda2 + abs(lambda2)) + lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + + alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + f1p = 0.5 * rho / equations.gamma * alpha_p + f2p = 0.5 * rho / equations.gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) + f3p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * v1^2 + a * v1 * (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + + return SVector(f1p, f2p, f3p) +end + +@inline function steger_warming_splitting(u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5 * rho_v1 * v1) + a = sqrt(equations.gamma * p / rho) + + lambda1 = v1 + lambda2 = v1 + a + lambda3 = v1 - a + + lambda1_m = 0.5 * (lambda1 - abs(lambda1)) + lambda2_m = 0.5 * (lambda2 - abs(lambda2)) + lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + + alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + f1m = 0.5 * rho / equations.gamma * alpha_m + f2m = 0.5 * rho / equations.gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) + f3m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * v1^2 + a * v1 * (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + + return SVector(f1m, f2m, f3m) +end + + +""" + vanleer_haenel_splitting(u, ::Symbol, orientation::Integer, + equations::CompressibleEulerEquations1D) + +Splitting of the compressible Euler flux from van Leer. The `Symbol` +indicates if the routine computes all the components with positive eigenvalue `:plus` +or all the negative eigenvalue components `:minus`. 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. + +- 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) +""" +# TODO: separate this as a separate splitting the runs el_diablo +@inline function vanleer_haenel_splitting(u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5 * 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.5 * (1.0 + equations.gamma * M) * p + + f1p = 0.25 * rho * a * (M + 1)^2 + f2p = f1p * v1 + p_plus + f3p = f1p * H + + return SVector(f1p, f2p, f3p) +end + +@inline function vanleer_haenel_splitting(u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5 * 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.5 * (1.0 - equations.gamma * M) * p + + f1m= -0.25 * rho * a * (M - 1)^2 + f2m = f1m * v1 + p_minus + f3m = f1m * H + + return SVector(f1m, f2m, f3m) +end + + +""" + coirier_vanleer_splitting(u, ::Symbol, orientation::Integer, + equations::CompressibleEulerEquations1D) + +Splitting of the compressible Euler flux from Coirier and van Leer. The `Symbol` +indicates if the routine computes all the components with positive eigenvalue `:plus` +or all the negative eigenvalue components `:minus`. 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. + +- 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) +""" +# 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? +@inline function coirier_vanleer_splitting(u, ::Val{:plus}, orientation::Integer, + equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5 * 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.0 + nu = 0.75 + omega = 2.0 # adjusted from suggested value of 1.5 + + p_plus = 0.25 * ((M + 1)^2 * (2 - M) - nu * M * (M^2 - 1)^P) * p + + f1p = 0.25 * 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 coirier_vanleer_splitting(u, ::Val{:minus}, orientation::Integer, + equations::CompressibleEulerEquations1D) + rho, rho_v1, rho_e = u + v1 = rho_v1 / rho + p = (equations.gamma - 1) * (rho_e - 0.5 * 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.0 + nu = 0.75 + omega = 2.0 # adjusted from suggested value of 1.5 + + p_minus = 0.25 * ((M - 1)^2 * (2 + M) + nu * M * (M^2 - 1)^P) * p + + f1m = -0.25 * 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 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) @@ -472,8 +682,6 @@ function flux_hllc(u_ll, u_rr, orientation::Integer, equations::CompressibleEule end - - @inline function max_abs_speeds(u, equations::CompressibleEulerEquations1D) rho, rho_v1, rho_e = u v1 = rho_v1 / rho diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index 7bda9999605..2ed078e72c0 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -368,7 +368,7 @@ Should be used together with [`StructuredMesh`](@ref). end -# Calculate 1D flux for a single point +# 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 @@ -388,7 +388,7 @@ end return SVector(f1, f2, f3, f4) end -# Calculate 1D flux for a single point in the normal direction +# 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) @@ -664,6 +664,255 @@ end end +""" + steger_warming_splitting(u, ::Symbol, orientation::Integer, + equations::CompressibleEulerEquations2D) + +Splitting of the compressible Euler flux of Steger and Warming. The `Symbol` +indicates if the routine computes all the components with positive eigenvalue `:plus` +or all the negative eigenvalue components `:minus`. +- 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 steger_warming_splitting(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.5 * (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 = 0.5 * (lambda1 + abs(lambda1)) + lambda2_p = 0.5 * (lambda2 + abs(lambda2)) + lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + + alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + f1p = 0.5 * rho / equations.gamma * alpha_p + f2p = 0.5 * rho / equations.gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) + f3p = 0.5 * rho / equations.gamma * alpha_p * v2 + f4p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2) + a * v1 * (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + else + lambda1 = v2 + lambda2 = v2 + a + lambda3 = v2 - a + + lambda1_p = 0.5 * (lambda1 + abs(lambda1)) + lambda2_p = 0.5 * (lambda2 + abs(lambda2)) + lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + + alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + f1p = 0.5 * rho / equations.gamma * alpha_p + f2p = 0.5 * rho / equations.gamma * alpha_p * v1 + f3p = 0.5 * rho / equations.gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) + f4p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2) + a * v2 * (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + end + return SVector(f1p, f2p, f3p, f4p) +end + +@inline function steger_warming_splitting(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.5 * (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 = 0.5 * (lambda1 - abs(lambda1)) + lambda2_m = 0.5 * (lambda2 - abs(lambda2)) + lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + + alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + f1m = 0.5 * rho / equations.gamma * alpha_m + f2m = 0.5 * rho / equations.gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) + f3m = 0.5 * rho / equations.gamma * alpha_m * v2 + f4m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2) + a * v1 * (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + else + lambda1 = v2 + lambda2 = v2 + a + lambda3 = v2 - a + + lambda1_m = 0.5 * (lambda1 - abs(lambda1)) + lambda2_m = 0.5 * (lambda2 - abs(lambda2)) + lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + + alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + f1m = 0.5 * rho / equations.gamma * alpha_m + f2m = 0.5 * rho / equations.gamma * alpha_m * v1 + f3m = 0.5 * rho / equations.gamma * (alpha_m * v2 + a * (lambda2_m-lambda3_m)) + f4m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2) + a * v2 * (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + end + return SVector(f1m, f2m, f3m, f4m) +end + + +""" + vanleer_haenel_splitting(u, ::Symbol, orientation::Integer, + equations::CompressibleEulerEquations1D) + +Splitting of the compressible Euler flux from van Leer. The `Symbol` +indicates if the routine computes all the components with positive eigenvalue `:plus` +or all the negative eigenvalue components `:minus`. 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. + +- 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) +""" +#TODO: generic central flux where f_plus = 0.5 * f and f_minus = 0.5 * f +# combine with interface terms for experimentation +@inline function vanleer_haenel_splitting(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.5 * (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.5 * (1 + equations.gamma * M) * p + + f1p = 0.25 * rho * a * (M + 1)^2 + f2p = f1p * v1 + p_plus + f3p = f1p * v2 + f4p = f1p * H + else + M = v2 / a + p_plus = 0.5 * (1 + equations.gamma * M) * p + + f1p = 0.25 * 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 vanleer_haenel_splitting(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.5 * (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.5 * (1 - equations.gamma * M) * p + + f1m= -0.25 * rho * a * (M - 1)^2 + f2m = f1m * v1 + p_minus + f3m = f1m * v2 + f4m = f1m * H + else + M = v2 / a + p_minus = 0.5 * (1 - equations.gamma * M) * p + + f1m= -0.25 * rho * a * (M - 1)^2 + f2m = f1m * v1 + f3m = f1m * v2 + p_minus + f4m = f1m * H + end + return SVector(f1m, f2m, f3m, f4m) +end + + +""" + lax_friedrichs_splitting(u, ::Symbol, orientation::Integer, + equations::CompressibleEulerEquations2D) + +Naive 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. +""" +@inline function lax_friedrichs_splitting(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.5 * (rho_v1 * v1 + rho_v2 * v2)) + + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + lambda = 0.5 * (sqrt(v1^2 + v2^2) + a) + + if orientation == 1 + #lambda = 0.5 * (abs(v1) + a) + f1p = 0.5 * rho * v1 + lambda * u[1] + f2p = 0.5 * rho * v1 * v1 + 0.5 * p + lambda * u[2] + f3p = 0.5 * rho * v1 * v2 + lambda * u[3] + f4p = 0.5 * rho * v1 * H + lambda * u[4] + else + #lambda = 0.5 * (abs(v2) + a) + f1p = 0.5 * rho * v2 + lambda * u[1] + f2p = 0.5 * rho * v2 * v1 + lambda * u[2] + f3p = 0.5 * rho * v2 * v2 + 0.5 * p + lambda * u[3] + f4p = 0.5 * rho * v2 * H + lambda * u[4] + end + return SVector(f1p, f2p, f3p, f4p) +end + +@inline function lax_friedrichs_splitting(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.5 * (rho_v1 * v1 + rho_v2 * v2)) + + a = sqrt(equations.gamma * p / rho) + H = (rho_e + p) / rho + lambda = 0.5 * (sqrt(v1^2+v2^2) + a) + + if orientation == 1 + #lambda = 0.5 * (abs(v1) + a) + f1m = 0.5 * rho * v1 - lambda * u[1] + f2m = 0.5 * rho * v1 * v1 + 0.5 * p - lambda * u[2] + f3m = 0.5 * rho * v1 * v2 - lambda * u[3] + f4m = 0.5 * rho * v1 * H - lambda * u[4] + else + #lambda = 0.5 * (abs(v2) + a) + f1m = 0.5 * rho * v2 - lambda * u[1] + f2m = 0.5 * rho * v2 * v1 - lambda * u[2] + f3m = 0.5 * rho * v2 * v2 + 0.5 * p - lambda * u[3] + f4m = 0.5 * rho * v2 * H - lambda * u[4] + end + 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) @@ -964,6 +1213,33 @@ function flux_hlle(u_ll, u_rr, orientation::Integer, equations::CompressibleEule end +# """ +# flux_upwind(u_ll, u_rr, orientation, equations::CompressibleEulerEquations2D) + +# Fully upwind SAT coupling but written in the style of the strong form DG type method. +# Should be used together with [SurfaceIntegralStrongForm](@ref) and a finite difference +# summation-by-parts (FDSBP) solver. + +# TODO: reference? Maybe van Leer paper "...for the 90s" +# """ +# # TODO: do we want this? +# @inline function flux_upwind(u_ll, u_rr, orientation::Integer, equations::CompressibleEulerEquations2D) + +# # Compute the upwind coupling terms with right-traveling from the left +# # and left-traveling information from the right +# # TODO: How to make this easier to switch out other splittings? +# f_plus_ll = steger_warming_splitting(u_ll, :plus, orientation, equations) +# f_minus_rr = steger_warming_splitting(u_rr, :minus, orientation, equations) + +# # Combine the upwind terms to pass back as a type of numerical flux +# f1 = f_plus_ll[1] + f_minus_rr[1] +# f2 = f_plus_ll[2] + f_minus_rr[2] +# f3 = f_plus_ll[3] + f_minus_rr[3] +# f4 = f_plus_ll[4] + f_minus_rr[4] +# return SVector(f1, f2, f3, f4) +# end + + @inline function max_abs_speeds(u, equations::CompressibleEulerEquations2D) rho, v1, v2, p = cons2prim(u, equations) c = sqrt(equations.gamma * p / rho) diff --git a/src/equations/compressible_euler_3d.jl b/src/equations/compressible_euler_3d.jl index 1b93674c8a2..8c9427b3dee 100644 --- a/src/equations/compressible_euler_3d.jl +++ b/src/equations/compressible_euler_3d.jl @@ -680,6 +680,147 @@ end end +""" + steger_warming_splitting(u, ::Symbol, orientation::Integer, + equations::CompressibleEulerEquations3D) + +Splitting of the compressible Euler flux of Steger and Warming. The `Symbol` +indicates if the routine computes all the components with positive eigenvalue `:plus` +or all the negative eigenvalue components `:minus`. +- 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 steger_warming_splitting(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.5 * (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 = 0.5 * (lambda1 + abs(lambda1)) + lambda2_p = 0.5 * (lambda2 + abs(lambda2)) + lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + + alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + f1p = 0.5 * rho / equations.gamma * alpha_p + f2p = 0.5 * rho / equations.gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) + f3p = 0.5 * rho / equations.gamma * alpha_p * v2 + f4p = 0.5 * rho / equations.gamma * alpha_p * v3 + f5p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2 + v3^2) + a * v1 * (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + elseif orientation == 2 + lambda1 = v2 + lambda2 = v2 + a + lambda3 = v2 - a + + lambda1_p = 0.5 * (lambda1 + abs(lambda1)) + lambda2_p = 0.5 * (lambda2 + abs(lambda2)) + lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + + alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + f1p = 0.5 * rho / equations.gamma * alpha_p + f2p = 0.5 * rho / equations.gamma * alpha_p * v1 + f3p = 0.5 * rho / equations.gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) + f4p = 0.5 * rho / equations.gamma * alpha_p * v3 + f5p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2 + v3^2) + a * v2 * (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + else + lambda1 = v3 + lambda2 = v3 + a + lambda3 = v3 - a + + lambda1_p = 0.5 * (lambda1 + abs(lambda1)) + lambda2_p = 0.5 * (lambda2 + abs(lambda2)) + lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + + alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + + f1p = 0.5 * rho / equations.gamma * alpha_p + f2p = 0.5 * rho / equations.gamma * alpha_p * v1 + f3p = 0.5 * rho / equations.gamma * alpha_p * v2 + f4p = 0.5 * rho / equations.gamma * (alpha_p * v3 + a * (lambda2_p - lambda3_p)) + f5p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2 + v3^2) + a * v3 * (lambda2_p - lambda3_p) + + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + end + return SVector(f1p, f2p, f3p, f4p, f5p) +end + +@inline function steger_warming_splitting(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.5 * (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 = 0.5 * (lambda1 - abs(lambda1)) + lambda2_m = 0.5 * (lambda2 - abs(lambda2)) + lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + + alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + f1m = 0.5 * rho / equations.gamma * alpha_m + f2m = 0.5 * rho / equations.gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) + f3m = 0.5 * rho / equations.gamma * alpha_m * v2 + f4m = 0.5 * rho / equations.gamma * alpha_m * v3 + f5m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2 + v3^2) + a * v1 * (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + elseif orientation == 2 + lambda1 = v2 + lambda2 = v2 + a + lambda3 = v2 - a + + lambda1_m = 0.5 * (lambda1 - abs(lambda1)) + lambda2_m = 0.5 * (lambda2 - abs(lambda2)) + lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + + alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + f1m = 0.5 * rho / equations.gamma * alpha_m + f2m = 0.5 * rho / equations.gamma * alpha_m * v1 + f3m = 0.5 * rho / equations.gamma * (alpha_m * v2 + a * (lambda2_m - lambda3_m)) + f4m = 0.5 * rho / equations.gamma * alpha_m * v3 + f5m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2 + v3^2) + a * v2 * (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + else + lambda1 = v3 + lambda2 = v3 + a + lambda3 = v3 - a + + lambda1_m = 0.5 * (lambda1 - abs(lambda1)) + lambda2_m = 0.5 * (lambda2 - abs(lambda2)) + lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + + alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + + f1m = 0.5 * rho / equations.gamma * alpha_m + f2m = 0.5 * rho / equations.gamma * alpha_m * v1 + f3m = 0.5 * rho / equations.gamma * alpha_m * v2 + f4m = 0.5 * rho / equations.gamma * (alpha_m * v3 + a * (lambda2_m - lambda3_m)) + f5m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2 + v3^2) + a * v3 * (lambda2_m - lambda3_m) + + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + end + return SVector(f1m, f2m, f3m, f4m, f5m) +end + + """ FluxLMARS(c)(u_ll, u_rr, orientation_or_normal_direction, equations::CompressibleEulerEquations3D) diff --git a/src/equations/inviscid_burgers_1d.jl b/src/equations/inviscid_burgers_1d.jl index 258aff5686c..98479d10f6c 100644 --- a/src/equations/inviscid_burgers_1d.jl +++ b/src/equations/inviscid_burgers_1d.jl @@ -90,7 +90,7 @@ end @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) @@ -130,6 +130,30 @@ function flux_engquist_osher(u_ll, u_rr, orientation, equation::InviscidBurgersE return SVector(0.5 * (max(u_L, zero(u_L))^2 + min(u_R, zero(u_R))^2)) end + +""" + lax_friedrichs_splitting(u, ::Symbol, orientation::Integer, + equations::InviscidBurgersEquation1D) + +Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ u)` +and `f⁻ = 0.5 (f - λ u)` where λ = abs(u). +""" +@inline function lax_friedrichs_splitting(u, ::Val{:plus}, orientation::Integer, + equations::InviscidBurgersEquation1D) + f = 0.5 * u[1]^2 + lambda = abs(u[1]) + return SVector(0.5 * (f + lambda * u[1])) +end + +@inline function lax_friedrichs_splitting(u, ::Val{:minus}, orientation::Integer, + equations::InviscidBurgersEquation1D) + f = 0.5 * u[1]^2 + lambda = abs(u[1]) + + return SVector(0.5 * (f - lambda * u[1])) +end + + # Convert conservative variables to primitive @inline cons2prim(u, equation::InviscidBurgersEquation1D) = u diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 0fcb16c9911..85fe83b950c 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -128,6 +128,38 @@ function Base.show(io::IO, mime::MIME"text/plain", integral::VolumeIntegralShock end +""" + VolumeIntegralUpwind + +Specialized volume integral for finite difference summation-by-parts (FDSBP) +solver. Uses the upwind SBP operators developed in +- 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) + +Depends on a particular `FluxSplitting` like that of Steger-Warming +TODO: put in refs +""" +# TODO: should this definition live in a different file because it is +# not a DG method +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 + + """ VolumeIntegralPureLGLFiniteVolume(volume_flux_fv) @@ -242,6 +274,33 @@ function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralStrongFo end +""" + SurfaceIntegralUpwind(splitting) + +Couple elements with upwind simulataneous approximation terms (SATs) +that use a particular `FluxSplitting`. + +See also [`VolumeIntegralUpwind`](@ref). +""" +# TODO: should this definition live in a different file because it is +# not a DG method +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) @@ -514,7 +573,9 @@ include("dgsem_p4est/dg.jl") # 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_tree/fdsbp_1d.jl") include("fdsbp_tree/fdsbp_2d.jl") - +include("fdsbp_tree/fdsbp_3d.jl") end # @muladd diff --git a/src/solvers/fdsbp_tree/fdsbp.jl b/src/solvers/fdsbp_tree/fdsbp.jl new file mode 100644 index 00000000000..d74adeadf8d --- /dev/null +++ b/src/solvers/fdsbp_tree/fdsbp.jl @@ -0,0 +1,46 @@ +# 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 + +# For dispatch +const FDSBP = DG{Basis} where {Basis<:AbstractDerivativeOperator} + + +# 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) + + +# 2D containers +init_mortars(cell_ids, mesh, elements, mortar) = nothing + + +create_cache(mesh, equations, mortar, uEltype) = NamedTuple() +nmortars(mortar) = 0 + + +function prolong2mortars!(cache, u, mesh, equations, mortar, + surface_integral, dg::DG) +@assert isempty(eachmortar(dg, cache)) +end + + +function calc_mortar_flux!(surface_flux_values, mesh, + nonconservative_terms, equations, + mortar, + surface_integral, dg::DG, cache) +@assert isempty(eachmortar(dg, cache)) +end + + +SolutionAnalyzer(D::AbstractDerivativeOperator) = D + +end diff --git a/src/solvers/fdsbp_tree/fdsbp_1d.jl b/src/solvers/fdsbp_tree/fdsbp_1d.jl new file mode 100644 index 00000000000..c806a5ca20b --- /dev/null +++ b/src/solvers/fdsbp_tree/fdsbp_1d.jl @@ -0,0 +1,286 @@ +# 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 + +# 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) + + prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( + undef, ntuple(_ -> nnodes(dg), ndims(mesh))...) + f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + f_minus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + + return (; f_plus_threaded, f_minus_threaded,) +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 infomation into right-traveling and +# left traveling information. So we only need to compute the approriate +# flux information at each side of an interface. +function calc_interface_flux!(surface_flux_values, + mesh::TreeMesh{1}, + nonconservative_terms::Val{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_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], equations) + flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface], equations) + + # Save the upwind coupling into the approriate 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 + + +# 2D volume integral contributions for `VolumeIntegralStrongForm` +function calc_volume_integral!(du, u, + mesh::TreeMesh{1}, + nonconservative_terms::Val{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 + + # 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 + + +# 2D volume integral contributions for `VolumeIntegralUpwind`. +# Note that the plus / minus notation does not refer to the upwind / downwind directions. +# 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::Val{false}, equations, + volume_integral::VolumeIntegralUpwind, + dg::FDSBP, cache) + D_plus = dg.basis # Upwind SBP D^+ derivative operator + # TODO: Super hacky. For now the other derivative operator is passed via the mortars + D_minus = dg.mortar # Upwind SBP D^- derivative operator + @unpack f_plus_threaded, f_minus_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_plus_element = f_plus_threaded[Threads.threadid()] + f_minus_element = f_minus_threaded[Threads.threadid()] + u_element = view(u_vectors, :, element) + + # x direction + @. f_plus_element = splitting(u_element, Val{:plus}(), 1, equations) + @. f_minus_element = splitting(u_element, Val{:minus}(), 1, equations) + mul!(view(du_vectors, :, element), D_minus, view(f_plus_element, :), + one(eltype(du)), one(eltype(du))) + mul!(view(du_vectors, :, element), D_plus, view(f_minus_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) + end + + return nothing +end + + +# TODO: comments about this crazy SATs +# 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 + + +# 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... + weights = diag(SummationByPartsOperators.mass_matrix(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 + + return integral +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... + weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) + @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 + + # 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 e8687de82fa..b9d1145ca1c 100644 --- a/src/solvers/fdsbp_tree/fdsbp_2d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_2d.jl @@ -5,23 +5,6 @@ @muladd begin -# General interface methods for SummationByPartsOperators.jl and Trixi.jl -# TODO: FD. Move to another file -nnodes(D::AbstractDerivativeOperator) = size(D, 1) -eachnode(D::AbstractDerivativeOperator) = Base.OneTo(nnodes(D)) -get_nodes(D::AbstractDerivativeOperator) = grid(D) - - -# For dispatch -# TODO: FD. Move to another file -const FDSBP = DG{Basis} where {Basis<:AbstractDerivativeOperator} - - -# 2D containers -# TODO: FD. Move to another file -init_mortars(cell_ids, mesh, elements, mortar::Nothing) = nothing - - # 2D caches function create_cache(mesh::TreeMesh{2}, equations, volume_integral::VolumeIntegralStrongForm, dg, uEltype) @@ -33,11 +16,65 @@ function create_cache(mesh::TreeMesh{2}, equations, return (; f_threaded,) end -create_cache(mesh, equations, mortar::Nothing, uEltype) = NamedTuple() -nmortars(mortar::Nothing) = 0 +function create_cache(mesh::TreeMesh{2}, equations, + volume_integral::VolumeIntegralUpwind, dg, uEltype) + + prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( + undef, ntuple(_ -> nnodes(dg), ndims(mesh))...) + f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + f_minus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + + return (; f_plus_threaded, f_minus_threaded,) +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 infomation into right-traveling and +# left traveling information. So we only need to compute the approriate +# flux information at each side of an interface. +function calc_interface_flux!(surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::Val{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 -# 2D RHS + 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_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], equations) + flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface], equations) + + # Save the upwind coupling into the approriate 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 + + +# 2D volume integral contributions for `VolumeIntegralStrongForm` function calc_volume_integral!(du, u, mesh::TreeMesh{2}, nonconservative_terms::Val{false}, equations, @@ -86,16 +123,67 @@ function calc_volume_integral!(du, u, end -function prolong2mortars!(cache, u, mesh, equations, mortar::Nothing, - surface_integral, dg::DG) - @assert isempty(eachmortar(dg, cache)) -end +# 2D volume integral contributions for `VolumeIntegralUpwind`. +# Note that the plus / minus notation does not refer to the upwind / downwind directions. +# 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::Val{false}, equations, + volume_integral::VolumeIntegralUpwind, + dg::FDSBP, cache) + D_plus = dg.basis # Upwind SBP D^+ derivative operator + # TODO: Super hacky. For now the other derivative operator is passed via the mortars + D_minus = dg.mortar # Upwind SBP D^- derivative operator + @unpack f_plus_threaded, f_minus_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 + + # 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_plus_element = f_plus_threaded[Threads.threadid()] + f_minus_element = f_minus_threaded[Threads.threadid()] + u_element = view(u_vectors, :, :, element) + + # x direction + @. f_plus_element = splitting(u_element, Val{:plus}(), 1, equations) + @. f_minus_element = splitting(u_element, Val{:minus}(), 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 -function calc_mortar_flux!(surface_flux_values, mesh, - nonconservative_terms, equations, - mortar::Nothing, - surface_integral, dg::DG, cache) - @assert isempty(eachmortar(dg, cache)) + # y direction + @. f_plus_element = splitting(u_element, Val{:plus}(), 2, equations) + @. f_minus_element = splitting(u_element, Val{:minus}(), 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 @@ -142,10 +230,56 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{2}, end -# AnalysisCallback -# TODO: FD. Move to another file -SolutionAnalyzer(D::AbstractDerivativeOperator) = D +# 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 + + return nothing +end + + +# AnalysisCallback function integrate_via_indices(func::Func, u, mesh::TreeMesh{2}, equations, dg::FDSBP, cache, args...; normalize=true) where {Func} diff --git a/src/solvers/fdsbp_tree/fdsbp_3d.jl b/src/solvers/fdsbp_tree/fdsbp_3d.jl new file mode 100644 index 00000000000..90fd04df2d1 --- /dev/null +++ b/src/solvers/fdsbp_tree/fdsbp_3d.jl @@ -0,0 +1,387 @@ +# 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 + +# 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) + + prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( + undef, ntuple(_ -> nnodes(dg), ndims(mesh))...) + f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + f_minus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + + return (; f_plus_threaded, f_minus_threaded,) +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 infomation into right-traveling and +# left traveling information. So we only need to compute the approriate +# flux information at each side of an interface. +function calc_interface_flux!(surface_flux_values, + mesh::TreeMesh{3}, + nonconservative_terms::Val{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_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], equations) + flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface], equations) + + # Save the upwind coupling into the approriate 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 + + +# 3D volume integral contributions for `VolumeIntegralStrongForm` +function calc_volume_integral!(du, u, + mesh::TreeMesh{3}, + nonconservative_terms::Val{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 + + # 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 + + # 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 + + return nothing +end + + +# 3D volume integral contributions for `VolumeIntegralUpwind`. +# Note that the plus / minus notation does not refer to the upwind / downwind directions. +# 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::Val{false}, equations, + volume_integral::VolumeIntegralUpwind, + dg::FDSBP, cache) + D_plus = dg.basis # Upwind SBP D^+ derivative operator + # TODO: Super hacky. For now the other derivative operator is passed via the mortars + D_minus = dg.mortar # Upwind SBP D^- derivative operator + @unpack f_plus_threaded, f_minus_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 + + # 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_plus_element = f_plus_threaded[Threads.threadid()] + f_minus_element = f_minus_threaded[Threads.threadid()] + u_element = view(u_vectors, :, :, :, element) + + # x direction + @. f_plus_element = splitting(u_element, Val{:plus}(), 1, equations) + @. f_minus_element = splitting(u_element, Val{:minus}(), 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_plus_element = splitting(u_element, Val{:plus}(), 2, equations) + @. f_minus_element = splitting(u_element, Val{:minus}(), 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_plus_element = splitting(u_element, Val{:plus}(), 3, equations) + @. f_minus_element = splitting(u_element, Val{:minus}(), 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 + + +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 + + +# 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 + + 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... + weights = diag(SummationByPartsOperators.mass_matrix(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 + + # Normalize with total volume + if normalize + integral = integral / total_volume(mesh) + end + + return integral +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... + weights = diag(SummationByPartsOperators.mass_matrix(dg.basis)) + @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 + + # 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/test/test_tree_1d.jl b/test/test_tree_1d.jl index 25ab72a7b0a..4c15886ce0f 100644 --- a/test/test_tree_1d.jl +++ b/test/test_tree_1d.jl @@ -25,7 +25,6 @@ isdir(outdir) && rm(outdir, recursive=true) # Hyperbolic diffusion include("test_tree_1d_hypdiff.jl") - # Compressible Euler include("test_tree_1d_euler.jl") @@ -41,8 +40,11 @@ isdir(outdir) && rm(outdir, recursive=true) # Compressible Euler with self-gravity include("test_tree_1d_eulergravity.jl") - # Shallow water + # Shallow water include("test_tree_1d_shallowwater.jl") + + # FDSBP methods on the TreeMesh + include("test_tree_1d_fdsbp.jl") end # Coverage test for all initial conditions diff --git a/test/test_tree_1d_fdsbp.jl b/test/test_tree_1d_fdsbp.jl new file mode 100644 index 00000000000..5135c9d911e --- /dev/null +++ b/test/test_tree_1d_fdsbp.jl @@ -0,0 +1,51 @@ +module TestTree1DFDSBP + +using Test +using Trixi + +include("test_trixi.jl") + +# pathof(Trixi) returns /path/to/Trixi/src/Trixi.jl, dirname gives the parent directory +EXAMPLES_DIR = joinpath(pathof(Trixi) |> dirname |> dirname, "examples", "tree_1d_fdsbp") + +@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)) + 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)) + end +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)) + end + + @trixi_testset "elixir_euler_convergence.jl with vanleer_haenel_splitting" 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 = vanleer_haenel_splitting) + 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) + end +end + +end # module diff --git a/test/test_tree_2d_fdsbp.jl b/test/test_tree_2d_fdsbp.jl index 98d886dd840..daaec6ebd05 100644 --- a/test/test_tree_2d_fdsbp.jl +++ b/test/test_tree_2d_fdsbp.jl @@ -11,10 +11,33 @@ EXAMPLES_DIR = joinpath(pathof(Trixi) |> dirname |> dirname, "examples", "tree_2 @testset "Linear scalar advection" begin @trixi_testset "elixir_advection_extended.jl" begin @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_advection_extended.jl"), - l2 = [2.898644263922225e-6], + l2 = [2.898644263922225e-6], linf = [8.491517930142578e-6], rtol = 1.0e-7) # These results change a little bit and depend on the CI system 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)) + end + + @trixi_testset "elixir_euler_khi.jl" begin + @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_khi.jl"), + l2 = [0.02607850081951497, 0.020357717558016252, 0.028510191844948945, 0.02951535039734857], + linf = [0.12185328623662173, 0.1065055387595834, 0.06257122956937419, 0.11992349951978643], + tspan=(0.0, 0.1)) + 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)) + end +end + end # module diff --git a/test/test_tree_3d_fdsbp.jl b/test/test_tree_3d_fdsbp.jl new file mode 100644 index 00000000000..1b65f5a9588 --- /dev/null +++ b/test/test_tree_3d_fdsbp.jl @@ -0,0 +1,27 @@ +module TestTree3DFDSBP + +using Test +using Trixi + +include("test_trixi.jl") + +# pathof(Trixi) returns /path/to/Trixi/src/Trixi.jl, dirname gives the parent directory +EXAMPLES_DIR = joinpath(pathof(Trixi) |> dirname |> dirname, "examples", "tree_3d_fdsbp") + +@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)) + 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) + end +end + +end # module diff --git a/test/test_tree_3d_part3.jl b/test/test_tree_3d_part3.jl index fe148265b74..fe9601d635d 100644 --- a/test/test_tree_3d_part3.jl +++ b/test/test_tree_3d_part3.jl @@ -18,6 +18,9 @@ isdir(outdir) && rm(outdir, recursive=true) # Lattice-Boltzmann include("test_tree_3d_lbm.jl") + + # FDSBP methods on the TreeMesh + include("test_tree_3d_fdsbp.jl") end From a8b5e4bbf7d210a14db00760b26da4fa845903b1 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Tue, 29 Nov 2022 08:14:43 +0100 Subject: [PATCH 2/7] Clean-up upwind SBP stuff (#1276) * cleaner interface for upwind operators * reexport upwind_operators * clean up elixirs * more experimental warnings * fix docstring Co-authored-by: Michael Schlottke-Lakemper * adapt new elixirs * fix docstring * rename splittings for consistency with conventions * improve docstrings of splittings * floating point stuff * more comments, consistent code ordering * rename KHI elixir for consistency Co-authored-by: Michael Schlottke-Lakemper --- Project.toml | 2 +- .../tree_1d_fdsbp/elixir_burgers_basic.jl | 26 ++-- .../elixir_burgers_linear_stability.jl | 26 ++-- .../tree_1d_fdsbp/elixir_euler_convergence.jl | 28 ++--- .../elixir_euler_density_wave.jl | 27 ++-- .../tree_2d_fdsbp/elixir_euler_convergence.jl | 22 ++-- ...xir_euler_kelvin_helmholtz_instability.jl} | 30 ++--- examples/tree_2d_fdsbp/elixir_euler_vortex.jl | 26 ++-- .../tree_3d_fdsbp/elixir_euler_convergence.jl | 26 ++-- .../elixir_euler_taylor_green_vortex.jl | 28 ++--- src/Trixi.jl | 7 +- src/equations/compressible_euler_1d.jl | 101 +++++++++------ src/equations/compressible_euler_2d.jl | 92 ++++++++------ src/equations/compressible_euler_3d.jl | 45 ++++--- src/equations/inviscid_burgers_1d.jl | 14 ++- src/solvers/dg.jl | 32 ++--- src/solvers/fdsbp_tree/fdsbp.jl | 35 +++--- src/solvers/fdsbp_tree/fdsbp_1d.jl | 113 ++++++++--------- src/solvers/fdsbp_tree/fdsbp_2d.jl | 113 ++++++++--------- src/solvers/fdsbp_tree/fdsbp_3d.jl | 119 +++++++++--------- test/test_tree_1d_fdsbp.jl | 4 +- test/test_tree_2d_fdsbp.jl | 4 +- 22 files changed, 469 insertions(+), 451 deletions(-) rename examples/tree_2d_fdsbp/{elixir_euler_khi.jl => elixir_euler_kelvin_helmholtz_instability.jl} (70%) diff --git a/Project.toml b/Project.toml index bf0004509b1..a7649b4f28e 100644 --- a/Project.toml +++ b/Project.toml @@ -67,7 +67,7 @@ Static = "0.3, 0.4, 0.5, 0.6, 0.7, 0.8" StaticArrays = "1" StrideArrays = "0.1.18" StructArrays = "0.6" -SummationByPartsOperators = "0.5.10" +SummationByPartsOperators = "0.5.25" TimerOutputs = "0.5" Triangulate = "2.0" TriplotBase = "0.1" diff --git a/examples/tree_1d_fdsbp/elixir_burgers_basic.jl b/examples/tree_1d_fdsbp/elixir_burgers_basic.jl index fb8dea2055e..2216546bb4c 100644 --- a/examples/tree_1d_fdsbp/elixir_burgers_basic.jl +++ b/examples/tree_1d_fdsbp/elixir_burgers_basic.jl @@ -1,3 +1,6 @@ +# TODO: FD +# !!! warning "Experimental feature" +# This is an experimental feature and may change in any future releases. using OrdinaryDiffEq using Trixi @@ -9,21 +12,13 @@ equations = InviscidBurgersEquation1D() initial_condition = initial_condition_convergence_test -D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=32) -D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=32) - -# TODO: Super hacky. -# Abuse the mortars to save the second derivative operator and get it into the run -flux_splitting = lax_friedrichs_splitting -solver = DG(D_plus, D_minus #= mortar =#, +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 = DG(D_upw, nothing #= mortar =#, SurfaceIntegralUpwind(flux_splitting), VolumeIntegralUpwind(flux_splitting)) @@ -33,7 +28,6 @@ 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) diff --git a/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl b/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl index 48cd5090e2c..58cbad39760 100644 --- a/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl +++ b/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl @@ -1,3 +1,6 @@ +# TODO: FD +# !!! warning "Experimental feature" +# This is an experimental feature and may change in any future releases. using OrdinaryDiffEq using Trixi @@ -12,21 +15,13 @@ function initial_condition_linear_stability(x, t, equation::InviscidBurgersEquat 2 + sinpi(k * (x[1] - 0.7)) |> SVector end -D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) -D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) - -# TODO: Super hacky. -# Abuse the mortars to save the second derivative operator and get it into the run -flux_splitting = lax_friedrichs_splitting -solver = DG(D_plus, D_minus #= mortar =#, +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 = DG(D_upw, nothing #= mortar =#, SurfaceIntegralUpwind(flux_splitting), VolumeIntegralUpwind(flux_splitting)) @@ -36,7 +31,6 @@ mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level=4, n_cells_max=10_000) - semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition_linear_stability, solver) diff --git a/examples/tree_1d_fdsbp/elixir_euler_convergence.jl b/examples/tree_1d_fdsbp/elixir_euler_convergence.jl index 6760680745f..4bb2700121b 100644 --- a/examples/tree_1d_fdsbp/elixir_euler_convergence.jl +++ b/examples/tree_1d_fdsbp/elixir_euler_convergence.jl @@ -1,3 +1,6 @@ +# TODO: FD +# !!! warning "Experimental feature" +# This is an experimental feature and may change in any future releases. using OrdinaryDiffEq using Trixi @@ -9,22 +12,13 @@ equations = CompressibleEulerEquations1D(1.4) initial_condition = initial_condition_convergence_test -# Note that the expected EOC of 4 when the value of N is increased -D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=32) -D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=32) - -# TODO: Super hacky. -# Abuse the mortars to save the second derivative operator and get it into the run -flux_splitting = steger_warming_splitting -solver = DG(D_plus, D_minus #= mortar =#, +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 = DG(D_upw, nothing #= mortar =#, SurfaceIntegralUpwind(flux_splitting), VolumeIntegralUpwind(flux_splitting)) @@ -37,6 +31,7 @@ mesh = TreeMesh(coordinates_min, coordinates_max, semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, source_terms=source_terms_convergence_test) + ############################################################################### # ODE solvers, callbacks etc. @@ -61,6 +56,7 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + ############################################################################### # run the simulation diff --git a/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl b/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl index c82b310e540..f70003e37e7 100644 --- a/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl +++ b/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl @@ -1,3 +1,6 @@ +# TODO: FD +# !!! warning "Experimental feature" +# This is an experimental feature and may change in any future releases. using OrdinaryDiffEq using Trixi @@ -8,21 +11,13 @@ equations = CompressibleEulerEquations1D(1.4) initial_condition = initial_condition_density_wave -D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) -D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) - -# TODO: Super hacky. -# Abuse the mortars to save the second derivative operator and get it into the run -flux_splitting = coirier_vanleer_splitting -solver = DG(D_plus, D_minus #= mortar =#, +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 = DG(D_upw, nothing #= mortar =#, SurfaceIntegralUpwind(flux_splitting), VolumeIntegralUpwind(flux_splitting)) @@ -34,6 +29,7 @@ mesh = TreeMesh(coordinates_min, coordinates_max, semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + ############################################################################### # ODE solvers, callbacks etc. @@ -56,6 +52,7 @@ callbacks = CallbackSet(summary_callback, analysis_callback, alive_callback, save_solution) + ############################################################################### # run the simulation diff --git a/examples/tree_2d_fdsbp/elixir_euler_convergence.jl b/examples/tree_2d_fdsbp/elixir_euler_convergence.jl index c394ded1a7f..a14bacba280 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_convergence.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_convergence.jl @@ -12,21 +12,13 @@ equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test -D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) -D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) - -# TODO: Super hacky. -# Abuse the mortars to save the second derivative operator and get it into the run -flux_splitting = steger_warming_splitting -solver = DG(D_plus, D_minus #= mortar =#, +D = upwind_operators(SummationByPartsOperators.Mattsson2017, + derivative_order=1, + accuracy_order=4, + xmin=-1.0, xmax=1.0, + N=16) +flux_splitting = splitting_steger_warming +solver = DG(D, nothing #= mortar =#, SurfaceIntegralUpwind(flux_splitting), VolumeIntegralUpwind(flux_splitting)) diff --git a/examples/tree_2d_fdsbp/elixir_euler_khi.jl b/examples/tree_2d_fdsbp/elixir_euler_kelvin_helmholtz_instability.jl similarity index 70% rename from examples/tree_2d_fdsbp/elixir_euler_khi.jl rename to examples/tree_2d_fdsbp/elixir_euler_kelvin_helmholtz_instability.jl index 18edac8294e..2e8ff77ce47 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_khi.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_kelvin_helmholtz_instability.jl @@ -1,6 +1,7 @@ # TODO: FD # !!! warning "Experimental feature" # This is an experimental feature and may change in any future releases. + using OrdinaryDiffEq using Trixi @@ -24,27 +25,14 @@ end initial_condition = initial_condition_kelvin_helmholtz_instability -D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), -#D_plus = derivative_operator(SummationByPartsOperators.WIP(:plus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) -D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), -#D_minus = derivative_operator(SummationByPartsOperators.WIP(:minus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) -# TODO: Super hacky. -# Abuse the mortars to save the second derivative operator and get it into the run -#flux_splitting = steger_warming_splitting -flux_splitting = vanleer_haenel_splitting -#flux_splitting = lax_friedrichs_splitting -#surface_flux = flux_hllc -solver = DG(D_plus, D_minus #= mortar =#, +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 = DG(D_upw, nothing #= mortar =#, SurfaceIntegralUpwind(flux_splitting), - #SurfaceIntegralStrongForm(surface_flux), VolumeIntegralUpwind(flux_splitting)) coordinates_min = (-1.0, -1.0) @@ -56,6 +44,7 @@ mesh = TreeMesh(coordinates_min, coordinates_max, semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + ############################################################################### # ODE solvers, callbacks etc. @@ -82,6 +71,7 @@ callbacks = CallbackSet(summary_callback, save_solution, alive_callback) + ############################################################################### # run the simulation diff --git a/examples/tree_2d_fdsbp/elixir_euler_vortex.jl b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl index 8effc717715..450a360671a 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_vortex.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl @@ -55,21 +55,13 @@ end initial_condition = initial_condition_isentropic_vortex -D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) -D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) - -# TODO: Super hacky. -# Abuse the mortars to save the second derivative operator and get it into the run -flux_splitting = steger_warming_splitting -solver = DG(D_plus, D_minus #= mortar =#, +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 = DG(D_upw, nothing #= mortar =#, SurfaceIntegralUpwind(flux_splitting), VolumeIntegralUpwind(flux_splitting)) @@ -81,6 +73,8 @@ mesh = TreeMesh(coordinates_min, coordinates_max, periodicity=true) semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + + ############################################################################### # ODE solvers, callbacks etc. @@ -103,6 +97,8 @@ callbacks = CallbackSet(summary_callback, analysis_callback, save_solution, alive_callback) + + ############################################################################### # run the simulation diff --git a/examples/tree_3d_fdsbp/elixir_euler_convergence.jl b/examples/tree_3d_fdsbp/elixir_euler_convergence.jl index d6e0cd2bc33..26460f9428b 100644 --- a/examples/tree_3d_fdsbp/elixir_euler_convergence.jl +++ b/examples/tree_3d_fdsbp/elixir_euler_convergence.jl @@ -1,3 +1,6 @@ +# TODO: FD +# !!! warning "Experimental feature" +# This is an experimental feature and may change in any future releases. using OrdinaryDiffEq using Trixi @@ -9,21 +12,13 @@ equations = CompressibleEulerEquations3D(1.4) initial_condition = initial_condition_convergence_test -D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) -D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) - -# TODO: Super hacky. -# Abuse the mortars to save the second derivative operator and get it into the run -flux_splitting = steger_warming_splitting -solver = DG(D_plus, D_minus #= mortar =#, +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 = DG(D_upw, nothing #= mortar =#, SurfaceIntegralUpwind(flux_splitting), VolumeIntegralUpwind(flux_splitting)) @@ -36,6 +31,7 @@ mesh = TreeMesh(coordinates_min, coordinates_max, semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver, source_terms=source_terms_convergence_test) + ############################################################################### # ODE solvers, callbacks etc. 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 9a1e347c040..e977c80889f 100644 --- a/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl +++ b/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl @@ -1,3 +1,6 @@ +# TODO: FD +# !!! warning "Experimental feature" +# This is an experimental feature and may change in any future releases. using OrdinaryDiffEq using Trixi @@ -27,21 +30,13 @@ function initial_condition_taylor_green_vortex(x, t, equations::CompressibleEule end initial_condition = initial_condition_taylor_green_vortex -D_plus = derivative_operator(SummationByPartsOperators.Mattsson2017(:plus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) -D_minus = derivative_operator(SummationByPartsOperators.Mattsson2017(:minus), - derivative_order=1, - accuracy_order=4, - xmin=-1.0, xmax=1.0, - N=16) - -# TODO: Super hacky. -# Abuse the mortars to save the second derivative operator and get it into the run -flux_splitting = steger_warming_splitting -solver = DG(D_plus, D_minus #= mortar =#, +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 = DG(D_upw, nothing #= mortar =#, SurfaceIntegralUpwind(flux_splitting), VolumeIntegralUpwind(flux_splitting)) @@ -51,9 +46,9 @@ mesh = TreeMesh(coordinates_min, coordinates_max, initial_refinement_level=2, n_cells_max=100_000) - semi = SemidiscretizationHyperbolic(mesh, equations, initial_condition, solver) + ############################################################################### # ODE solvers, callbacks etc. @@ -81,6 +76,7 @@ callbacks = CallbackSet(summary_callback, alive_callback, save_solution) + ############################################################################### # run the simulation diff --git a/src/Trixi.jl b/src/Trixi.jl index 61ab53a948d..d3e786ee438 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -72,7 +72,8 @@ using SummationByPartsOperators: AbstractDerivativeOperator, import SummationByPartsOperators: integrate, semidiscretize, left_boundary_weight, right_boundary_weight @reexport using SummationByPartsOperators: - SummationByPartsOperators, derivative_operator, periodic_derivative_operator + SummationByPartsOperators, derivative_operator, periodic_derivative_operator, + upwind_operators # DGMulti solvers @reexport using StartUpDG: StartUpDG, Polynomial, SBP, Line, Tri, Quad, Hex, Tet @@ -154,8 +155,8 @@ export flux, flux_central, flux_lax_friedrichs, flux_hll, flux_hllc, flux_hlle, flux_shima_etal_turbo, flux_ranocha_turbo, FluxHydrostaticReconstruction -export steger_warming_splitting, vanleer_haenel_splitting, - coirier_vanleer_splitting, lax_friedrichs_splitting +export splitting_steger_warming, splitting_vanleer_haenel, + splitting_coirier_vanleer, splitting_lax_friedrichs export initial_condition_constant, initial_condition_gauss, diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 57d014f136c..2aeb484102d 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -361,18 +361,25 @@ end """ - steger_warming_splitting(u, ::Symbol, orientation::Integer, + splitting_steger_warming(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, equations::CompressibleEulerEquations1D) -Splitting of the compressible Euler flux of Steger and Warming. The `Symbol` -indicates if the routine computes all the components with positive eigenvalue `:plus` -or all the negative eigenvalue components `:minus`. +Splitting of the compressible Euler flux of Steger and Warming. + +Returns the flux "minus" (associated with waves going into the +negative axis direction) or "plus" (associated with waves going into the +positive axis direction), determined by the argument `which` set to +`Val{:minus}()` or `Val{:plus}`. + +## 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 steger_warming_splitting(u, ::Val{:plus}, orientation::Integer, +@inline function splitting_steger_warming(u, ::Val{:plus}, orientation::Integer, equations::CompressibleEulerEquations1D) rho, rho_v1, rho_e = u v1 = rho_v1 / rho @@ -387,17 +394,17 @@ or all the negative eigenvalue components `:minus`. lambda2_p = 0.5 * (lambda2 + abs(lambda2)) lambda3_p = 0.5 * (lambda3 + abs(lambda3)) - alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p f1p = 0.5 * rho / equations.gamma * alpha_p f2p = 0.5 * rho / equations.gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) f3p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * v1^2 + a * v1 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) return SVector(f1p, f2p, f3p) end -@inline function steger_warming_splitting(u, ::Val{:minus}, orientation::Integer, +@inline function splitting_steger_warming(u, ::Val{:minus}, orientation::Integer, equations::CompressibleEulerEquations1D) rho, rho_v1, rho_e = u v1 = rho_v1 / rho @@ -412,28 +419,35 @@ end lambda2_m = 0.5 * (lambda2 - abs(lambda2)) lambda3_m = 0.5 * (lambda3 - abs(lambda3)) - alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m f1m = 0.5 * rho / equations.gamma * alpha_m f2m = 0.5 * rho / equations.gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) f3m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * v1^2 + a * v1 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) return SVector(f1m, f2m, f3m) end """ - vanleer_haenel_splitting(u, ::Symbol, orientation::Integer, + splitting_vanleer_haenel(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, equations::CompressibleEulerEquations1D) -Splitting of the compressible Euler flux from van Leer. The `Symbol` -indicates if the routine computes all the components with positive eigenvalue `:plus` -or all the negative eigenvalue components `:minus`. 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. +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 the flux "minus" (associated with waves going into the +negative axis direction) or "plus" (associated with waves going into the +positive axis direction), determined by the argument `which` set to +`Val{:minus}()` or `Val{:plus}`. + +## References - Bram van Leer (1982) Flux-Vector Splitting for the Euler Equation @@ -445,8 +459,7 @@ the 'p4' variant suggested by Liou and Steffen as it proved the most robust in p High-Order Polynomial Expansions (HOPE) for Flux-Vector Splitting [NASA Technical Memorandum](https://ntrs.nasa.gov/citations/19910016425) """ -# TODO: separate this as a separate splitting the runs el_diablo -@inline function vanleer_haenel_splitting(u, ::Val{:plus}, orientation::Integer, +@inline function splitting_vanleer_haenel(u, ::Val{:plus}, orientation::Integer, equations::CompressibleEulerEquations1D) rho, rho_v1, rho_e = u v1 = rho_v1 / rho @@ -459,7 +472,7 @@ the 'p4' variant suggested by Liou and Steffen as it proved the most robust in p # signed Mach number M = v1 / a - p_plus = 0.5 * (1.0 + equations.gamma * M) * p + p_plus = 0.5 * (1 + equations.gamma * M) * p f1p = 0.25 * rho * a * (M + 1)^2 f2p = f1p * v1 + p_plus @@ -468,7 +481,7 @@ the 'p4' variant suggested by Liou and Steffen as it proved the most robust in p return SVector(f1p, f2p, f3p) end -@inline function vanleer_haenel_splitting(u, ::Val{:minus}, orientation::Integer, +@inline function splitting_vanleer_haenel(u, ::Val{:minus}, orientation::Integer, equations::CompressibleEulerEquations1D) rho, rho_v1, rho_e = u v1 = rho_v1 / rho @@ -481,7 +494,7 @@ end # signed Mach number M = v1 / a - p_minus = 0.5 * (1.0 - equations.gamma * M) * p + p_minus = 0.5 * (1 - equations.gamma * M) * p f1m= -0.25 * rho * a * (M - 1)^2 f2m = f1m * v1 + p_minus @@ -491,21 +504,7 @@ end end -""" - coirier_vanleer_splitting(u, ::Symbol, orientation::Integer, - equations::CompressibleEulerEquations1D) - -Splitting of the compressible Euler flux from Coirier and van Leer. The `Symbol` -indicates if the routine computes all the components with positive eigenvalue `:plus` -or all the negative eigenvalue components `:minus`. 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. - -- 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) -""" +# 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); @@ -515,7 +514,29 @@ motivation for these corrections are to handle flows at the low Mach number limi # 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? -@inline function coirier_vanleer_splitting(u, ::Val{:plus}, orientation::Integer, +""" + 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 the flux "minus" (associated with waves going into the +negative axis direction) or "plus" (associated with waves going into the +positive axis direction), determined by the argument `which` set to +`Val{:minus}()` or `Val{:plus}`. + +## 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, ::Val{:plus}, orientation::Integer, equations::CompressibleEulerEquations1D) rho, rho_v1, rho_e = u v1 = rho_v1 / rho @@ -542,7 +563,7 @@ motivation for these corrections are to handle flows at the low Mach number limi return SVector(f1p, f2p, f3p) end -@inline function coirier_vanleer_splitting(u, ::Val{:minus}, orientation::Integer, +@inline function splitting_coirier_vanleer(u, ::Val{:minus}, orientation::Integer, equations::CompressibleEulerEquations1D) rho, rho_v1, rho_e = u v1 = rho_v1 / rho diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index 2ed078e72c0..072da58ac4f 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -219,7 +219,7 @@ in combination with [`initial_condition_eoc_test_coupled_euler_gravity`](@ref). c = 2.0 A = 0.1 G = 1.0 # gravitational constant, must match coupling solver - C_grav = -2.0 * G / pi # 2 == 4 / ndims + C_grav = -2 * G / pi # 2 == 4 / ndims x1, x2 = x si, co = sincos(pi * (x1 + x2 - t)) @@ -308,7 +308,7 @@ Should be used together with [`UnstructuredMesh2D`](@ref). # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) if v_normal <= 0.0 sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed - p_star = p_local * (1.0 + 0.5 * (equations.gamma - 1) * v_normal / sound_speed)^(2.0 * equations.gamma * equations.inv_gamma_minus_one) + p_star = p_local * (1.0 + 0.5 * (equations.gamma - 1) * v_normal / sound_speed)^(2 * equations.gamma * equations.inv_gamma_minus_one) else # v_normal > 0.0 A = 2.0 / ((equations.gamma + 1) * rho_local) B = p_local * (equations.gamma - 1) / (equations.gamma + 1) @@ -665,18 +665,25 @@ end """ - steger_warming_splitting(u, ::Symbol, orientation::Integer, + splitting_steger_warming(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, equations::CompressibleEulerEquations2D) -Splitting of the compressible Euler flux of Steger and Warming. The `Symbol` -indicates if the routine computes all the components with positive eigenvalue `:plus` -or all the negative eigenvalue components `:minus`. +Splitting of the compressible Euler flux of Steger and Warming. + +Returns the flux "minus" (associated with waves going into the +negative axis direction) or "plus" (associated with waves going into the +positive axis direction), determined by the argument `which` set to +`Val{:minus}()` or `Val{:plus}`. + +## 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 steger_warming_splitting(u, ::Val{:plus}, orientation::Integer, +@inline function splitting_steger_warming(u, ::Val{:plus}, orientation::Integer, equations::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u v1 = rho_v1 / rho @@ -693,13 +700,13 @@ or all the negative eigenvalue components `:minus`. lambda2_p = 0.5 * (lambda2 + abs(lambda2)) lambda3_p = 0.5 * (lambda3 + abs(lambda3)) - alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p f1p = 0.5 * rho / equations.gamma * alpha_p f2p = 0.5 * rho / equations.gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) f3p = 0.5 * rho / equations.gamma * alpha_p * v2 f4p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2) + a * v1 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) else lambda1 = v2 lambda2 = v2 + a @@ -709,18 +716,18 @@ or all the negative eigenvalue components `:minus`. lambda2_p = 0.5 * (lambda2 + abs(lambda2)) lambda3_p = 0.5 * (lambda3 + abs(lambda3)) - alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p f1p = 0.5 * rho / equations.gamma * alpha_p f2p = 0.5 * rho / equations.gamma * alpha_p * v1 f3p = 0.5 * rho / equations.gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) f4p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2) + a * v2 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) end return SVector(f1p, f2p, f3p, f4p) end -@inline function steger_warming_splitting(u, ::Val{:minus}, orientation::Integer, +@inline function splitting_steger_warming(u, ::Val{:minus}, orientation::Integer, equations::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u v1 = rho_v1 / rho @@ -737,13 +744,13 @@ end lambda2_m = 0.5 * (lambda2 - abs(lambda2)) lambda3_m = 0.5 * (lambda3 - abs(lambda3)) - alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m f1m = 0.5 * rho / equations.gamma * alpha_m f2m = 0.5 * rho / equations.gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) f3m = 0.5 * rho / equations.gamma * alpha_m * v2 f4m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2) + a * v1 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) else lambda1 = v2 lambda2 = v2 + a @@ -753,29 +760,36 @@ end lambda2_m = 0.5 * (lambda2 - abs(lambda2)) lambda3_m = 0.5 * (lambda3 - abs(lambda3)) - alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m f1m = 0.5 * rho / equations.gamma * alpha_m f2m = 0.5 * rho / equations.gamma * alpha_m * v1 f3m = 0.5 * rho / equations.gamma * (alpha_m * v2 + a * (lambda2_m-lambda3_m)) f4m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2) + a * v2 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) end return SVector(f1m, f2m, f3m, f4m) end """ - vanleer_haenel_splitting(u, ::Symbol, orientation::Integer, - equations::CompressibleEulerEquations1D) + splitting_vanleer_haenel(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, + 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. -Splitting of the compressible Euler flux from van Leer. The `Symbol` -indicates if the routine computes all the components with positive eigenvalue `:plus` -or all the negative eigenvalue components `:minus`. 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 the flux "minus" (associated with waves going into the +negative axis direction) or "plus" (associated with waves going into the +positive axis direction), determined by the argument `which` set to +`Val{:minus}()` or `Val{:plus}`. + +## References - Bram van Leer (1982) Flux-Vector Splitting for the Euler Equation @@ -787,9 +801,7 @@ the 'p4' variant suggested by Liou and Steffen as it proved the most robust in p High-Order Polynomial Expansions (HOPE) for Flux-Vector Splitting [NASA Technical Memorandum](https://ntrs.nasa.gov/citations/19910016425) """ -#TODO: generic central flux where f_plus = 0.5 * f and f_minus = 0.5 * f -# combine with interface terms for experimentation -@inline function vanleer_haenel_splitting(u, ::Val{:plus}, orientation::Integer, +@inline function splitting_vanleer_haenel(u, ::Val{:plus}, orientation::Integer, equations::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u v1 = rho_v1 / rho @@ -819,7 +831,7 @@ the 'p4' variant suggested by Liou and Steffen as it proved the most robust in p return SVector(f1p, f2p, f3p, f4p) end -@inline function vanleer_haenel_splitting(u, ::Val{:minus}, orientation::Integer, +@inline function splitting_vanleer_haenel(u, ::Val{:minus}, orientation::Integer, equations::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u v1 = rho_v1 / rho @@ -851,14 +863,20 @@ end """ - lax_friedrichs_splitting(u, ::Symbol, orientation::Integer, + splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, equations::CompressibleEulerEquations2D) -Naive Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ u)` +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 the flux "minus" (associated with waves going into the +negative axis direction) or "plus" (associated with waves going into the +positive axis direction), determined by the argument `which` set to +`Val{:minus}()` or `Val{:plus}`. """ -@inline function lax_friedrichs_splitting(u, ::Val{:plus}, orientation::Integer, +@inline function splitting_lax_friedrichs(u, ::Val{:plus}, orientation::Integer, equations::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u v1 = rho_v1 / rho @@ -885,7 +903,7 @@ to Burgers' equation. return SVector(f1p, f2p, f3p, f4p) end -@inline function lax_friedrichs_splitting(u, ::Val{:minus}, orientation::Integer, +@inline function splitting_lax_friedrichs(u, ::Val{:minus}, orientation::Integer, equations::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u v1 = rho_v1 / rho @@ -1220,16 +1238,16 @@ end # Should be used together with [SurfaceIntegralStrongForm](@ref) and a finite difference # summation-by-parts (FDSBP) solver. -# TODO: reference? Maybe van Leer paper "...for the 90s" +# TODO: FD. reference? Maybe van Leer paper "...for the 90s" # """ -# # TODO: do we want this? +# # TODO: FD. Do we want this? # @inline function flux_upwind(u_ll, u_rr, orientation::Integer, equations::CompressibleEulerEquations2D) # # Compute the upwind coupling terms with right-traveling from the left # # and left-traveling information from the right # # TODO: How to make this easier to switch out other splittings? -# f_plus_ll = steger_warming_splitting(u_ll, :plus, orientation, equations) -# f_minus_rr = steger_warming_splitting(u_rr, :minus, orientation, equations) +# f_plus_ll = splitting_steger_warming(u_ll, :plus, orientation, equations) +# f_minus_rr = splitting_steger_warming(u_rr, :minus, orientation, equations) # # Combine the upwind terms to pass back as a type of numerical flux # f1 = f_plus_ll[1] + f_minus_rr[1] diff --git a/src/equations/compressible_euler_3d.jl b/src/equations/compressible_euler_3d.jl index 8c9427b3dee..0c6deb9463d 100644 --- a/src/equations/compressible_euler_3d.jl +++ b/src/equations/compressible_euler_3d.jl @@ -308,7 +308,7 @@ Details about the 1D pressure Riemann solution can be found in Section 6.3.3 of # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) if v_normal <= 0.0 sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed - p_star = p_local * (1.0 + 0.5 * (equations.gamma - 1) * v_normal / sound_speed)^(2.0 * equations.gamma * equations.inv_gamma_minus_one) + p_star = p_local * (1.0 + 0.5 * (equations.gamma - 1) * v_normal / sound_speed)^(2 * equations.gamma * equations.inv_gamma_minus_one) else # v_normal > 0.0 A = 2.0 / ((equations.gamma + 1) * rho_local) B = p_local * (equations.gamma - 1) / (equations.gamma + 1) @@ -681,18 +681,25 @@ end """ - steger_warming_splitting(u, ::Symbol, orientation::Integer, + splitting_steger_warming(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, equations::CompressibleEulerEquations3D) -Splitting of the compressible Euler flux of Steger and Warming. The `Symbol` -indicates if the routine computes all the components with positive eigenvalue `:plus` -or all the negative eigenvalue components `:minus`. +Splitting of the compressible Euler flux of Steger and Warming. + +Returns the flux "minus" (associated with waves going into the +negative axis direction) or "plus" (associated with waves going into the +positive axis direction), determined by the argument `which` set to +`Val{:minus}()` or `Val{:plus}`. + +## 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 steger_warming_splitting(u, ::Val{:plus}, orientation::Integer, +@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 @@ -710,14 +717,14 @@ or all the negative eigenvalue components `:minus`. lambda2_p = 0.5 * (lambda2 + abs(lambda2)) lambda3_p = 0.5 * (lambda3 + abs(lambda3)) - alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p f1p = 0.5 * rho / equations.gamma * alpha_p f2p = 0.5 * rho / equations.gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) f3p = 0.5 * rho / equations.gamma * alpha_p * v2 f4p = 0.5 * rho / equations.gamma * alpha_p * v3 f5p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2 + v3^2) + a * v1 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) elseif orientation == 2 lambda1 = v2 lambda2 = v2 + a @@ -727,14 +734,14 @@ or all the negative eigenvalue components `:minus`. lambda2_p = 0.5 * (lambda2 + abs(lambda2)) lambda3_p = 0.5 * (lambda3 + abs(lambda3)) - alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p f1p = 0.5 * rho / equations.gamma * alpha_p f2p = 0.5 * rho / equations.gamma * alpha_p * v1 f3p = 0.5 * rho / equations.gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) f4p = 0.5 * rho / equations.gamma * alpha_p * v3 f5p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2 + v3^2) + a * v2 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) else lambda1 = v3 lambda2 = v3 + a @@ -744,19 +751,19 @@ or all the negative eigenvalue components `:minus`. lambda2_p = 0.5 * (lambda2 + abs(lambda2)) lambda3_p = 0.5 * (lambda3 + abs(lambda3)) - alpha_p = 2.0 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p + alpha_p = 2 * (equations.gamma - 1) * lambda1_p + lambda2_p + lambda3_p f1p = 0.5 * rho / equations.gamma * alpha_p f2p = 0.5 * rho / equations.gamma * alpha_p * v1 f3p = 0.5 * rho / equations.gamma * alpha_p * v2 f4p = 0.5 * rho / equations.gamma * (alpha_p * v3 + a * (lambda2_p - lambda3_p)) f5p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2 + v3^2) + a * v3 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) / (equations.gamma - 1)) + + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) end return SVector(f1p, f2p, f3p, f4p, f5p) end -@inline function steger_warming_splitting(u, ::Val{:minus}, orientation::Integer, +@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 @@ -774,14 +781,14 @@ end lambda2_m = 0.5 * (lambda2 - abs(lambda2)) lambda3_m = 0.5 * (lambda3 - abs(lambda3)) - alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m f1m = 0.5 * rho / equations.gamma * alpha_m f2m = 0.5 * rho / equations.gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) f3m = 0.5 * rho / equations.gamma * alpha_m * v2 f4m = 0.5 * rho / equations.gamma * alpha_m * v3 f5m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2 + v3^2) + a * v1 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) elseif orientation == 2 lambda1 = v2 lambda2 = v2 + a @@ -791,14 +798,14 @@ end lambda2_m = 0.5 * (lambda2 - abs(lambda2)) lambda3_m = 0.5 * (lambda3 - abs(lambda3)) - alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m f1m = 0.5 * rho / equations.gamma * alpha_m f2m = 0.5 * rho / equations.gamma * alpha_m * v1 f3m = 0.5 * rho / equations.gamma * (alpha_m * v2 + a * (lambda2_m - lambda3_m)) f4m = 0.5 * rho / equations.gamma * alpha_m * v3 f5m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2 + v3^2) + a * v2 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) else lambda1 = v3 lambda2 = v3 + a @@ -808,14 +815,14 @@ end lambda2_m = 0.5 * (lambda2 - abs(lambda2)) lambda3_m = 0.5 * (lambda3 - abs(lambda3)) - alpha_m = 2.0 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m + alpha_m = 2 * (equations.gamma - 1) * lambda1_m + lambda2_m + lambda3_m f1m = 0.5 * rho / equations.gamma * alpha_m f2m = 0.5 * rho / equations.gamma * alpha_m * v1 f3m = 0.5 * rho / equations.gamma * alpha_m * v2 f4m = 0.5 * rho / equations.gamma * (alpha_m * v3 + a * (lambda2_m - lambda3_m)) f5m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2 + v3^2) + a * v3 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) / (equations.gamma - 1)) + + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) end return SVector(f1m, f2m, f3m, f4m, f5m) end diff --git a/src/equations/inviscid_burgers_1d.jl b/src/equations/inviscid_burgers_1d.jl index 98479d10f6c..90ab5b0c65c 100644 --- a/src/equations/inviscid_burgers_1d.jl +++ b/src/equations/inviscid_burgers_1d.jl @@ -132,20 +132,26 @@ end """ - lax_friedrichs_splitting(u, ::Symbol, orientation::Integer, + splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} + orientation::Integer, equations::InviscidBurgersEquation1D) -Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ u)` +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 the flux "minus" (associated with waves going into the +negative axis direction) or "plus" (associated with waves going into the +positive axis direction), determined by the argument `which` set to +`Val{:minus}()` or `Val{:plus}`. """ -@inline function lax_friedrichs_splitting(u, ::Val{:plus}, orientation::Integer, +@inline function splitting_lax_friedrichs(u, ::Val{:plus}, orientation::Integer, equations::InviscidBurgersEquation1D) f = 0.5 * u[1]^2 lambda = abs(u[1]) return SVector(0.5 * (f + lambda * u[1])) end -@inline function lax_friedrichs_splitting(u, ::Val{:minus}, orientation::Integer, +@inline function splitting_lax_friedrichs(u, ::Val{:minus}, orientation::Integer, equations::InviscidBurgersEquation1D) f = 0.5 * u[1]^2 lambda = abs(u[1]) diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 85fe83b950c..575aeea754f 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -128,20 +128,25 @@ function Base.show(io::IO, mime::MIME"text/plain", integral::VolumeIntegralShock end +# TODO: FD. Should this definition live in a different file because it is +# not strictly a DG method? """ - VolumeIntegralUpwind + VolumeIntegralUpwind(splitting) Specialized volume integral for finite difference summation-by-parts (FDSBP) -solver. Uses the upwind SBP operators developed in +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) - -Depends on a particular `FluxSplitting` like that of Steger-Warming -TODO: put in refs """ -# TODO: should this definition live in a different file because it is -# not a DG method struct VolumeIntegralUpwind{FluxSplitting} <: AbstractVolumeIntegral splitting::FluxSplitting end @@ -274,16 +279,17 @@ function Base.show(io::IO, ::MIME"text/plain", integral::SurfaceIntegralStrongFo 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 simulataneous approximation terms (SATs) -that use a particular `FluxSplitting`. +Couple elements with upwind simultaneous approximation terms (SATs) +that use a particular flux `splitting`, e.g., +[`splitting_steger_warming`](@ref). See also [`VolumeIntegralUpwind`](@ref). """ -# TODO: should this definition live in a different file because it is -# not a DG method struct SurfaceIntegralUpwind{FluxSplitting} <: AbstractSurfaceIntegral splitting::FluxSplitting end @@ -574,8 +580,6 @@ include("dgsem_p4est/dg.jl") # and boundary conditions weakly. Thus, these methods can re-use a lot of # functionality implemented for DGSEM. include("fdsbp_tree/fdsbp.jl") -include("fdsbp_tree/fdsbp_1d.jl") -include("fdsbp_tree/fdsbp_2d.jl") -include("fdsbp_tree/fdsbp_3d.jl") + end # @muladd diff --git a/src/solvers/fdsbp_tree/fdsbp.jl b/src/solvers/fdsbp_tree/fdsbp.jl index d74adeadf8d..5180eccbac3 100644 --- a/src/solvers/fdsbp_tree/fdsbp.jl +++ b/src/solvers/fdsbp_tree/fdsbp.jl @@ -4,6 +4,7 @@ # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin + # For dispatch const FDSBP = DG{Basis} where {Basis<:AbstractDerivativeOperator} @@ -19,28 +20,32 @@ polydeg(D::AbstractDerivativeOperator) = size(D, 1) - 1 polydeg(fdsbp::FDSBP) = polydeg(fdsbp.basis) -# 2D containers -init_mortars(cell_ids, mesh, elements, mortar) = nothing - - -create_cache(mesh, equations, mortar, uEltype) = NamedTuple() -nmortars(mortar) = 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, - surface_integral, dg::DG) -@assert isempty(eachmortar(dg, cache)) +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, - surface_integral, dg::DG, cache) -@assert isempty(eachmortar(dg, cache)) + 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 + +# dimension-specific implementations +include("fdsbp_1d.jl") +include("fdsbp_2d.jl") +include("fdsbp_3d.jl") + + end diff --git a/src/solvers/fdsbp_tree/fdsbp_1d.jl b/src/solvers/fdsbp_tree/fdsbp_1d.jl index c806a5ca20b..7fe84b13a14 100644 --- a/src/solvers/fdsbp_tree/fdsbp_1d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_1d.jl @@ -4,6 +4,7 @@ # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin + # 1D caches function create_cache(mesh::TreeMesh{1}, equations, volume_integral::VolumeIntegralStrongForm, dg, uEltype) @@ -20,53 +21,10 @@ function create_cache(mesh::TreeMesh{1}, equations, prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( undef, ntuple(_ -> nnodes(dg), ndims(mesh))...) - f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] f_minus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] - return (; f_plus_threaded, f_minus_threaded,) -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 infomation into right-traveling and -# left traveling information. So we only need to compute the approriate -# flux information at each side of an interface. -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{1}, - nonconservative_terms::Val{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_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], equations) - flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface], equations) - - # Save the upwind coupling into the approriate 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 + return (; f_minus_threaded, f_plus_threaded,) end @@ -111,7 +69,8 @@ end # 2D volume integral contributions for `VolumeIntegralUpwind`. -# Note that the plus / minus notation does not refer to the upwind / downwind directions. +# 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 @@ -121,10 +80,11 @@ function calc_volume_integral!(du, u, nonconservative_terms::Val{false}, equations, volume_integral::VolumeIntegralUpwind, dg::FDSBP, cache) - D_plus = dg.basis # Upwind SBP D^+ derivative operator - # TODO: Super hacky. For now the other derivative operator is passed via the mortars - D_minus = dg.mortar # Upwind SBP D^- derivative operator - @unpack f_plus_threaded, f_minus_threaded = 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_threaded, f_plus_threaded = cache @unpack splitting = volume_integral # SBP operators from SummationByPartsOperators.jl implement the basic interface @@ -145,17 +105,17 @@ function calc_volume_integral!(du, u, # 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_plus_element = f_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_plus_element = splitting(u_element, Val{:plus}(), 1, equations) @. f_minus_element = splitting(u_element, Val{:minus}(), 1, equations) - mul!(view(du_vectors, :, element), D_minus, view(f_plus_element, :), - one(eltype(du)), one(eltype(du))) + @. f_plus_element = splitting(u_element, Val{:plus}(), 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 @@ -189,7 +149,48 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{1}, end -# TODO: comments about this crazy SATs +# Specialized interface flux computation because the upwind solver does +# not require a standard numerical flux (Riemann solver). The flux splitting +# already separates the solution infomation into right-traveling and +# left traveling information. So we only need to compute the approriate +# flux information at each side of an interface. +function calc_interface_flux!(surface_flux_values, + mesh::TreeMesh{1}, + nonconservative_terms::Val{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 approriate 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 + # 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 @@ -202,7 +203,6 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{1}, @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) @@ -283,4 +283,5 @@ function calc_error_norms(func, u, t, analyzer, 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 b9d1145ca1c..386ce7dbc28 100644 --- a/src/solvers/fdsbp_tree/fdsbp_2d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_2d.jl @@ -21,56 +21,10 @@ function create_cache(mesh::TreeMesh{2}, equations, prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( undef, ntuple(_ -> nnodes(dg), ndims(mesh))...) - f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] f_minus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] - return (; f_plus_threaded, f_minus_threaded,) -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 infomation into right-traveling and -# left traveling information. So we only need to compute the approriate -# flux information at each side of an interface. -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{2}, - nonconservative_terms::Val{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_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], equations) - flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface], equations) - - # Save the upwind coupling into the approriate 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 + return (; f_minus_threaded, f_plus_threaded,) end @@ -124,7 +78,8 @@ end # 2D volume integral contributions for `VolumeIntegralUpwind`. -# Note that the plus / minus notation does not refer to the upwind / downwind directions. +# 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 @@ -134,10 +89,11 @@ function calc_volume_integral!(du, u, nonconservative_terms::Val{false}, equations, volume_integral::VolumeIntegralUpwind, dg::FDSBP, cache) - D_plus = dg.basis # Upwind SBP D^+ derivative operator - # TODO: Super hacky. For now the other derivative operator is passed via the mortars - D_minus = dg.mortar # Upwind SBP D^- derivative operator - @unpack f_plus_threaded, f_minus_threaded = 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_threaded, f_plus_threaded = cache @unpack splitting = volume_integral # SBP operators from SummationByPartsOperators.jl implement the basic interface @@ -158,13 +114,13 @@ function calc_volume_integral!(du, u, # 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_plus_element = f_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_plus_element = splitting(u_element, Val{:plus}(), 1, equations) @. f_minus_element = splitting(u_element, Val{:minus}(), 1, equations) + @. f_plus_element = splitting(u_element, Val{:plus}(), 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))) @@ -173,8 +129,8 @@ function calc_volume_integral!(du, u, end # y direction - @. f_plus_element = splitting(u_element, Val{:plus}(), 2, equations) @. f_minus_element = splitting(u_element, Val{:minus}(), 2, equations) + @. f_plus_element = splitting(u_element, Val{:plus}(), 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))) @@ -230,6 +186,51 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{2}, 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 infomation into right-traveling and +# left traveling information. So we only need to compute the approriate +# flux information at each side of an interface. +function calc_interface_flux!(surface_flux_values, + mesh::TreeMesh{2}, + nonconservative_terms::Val{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 approriate 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 + # 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 diff --git a/src/solvers/fdsbp_tree/fdsbp_3d.jl b/src/solvers/fdsbp_tree/fdsbp_3d.jl index 90fd04df2d1..a82b5c8f1c3 100644 --- a/src/solvers/fdsbp_tree/fdsbp_3d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_3d.jl @@ -4,6 +4,7 @@ # See https://ranocha.de/blog/Optimizing_EC_Trixi for further details. @muladd begin + # 3D caches function create_cache(mesh::TreeMesh{3}, equations, volume_integral::VolumeIntegralStrongForm, dg, uEltype) @@ -20,57 +21,10 @@ function create_cache(mesh::TreeMesh{3}, equations, prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( undef, ntuple(_ -> nnodes(dg), ndims(mesh))...) - f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] f_minus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] - return (; f_plus_threaded, f_minus_threaded,) -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 infomation into right-traveling and -# left traveling information. So we only need to compute the approriate -# flux information at each side of an interface. -function calc_interface_flux!(surface_flux_values, - mesh::TreeMesh{3}, - nonconservative_terms::Val{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_plus_ll = splitting(u_ll, Val{:plus}(), orientations[interface], equations) - flux_minus_rr = splitting(u_rr, Val{:minus}(), orientations[interface], equations) - - # Save the upwind coupling into the approriate 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 + return (; f_minus_threaded, f_plus_threaded,) end @@ -131,7 +85,8 @@ end # 3D volume integral contributions for `VolumeIntegralUpwind`. -# Note that the plus / minus notation does not refer to the upwind / downwind directions. +# 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 @@ -141,10 +96,11 @@ function calc_volume_integral!(du, u, nonconservative_terms::Val{false}, equations, volume_integral::VolumeIntegralUpwind, dg::FDSBP, cache) - D_plus = dg.basis # Upwind SBP D^+ derivative operator - # TODO: Super hacky. For now the other derivative operator is passed via the mortars - D_minus = dg.mortar # Upwind SBP D^- derivative operator - @unpack f_plus_threaded, f_minus_threaded = 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_threaded, f_plus_threaded = cache @unpack splitting = volume_integral # SBP operators from SummationByPartsOperators.jl implement the basic interface @@ -165,13 +121,13 @@ function calc_volume_integral!(du, u, # 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_plus_element = f_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_plus_element = splitting(u_element, Val{:plus}(), 1, equations) @. f_minus_element = splitting(u_element, Val{:minus}(), 1, equations) + @. f_plus_element = splitting(u_element, Val{:plus}(), 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))) @@ -180,8 +136,8 @@ function calc_volume_integral!(du, u, end # y direction - @. f_plus_element = splitting(u_element, Val{:plus}(), 2, equations) @. f_minus_element = splitting(u_element, Val{:minus}(), 2, equations) + @. f_plus_element = splitting(u_element, Val{:plus}(), 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))) @@ -190,8 +146,8 @@ function calc_volume_integral!(du, u, end # z direction - @. f_plus_element = splitting(u_element, Val{:plus}(), 3, equations) @. f_minus_element = splitting(u_element, Val{:minus}(), 3, equations) + @. f_plus_element = splitting(u_element, Val{:plus}(), 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))) @@ -261,6 +217,52 @@ function calc_surface_integral!(du, u, mesh::TreeMesh{3}, 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 infomation into right-traveling and +# left traveling information. So we only need to compute the approriate +# flux information at each side of an interface. +function calc_interface_flux!(surface_flux_values, + mesh::TreeMesh{3}, + nonconservative_terms::Val{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 approriate 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 + # 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 @@ -384,4 +386,5 @@ function calc_error_norms(func, u, t, analyzer, return l2_error, linf_error end + end # @muladd diff --git a/test/test_tree_1d_fdsbp.jl b/test/test_tree_1d_fdsbp.jl index 5135c9d911e..7cb9925125f 100644 --- a/test/test_tree_1d_fdsbp.jl +++ b/test/test_tree_1d_fdsbp.jl @@ -32,12 +32,12 @@ end tspan = (0.0, 0.5)) end - @trixi_testset "elixir_euler_convergence.jl with vanleer_haenel_splitting" begin + @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 = vanleer_haenel_splitting) + flux_splitting = splitting_vanleer_haenel) end @trixi_testset "elixir_euler_density_wave.jl" begin diff --git a/test/test_tree_2d_fdsbp.jl b/test/test_tree_2d_fdsbp.jl index daaec6ebd05..72ef7b91328 100644 --- a/test/test_tree_2d_fdsbp.jl +++ b/test/test_tree_2d_fdsbp.jl @@ -25,8 +25,8 @@ end tspan=(0.0, 0.1)) end - @trixi_testset "elixir_euler_khi.jl" begin - @test_trixi_include(joinpath(EXAMPLES_DIR, "elixir_euler_khi.jl"), + @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)) From ce580171ff0c8a985d2f7df8745a16b2840f7543 Mon Sep 17 00:00:00 2001 From: Andrew Winters Date: Tue, 29 Nov 2022 12:57:17 +0100 Subject: [PATCH 3/7] Fix typos in Upwind solver (#1281) * typo and spacing fixes * forgot the comment in the vortex elixir * add Experimental implemenation to upwind Volume and Surface integral --- examples/tree_2d_fdsbp/elixir_euler_vortex.jl | 4 ++-- src/equations/compressible_euler_2d.jl | 2 +- src/equations/inviscid_burgers_1d.jl | 1 - src/solvers/dg.jl | 6 ++++++ src/solvers/fdsbp_tree/fdsbp_1d.jl | 5 +++-- src/solvers/fdsbp_tree/fdsbp_2d.jl | 5 +++-- src/solvers/fdsbp_tree/fdsbp_3d.jl | 5 +++-- 7 files changed, 18 insertions(+), 10 deletions(-) diff --git a/examples/tree_2d_fdsbp/elixir_euler_vortex.jl b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl index 450a360671a..af1d6b9b2cb 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_vortex.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl @@ -20,8 +20,8 @@ The classical isentropic vortex test case of """ function initial_condition_isentropic_vortex(x, t, equations::CompressibleEulerEquations2D) # needs appropriate mesh size, e.g. [-10,-10]x[10,10] - # for error convergence: make sure that the end time such, that the is back at the initial state!! - # for the current vel and domain size: t_end should be a multiple of 20s + # for error convergence: make sure that the end time is such that the is back at the initial state!! + # for the current velocity and domain size: t_end should be a multiple of 20s # initial center of the vortex inicenter = SVector(0.0, 0.0) # size and strength of the vortex diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index 072da58ac4f..0dfbc609d2a 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -912,7 +912,7 @@ end a = sqrt(equations.gamma * p / rho) H = (rho_e + p) / rho - lambda = 0.5 * (sqrt(v1^2+v2^2) + a) + lambda = 0.5 * (sqrt(v1^2 + v2^2) + a) if orientation == 1 #lambda = 0.5 * (abs(v1) + a) diff --git a/src/equations/inviscid_burgers_1d.jl b/src/equations/inviscid_burgers_1d.jl index 90ab5b0c65c..e440dcde83e 100644 --- a/src/equations/inviscid_burgers_1d.jl +++ b/src/equations/inviscid_burgers_1d.jl @@ -155,7 +155,6 @@ end equations::InviscidBurgersEquation1D) f = 0.5 * u[1]^2 lambda = abs(u[1]) - return SVector(0.5 * (f - lambda * u[1])) end diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 575aeea754f..5e28b1db1e1 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -146,6 +146,9 @@ See also [`splitting_steger_warming`](@ref), [`splitting_lax_friedrichs`](@ref), - 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" + This is an experimental feature and may change in future releases. """ struct VolumeIntegralUpwind{FluxSplitting} <: AbstractVolumeIntegral splitting::FluxSplitting @@ -289,6 +292,9 @@ that use a particular flux `splitting`, e.g., [`splitting_steger_warming`](@ref). See also [`VolumeIntegralUpwind`](@ref). + +!!! warning "Experimental implementation" + This is an experimental feature and may change in future releases. """ struct SurfaceIntegralUpwind{FluxSplitting} <: AbstractSurfaceIntegral splitting::FluxSplitting diff --git a/src/solvers/fdsbp_tree/fdsbp_1d.jl b/src/solvers/fdsbp_tree/fdsbp_1d.jl index 7fe84b13a14..49db28c60ee 100644 --- a/src/solvers/fdsbp_tree/fdsbp_1d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_1d.jl @@ -152,7 +152,7 @@ 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 infomation into right-traveling and -# left traveling information. So we only need to compute the approriate +# 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}, @@ -181,7 +181,7 @@ function calc_interface_flux!(surface_flux_values, 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 approriate side of the elements + # 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] @@ -191,6 +191,7 @@ function calc_interface_flux!(surface_flux_values, 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 diff --git a/src/solvers/fdsbp_tree/fdsbp_2d.jl b/src/solvers/fdsbp_tree/fdsbp_2d.jl index 386ce7dbc28..5c626cd11c4 100644 --- a/src/solvers/fdsbp_tree/fdsbp_2d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_2d.jl @@ -189,7 +189,7 @@ 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 infomation into right-traveling and -# left traveling information. So we only need to compute the approriate +# 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}, @@ -220,7 +220,7 @@ function calc_interface_flux!(surface_flux_values, 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 approriate side of the elements + # 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] @@ -231,6 +231,7 @@ function calc_interface_flux!(surface_flux_values, 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 diff --git a/src/solvers/fdsbp_tree/fdsbp_3d.jl b/src/solvers/fdsbp_tree/fdsbp_3d.jl index a82b5c8f1c3..67a4ef5c7d6 100644 --- a/src/solvers/fdsbp_tree/fdsbp_3d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_3d.jl @@ -220,7 +220,7 @@ 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 infomation into right-traveling and -# left traveling information. So we only need to compute the approriate +# 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}, @@ -252,7 +252,7 @@ function calc_interface_flux!(surface_flux_values, 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 approriate side of the elements + # 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] @@ -263,6 +263,7 @@ function calc_interface_flux!(surface_flux_values, 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 From 9f0c738122076fd475f1084418dede1889b0ab48 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Tue, 29 Nov 2022 14:38:30 +0100 Subject: [PATCH 4/7] upwind flux using flux vector splitting (#1278) * improve some docstrings (#1274) * upwind flux using flux vector splitting * more tests * fix tests * fix tests --- src/Trixi.jl | 3 ++- src/equations/numerical_fluxes.jl | 24 ++++++++++++++++++++++++ test/test_tree_1d_fdsbp.jl | 20 +++++++++++++++++++- test/test_tree_2d_fdsbp.jl | 13 ++++++++++--- test/test_tree_3d_fdsbp.jl | 8 ++++++++ 5 files changed, 63 insertions(+), 5 deletions(-) diff --git a/src/Trixi.jl b/src/Trixi.jl index d3e786ee438..0de19e38f4b 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -153,7 +153,8 @@ export flux, flux_central, flux_lax_friedrichs, flux_hll, flux_hllc, flux_hlle, FluxLMARS, FluxRotated, flux_shima_etal_turbo, flux_ranocha_turbo, - FluxHydrostaticReconstruction + FluxHydrostaticReconstruction, + FluxUpwind export splitting_steger_warming, splitting_vanleer_haenel, splitting_coirier_vanleer, splitting_lax_friedrichs diff --git a/src/equations/numerical_fluxes.jl b/src/equations/numerical_fluxes.jl index 632b4a6d1a3..9718ef433ac 100644 --- a/src/equations/numerical_fluxes.jl +++ b/src/equations/numerical_fluxes.jl @@ -318,4 +318,28 @@ end 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 analytically +equivalent to the [`SurfaceIntegralStrongForm`](@ref) with `FluxUpwind(splitting)` +as numerical flux. +""" +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 + +Base.show(io::IO, f::FluxUpwind) = print(io, "FluxUpwind(", f.splitting, ")") + + end # @muladd diff --git a/test/test_tree_1d_fdsbp.jl b/test/test_tree_1d_fdsbp.jl index 7cb9925125f..7733e8216c9 100644 --- a/test/test_tree_1d_fdsbp.jl +++ b/test/test_tree_1d_fdsbp.jl @@ -16,11 +16,21 @@ EXAMPLES_DIR = joinpath(pathof(Trixi) |> dirname |> dirname, "examples", "tree_1 tspan = (0.0, 0.5)) 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))) + 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)) + tspan = (0.0, 0.25)) end end @@ -40,6 +50,14 @@ end flux_splitting = splitting_vanleer_haenel) 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())) + 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], diff --git a/test/test_tree_2d_fdsbp.jl b/test/test_tree_2d_fdsbp.jl index 72ef7b91328..0f4b9c9f096 100644 --- a/test/test_tree_2d_fdsbp.jl +++ b/test/test_tree_2d_fdsbp.jl @@ -22,21 +22,28 @@ end @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)) + tspan = (0.0, 0.1)) + 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) 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)) + tspan = (0.0, 0.1)) 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)) + tspan = (0.0, 0.25)) end end diff --git a/test/test_tree_3d_fdsbp.jl b/test/test_tree_3d_fdsbp.jl index 1b65f5a9588..2072e88c848 100644 --- a/test/test_tree_3d_fdsbp.jl +++ b/test/test_tree_3d_fdsbp.jl @@ -16,6 +16,14 @@ EXAMPLES_DIR = joinpath(pathof(Trixi) |> dirname |> dirname, "examples", "tree_3 tspan = (0.0, 0.2)) 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())) + 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], From 545783c8b3d2ac553be8eb7083cfd3c6bdf76b6f Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Wed, 30 Nov 2022 13:39:40 +0100 Subject: [PATCH 5/7] Upwind FDSBP performance improvements (#1282) * improve some docstrings (#1274) * fix convergence_test for elixirs without trailing new line (#1280) * Fix `initial_condition_isentropic_vortex` (#1279) * strengthen the isentropic vortex initial conditions in TreeMesh elixirs * update test values for new isentropic vortex initial conditions * update vortex initial condition in special elixir and docs * fix typos in the IC * update tree_mpi test values * remove comment lines because them seem to break literate Co-authored-by: Michael Schlottke-Lakemper * improve performance of 3D volume integral with steger_warming_splitting by ca. 1/4 for Taylor-Green (serial) * lose 2 percent of threaded volume terms performance but simplify code and reduce duplications * remove flux_upwind stub * 2D and 1D as well; improvement ca. 20% in 3D, 10% in 2D, not much in 1D * positive_part, negative_part for 3D Steger-Warming; improves serial PID of TGV by ca. 10% * positive_part, negative_part for 2D, 1D * simplify CSE for LLVM (ca. 2 % for TGV) * comments on f_minus_plus etc. * improve comment * comments on positive/negative part Co-authored-by: Andrew Winters Co-authored-by: Michael Schlottke-Lakemper --- src/auxiliary/math.jl | 22 ++++ src/equations/compressible_euler_1d.jl | 81 +++++++++---- src/equations/compressible_euler_2d.jl | 158 +++++++++++++------------ src/equations/compressible_euler_3d.jl | 135 +++++++++++---------- src/equations/inviscid_burgers_1d.jl | 17 ++- src/solvers/dg.jl | 84 +++++++------ src/solvers/fdsbp_tree/fdsbp_1d.jl | 28 +++-- src/solvers/fdsbp_tree/fdsbp_2d.jl | 31 +++-- src/solvers/fdsbp_tree/fdsbp_3d.jl | 34 ++++-- 9 files changed, 350 insertions(+), 240 deletions(-) diff --git a/src/auxiliary/math.jl b/src/auxiliary/math.jl index 47b78ee3f64..2e350b51036 100644 --- a/src/auxiliary/math.jl +++ b/src/auxiliary/math.jl @@ -192,5 +192,27 @@ julia> min(2, 5, 1) +""" + 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) + o = zero(x) + return ifelse(x > o, x, o) +end + +""" + 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) + o = zero(x) + return ifelse(x < o, x, o) +end + end # @muladd diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 2aeb484102d..05879e62eee 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -361,16 +361,18 @@ 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 the flux "minus" (associated with waves going into the -negative axis direction) or "plus" (associated with waves going into the -positive axis direction), determined by the argument `which` set to -`Val{:minus}()` or `Val{:plus}`. +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}`. ## References @@ -379,6 +381,13 @@ positive axis direction), determined by the argument `which` set to 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 @@ -390,16 +399,17 @@ positive axis direction), determined by the argument `which` set to lambda2 = v1 + a lambda3 = v1 - a - lambda1_p = 0.5 * (lambda1 + abs(lambda1)) - lambda2_p = 0.5 * (lambda2 + abs(lambda2)) - lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + 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 - f1p = 0.5 * rho / equations.gamma * alpha_p - f2p = 0.5 * rho / equations.gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) - f3p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * v1^2 + a * v1 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) + rho_2gamma = 0.5 * 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.5 * 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 @@ -415,22 +425,25 @@ end lambda2 = v1 + a lambda3 = v1 - a - lambda1_m = 0.5 * (lambda1 - abs(lambda1)) - lambda2_m = 0.5 * (lambda2 - abs(lambda2)) - lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + 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 - f1m = 0.5 * rho / equations.gamma * alpha_m - f2m = 0.5 * rho / equations.gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) - f3m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * v1^2 + a * v1 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) + rho_2gamma = 0.5 * 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.5 * 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) @@ -442,10 +455,10 @@ 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 the flux "minus" (associated with waves going into the -negative axis direction) or "plus" (associated with waves going into the -positive axis direction), determined by the argument `which` set to -`Val{:minus}()` or `Val{:plus}`. +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}`. ## References @@ -459,6 +472,13 @@ positive axis direction), determined by the argument `which` set to 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 @@ -515,6 +535,8 @@ end # 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) @@ -524,10 +546,10 @@ 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 the flux "minus" (associated with waves going into the -negative axis direction) or "plus" (associated with waves going into the -positive axis direction), determined by the argument `which` set to -`Val{:minus}()` or `Val{:plus}`. +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}`. ## References @@ -536,6 +558,13 @@ positive axis direction), determined by the argument `which` set to 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 diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index 0dfbc609d2a..cd4085b3b80 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -665,16 +665,18 @@ 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. -Returns the flux "minus" (associated with waves going into the -negative axis direction) or "plus" (associated with waves going into the -positive axis direction), determined by the argument `which` set to -`Val{:minus}()` or `Val{:plus}`. +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}`. ## References @@ -683,6 +685,13 @@ positive axis direction), determined by the argument `which` set to 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 @@ -696,33 +705,35 @@ positive axis direction), determined by the argument `which` set to lambda2 = v1 + a lambda3 = v1 - a - lambda1_p = 0.5 * (lambda1 + abs(lambda1)) - lambda2_p = 0.5 * (lambda2 + abs(lambda2)) - lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + 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 - f1p = 0.5 * rho / equations.gamma * alpha_p - f2p = 0.5 * rho / equations.gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) - f3p = 0.5 * rho / equations.gamma * alpha_p * v2 - f4p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2) + a * v1 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) - else + rho_2gamma = 0.5 * 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.5 * (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 = 0.5 * (lambda1 + abs(lambda1)) - lambda2_p = 0.5 * (lambda2 + abs(lambda2)) - lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + 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 - f1p = 0.5 * rho / equations.gamma * alpha_p - f2p = 0.5 * rho / equations.gamma * alpha_p * v1 - f3p = 0.5 * rho / equations.gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) - f4p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2) + a * v2 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) + rho_2gamma = 0.5 * 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.5 * (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 @@ -740,39 +751,43 @@ end lambda2 = v1 + a lambda3 = v1 - a - lambda1_m = 0.5 * (lambda1 - abs(lambda1)) - lambda2_m = 0.5 * (lambda2 - abs(lambda2)) - lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + 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 - f1m = 0.5 * rho / equations.gamma * alpha_m - f2m = 0.5 * rho / equations.gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) - f3m = 0.5 * rho / equations.gamma * alpha_m * v2 - f4m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2) + a * v1 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) - else + rho_2gamma = 0.5 * 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.5 * (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 = 0.5 * (lambda1 - abs(lambda1)) - lambda2_m = 0.5 * (lambda2 - abs(lambda2)) - lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + 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 - f1m = 0.5 * rho / equations.gamma * alpha_m - f2m = 0.5 * rho / equations.gamma * alpha_m * v1 - f3m = 0.5 * rho / equations.gamma * (alpha_m * v2 + a * (lambda2_m-lambda3_m)) - f4m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2) + a * v2 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) + rho_2gamma = 0.5 * 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.5 * (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_vanleer_haenel(u, orientation::Integer, + equations::CompressibleEulerEquations2D) splitting_vanleer_haenel(u, which::Union{Val{:minus}, Val{:plus}} orientation::Integer, equations::CompressibleEulerEquations2D) @@ -784,10 +799,10 @@ 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 the flux "minus" (associated with waves going into the -negative axis direction) or "plus" (associated with waves going into the -positive axis direction), determined by the argument `which` set to -`Val{:minus}()` or `Val{:plus}`. +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}`. ## References @@ -801,6 +816,13 @@ positive axis direction), determined by the argument `which` set to 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::CompressibleEulerEquations2D) + 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::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u @@ -819,7 +841,7 @@ positive axis direction), determined by the argument `which` set to f2p = f1p * v1 + p_plus f3p = f1p * v2 f4p = f1p * H - else + else # orientation == 2 M = v2 / a p_plus = 0.5 * (1 + equations.gamma * M) * p @@ -849,7 +871,7 @@ end f2m = f1m * v1 + p_minus f3m = f1m * v2 f4m = f1m * H - else + else # orientation == 2 M = v2 / a p_minus = 0.5 * (1 - equations.gamma * M) * p @@ -863,6 +885,8 @@ end """ + splitting_lax_friedrichs(u, orientation::Integer, + equations::CompressibleEulerEquations2D) splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} orientation::Integer, equations::CompressibleEulerEquations2D) @@ -871,11 +895,18 @@ Naive local Lax-Friedrichs style flux splitting of the form `f⁺ = 0.5 (f + λ and `f⁻ = 0.5 (f - λ u)` similar to a flux splitting one would apply, e.g., to Burgers' equation. -Returns the flux "minus" (associated with waves going into the -negative axis direction) or "plus" (associated with waves going into the -positive axis direction), determined by the argument `which` set to -`Val{:minus}()` or `Val{:plus}`. +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}`. """ +@inline function splitting_lax_friedrichs(u, orientation::Integer, + equations::CompressibleEulerEquations2D) + 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::CompressibleEulerEquations2D) rho, rho_v1, rho_v2, rho_e = u @@ -893,7 +924,7 @@ positive axis direction), determined by the argument `which` set to f2p = 0.5 * rho * v1 * v1 + 0.5 * p + lambda * u[2] f3p = 0.5 * rho * v1 * v2 + lambda * u[3] f4p = 0.5 * rho * v1 * H + lambda * u[4] - else + else # orientation == 2 #lambda = 0.5 * (abs(v2) + a) f1p = 0.5 * rho * v2 + lambda * u[1] f2p = 0.5 * rho * v2 * v1 + lambda * u[2] @@ -920,7 +951,7 @@ end f2m = 0.5 * rho * v1 * v1 + 0.5 * p - lambda * u[2] f3m = 0.5 * rho * v1 * v2 - lambda * u[3] f4m = 0.5 * rho * v1 * H - lambda * u[4] - else + else # orientation == 2 #lambda = 0.5 * (abs(v2) + a) f1m = 0.5 * rho * v2 - lambda * u[1] f2m = 0.5 * rho * v2 * v1 - lambda * u[2] @@ -1231,33 +1262,6 @@ function flux_hlle(u_ll, u_rr, orientation::Integer, equations::CompressibleEule end -# """ -# flux_upwind(u_ll, u_rr, orientation, equations::CompressibleEulerEquations2D) - -# Fully upwind SAT coupling but written in the style of the strong form DG type method. -# Should be used together with [SurfaceIntegralStrongForm](@ref) and a finite difference -# summation-by-parts (FDSBP) solver. - -# TODO: FD. reference? Maybe van Leer paper "...for the 90s" -# """ -# # TODO: FD. Do we want this? -# @inline function flux_upwind(u_ll, u_rr, orientation::Integer, equations::CompressibleEulerEquations2D) - -# # Compute the upwind coupling terms with right-traveling from the left -# # and left-traveling information from the right -# # TODO: How to make this easier to switch out other splittings? -# f_plus_ll = splitting_steger_warming(u_ll, :plus, orientation, equations) -# f_minus_rr = splitting_steger_warming(u_rr, :minus, orientation, equations) - -# # Combine the upwind terms to pass back as a type of numerical flux -# f1 = f_plus_ll[1] + f_minus_rr[1] -# f2 = f_plus_ll[2] + f_minus_rr[2] -# f3 = f_plus_ll[3] + f_minus_rr[3] -# f4 = f_plus_ll[4] + f_minus_rr[4] -# return SVector(f1, f2, f3, f4) -# end - - @inline function max_abs_speeds(u, equations::CompressibleEulerEquations2D) rho, v1, v2, p = cons2prim(u, equations) c = sqrt(equations.gamma * p / rho) diff --git a/src/equations/compressible_euler_3d.jl b/src/equations/compressible_euler_3d.jl index 0c6deb9463d..ebf6322f107 100644 --- a/src/equations/compressible_euler_3d.jl +++ b/src/equations/compressible_euler_3d.jl @@ -681,16 +681,18 @@ 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 the flux "minus" (associated with waves going into the -negative axis direction) or "plus" (associated with waves going into the -positive axis direction), determined by the argument `which` set to -`Val{:minus}()` or `Val{:plus}`. +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}`. ## References @@ -699,6 +701,13 @@ positive axis direction), determined by the argument `which` set to 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 @@ -713,52 +722,55 @@ positive axis direction), determined by the argument `which` set to lambda2 = v1 + a lambda3 = v1 - a - lambda1_p = 0.5 * (lambda1 + abs(lambda1)) - lambda2_p = 0.5 * (lambda2 + abs(lambda2)) - lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + 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 - f1p = 0.5 * rho / equations.gamma * alpha_p - f2p = 0.5 * rho / equations.gamma * (alpha_p * v1 + a * (lambda2_p - lambda3_p)) - f3p = 0.5 * rho / equations.gamma * alpha_p * v2 - f4p = 0.5 * rho / equations.gamma * alpha_p * v3 - f5p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2 + v3^2) + a * v1 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) + rho_2gamma = 0.5 * 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.5 * (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 = 0.5 * (lambda1 + abs(lambda1)) - lambda2_p = 0.5 * (lambda2 + abs(lambda2)) - lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + 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 - f1p = 0.5 * rho / equations.gamma * alpha_p - f2p = 0.5 * rho / equations.gamma * alpha_p * v1 - f3p = 0.5 * rho / equations.gamma * (alpha_p * v2 + a * (lambda2_p - lambda3_p)) - f4p = 0.5 * rho / equations.gamma * alpha_p * v3 - f5p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2 + v3^2) + a * v2 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) - else + rho_2gamma = 0.5 * 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.5 * (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 = 0.5 * (lambda1 + abs(lambda1)) - lambda2_p = 0.5 * (lambda2 + abs(lambda2)) - lambda3_p = 0.5 * (lambda3 + abs(lambda3)) + 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 - f1p = 0.5 * rho / equations.gamma * alpha_p - f2p = 0.5 * rho / equations.gamma * alpha_p * v1 - f3p = 0.5 * rho / equations.gamma * alpha_p * v2 - f4p = 0.5 * rho / equations.gamma * (alpha_p * v3 + a * (lambda2_p - lambda3_p)) - f5p = 0.5 * rho / equations.gamma * (alpha_p * 0.5 * (v1^2 + v2^2 + v3^2) + a * v3 * (lambda2_p - lambda3_p) - + a^2 * (lambda2_p + lambda3_p) * equations.inv_gamma_minus_one) + rho_2gamma = 0.5 * 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.5 * (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 @@ -777,52 +789,55 @@ end lambda2 = v1 + a lambda3 = v1 - a - lambda1_m = 0.5 * (lambda1 - abs(lambda1)) - lambda2_m = 0.5 * (lambda2 - abs(lambda2)) - lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + 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 - f1m = 0.5 * rho / equations.gamma * alpha_m - f2m = 0.5 * rho / equations.gamma * (alpha_m * v1 + a * (lambda2_m - lambda3_m)) - f3m = 0.5 * rho / equations.gamma * alpha_m * v2 - f4m = 0.5 * rho / equations.gamma * alpha_m * v3 - f5m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2 + v3^2) + a * v1 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) + rho_2gamma = 0.5 * 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.5 * (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 = 0.5 * (lambda1 - abs(lambda1)) - lambda2_m = 0.5 * (lambda2 - abs(lambda2)) - lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + 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 - f1m = 0.5 * rho / equations.gamma * alpha_m - f2m = 0.5 * rho / equations.gamma * alpha_m * v1 - f3m = 0.5 * rho / equations.gamma * (alpha_m * v2 + a * (lambda2_m - lambda3_m)) - f4m = 0.5 * rho / equations.gamma * alpha_m * v3 - f5m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2 + v3^2) + a * v2 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) - else + rho_2gamma = 0.5 * 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.5 * (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 = 0.5 * (lambda1 - abs(lambda1)) - lambda2_m = 0.5 * (lambda2 - abs(lambda2)) - lambda3_m = 0.5 * (lambda3 - abs(lambda3)) + 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 - f1m = 0.5 * rho / equations.gamma * alpha_m - f2m = 0.5 * rho / equations.gamma * alpha_m * v1 - f3m = 0.5 * rho / equations.gamma * alpha_m * v2 - f4m = 0.5 * rho / equations.gamma * (alpha_m * v3 + a * (lambda2_m - lambda3_m)) - f5m = 0.5 * rho / equations.gamma * (alpha_m * 0.5 * (v1^2 + v2^2 + v3^2) + a * v3 * (lambda2_m - lambda3_m) - + a^2 * (lambda2_m + lambda3_m) * equations.inv_gamma_minus_one) + rho_2gamma = 0.5 * 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.5 * (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 diff --git a/src/equations/inviscid_burgers_1d.jl b/src/equations/inviscid_burgers_1d.jl index e440dcde83e..6fec8f533c7 100644 --- a/src/equations/inviscid_burgers_1d.jl +++ b/src/equations/inviscid_burgers_1d.jl @@ -132,6 +132,8 @@ end """ + splitting_lax_friedrichs(u, orientation::Integer, + equations::InviscidBurgersEquation1D) splitting_lax_friedrichs(u, which::Union{Val{:minus}, Val{:plus}} orientation::Integer, equations::InviscidBurgersEquation1D) @@ -139,11 +141,18 @@ end 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 the flux "minus" (associated with waves going into the -negative axis direction) or "plus" (associated with waves going into the -positive axis direction), determined by the argument `which` set to -`Val{:minus}()` or `Val{:plus}`. +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}`. """ +@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.5 * u[1]^2 diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 5e28b1db1e1..06599f17e03 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -127,44 +127,11 @@ function Base.show(io::IO, mime::MIME"text/plain", integral::VolumeIntegralShock end 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" - 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 +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 @@ -205,13 +172,44 @@ function Base.show(io::IO, ::MIME"text/plain", integral::VolumeIntegralPureLGLFi 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) +# 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" + 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 diff --git a/src/solvers/fdsbp_tree/fdsbp_1d.jl b/src/solvers/fdsbp_tree/fdsbp_1d.jl index 49db28c60ee..8e1f1ef9669 100644 --- a/src/solvers/fdsbp_tree/fdsbp_1d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_1d.jl @@ -19,12 +19,20 @@ end function create_cache(mesh::TreeMesh{1}, equations, volume_integral::VolumeIntegralUpwind, dg, uEltype) - prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( - undef, ntuple(_ -> nnodes(dg), ndims(mesh))...) - f_minus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] - f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + 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_threaded, f_plus_threaded,) + return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded,) end @@ -84,7 +92,7 @@ function calc_volume_integral!(du, u, # 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_threaded, f_plus_threaded = cache + @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 @@ -105,13 +113,17 @@ function calc_volume_integral!(du, u, # 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_element = splitting(u_element, Val{:minus}(), 1, equations) - @. f_plus_element = splitting(u_element, Val{:plus}(), 1, equations) + @. 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, :), diff --git a/src/solvers/fdsbp_tree/fdsbp_2d.jl b/src/solvers/fdsbp_tree/fdsbp_2d.jl index 5c626cd11c4..097b3e3f6df 100644 --- a/src/solvers/fdsbp_tree/fdsbp_2d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_2d.jl @@ -19,12 +19,20 @@ end function create_cache(mesh::TreeMesh{2}, equations, volume_integral::VolumeIntegralUpwind, dg, uEltype) - prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( - undef, ntuple(_ -> nnodes(dg), ndims(mesh))...) - f_minus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] - f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + 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_threaded, f_plus_threaded,) + return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded,) end @@ -93,7 +101,7 @@ function calc_volume_integral!(du, u, # 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_threaded, f_plus_threaded = cache + @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 @@ -114,13 +122,17 @@ function calc_volume_integral!(du, u, # 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_element = splitting(u_element, Val{:minus}(), 1, equations) - @. f_plus_element = splitting(u_element, Val{:plus}(), 1, equations) + @. 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))) @@ -129,8 +141,7 @@ function calc_volume_integral!(du, u, end # y direction - @. f_minus_element = splitting(u_element, Val{:minus}(), 2, equations) - @. f_plus_element = splitting(u_element, Val{:plus}(), 2, equations) + @. 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))) diff --git a/src/solvers/fdsbp_tree/fdsbp_3d.jl b/src/solvers/fdsbp_tree/fdsbp_3d.jl index 67a4ef5c7d6..e6a537ee2bc 100644 --- a/src/solvers/fdsbp_tree/fdsbp_3d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_3d.jl @@ -19,12 +19,20 @@ end function create_cache(mesh::TreeMesh{3}, equations, volume_integral::VolumeIntegralUpwind, dg, uEltype) - prototype = Array{SVector{nvariables(equations), uEltype}, ndims(mesh)}( - undef, ntuple(_ -> nnodes(dg), ndims(mesh))...) - f_minus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] - f_plus_threaded = [similar(prototype) for _ in 1:Threads.nthreads()] + 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_threaded, f_plus_threaded,) + return (; f_minus_plus_threaded, f_minus_threaded, f_plus_threaded,) end @@ -100,7 +108,7 @@ function calc_volume_integral!(du, u, # 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_threaded, f_plus_threaded = cache + @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 @@ -121,13 +129,17 @@ function calc_volume_integral!(du, u, # 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_element = splitting(u_element, Val{:minus}(), 1, equations) - @. f_plus_element = splitting(u_element, Val{:plus}(), 1, equations) + @. 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))) @@ -136,8 +148,7 @@ function calc_volume_integral!(du, u, end # y direction - @. f_minus_element = splitting(u_element, Val{:minus}(), 2, equations) - @. f_plus_element = splitting(u_element, Val{:plus}(), 2, equations) + @. 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))) @@ -146,8 +157,7 @@ function calc_volume_integral!(du, u, end # z direction - @. f_minus_element = splitting(u_element, Val{:minus}(), 3, equations) - @. f_plus_element = splitting(u_element, Val{:plus}(), 3, equations) + @. 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))) From 16385b3d4ddffc94f77a967aa2813eec4621e446 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Fri, 2 Dec 2022 12:45:22 +0100 Subject: [PATCH 6/7] Upwind FDSBP cleanup, part 2 (#1285) * implementation of positive/negative part preferred by Michael Co-authored-by: Michael Schlottke-Lakemper * more experimental warnings * let Julia handle promotion of numbers * improve docstring of FluxUpwind * FDSBP constructor * use D_upw as canonical name in examples Co-authored-by: Michael Schlottke-Lakemper --- .../tree_1d_fdsbp/elixir_burgers_basic.jl | 11 ++++---- .../elixir_burgers_linear_stability.jl | 11 ++++---- .../tree_1d_fdsbp/elixir_euler_convergence.jl | 11 ++++---- .../elixir_euler_density_wave.jl | 11 ++++---- .../elixir_advection_extended.jl | 11 ++++---- .../tree_2d_fdsbp/elixir_euler_convergence.jl | 21 +++++++-------- ...ixir_euler_kelvin_helmholtz_instability.jl | 11 ++++---- examples/tree_2d_fdsbp/elixir_euler_vortex.jl | 11 ++++---- .../tree_3d_fdsbp/elixir_euler_convergence.jl | 11 ++++---- .../elixir_euler_taylor_green_vortex.jl | 11 ++++---- src/Trixi.jl | 1 + src/auxiliary/math.jl | 6 ++--- src/equations/compressible_euler_1d.jl | 9 +++++++ src/equations/compressible_euler_2d.jl | 15 ++++++++--- src/equations/compressible_euler_3d.jl | 9 ++++--- src/equations/inviscid_burgers_1d.jl | 3 +++ src/equations/numerical_fluxes.jl | 9 ++++--- src/solvers/dg.jl | 4 +-- src/solvers/fdsbp_tree/fdsbp.jl | 27 +++++++++++++++++-- src/solvers/fdsbp_tree/fdsbp_1d.jl | 3 +++ src/solvers/fdsbp_tree/fdsbp_2d.jl | 11 +++----- src/solvers/fdsbp_tree/fdsbp_3d.jl | 3 +++ 22 files changed, 130 insertions(+), 90 deletions(-) diff --git a/examples/tree_1d_fdsbp/elixir_burgers_basic.jl b/examples/tree_1d_fdsbp/elixir_burgers_basic.jl index 2216546bb4c..e7008a520a4 100644 --- a/examples/tree_1d_fdsbp/elixir_burgers_basic.jl +++ b/examples/tree_1d_fdsbp/elixir_burgers_basic.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -18,9 +17,9 @@ D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, xmin=-1.0, xmax=1.0, N=32) flux_splitting = splitting_lax_friedrichs -solver = DG(D_upw, nothing #= mortar =#, - SurfaceIntegralUpwind(flux_splitting), - 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 diff --git a/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl b/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl index 58cbad39760..e5596063eba 100644 --- a/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl +++ b/examples/tree_1d_fdsbp/elixir_burgers_linear_stability.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -21,9 +20,9 @@ D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, xmin=-1.0, xmax=1.0, N=16) flux_splitting = splitting_lax_friedrichs -solver = DG(D_upw, nothing #= mortar =#, - SurfaceIntegralUpwind(flux_splitting), - 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 diff --git a/examples/tree_1d_fdsbp/elixir_euler_convergence.jl b/examples/tree_1d_fdsbp/elixir_euler_convergence.jl index 4bb2700121b..34717b2c52f 100644 --- a/examples/tree_1d_fdsbp/elixir_euler_convergence.jl +++ b/examples/tree_1d_fdsbp/elixir_euler_convergence.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -18,9 +17,9 @@ D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, xmin=-1.0, xmax=1.0, N=32) flux_splitting = splitting_steger_warming -solver = DG(D_upw, nothing #= mortar =#, - SurfaceIntegralUpwind(flux_splitting), - 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 diff --git a/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl b/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl index f70003e37e7..a11afaf78ff 100644 --- a/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl +++ b/examples/tree_1d_fdsbp/elixir_euler_density_wave.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -17,9 +16,9 @@ D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, xmin=-1.0, xmax=1.0, N=16) flux_splitting = splitting_coirier_vanleer -solver = DG(D_upw, nothing #= mortar =#, - SurfaceIntegralUpwind(flux_splitting), - 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 diff --git a/examples/tree_2d_fdsbp/elixir_advection_extended.jl b/examples/tree_2d_fdsbp/elixir_advection_extended.jl index bf46714dd34..d20894c278c 100644 --- a/examples/tree_2d_fdsbp/elixir_advection_extended.jl +++ b/examples/tree_2d_fdsbp/elixir_advection_extended.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -16,9 +15,9 @@ 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 = DG(D_SBP, nothing #= mortar =#, - SurfaceIntegralStrongForm(flux_lax_friedrichs), - VolumeIntegralStrongForm()) +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) diff --git a/examples/tree_2d_fdsbp/elixir_euler_convergence.jl b/examples/tree_2d_fdsbp/elixir_euler_convergence.jl index a14bacba280..0619e89afcd 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_convergence.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_convergence.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -12,15 +11,15 @@ equations = CompressibleEulerEquations2D(1.4) initial_condition = initial_condition_convergence_test source_terms = source_terms_convergence_test -D = 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 = DG(D, nothing #= mortar =#, - SurfaceIntegralUpwind(flux_splitting), - 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) 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 2e8ff77ce47..7e86f36fa40 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_kelvin_helmholtz_instability.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_kelvin_helmholtz_instability.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -31,9 +30,9 @@ D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, xmin=-1.0, xmax=1.0, N=16) flux_splitting = splitting_vanleer_haenel -solver = DG(D_upw, nothing #= mortar =#, - SurfaceIntegralUpwind(flux_splitting), - 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) diff --git a/examples/tree_2d_fdsbp/elixir_euler_vortex.jl b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl index af1d6b9b2cb..10f6106f4f7 100644 --- a/examples/tree_2d_fdsbp/elixir_euler_vortex.jl +++ b/examples/tree_2d_fdsbp/elixir_euler_vortex.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -61,9 +60,9 @@ D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, xmin=-1.0, xmax=1.0, N=16) flux_splitting = splitting_steger_warming -solver = DG(D_upw, nothing #= mortar =#, - SurfaceIntegralUpwind(flux_splitting), - 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) diff --git a/examples/tree_3d_fdsbp/elixir_euler_convergence.jl b/examples/tree_3d_fdsbp/elixir_euler_convergence.jl index 26460f9428b..576a07e6aba 100644 --- a/examples/tree_3d_fdsbp/elixir_euler_convergence.jl +++ b/examples/tree_3d_fdsbp/elixir_euler_convergence.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -18,9 +17,9 @@ D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, xmin=-1.0, xmax=1.0, N=16) flux_splitting = splitting_steger_warming -solver = DG(D_upw, nothing #= mortar =#, - SurfaceIntegralUpwind(flux_splitting), - 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) 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 e977c80889f..a138cf94e39 100644 --- a/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl +++ b/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl @@ -1,6 +1,5 @@ -# TODO: FD -# !!! warning "Experimental feature" -# This is an experimental feature and may change in any future releases. +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. using OrdinaryDiffEq using Trixi @@ -36,9 +35,9 @@ D_upw = upwind_operators(SummationByPartsOperators.Mattsson2017, xmin=-1.0, xmax=1.0, N=16) flux_splitting = splitting_steger_warming -solver = DG(D_upw, nothing #= mortar =#, - SurfaceIntegralUpwind(flux_splitting), - 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 diff --git a/src/Trixi.jl b/src/Trixi.jl index 0de19e38f4b..d3940460063 100644 --- a/src/Trixi.jl +++ b/src/Trixi.jl @@ -189,6 +189,7 @@ export TreeMesh, StructuredMesh, UnstructuredMesh2D, P4estMesh export DG, DGSEM, LobattoLegendreBasis, + FDSBP, VolumeIntegralWeakForm, VolumeIntegralStrongForm, VolumeIntegralFluxDifferencing, VolumeIntegralPureLGLFiniteVolume, diff --git a/src/auxiliary/math.jl b/src/auxiliary/math.jl index 2e350b51036..78340c86cc3 100644 --- a/src/auxiliary/math.jl +++ b/src/auxiliary/math.jl @@ -199,8 +199,7 @@ 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) - o = zero(x) - return ifelse(x > o, x, o) + return max(x, zero(x)) end """ @@ -210,8 +209,7 @@ 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) - o = zero(x) - return ifelse(x < o, x, o) + return min(x, zero(x)) end diff --git a/src/equations/compressible_euler_1d.jl b/src/equations/compressible_euler_1d.jl index 05879e62eee..49481d658cc 100644 --- a/src/equations/compressible_euler_1d.jl +++ b/src/equations/compressible_euler_1d.jl @@ -374,6 +374,9 @@ 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) @@ -460,6 +463,9 @@ 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) @@ -551,6 +557,9 @@ 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) diff --git a/src/equations/compressible_euler_2d.jl b/src/equations/compressible_euler_2d.jl index cd4085b3b80..e4d429f701d 100644 --- a/src/equations/compressible_euler_2d.jl +++ b/src/equations/compressible_euler_2d.jl @@ -308,11 +308,11 @@ Should be used together with [`UnstructuredMesh2D`](@ref). # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) if v_normal <= 0.0 sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed - p_star = p_local * (1.0 + 0.5 * (equations.gamma - 1) * v_normal / sound_speed)^(2 * equations.gamma * equations.inv_gamma_minus_one) + p_star = p_local * (1 + 0.5 * (equations.gamma - 1) * v_normal / sound_speed)^(2 * equations.gamma * equations.inv_gamma_minus_one) else # v_normal > 0.0 - A = 2.0 / ((equations.gamma + 1) * rho_local) + A = 2 / ((equations.gamma + 1) * rho_local) B = p_local * (equations.gamma - 1) / (equations.gamma + 1) - p_star = p_local + 0.5 * v_normal / A * (v_normal + sqrt(v_normal^2 + 4.0 * A * (p_local + B))) + p_star = p_local + 0.5 * 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 @@ -678,6 +678,9 @@ 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) @@ -804,6 +807,9 @@ 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) @@ -899,6 +905,9 @@ 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::CompressibleEulerEquations2D) diff --git a/src/equations/compressible_euler_3d.jl b/src/equations/compressible_euler_3d.jl index ebf6322f107..633363023e8 100644 --- a/src/equations/compressible_euler_3d.jl +++ b/src/equations/compressible_euler_3d.jl @@ -308,11 +308,11 @@ Details about the 1D pressure Riemann solution can be found in Section 6.3.3 of # [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761) if v_normal <= 0.0 sound_speed = sqrt(equations.gamma * p_local / rho_local) # local sound speed - p_star = p_local * (1.0 + 0.5 * (equations.gamma - 1) * v_normal / sound_speed)^(2 * equations.gamma * equations.inv_gamma_minus_one) + p_star = p_local * (1 + 0.5 * (equations.gamma - 1) * v_normal / sound_speed)^(2 * equations.gamma * equations.inv_gamma_minus_one) else # v_normal > 0.0 - A = 2.0 / ((equations.gamma + 1) * rho_local) + A = 2 / ((equations.gamma + 1) * rho_local) B = p_local * (equations.gamma - 1) / (equations.gamma + 1) - p_star = p_local + 0.5 * v_normal / A * (v_normal + sqrt(v_normal^2 + 4.0 * A * (p_local + B))) + p_star = p_local + 0.5 * 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 @@ -694,6 +694,9 @@ 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) diff --git a/src/equations/inviscid_burgers_1d.jl b/src/equations/inviscid_burgers_1d.jl index 6fec8f533c7..18e2ed4600b 100644 --- a/src/equations/inviscid_burgers_1d.jl +++ b/src/equations/inviscid_burgers_1d.jl @@ -145,6 +145,9 @@ 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) diff --git a/src/equations/numerical_fluxes.jl b/src/equations/numerical_fluxes.jl index 9718ef433ac..821477e91de 100644 --- a/src/equations/numerical_fluxes.jl +++ b/src/equations/numerical_fluxes.jl @@ -324,9 +324,12 @@ end 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 analytically -equivalent to the [`SurfaceIntegralStrongForm`](@ref) with `FluxUpwind(splitting)` -as numerical flux. +The [`SurfaceIntegralUpwind`](@ref) with a given `splitting` is equivalent to +the [`SurfaceIntegralStrongForm`](@ref) with `FluxUpwind(splitting)` +as numerical flux (up to floating point differences). + +!!! warning "Experimental implementation (upwind SBP)" + This is an experimental feature and may change in future releases. """ struct FluxUpwind{Splitting} splitting::Splitting diff --git a/src/solvers/dg.jl b/src/solvers/dg.jl index 06599f17e03..d5fa15d3e1c 100644 --- a/src/solvers/dg.jl +++ b/src/solvers/dg.jl @@ -191,7 +191,7 @@ See also [`splitting_steger_warming`](@ref), [`splitting_lax_friedrichs`](@ref), 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" +!!! warning "Experimental implementation (upwind SBP)" This is an experimental feature and may change in future releases. """ struct VolumeIntegralUpwind{FluxSplitting} <: AbstractVolumeIntegral @@ -291,7 +291,7 @@ that use a particular flux `splitting`, e.g., See also [`VolumeIntegralUpwind`](@ref). -!!! warning "Experimental implementation" +!!! warning "Experimental implementation (upwind SBP)" This is an experimental feature and may change in future releases. """ struct SurfaceIntegralUpwind{FluxSplitting} <: AbstractSurfaceIntegral diff --git a/src/solvers/fdsbp_tree/fdsbp.jl b/src/solvers/fdsbp_tree/fdsbp.jl index 5180eccbac3..b89d59c1156 100644 --- a/src/solvers/fdsbp_tree/fdsbp.jl +++ b/src/solvers/fdsbp_tree/fdsbp.jl @@ -1,3 +1,6 @@ +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. + # 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. @@ -5,9 +8,29 @@ @muladd begin -# For dispatch +""" + 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. + +`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} +function FDSBP(D_SBP::AbstractDerivativeOperator; surface_integral, volume_integral) + return DG(D_SBP, nothing #= mortar =#, surface_integral, volume_integral) +end + # General interface methods for SummationByPartsOperators.jl and Trixi.jl nnodes(D::AbstractDerivativeOperator) = size(D, 1) @@ -48,4 +71,4 @@ include("fdsbp_2d.jl") include("fdsbp_3d.jl") -end +end # @muladd diff --git a/src/solvers/fdsbp_tree/fdsbp_1d.jl b/src/solvers/fdsbp_tree/fdsbp_1d.jl index 8e1f1ef9669..fb5a489b40a 100644 --- a/src/solvers/fdsbp_tree/fdsbp_1d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_1d.jl @@ -1,3 +1,6 @@ +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. + # 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. diff --git a/src/solvers/fdsbp_tree/fdsbp_2d.jl b/src/solvers/fdsbp_tree/fdsbp_2d.jl index 097b3e3f6df..83d75ff3e81 100644 --- a/src/solvers/fdsbp_tree/fdsbp_2d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_2d.jl @@ -1,3 +1,6 @@ +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. + # 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. @@ -352,12 +355,4 @@ function calc_error_norms(func, u, t, analyzer, end -# TODO: FD. Visualization -# We need to define better interfaces for all the plotting stuff. -# Right now, the easiest solution is to use scatter plots such as -# x = semi.cache.elements.node_coordinates[1, :, :, :] |> vec -# y = semi.cache.elements.node_coordinates[2, :, :, :] |> vec -# scatter(x, y, sol.u[end]) - - end # @muladd diff --git a/src/solvers/fdsbp_tree/fdsbp_3d.jl b/src/solvers/fdsbp_tree/fdsbp_3d.jl index e6a537ee2bc..e2ac4461cf7 100644 --- a/src/solvers/fdsbp_tree/fdsbp_3d.jl +++ b/src/solvers/fdsbp_tree/fdsbp_3d.jl @@ -1,3 +1,6 @@ +# !!! warning "Experimental implementation (upwind SBP)" +# This is an experimental feature and may change in future releases. + # 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. From 94989209b2f0350eea4662c9d15997b61926d302 Mon Sep 17 00:00:00 2001 From: Hendrik Ranocha Date: Fri, 2 Dec 2022 16:34:03 +0100 Subject: [PATCH 7/7] upwind tutorial (#1283) * add NEWS * WIP: tutorial on upwind FD SBP methods * first draft of tutorial * add to make.jl * fix tutorials? * Update upwind_fdsbp.jl * Apply suggestions from code review Co-authored-by: Michael Schlottke-Lakemper * explain bias Co-authored-by: Michael Schlottke-Lakemper --- NEWS.md | 3 ++ docs/literate/src/files/index.jl | 22 ++++++--- docs/literate/src/files/upwind_fdsbp.jl | 64 +++++++++++++++++++++++++ docs/make.jl | 1 + 4 files changed, 83 insertions(+), 7 deletions(-) create mode 100644 docs/literate/src/files/upwind_fdsbp.jl diff --git a/NEWS.md b/NEWS.md index 1cddb754d07..fbe6abc48c8 100644 --- a/NEWS.md +++ b/NEWS.md @@ -9,6 +9,9 @@ for human readability. #### Added +- Experimental support for upwind finite difference summation by parts (FDSBP) + has been added. The first implementation requires a `TreeMesh` and comes + with several examples in the `examples_dir()` of Trixi.jl. - Experimental support for 2D parabolic diffusion terms has been added. * `LaplaceDiffusion2D` and `CompressibleNavierStokesDiffusion2D` can be used to add diffusion to systems. `LaplaceDiffusion2D` can be used to add scalar diffusion to each diff --git a/docs/literate/src/files/index.jl b/docs/literate/src/files/index.jl index f4a8d83b845..899c18ff8d7 100644 --- a/docs/literate/src/files/index.jl +++ b/docs/literate/src/files/index.jl @@ -56,33 +56,41 @@ # For instance, we show how to set up a finite differences (FD) scheme and a continuous Galerkin # (CGSEM) method. -# ### [7 Adding a new scalar conservation law](@ref adding_new_scalar_equations) +# ### [7 Upwind FD SBP schemes](@ref upwind_fdsbp) +#- +# General SBP schemes can not only be used via the [`DGMulti`](@ref) solver but +# also with a general `DG` solver. In particular, upwind finite difference SBP +# methods can be used together with the `TreeMesh`. Similar to general SBP +# schemes in the `DGMulti` framework, the interface is based on the package +# [SummationByPartsOperators.jl](https://github.com/ranocha/SummationByPartsOperators.jl). + +# ### [8 Adding a new scalar conservation law](@ref adding_new_scalar_equations) #- # This tutorial explains how to add a new physics model using the example of the cubic conservation # law. First, we define the equation using a `struct` `CubicEquation` and the physical flux. Then, # the corresponding standard setup in Trixi.jl (`mesh`, `solver`, `semi` and `ode`) is implemented # and the ODE problem is solved by OrdinaryDiffEq's `solve` method. -# ### [8 Adding a non-conservative equation](@ref adding_nonconservative_equation) +# ### [9 Adding a non-conservative equation](@ref adding_nonconservative_equation) #- # In this part, another physics model is implemented, the nonconservative linear advection equation. # We run two different simulations with different levels of refinement and compare the resulting errors. -# ### [9 Adaptive mesh refinement](@ref adaptive_mesh_refinement) +# ### [10 Adaptive mesh refinement](@ref adaptive_mesh_refinement) #- # Adaptive mesh refinement (AMR) helps to increase the accuracy in sensitive or turbolent regions while # not wasting ressources for less interesting parts of the domain. This leads to much more efficient # simulations. This tutorial presents the implementation strategy of AMR in Trixi, including the use of # different indicators and controllers. -# ### [10 Structured mesh with curvilinear mapping](@ref structured_mesh_mapping) +# ### [11 Structured mesh with curvilinear mapping](@ref structured_mesh_mapping) #- # In this tutorial, the use of Trixi's structured curved mesh type [`StructuredMesh`](@ref) is explained. # We present the two basic option to initialize such a mesh. First, the curved domain boundaries # of a circular cylinder are set by explicit boundary functions. Then, a fully curved mesh is # defined by passing the transformation mapping. -# ### [11 Unstructured meshes with HOHQMesh.jl](@ref hohqmesh_tutorial) +# ### [12 Unstructured meshes with HOHQMesh.jl](@ref hohqmesh_tutorial) #- # The purpose of this tutorial is to demonstrate how to use the [`UnstructuredMesh2D`](@ref) # functionality of Trixi.jl. This begins by running and visualizing an available unstructured @@ -91,13 +99,13 @@ # software in the Trixi.jl ecosystem, and then run a simulation using Trixi.jl on said mesh. # In the end, the tutorial briefly explains how to simulate an example using AMR via `P4estMesh`. -# ### [12 Explicit time stepping](@ref time_stepping) +# ### [13 Explicit time stepping](@ref time_stepping) # - # This tutorial is about time integration using [OrdinaryDiffEq.jl](https://github.com/SciML/OrdinaryDiffEq.jl). # It explains how to use their algorithms and presents two types of time step choices - with error-based # and CFL-based adaptive step size control. -# ### [13 Differentiable programming](@ref differentiable_programming) +# ### [14 Differentiable programming](@ref differentiable_programming) #- # This part deals with some basic differentiable programming topics. For example, a Jacobian, its # eigenvalues and a curve of total energy (through the simulation) are calculated and plotted for diff --git a/docs/literate/src/files/upwind_fdsbp.jl b/docs/literate/src/files/upwind_fdsbp.jl new file mode 100644 index 00000000000..36ca1b57404 --- /dev/null +++ b/docs/literate/src/files/upwind_fdsbp.jl @@ -0,0 +1,64 @@ +#src # Upwind FD SBP schemes + +# General tensor product SBP methods are supported via the `DGMulti` solver +# in a reasonably complete way, see [the previous tutorial](@ref DGMulti_2). +# Nevertheless, there is also experimental support for SBP methods with +# other solver and mesh types. + +# 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) +# 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 +# compare it to coefficients presented in the literature, you can convert it +# to a matrix. +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) +# 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 +# derivative approximation at a given node (in the interior of the domain). +# In matrix form, this means more non-zero entries are left from the diagonal. +Matrix(D_upw.minus) +# Analogously, the "plus" variant has a bias towards the right side. +Matrix(D_upw.plus) +# For more information on upwind SBP operators, please refer to the documentation +# of [SummationByPartsOperators.jl](https://github.com/ranocha/SummationByPartsOperators.jl) +# and references cited there. + +# The basic idea of upwind SBP schemes is to apply a flux vector splitting and +# use appropriate upwind operators for both parts of the flux. In 1D, this means +# to split the flux +# ```math +# f(u) = f^-(u) + f^+(u) +# ``` +# such that $f^-(u)$ is associated with left-going waves and $f^+(u)$ with +# right-going waves. Then, we apply upwind SBP operators $D^-, D^+$ with an +# appropriate upwind bias, resulting in +# ```math +# \partial_x f(u) \approx D^+ f^-(u) + D^- f^+(u) +# ``` +# Note that the established notations of upwind operators $D^\pm$ and flux +# splittings $f^\pm$ clash. The right-going waves from $f^+$ need an operator +# biased towards their upwind side, i.e., the left side. This upwind bias is +# provided by the operator $D^-$. + +# Many classical flux vector splittings have been developed for finite volume +# methods and are described in the book "Riemann Solvers and Numerical Methods +# for Fluid Dynamics: A Practical Introduction" of Eleuterio F. Toro (2009), +# [DOI: 10.1007/b79761](https://doi.org/10.1007/b79761). One such a well-known +# splitting provided by Trixi.jl is [`splitting_steger_warming`](@ref). + +# Trixi.jl comes with several example setups using upwind SBP methods with +# flux vector splitting, e.g., +# - [`elixir_euler_vortex.jl`](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/tree_2d_fdsbp/elixir_euler_vortex.jl) +# - [`elixir_euler_taylor_green_vortex.jl`](https://github.com/trixi-framework/Trixi.jl/blob/main/examples/tree_3d_fdsbp/elixir_euler_taylor_green_vortex.jl) diff --git a/docs/make.jl b/docs/make.jl index d8d1298fba6..4e0980e6e10 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -36,6 +36,7 @@ files = [ "Non-periodic boundaries" => "non_periodic_boundaries.jl", "DG schemes via `DGMulti` solver" => "DGMulti_1.jl", "Other SBP schemes (FD, CGSEM) via `DGMulti` solver" => "DGMulti_2.jl", + "Upwind FD SBP schemes" => "upwind_fdsbp.jl", # Topic: equations "Adding a new scalar conservation law" => "adding_new_scalar_equations.jl", "Adding a non-conservative equation" => "adding_nonconservative_equation.jl",